From 5860171184dca45e63d4f869279e82d8f33b9b1a Mon Sep 17 00:00:00 2001 From: Idrees Hassan Date: Wed, 22 Oct 2025 21:46:12 -0400 Subject: [PATCH 1/8] Remove afk delay on mobile --- birb.js | 2 +- dist/birb.js | 2 +- dist/birb.user.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/birb.js b/birb.js index 4a416e9..8c365af 100644 --- a/birb.js +++ b/birb.js @@ -32,7 +32,7 @@ const HOP_SPEED = CONFIG.hopSpeed; const FLY_SPEED = CONFIG.flySpeed; const HOP_DISTANCE = CONFIG.hopDistance; // Time in milliseconds until the user is considered AFK -const AFK_TIME = debugMode ? 0 : 1000 * 30; +const AFK_TIME = (debugMode || isMobile()) ? 0 : 1000 * 30; const SPRITE_HEIGHT = 32; const MENU_ID = "birb-menu"; const MENU_EXIT_ID = "birb-menu-exit"; diff --git a/dist/birb.js b/dist/birb.js index 9c192d2..443547b 100644 --- a/dist/birb.js +++ b/dist/birb.js @@ -32,7 +32,7 @@ const HOP_SPEED = CONFIG.hopSpeed; const FLY_SPEED = CONFIG.flySpeed; const HOP_DISTANCE = CONFIG.hopDistance; // Time in milliseconds until the user is considered AFK -const AFK_TIME = debugMode ? 0 : 1000 * 30; +const AFK_TIME = (debugMode || isMobile()) ? 0 : 1000 * 30; const SPRITE_HEIGHT = 32; const MENU_ID = "birb-menu"; const MENU_EXIT_ID = "birb-menu-exit"; diff --git a/dist/birb.user.js b/dist/birb.user.js index 37cd231..25d0745 100644 --- a/dist/birb.user.js +++ b/dist/birb.user.js @@ -46,7 +46,7 @@ const HOP_SPEED = CONFIG.hopSpeed; const FLY_SPEED = CONFIG.flySpeed; const HOP_DISTANCE = CONFIG.hopDistance; // Time in milliseconds until the user is considered AFK -const AFK_TIME = debugMode ? 0 : 1000 * 30; +const AFK_TIME = (debugMode || isMobile()) ? 0 : 1000 * 30; const SPRITE_HEIGHT = 32; const MENU_ID = "birb-menu"; const MENU_EXIT_ID = "birb-menu-exit"; From 9fca0b204605be32d449a92c44d2b110e4600e52 Mon Sep 17 00:00:00 2001 From: Idrees Hassan Date: Wed, 22 Oct 2025 21:55:48 -0400 Subject: [PATCH 2/8] Bump version --- build.js | 2 +- dist/birb.user.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.js b/build.js index e255782..587d786 100644 --- a/build.js +++ b/build.js @@ -24,7 +24,7 @@ const userScriptHeader = `// ==UserScript== // @name Browser Bird // @namespace https://idreesinc.com -// @version 2025-09-15-01 +// @version 2025-10-22-01 // @description birb // @author Idrees // @downloadURL https://github.com/IdreesInc/Browser-Bird/raw/refs/heads/main/dist/birb.user.js diff --git a/dist/birb.user.js b/dist/birb.user.js index 25d0745..aa59c32 100644 --- a/dist/birb.user.js +++ b/dist/birb.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Browser Bird // @namespace https://idreesinc.com -// @version 2025-09-15-01 +// @version 2025-10-22-01 // @description birb // @author Idrees // @downloadURL https://github.com/IdreesInc/Browser-Bird/raw/refs/heads/main/dist/birb.user.js From 270367139d3bb4c31ed146f6dc7e973a9213c3c5 Mon Sep 17 00:00:00 2001 From: Idrees Hassan Date: Wed, 22 Oct 2025 22:00:01 -0400 Subject: [PATCH 3/8] Don't focus on ground immediately for mobile --- birb.js | 2 +- build.js | 2 +- dist/birb.js | 2 +- dist/birb.user.js | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/birb.js b/birb.js index 8c365af..5d0f224 100644 --- a/birb.js +++ b/birb.js @@ -958,7 +958,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI lastActionTimestamp = Date.now(); // Can't keep up with scrolling on mobile devices so fly down instead if (isMobile()) { - focusOnGround(); + // focusOnGround(); } }); diff --git a/build.js b/build.js index 587d786..fe30581 100644 --- a/build.js +++ b/build.js @@ -24,7 +24,7 @@ const userScriptHeader = `// ==UserScript== // @name Browser Bird // @namespace https://idreesinc.com -// @version 2025-10-22-01 +// @version 2025-10-22-02 // @description birb // @author Idrees // @downloadURL https://github.com/IdreesInc/Browser-Bird/raw/refs/heads/main/dist/birb.user.js diff --git a/dist/birb.js b/dist/birb.js index 443547b..9298bab 100644 --- a/dist/birb.js +++ b/dist/birb.js @@ -1297,7 +1297,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI lastActionTimestamp = Date.now(); // Can't keep up with scrolling on mobile devices so fly down instead if (isMobile()) { - focusOnGround(); + // focusOnGround(); } }); diff --git a/dist/birb.user.js b/dist/birb.user.js index aa59c32..d9613f2 100644 --- a/dist/birb.user.js +++ b/dist/birb.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Browser Bird // @namespace https://idreesinc.com -// @version 2025-10-22-01 +// @version 2025-10-22-02 // @description birb // @author Idrees // @downloadURL https://github.com/IdreesInc/Browser-Bird/raw/refs/heads/main/dist/birb.user.js @@ -1311,7 +1311,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI lastActionTimestamp = Date.now(); // Can't keep up with scrolling on mobile devices so fly down instead if (isMobile()) { - focusOnGround(); + // focusOnGround(); } }); From 2767caeb84190fda2d34f053f602c8ef6ee052e0 Mon Sep 17 00:00:00 2001 From: Idrees Hassan Date: Wed, 22 Oct 2025 22:03:07 -0400 Subject: [PATCH 4/8] Update userscript name --- build.js | 6 +++--- dist/birb.user.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build.js b/build.js index fe30581..ed4ef67 100644 --- a/build.js +++ b/build.js @@ -22,13 +22,13 @@ const STYLESHEET_KEY = "___STYLESHEET___"; const userScriptHeader = `// ==UserScript== -// @name Browser Bird +// @name Pocket Bird // @namespace https://idreesinc.com // @version 2025-10-22-02 // @description birb // @author Idrees -// @downloadURL https://github.com/IdreesInc/Browser-Bird/raw/refs/heads/main/dist/birb.user.js -// @updateURL https://github.com/IdreesInc/Browser-Bird/raw/refs/heads/main/dist/birb.user.js +// @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js +// @updateURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js // @match *://*/* // @grant GM_setValue // @grant GM_getValue diff --git a/dist/birb.user.js b/dist/birb.user.js index d9613f2..2da0b1b 100644 --- a/dist/birb.user.js +++ b/dist/birb.user.js @@ -1,11 +1,11 @@ // ==UserScript== -// @name Browser Bird +// @name Pocket Bird // @namespace https://idreesinc.com // @version 2025-10-22-02 // @description birb // @author Idrees -// @downloadURL https://github.com/IdreesInc/Browser-Bird/raw/refs/heads/main/dist/birb.user.js -// @updateURL https://github.com/IdreesInc/Browser-Bird/raw/refs/heads/main/dist/birb.user.js +// @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js +// @updateURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js // @match *://*/* // @grant GM_setValue // @grant GM_getValue From bae44cc98ce4d2de8f00f5f2531eca59b0f747ee Mon Sep 17 00:00:00 2001 From: Idrees Hassan Date: Wed, 22 Oct 2025 22:33:27 -0400 Subject: [PATCH 5/8] Adjust startY to account for scrolling --- birb.js | 5 ++++- build.js | 2 +- dist/birb.js | 5 ++++- dist/birb.user.js | 7 +++++-- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/birb.js b/birb.js index 5d0f224..bb028d0 100644 --- a/birb.js +++ b/birb.js @@ -1071,13 +1071,16 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } if (focusedElement === null) { - if (Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) { + if (currentState === States.IDLE && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) { // Fly to an element if the user is AFK focusOnElement(); lastActionTimestamp = Date.now(); } } else if (focusedElement !== null) { + const oldTargetY = targetY; targetY = getFocusedElementY(); + // Adjust startY to account for scrolling of the focused element + startY += targetY - oldTargetY; if (targetY < 0 || targetY > window.innerHeight) { // Fly to ground if the focused element moves out of bounds focusOnGround(); diff --git a/build.js b/build.js index ed4ef67..61f6b40 100644 --- a/build.js +++ b/build.js @@ -24,7 +24,7 @@ const userScriptHeader = `// ==UserScript== // @name Pocket Bird // @namespace https://idreesinc.com -// @version 2025-10-22-02 +// @version 2025-10-22-03 // @description birb // @author Idrees // @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js diff --git a/dist/birb.js b/dist/birb.js index 9298bab..98181e6 100644 --- a/dist/birb.js +++ b/dist/birb.js @@ -1410,13 +1410,16 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } if (focusedElement === null) { - if (Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) { + if (currentState === States.IDLE && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) { // Fly to an element if the user is AFK focusOnElement(); lastActionTimestamp = Date.now(); } } else if (focusedElement !== null) { + const oldTargetY = targetY; targetY = getFocusedElementY(); + // Adjust startY to account for scrolling of the focused element + startY += targetY - oldTargetY; if (targetY < 0 || targetY > window.innerHeight) { // Fly to ground if the focused element moves out of bounds focusOnGround(); diff --git a/dist/birb.user.js b/dist/birb.user.js index 2da0b1b..a0164b4 100644 --- a/dist/birb.user.js +++ b/dist/birb.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Pocket Bird // @namespace https://idreesinc.com -// @version 2025-10-22-02 +// @version 2025-10-22-03 // @description birb // @author Idrees // @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js @@ -1424,13 +1424,16 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } if (focusedElement === null) { - if (Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) { + if (currentState === States.IDLE && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) { // Fly to an element if the user is AFK focusOnElement(); lastActionTimestamp = Date.now(); } } else if (focusedElement !== null) { + const oldTargetY = targetY; targetY = getFocusedElementY(); + // Adjust startY to account for scrolling of the focused element + startY += targetY - oldTargetY; if (targetY < 0 || targetY > window.innerHeight) { // Fly to ground if the focused element moves out of bounds focusOnGround(); From bed3d37940beee167b48790de0bd5c299d78f546 Mon Sep 17 00:00:00 2001 From: Idrees Hassan Date: Wed, 22 Oct 2025 22:38:04 -0400 Subject: [PATCH 6/8] Bump version --- build.js | 2 +- dist/birb.user.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.js b/build.js index 61f6b40..e369220 100644 --- a/build.js +++ b/build.js @@ -24,7 +24,7 @@ const userScriptHeader = `// ==UserScript== // @name Pocket Bird // @namespace https://idreesinc.com -// @version 2025-10-22-03 +// @version 2025-10-22-04 // @description birb // @author Idrees // @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js diff --git a/dist/birb.user.js b/dist/birb.user.js index a0164b4..9d11b2c 100644 --- a/dist/birb.user.js +++ b/dist/birb.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Pocket Bird // @namespace https://idreesinc.com -// @version 2025-10-22-03 +// @version 2025-10-22-04 // @description birb // @author Idrees // @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js From 5298b7801b75414f38ccfa596c427d0302479cc3 Mon Sep 17 00:00:00 2001 From: Idrees Hassan Date: Thu, 23 Oct 2025 21:25:43 -0400 Subject: [PATCH 7/8] Treat ground as yet another element --- birb.js | 117 +++++++++++++++++++++++----------------------- dist/birb.js | 117 +++++++++++++++++++++++----------------------- dist/birb.user.js | 117 +++++++++++++++++++++++----------------------- 3 files changed, 177 insertions(+), 174 deletions(-) diff --git a/birb.js b/birb.js index bb028d0..0b0458b 100644 --- a/birb.js +++ b/birb.js @@ -685,6 +685,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI let targetY = 0; /** @type {HTMLElement|null} */ let focusedElement = null; + let focusedBounds = { left: 0, right: 0, top: 0 }; let lastActionTimestamp = Date.now(); /** @type {number[]} */ let petStack = []; @@ -1026,12 +1027,20 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI // Won't be restored on fullscreen exit } - if (currentState === States.IDLE) { - if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART && !isMenuOpen()) { + if (currentState === States.IDLE && !frozen && !isMenuOpen()) { + if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART) { hop(); - } else if (focusedElement !== null && Math.random() < 1 / (60 * 20) && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) { - focusOnElement(); - lastActionTimestamp = Date.now(); + } else if (Date.now() - lastActionTimestamp > AFK_TIME) { + // Idle for a while, do something + if (focusedElement === null) { + // Fly to an element + focusOnElement(); + lastActionTimestamp = Date.now(); + } else if (Math.random() < 1 / (60 * 20)) { + // Fly to another element if idle for a longer while + focusOnElement(); + lastActionTimestamp = Date.now(); + } } } else if (currentState === States.HOP) { if (updateParabolicPath(HOP_SPEED)) { @@ -1055,14 +1064,14 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI return; } + updateFocusedElementBounds(); + // Update the bird's position if (currentState === States.IDLE) { - if (focusedElement !== null) { - birdY = getFocusedElementY() - 0.5; - if (!isWithinHorizontalBounds()) { - focusOnGround(); - } + if (focusedElement && !isWithinHorizontalBounds()) { + focusOnGround(); } + birdY = getFocusedY(); } else if (currentState === States.FLYING) { // Fly to target location (even if in the air) if (updateParabolicPath(FLY_SPEED)) { @@ -1070,21 +1079,13 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } } - if (focusedElement === null) { - if (currentState === States.IDLE && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) { - // Fly to an element if the user is AFK - focusOnElement(); - lastActionTimestamp = Date.now(); - } - } else if (focusedElement !== null) { - const oldTargetY = targetY; - targetY = getFocusedElementY(); - // Adjust startY to account for scrolling of the focused element - startY += targetY - oldTargetY; - if (targetY < 0 || targetY > window.innerHeight) { - // Fly to ground if the focused element moves out of bounds - focusOnGround(); - } + const oldTargetY = targetY; + targetY = getFocusedY(); + // Adjust startY to account for scrolling + startY += targetY - oldTargetY; + if (targetY < 0 || targetY > window.innerHeight) { + // Fly to ground if the focused element moves out of bounds + focusOnGround(); } ctx.clearRect(0, 0, canvas.width, canvas.height); @@ -1660,34 +1661,29 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } function getFocusedElementRandomX() { - if (focusedElement === null) { - return Math.random() * window.innerWidth; - } - const rect = focusedElement.getBoundingClientRect(); - return Math.random() * (rect.right - rect.left) + rect.left; + return Math.random() * (focusedBounds.right - focusedBounds.left) + focusedBounds.left; } function isWithinHorizontalBounds() { - if (focusedElement === null) { - return true; - } - const rect = focusedElement.getBoundingClientRect(); - return birdX >= rect.left && birdX <= rect.right; + return birdX >= focusedBounds.left && birdX <= focusedBounds.right; } - function getFocusedElementY() { - if (focusedElement === null) { - return 0; - } - const rect = focusedElement.getBoundingClientRect(); - return window.innerHeight - rect.top; + // function getFocusedElementY() { + // if (focusedElement === null) { + // return 0; + // } + // const rect = focusedElement.getBoundingClientRect(); + // return window.innerHeight - rect.top; + // } + + function getFocusedY() { + return window.innerHeight - focusedBounds.top; } function focusOnGround() { - if (focusedElement === null) { - return; - } + console.log("Focusing on ground"); focusedElement = null; + focusedBounds = { left: 0, right: window.innerWidth, top: window.innerHeight }; flyTo(Math.random() * window.innerWidth, 0); } @@ -1709,7 +1705,23 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } const randomElement = largeElements[Math.floor(Math.random() * largeElements.length)]; focusedElement = randomElement; - flyTo(getFocusedElementRandomX(), getFocusedElementY()); + log("Focusing on element: ", focusedElement); + updateFocusedElementBounds(); + flyTo(getFocusedElementRandomX(), getFocusedY()); + } + + function updateFocusedElementBounds() { + if (focusedElement === null) { + // Update ground location to bottom of window + focusedBounds = { left: 0, right: window.innerWidth, top: window.innerHeight }; + return; + } + const rect = focusedElement.getBoundingClientRect(); + focusedBounds = { + left: rect.left, + right: rect.right, + top: rect.top + }; } function getCanvasWidth() { @@ -1725,25 +1737,14 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI return; } if (currentState === States.IDLE) { - // Determine bounds for hopping - let minX = 0; - let maxX = window.innerWidth; - let y = 0; - if (focusedElement !== null) { - // Hop on the element - const rect = focusedElement.getBoundingClientRect(); - minX = rect.left; - maxX = rect.right; - y = window.innerHeight - rect.top; - } setState(States.HOP); setAnimation(Animations.FLYING); - if ((Math.random() < 0.5 && birdX - HOP_DISTANCE > minX) || birdX + HOP_DISTANCE > maxX) { + if ((Math.random() < 0.5 && birdX - HOP_DISTANCE > focusedBounds.left) || birdX + HOP_DISTANCE > focusedBounds.right) { targetX = birdX - HOP_DISTANCE; } else { targetX = birdX + HOP_DISTANCE; } - targetY = y; + targetY = getFocusedY(); } } diff --git a/dist/birb.js b/dist/birb.js index 98181e6..aa3204d 100644 --- a/dist/birb.js +++ b/dist/birb.js @@ -1024,6 +1024,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI let targetY = 0; /** @type {HTMLElement|null} */ let focusedElement = null; + let focusedBounds = { left: 0, right: 0, top: 0 }; let lastActionTimestamp = Date.now(); /** @type {number[]} */ let petStack = []; @@ -1365,12 +1366,20 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI // Won't be restored on fullscreen exit } - if (currentState === States.IDLE) { - if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART && !isMenuOpen()) { + if (currentState === States.IDLE && !frozen && !isMenuOpen()) { + if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART) { hop(); - } else if (focusedElement !== null && Math.random() < 1 / (60 * 20) && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) { - focusOnElement(); - lastActionTimestamp = Date.now(); + } else if (Date.now() - lastActionTimestamp > AFK_TIME) { + // Idle for a while, do something + if (focusedElement === null) { + // Fly to an element + focusOnElement(); + lastActionTimestamp = Date.now(); + } else if (Math.random() < 1 / (60 * 20)) { + // Fly to another element if idle for a longer while + focusOnElement(); + lastActionTimestamp = Date.now(); + } } } else if (currentState === States.HOP) { if (updateParabolicPath(HOP_SPEED)) { @@ -1394,14 +1403,14 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI return; } + updateFocusedElementBounds(); + // Update the bird's position if (currentState === States.IDLE) { - if (focusedElement !== null) { - birdY = getFocusedElementY() - 0.5; - if (!isWithinHorizontalBounds()) { - focusOnGround(); - } + if (focusedElement && !isWithinHorizontalBounds()) { + focusOnGround(); } + birdY = getFocusedY(); } else if (currentState === States.FLYING) { // Fly to target location (even if in the air) if (updateParabolicPath(FLY_SPEED)) { @@ -1409,21 +1418,13 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } } - if (focusedElement === null) { - if (currentState === States.IDLE && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) { - // Fly to an element if the user is AFK - focusOnElement(); - lastActionTimestamp = Date.now(); - } - } else if (focusedElement !== null) { - const oldTargetY = targetY; - targetY = getFocusedElementY(); - // Adjust startY to account for scrolling of the focused element - startY += targetY - oldTargetY; - if (targetY < 0 || targetY > window.innerHeight) { - // Fly to ground if the focused element moves out of bounds - focusOnGround(); - } + const oldTargetY = targetY; + targetY = getFocusedY(); + // Adjust startY to account for scrolling + startY += targetY - oldTargetY; + if (targetY < 0 || targetY > window.innerHeight) { + // Fly to ground if the focused element moves out of bounds + focusOnGround(); } ctx.clearRect(0, 0, canvas.width, canvas.height); @@ -1999,34 +2000,29 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } function getFocusedElementRandomX() { - if (focusedElement === null) { - return Math.random() * window.innerWidth; - } - const rect = focusedElement.getBoundingClientRect(); - return Math.random() * (rect.right - rect.left) + rect.left; + return Math.random() * (focusedBounds.right - focusedBounds.left) + focusedBounds.left; } function isWithinHorizontalBounds() { - if (focusedElement === null) { - return true; - } - const rect = focusedElement.getBoundingClientRect(); - return birdX >= rect.left && birdX <= rect.right; + return birdX >= focusedBounds.left && birdX <= focusedBounds.right; } - function getFocusedElementY() { - if (focusedElement === null) { - return 0; - } - const rect = focusedElement.getBoundingClientRect(); - return window.innerHeight - rect.top; + // function getFocusedElementY() { + // if (focusedElement === null) { + // return 0; + // } + // const rect = focusedElement.getBoundingClientRect(); + // return window.innerHeight - rect.top; + // } + + function getFocusedY() { + return window.innerHeight - focusedBounds.top; } function focusOnGround() { - if (focusedElement === null) { - return; - } + console.log("Focusing on ground"); focusedElement = null; + focusedBounds = { left: 0, right: window.innerWidth, top: window.innerHeight }; flyTo(Math.random() * window.innerWidth, 0); } @@ -2048,7 +2044,23 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } const randomElement = largeElements[Math.floor(Math.random() * largeElements.length)]; focusedElement = randomElement; - flyTo(getFocusedElementRandomX(), getFocusedElementY()); + log("Focusing on element: ", focusedElement); + updateFocusedElementBounds(); + flyTo(getFocusedElementRandomX(), getFocusedY()); + } + + function updateFocusedElementBounds() { + if (focusedElement === null) { + // Update ground location to bottom of window + focusedBounds = { left: 0, right: window.innerWidth, top: window.innerHeight }; + return; + } + const rect = focusedElement.getBoundingClientRect(); + focusedBounds = { + left: rect.left, + right: rect.right, + top: rect.top + }; } function getCanvasWidth() { @@ -2064,25 +2076,14 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI return; } if (currentState === States.IDLE) { - // Determine bounds for hopping - let minX = 0; - let maxX = window.innerWidth; - let y = 0; - if (focusedElement !== null) { - // Hop on the element - const rect = focusedElement.getBoundingClientRect(); - minX = rect.left; - maxX = rect.right; - y = window.innerHeight - rect.top; - } setState(States.HOP); setAnimation(Animations.FLYING); - if ((Math.random() < 0.5 && birdX - HOP_DISTANCE > minX) || birdX + HOP_DISTANCE > maxX) { + if ((Math.random() < 0.5 && birdX - HOP_DISTANCE > focusedBounds.left) || birdX + HOP_DISTANCE > focusedBounds.right) { targetX = birdX - HOP_DISTANCE; } else { targetX = birdX + HOP_DISTANCE; } - targetY = y; + targetY = getFocusedY(); } } diff --git a/dist/birb.user.js b/dist/birb.user.js index 9d11b2c..bc1c881 100644 --- a/dist/birb.user.js +++ b/dist/birb.user.js @@ -1038,6 +1038,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI let targetY = 0; /** @type {HTMLElement|null} */ let focusedElement = null; + let focusedBounds = { left: 0, right: 0, top: 0 }; let lastActionTimestamp = Date.now(); /** @type {number[]} */ let petStack = []; @@ -1379,12 +1380,20 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI // Won't be restored on fullscreen exit } - if (currentState === States.IDLE) { - if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART && !isMenuOpen()) { + if (currentState === States.IDLE && !frozen && !isMenuOpen()) { + if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART) { hop(); - } else if (focusedElement !== null && Math.random() < 1 / (60 * 20) && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) { - focusOnElement(); - lastActionTimestamp = Date.now(); + } else if (Date.now() - lastActionTimestamp > AFK_TIME) { + // Idle for a while, do something + if (focusedElement === null) { + // Fly to an element + focusOnElement(); + lastActionTimestamp = Date.now(); + } else if (Math.random() < 1 / (60 * 20)) { + // Fly to another element if idle for a longer while + focusOnElement(); + lastActionTimestamp = Date.now(); + } } } else if (currentState === States.HOP) { if (updateParabolicPath(HOP_SPEED)) { @@ -1408,14 +1417,14 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI return; } + updateFocusedElementBounds(); + // Update the bird's position if (currentState === States.IDLE) { - if (focusedElement !== null) { - birdY = getFocusedElementY() - 0.5; - if (!isWithinHorizontalBounds()) { - focusOnGround(); - } + if (focusedElement && !isWithinHorizontalBounds()) { + focusOnGround(); } + birdY = getFocusedY(); } else if (currentState === States.FLYING) { // Fly to target location (even if in the air) if (updateParabolicPath(FLY_SPEED)) { @@ -1423,21 +1432,13 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } } - if (focusedElement === null) { - if (currentState === States.IDLE && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) { - // Fly to an element if the user is AFK - focusOnElement(); - lastActionTimestamp = Date.now(); - } - } else if (focusedElement !== null) { - const oldTargetY = targetY; - targetY = getFocusedElementY(); - // Adjust startY to account for scrolling of the focused element - startY += targetY - oldTargetY; - if (targetY < 0 || targetY > window.innerHeight) { - // Fly to ground if the focused element moves out of bounds - focusOnGround(); - } + const oldTargetY = targetY; + targetY = getFocusedY(); + // Adjust startY to account for scrolling + startY += targetY - oldTargetY; + if (targetY < 0 || targetY > window.innerHeight) { + // Fly to ground if the focused element moves out of bounds + focusOnGround(); } ctx.clearRect(0, 0, canvas.width, canvas.height); @@ -2013,34 +2014,29 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } function getFocusedElementRandomX() { - if (focusedElement === null) { - return Math.random() * window.innerWidth; - } - const rect = focusedElement.getBoundingClientRect(); - return Math.random() * (rect.right - rect.left) + rect.left; + return Math.random() * (focusedBounds.right - focusedBounds.left) + focusedBounds.left; } function isWithinHorizontalBounds() { - if (focusedElement === null) { - return true; - } - const rect = focusedElement.getBoundingClientRect(); - return birdX >= rect.left && birdX <= rect.right; + return birdX >= focusedBounds.left && birdX <= focusedBounds.right; } - function getFocusedElementY() { - if (focusedElement === null) { - return 0; - } - const rect = focusedElement.getBoundingClientRect(); - return window.innerHeight - rect.top; + // function getFocusedElementY() { + // if (focusedElement === null) { + // return 0; + // } + // const rect = focusedElement.getBoundingClientRect(); + // return window.innerHeight - rect.top; + // } + + function getFocusedY() { + return window.innerHeight - focusedBounds.top; } function focusOnGround() { - if (focusedElement === null) { - return; - } + console.log("Focusing on ground"); focusedElement = null; + focusedBounds = { left: 0, right: window.innerWidth, top: window.innerHeight }; flyTo(Math.random() * window.innerWidth, 0); } @@ -2062,7 +2058,23 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } const randomElement = largeElements[Math.floor(Math.random() * largeElements.length)]; focusedElement = randomElement; - flyTo(getFocusedElementRandomX(), getFocusedElementY()); + log("Focusing on element: ", focusedElement); + updateFocusedElementBounds(); + flyTo(getFocusedElementRandomX(), getFocusedY()); + } + + function updateFocusedElementBounds() { + if (focusedElement === null) { + // Update ground location to bottom of window + focusedBounds = { left: 0, right: window.innerWidth, top: window.innerHeight }; + return; + } + const rect = focusedElement.getBoundingClientRect(); + focusedBounds = { + left: rect.left, + right: rect.right, + top: rect.top + }; } function getCanvasWidth() { @@ -2078,25 +2090,14 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI return; } if (currentState === States.IDLE) { - // Determine bounds for hopping - let minX = 0; - let maxX = window.innerWidth; - let y = 0; - if (focusedElement !== null) { - // Hop on the element - const rect = focusedElement.getBoundingClientRect(); - minX = rect.left; - maxX = rect.right; - y = window.innerHeight - rect.top; - } setState(States.HOP); setAnimation(Animations.FLYING); - if ((Math.random() < 0.5 && birdX - HOP_DISTANCE > minX) || birdX + HOP_DISTANCE > maxX) { + if ((Math.random() < 0.5 && birdX - HOP_DISTANCE > focusedBounds.left) || birdX + HOP_DISTANCE > focusedBounds.right) { targetX = birdX - HOP_DISTANCE; } else { targetX = birdX + HOP_DISTANCE; } - targetY = y; + targetY = getFocusedY(); } } From 29f1766a95944dd11c0d92579a310a1dc4d18277 Mon Sep 17 00:00:00 2001 From: Idrees Hassan Date: Thu, 23 Oct 2025 23:32:01 -0400 Subject: [PATCH 8/8] Switch to absolute positioning when on element --- birb.js | 60 +++++++++++++++++++++++++++++------------- build.js | 2 +- dist/birb.js | 64 ++++++++++++++++++++++++++++++++------------- dist/birb.user.js | 66 +++++++++++++++++++++++++++++++++-------------- stylesheet.css | 4 +++ 5 files changed, 140 insertions(+), 56 deletions(-) diff --git a/birb.js b/birb.js index 0b0458b..3715d1f 100644 --- a/birb.js +++ b/birb.js @@ -32,7 +32,7 @@ const HOP_SPEED = CONFIG.hopSpeed; const FLY_SPEED = CONFIG.flySpeed; const HOP_DISTANCE = CONFIG.hopDistance; // Time in milliseconds until the user is considered AFK -const AFK_TIME = (debugMode || isMobile()) ? 0 : 1000 * 30; +const AFK_TIME = debugMode ? 0 : 1000 * 30; const SPRITE_HEIGHT = 32; const MENU_ID = "birb-menu"; const MENU_EXIT_ID = "birb-menu-exit"; @@ -957,11 +957,6 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI window.addEventListener("scroll", () => { lastActionTimestamp = Date.now(); - // Can't keep up with scrolling on mobile devices so fly down instead - if (isMobile()) { - // focusOnGround(); - } - }); onClick(document, (e) => { @@ -1668,22 +1663,30 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI return birdX >= focusedBounds.left && birdX <= focusedBounds.right; } - // function getFocusedElementY() { - // if (focusedElement === null) { - // return 0; - // } - // const rect = focusedElement.getBoundingClientRect(); - // return window.innerHeight - rect.top; - // } - function getFocusedY() { - return window.innerHeight - focusedBounds.top; + return getFullWindowHeight() - focusedBounds.top; } + /** + * @returns The render-safe height of the inner browser window + */ + function getSafeWindowHeight() { + // Necessary because iOS 26 Safari is terrible and won't render + // fixed elements behind the address bar + return window.innerHeight; + } + + /** + * @returns The true height of the inner browser window + */ + function getFullWindowHeight() { + return document.documentElement.clientHeight; + } + function focusOnGround() { console.log("Focusing on ground"); focusedElement = null; - focusedBounds = { left: 0, right: window.innerWidth, top: window.innerHeight }; + focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() }; flyTo(Math.random() * window.innerWidth, 0); } @@ -1713,7 +1716,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI function updateFocusedElementBounds() { if (focusedElement === null) { // Update ground location to bottom of window - focusedBounds = { left: 0, right: window.innerWidth, top: window.innerHeight }; + focusedBounds = { left: 0, right: window.innerWidth, top: getFullWindowHeight() }; return; } const rect = focusedElement.getBoundingClientRect(); @@ -1771,6 +1774,13 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI setAnimation(Animations.FLYING); } + /** + * @returns {boolean} Whether the bird should be absolutely positioned + */ + function isAbsolute() { + return focusedElement !== null && (currentState === States.IDLE || currentState === States.HOP); + } + /** * Set the current animation and reset the animation timer * @param {Anim} animation @@ -1792,6 +1802,12 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI if (state === States.IDLE) { setAnimation(Animations.BOB); } + if (isAbsolute()) { + canvas.classList.add("birb-absolute"); + } else { + canvas.classList.remove("birb-absolute"); + } + setY(birdY); } /** @@ -1806,7 +1822,15 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI * @param {number} y */ function setY(y) { - canvas.style.bottom = `${y}px`; + let bottom; + if (isAbsolute()) { + // Position is absolute, convert from fixed + bottom = y - window.scrollY; + } else { + // Position is fixed + bottom = y; + } + canvas.style.bottom = `${bottom}px`; } }); diff --git a/build.js b/build.js index e369220..4169937 100644 --- a/build.js +++ b/build.js @@ -24,7 +24,7 @@ const userScriptHeader = `// ==UserScript== // @name Pocket Bird // @namespace https://idreesinc.com -// @version 2025-10-22-04 +// @version 2025-10-23-01 // @description birb // @author Idrees // @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js diff --git a/dist/birb.js b/dist/birb.js index aa3204d..8e1113a 100644 --- a/dist/birb.js +++ b/dist/birb.js @@ -32,7 +32,7 @@ const HOP_SPEED = CONFIG.hopSpeed; const FLY_SPEED = CONFIG.flySpeed; const HOP_DISTANCE = CONFIG.hopDistance; // Time in milliseconds until the user is considered AFK -const AFK_TIME = (debugMode || isMobile()) ? 0 : 1000 * 30; +const AFK_TIME = debugMode ? 0 : 1000 * 30; const SPRITE_HEIGHT = 32; const MENU_ID = "birb-menu"; const MENU_EXIT_ID = "birb-menu-exit"; @@ -74,6 +74,10 @@ const STYLESHEET = `:root { cursor: pointer; } +.birb-absolute { + position: absolute !important; +} + .birb-decoration { image-rendering: pixelated; position: fixed; @@ -1296,11 +1300,6 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI window.addEventListener("scroll", () => { lastActionTimestamp = Date.now(); - // Can't keep up with scrolling on mobile devices so fly down instead - if (isMobile()) { - // focusOnGround(); - } - }); onClick(document, (e) => { @@ -2007,22 +2006,30 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI return birdX >= focusedBounds.left && birdX <= focusedBounds.right; } - // function getFocusedElementY() { - // if (focusedElement === null) { - // return 0; - // } - // const rect = focusedElement.getBoundingClientRect(); - // return window.innerHeight - rect.top; - // } - function getFocusedY() { - return window.innerHeight - focusedBounds.top; + return getFullWindowHeight() - focusedBounds.top; } + /** + * @returns The render-safe height of the inner browser window + */ + function getSafeWindowHeight() { + // Necessary because iOS 26 Safari is terrible and won't render + // fixed elements behind the address bar + return window.innerHeight; + } + + /** + * @returns The true height of the inner browser window + */ + function getFullWindowHeight() { + return document.documentElement.clientHeight; + } + function focusOnGround() { console.log("Focusing on ground"); focusedElement = null; - focusedBounds = { left: 0, right: window.innerWidth, top: window.innerHeight }; + focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() }; flyTo(Math.random() * window.innerWidth, 0); } @@ -2052,7 +2059,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI function updateFocusedElementBounds() { if (focusedElement === null) { // Update ground location to bottom of window - focusedBounds = { left: 0, right: window.innerWidth, top: window.innerHeight }; + focusedBounds = { left: 0, right: window.innerWidth, top: getFullWindowHeight() }; return; } const rect = focusedElement.getBoundingClientRect(); @@ -2110,6 +2117,13 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI setAnimation(Animations.FLYING); } + /** + * @returns {boolean} Whether the bird should be absolutely positioned + */ + function isAbsolute() { + return focusedElement !== null && (currentState === States.IDLE || currentState === States.HOP); + } + /** * Set the current animation and reset the animation timer * @param {Anim} animation @@ -2131,6 +2145,12 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI if (state === States.IDLE) { setAnimation(Animations.BOB); } + if (isAbsolute()) { + canvas.classList.add("birb-absolute"); + } else { + canvas.classList.remove("birb-absolute"); + } + setY(birdY); } /** @@ -2145,7 +2165,15 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI * @param {number} y */ function setY(y) { - canvas.style.bottom = `${y}px`; + let bottom; + if (isAbsolute()) { + // Position is absolute, convert from fixed + bottom = y - window.scrollY; + } else { + // Position is fixed + bottom = y; + } + canvas.style.bottom = `${bottom}px`; } }); diff --git a/dist/birb.user.js b/dist/birb.user.js index bc1c881..0827b5d 100644 --- a/dist/birb.user.js +++ b/dist/birb.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Pocket Bird // @namespace https://idreesinc.com -// @version 2025-10-22-04 +// @version 2025-10-23-01 // @description birb // @author Idrees // @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js @@ -46,7 +46,7 @@ const HOP_SPEED = CONFIG.hopSpeed; const FLY_SPEED = CONFIG.flySpeed; const HOP_DISTANCE = CONFIG.hopDistance; // Time in milliseconds until the user is considered AFK -const AFK_TIME = (debugMode || isMobile()) ? 0 : 1000 * 30; +const AFK_TIME = debugMode ? 0 : 1000 * 30; const SPRITE_HEIGHT = 32; const MENU_ID = "birb-menu"; const MENU_EXIT_ID = "birb-menu-exit"; @@ -88,6 +88,10 @@ const STYLESHEET = `:root { cursor: pointer; } +.birb-absolute { + position: absolute !important; +} + .birb-decoration { image-rendering: pixelated; position: fixed; @@ -1310,11 +1314,6 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI window.addEventListener("scroll", () => { lastActionTimestamp = Date.now(); - // Can't keep up with scrolling on mobile devices so fly down instead - if (isMobile()) { - // focusOnGround(); - } - }); onClick(document, (e) => { @@ -2021,22 +2020,30 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI return birdX >= focusedBounds.left && birdX <= focusedBounds.right; } - // function getFocusedElementY() { - // if (focusedElement === null) { - // return 0; - // } - // const rect = focusedElement.getBoundingClientRect(); - // return window.innerHeight - rect.top; - // } - function getFocusedY() { - return window.innerHeight - focusedBounds.top; + return getFullWindowHeight() - focusedBounds.top; } + /** + * @returns The render-safe height of the inner browser window + */ + function getSafeWindowHeight() { + // Necessary because iOS 26 Safari is terrible and won't render + // fixed elements behind the address bar + return window.innerHeight; + } + + /** + * @returns The true height of the inner browser window + */ + function getFullWindowHeight() { + return document.documentElement.clientHeight; + } + function focusOnGround() { console.log("Focusing on ground"); focusedElement = null; - focusedBounds = { left: 0, right: window.innerWidth, top: window.innerHeight }; + focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() }; flyTo(Math.random() * window.innerWidth, 0); } @@ -2066,7 +2073,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI function updateFocusedElementBounds() { if (focusedElement === null) { // Update ground location to bottom of window - focusedBounds = { left: 0, right: window.innerWidth, top: window.innerHeight }; + focusedBounds = { left: 0, right: window.innerWidth, top: getFullWindowHeight() }; return; } const rect = focusedElement.getBoundingClientRect(); @@ -2124,6 +2131,13 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI setAnimation(Animations.FLYING); } + /** + * @returns {boolean} Whether the bird should be absolutely positioned + */ + function isAbsolute() { + return focusedElement !== null && (currentState === States.IDLE || currentState === States.HOP); + } + /** * Set the current animation and reset the animation timer * @param {Anim} animation @@ -2145,6 +2159,12 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI if (state === States.IDLE) { setAnimation(Animations.BOB); } + if (isAbsolute()) { + canvas.classList.add("birb-absolute"); + } else { + canvas.classList.remove("birb-absolute"); + } + setY(birdY); } /** @@ -2159,7 +2179,15 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI * @param {number} y */ function setY(y) { - canvas.style.bottom = `${y}px`; + let bottom; + if (isAbsolute()) { + // Position is absolute, convert from fixed + bottom = y - window.scrollY; + } else { + // Position is fixed + bottom = y; + } + canvas.style.bottom = `${bottom}px`; } }); diff --git a/stylesheet.css b/stylesheet.css index f4de410..504506c 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -21,6 +21,10 @@ cursor: pointer; } +.birb-absolute { + position: absolute !important; +} + .birb-decoration { image-rendering: pixelated; position: fixed;