mirror of
https://github.com/NohamR/Pocket-Bird.git
synced 2026-05-25 12:17:22 +00:00
Merge branch 'main' into extension
This commit is contained in:
150
birb.js
150
birb.js
@@ -685,6 +685,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
let targetY = 0;
|
let targetY = 0;
|
||||||
/** @type {HTMLElement|null} */
|
/** @type {HTMLElement|null} */
|
||||||
let focusedElement = null;
|
let focusedElement = null;
|
||||||
|
let focusedBounds = { left: 0, right: 0, top: 0 };
|
||||||
let lastActionTimestamp = Date.now();
|
let lastActionTimestamp = Date.now();
|
||||||
/** @type {number[]} */
|
/** @type {number[]} */
|
||||||
let petStack = [];
|
let petStack = [];
|
||||||
@@ -958,11 +959,6 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
|
|
||||||
window.addEventListener("scroll", () => {
|
window.addEventListener("scroll", () => {
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
// Can't keep up with scrolling on mobile devices so fly down instead
|
|
||||||
if (isMobile()) {
|
|
||||||
// focusOnGround();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onClick(document, (e) => {
|
onClick(document, (e) => {
|
||||||
@@ -1028,12 +1024,20 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
// Won't be restored on fullscreen exit
|
// Won't be restored on fullscreen exit
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE && !frozen && !isMenuOpen()) {
|
||||||
if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART && !isMenuOpen()) {
|
if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART) {
|
||||||
hop();
|
hop();
|
||||||
} else if (focusedElement !== null && Math.random() < 1 / (60 * 20) && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
|
} else if (Date.now() - lastActionTimestamp > AFK_TIME) {
|
||||||
focusOnElement();
|
// Idle for a while, do something
|
||||||
lastActionTimestamp = Date.now();
|
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) {
|
} else if (currentState === States.HOP) {
|
||||||
if (updateParabolicPath(HOP_SPEED)) {
|
if (updateParabolicPath(HOP_SPEED)) {
|
||||||
@@ -1057,14 +1061,14 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFocusedElementBounds();
|
||||||
|
|
||||||
// Update the bird's position
|
// Update the bird's position
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE) {
|
||||||
if (focusedElement !== null) {
|
if (focusedElement && !isWithinHorizontalBounds()) {
|
||||||
birdY = getFocusedElementY() - 0.5;
|
focusOnGround();
|
||||||
if (!isWithinHorizontalBounds()) {
|
|
||||||
focusOnGround();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
birdY = getFocusedY();
|
||||||
} else if (currentState === States.FLYING) {
|
} else if (currentState === States.FLYING) {
|
||||||
// Fly to target location (even if in the air)
|
// Fly to target location (even if in the air)
|
||||||
if (updateParabolicPath(FLY_SPEED)) {
|
if (updateParabolicPath(FLY_SPEED)) {
|
||||||
@@ -1072,18 +1076,13 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (focusedElement === null) {
|
const oldTargetY = targetY;
|
||||||
if (Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
|
targetY = getFocusedY();
|
||||||
// Fly to an element if the user is AFK
|
// Adjust startY to account for scrolling
|
||||||
focusOnElement();
|
startY += targetY - oldTargetY;
|
||||||
lastActionTimestamp = Date.now();
|
if (targetY < 0 || targetY > window.innerHeight) {
|
||||||
}
|
// Fly to ground if the focused element moves out of bounds
|
||||||
} else if (focusedElement !== null) {
|
focusOnGround();
|
||||||
targetY = getFocusedElementY();
|
|
||||||
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);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
@@ -1659,34 +1658,37 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getFocusedElementRandomX() {
|
function getFocusedElementRandomX() {
|
||||||
if (focusedElement === null) {
|
return Math.random() * (focusedBounds.right - focusedBounds.left) + focusedBounds.left;
|
||||||
return Math.random() * window.innerWidth;
|
|
||||||
}
|
|
||||||
const rect = focusedElement.getBoundingClientRect();
|
|
||||||
return Math.random() * (rect.right - rect.left) + rect.left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isWithinHorizontalBounds() {
|
function isWithinHorizontalBounds() {
|
||||||
if (focusedElement === null) {
|
return birdX >= focusedBounds.left && birdX <= focusedBounds.right;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const rect = focusedElement.getBoundingClientRect();
|
|
||||||
return birdX >= rect.left && birdX <= rect.right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFocusedElementY() {
|
function getFocusedY() {
|
||||||
if (focusedElement === null) {
|
return getFullWindowHeight() - focusedBounds.top;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const rect = focusedElement.getBoundingClientRect();
|
|
||||||
return window.innerHeight - rect.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() {
|
function focusOnGround() {
|
||||||
if (focusedElement === null) {
|
console.log("Focusing on ground");
|
||||||
return;
|
|
||||||
}
|
|
||||||
focusedElement = null;
|
focusedElement = null;
|
||||||
|
focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() };
|
||||||
flyTo(Math.random() * window.innerWidth, 0);
|
flyTo(Math.random() * window.innerWidth, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1708,7 +1710,23 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
}
|
}
|
||||||
const randomElement = largeElements[Math.floor(Math.random() * largeElements.length)];
|
const randomElement = largeElements[Math.floor(Math.random() * largeElements.length)];
|
||||||
focusedElement = randomElement;
|
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: getFullWindowHeight() };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rect = focusedElement.getBoundingClientRect();
|
||||||
|
focusedBounds = {
|
||||||
|
left: rect.left,
|
||||||
|
right: rect.right,
|
||||||
|
top: rect.top
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCanvasWidth() {
|
function getCanvasWidth() {
|
||||||
@@ -1724,25 +1742,14 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currentState === States.IDLE) {
|
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);
|
setState(States.HOP);
|
||||||
setAnimation(Animations.FLYING);
|
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;
|
targetX = birdX - HOP_DISTANCE;
|
||||||
} else {
|
} else {
|
||||||
targetX = birdX + HOP_DISTANCE;
|
targetX = birdX + HOP_DISTANCE;
|
||||||
}
|
}
|
||||||
targetY = y;
|
targetY = getFocusedY();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1769,6 +1776,13 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
setAnimation(Animations.FLYING);
|
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
|
* Set the current animation and reset the animation timer
|
||||||
* @param {Anim} animation
|
* @param {Anim} animation
|
||||||
@@ -1790,6 +1804,12 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
if (state === States.IDLE) {
|
if (state === States.IDLE) {
|
||||||
setAnimation(Animations.BOB);
|
setAnimation(Animations.BOB);
|
||||||
}
|
}
|
||||||
|
if (isAbsolute()) {
|
||||||
|
canvas.classList.add("birb-absolute");
|
||||||
|
} else {
|
||||||
|
canvas.classList.remove("birb-absolute");
|
||||||
|
}
|
||||||
|
setY(birdY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1804,7 +1824,15 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
* @param {number} y
|
* @param {number} y
|
||||||
*/
|
*/
|
||||||
function setY(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`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
6
build.js
6
build.js
@@ -34,13 +34,13 @@ try {
|
|||||||
|
|
||||||
const userScriptHeader =
|
const userScriptHeader =
|
||||||
`// ==UserScript==
|
`// ==UserScript==
|
||||||
// @name Browser Bird
|
// @name Pocket Bird
|
||||||
// @namespace https://idreesinc.com
|
// @namespace https://idreesinc.com
|
||||||
// @version ${version}
|
// @version ${version}
|
||||||
// @description birb
|
// @description birb
|
||||||
// @author Idrees
|
// @author Idrees
|
||||||
// @downloadURL 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/Browser-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 *://*/*
|
// @match *://*/*
|
||||||
// @grant GM_setValue
|
// @grant GM_setValue
|
||||||
// @grant GM_getValue
|
// @grant GM_getValue
|
||||||
|
|||||||
154
dist/birb.js
vendored
154
dist/birb.js
vendored
@@ -74,6 +74,10 @@ const STYLESHEET = `:root {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.birb-absolute {
|
||||||
|
position: absolute !important;
|
||||||
|
}
|
||||||
|
|
||||||
.birb-decoration {
|
.birb-decoration {
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -1024,6 +1028,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
let targetY = 0;
|
let targetY = 0;
|
||||||
/** @type {HTMLElement|null} */
|
/** @type {HTMLElement|null} */
|
||||||
let focusedElement = null;
|
let focusedElement = null;
|
||||||
|
let focusedBounds = { left: 0, right: 0, top: 0 };
|
||||||
let lastActionTimestamp = Date.now();
|
let lastActionTimestamp = Date.now();
|
||||||
/** @type {number[]} */
|
/** @type {number[]} */
|
||||||
let petStack = [];
|
let petStack = [];
|
||||||
@@ -1297,11 +1302,6 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
|
|
||||||
window.addEventListener("scroll", () => {
|
window.addEventListener("scroll", () => {
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
// Can't keep up with scrolling on mobile devices so fly down instead
|
|
||||||
if (isMobile()) {
|
|
||||||
// focusOnGround();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onClick(document, (e) => {
|
onClick(document, (e) => {
|
||||||
@@ -1367,12 +1367,20 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
// Won't be restored on fullscreen exit
|
// Won't be restored on fullscreen exit
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE && !frozen && !isMenuOpen()) {
|
||||||
if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART && !isMenuOpen()) {
|
if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART) {
|
||||||
hop();
|
hop();
|
||||||
} else if (focusedElement !== null && Math.random() < 1 / (60 * 20) && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
|
} else if (Date.now() - lastActionTimestamp > AFK_TIME) {
|
||||||
focusOnElement();
|
// Idle for a while, do something
|
||||||
lastActionTimestamp = Date.now();
|
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) {
|
} else if (currentState === States.HOP) {
|
||||||
if (updateParabolicPath(HOP_SPEED)) {
|
if (updateParabolicPath(HOP_SPEED)) {
|
||||||
@@ -1396,14 +1404,14 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFocusedElementBounds();
|
||||||
|
|
||||||
// Update the bird's position
|
// Update the bird's position
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE) {
|
||||||
if (focusedElement !== null) {
|
if (focusedElement && !isWithinHorizontalBounds()) {
|
||||||
birdY = getFocusedElementY() - 0.5;
|
focusOnGround();
|
||||||
if (!isWithinHorizontalBounds()) {
|
|
||||||
focusOnGround();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
birdY = getFocusedY();
|
||||||
} else if (currentState === States.FLYING) {
|
} else if (currentState === States.FLYING) {
|
||||||
// Fly to target location (even if in the air)
|
// Fly to target location (even if in the air)
|
||||||
if (updateParabolicPath(FLY_SPEED)) {
|
if (updateParabolicPath(FLY_SPEED)) {
|
||||||
@@ -1411,18 +1419,13 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (focusedElement === null) {
|
const oldTargetY = targetY;
|
||||||
if (Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
|
targetY = getFocusedY();
|
||||||
// Fly to an element if the user is AFK
|
// Adjust startY to account for scrolling
|
||||||
focusOnElement();
|
startY += targetY - oldTargetY;
|
||||||
lastActionTimestamp = Date.now();
|
if (targetY < 0 || targetY > window.innerHeight) {
|
||||||
}
|
// Fly to ground if the focused element moves out of bounds
|
||||||
} else if (focusedElement !== null) {
|
focusOnGround();
|
||||||
targetY = getFocusedElementY();
|
|
||||||
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);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
@@ -1998,34 +2001,37 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getFocusedElementRandomX() {
|
function getFocusedElementRandomX() {
|
||||||
if (focusedElement === null) {
|
return Math.random() * (focusedBounds.right - focusedBounds.left) + focusedBounds.left;
|
||||||
return Math.random() * window.innerWidth;
|
|
||||||
}
|
|
||||||
const rect = focusedElement.getBoundingClientRect();
|
|
||||||
return Math.random() * (rect.right - rect.left) + rect.left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isWithinHorizontalBounds() {
|
function isWithinHorizontalBounds() {
|
||||||
if (focusedElement === null) {
|
return birdX >= focusedBounds.left && birdX <= focusedBounds.right;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const rect = focusedElement.getBoundingClientRect();
|
|
||||||
return birdX >= rect.left && birdX <= rect.right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFocusedElementY() {
|
function getFocusedY() {
|
||||||
if (focusedElement === null) {
|
return getFullWindowHeight() - focusedBounds.top;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const rect = focusedElement.getBoundingClientRect();
|
|
||||||
return window.innerHeight - rect.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() {
|
function focusOnGround() {
|
||||||
if (focusedElement === null) {
|
console.log("Focusing on ground");
|
||||||
return;
|
|
||||||
}
|
|
||||||
focusedElement = null;
|
focusedElement = null;
|
||||||
|
focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() };
|
||||||
flyTo(Math.random() * window.innerWidth, 0);
|
flyTo(Math.random() * window.innerWidth, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2047,7 +2053,23 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
}
|
}
|
||||||
const randomElement = largeElements[Math.floor(Math.random() * largeElements.length)];
|
const randomElement = largeElements[Math.floor(Math.random() * largeElements.length)];
|
||||||
focusedElement = randomElement;
|
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: getFullWindowHeight() };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rect = focusedElement.getBoundingClientRect();
|
||||||
|
focusedBounds = {
|
||||||
|
left: rect.left,
|
||||||
|
right: rect.right,
|
||||||
|
top: rect.top
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCanvasWidth() {
|
function getCanvasWidth() {
|
||||||
@@ -2063,25 +2085,14 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currentState === States.IDLE) {
|
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);
|
setState(States.HOP);
|
||||||
setAnimation(Animations.FLYING);
|
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;
|
targetX = birdX - HOP_DISTANCE;
|
||||||
} else {
|
} else {
|
||||||
targetX = birdX + HOP_DISTANCE;
|
targetX = birdX + HOP_DISTANCE;
|
||||||
}
|
}
|
||||||
targetY = y;
|
targetY = getFocusedY();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2108,6 +2119,13 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
setAnimation(Animations.FLYING);
|
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
|
* Set the current animation and reset the animation timer
|
||||||
* @param {Anim} animation
|
* @param {Anim} animation
|
||||||
@@ -2129,6 +2147,12 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
if (state === States.IDLE) {
|
if (state === States.IDLE) {
|
||||||
setAnimation(Animations.BOB);
|
setAnimation(Animations.BOB);
|
||||||
}
|
}
|
||||||
|
if (isAbsolute()) {
|
||||||
|
canvas.classList.add("birb-absolute");
|
||||||
|
} else {
|
||||||
|
canvas.classList.remove("birb-absolute");
|
||||||
|
}
|
||||||
|
setY(birdY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2143,7 +2167,15 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
* @param {number} y
|
* @param {number} y
|
||||||
*/
|
*/
|
||||||
function setY(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`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
160
dist/birb.user.js
vendored
160
dist/birb.user.js
vendored
@@ -1,11 +1,11 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Browser Bird
|
// @name Pocket Bird
|
||||||
// @namespace https://idreesinc.com
|
// @namespace https://idreesinc.com
|
||||||
// @version 2025.9.16.1
|
// @version 2025.9.16.1
|
||||||
// @description birb
|
// @description birb
|
||||||
// @author Idrees
|
// @author Idrees
|
||||||
// @downloadURL 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/Browser-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 *://*/*
|
// @match *://*/*
|
||||||
// @grant GM_setValue
|
// @grant GM_setValue
|
||||||
// @grant GM_getValue
|
// @grant GM_getValue
|
||||||
@@ -88,6 +88,10 @@ const STYLESHEET = `:root {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.birb-absolute {
|
||||||
|
position: absolute !important;
|
||||||
|
}
|
||||||
|
|
||||||
.birb-decoration {
|
.birb-decoration {
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -1038,6 +1042,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
let targetY = 0;
|
let targetY = 0;
|
||||||
/** @type {HTMLElement|null} */
|
/** @type {HTMLElement|null} */
|
||||||
let focusedElement = null;
|
let focusedElement = null;
|
||||||
|
let focusedBounds = { left: 0, right: 0, top: 0 };
|
||||||
let lastActionTimestamp = Date.now();
|
let lastActionTimestamp = Date.now();
|
||||||
/** @type {number[]} */
|
/** @type {number[]} */
|
||||||
let petStack = [];
|
let petStack = [];
|
||||||
@@ -1311,11 +1316,6 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
|
|
||||||
window.addEventListener("scroll", () => {
|
window.addEventListener("scroll", () => {
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
// Can't keep up with scrolling on mobile devices so fly down instead
|
|
||||||
if (isMobile()) {
|
|
||||||
// focusOnGround();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onClick(document, (e) => {
|
onClick(document, (e) => {
|
||||||
@@ -1381,12 +1381,20 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
// Won't be restored on fullscreen exit
|
// Won't be restored on fullscreen exit
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE && !frozen && !isMenuOpen()) {
|
||||||
if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART && !isMenuOpen()) {
|
if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART) {
|
||||||
hop();
|
hop();
|
||||||
} else if (focusedElement !== null && Math.random() < 1 / (60 * 20) && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
|
} else if (Date.now() - lastActionTimestamp > AFK_TIME) {
|
||||||
focusOnElement();
|
// Idle for a while, do something
|
||||||
lastActionTimestamp = Date.now();
|
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) {
|
} else if (currentState === States.HOP) {
|
||||||
if (updateParabolicPath(HOP_SPEED)) {
|
if (updateParabolicPath(HOP_SPEED)) {
|
||||||
@@ -1410,14 +1418,14 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFocusedElementBounds();
|
||||||
|
|
||||||
// Update the bird's position
|
// Update the bird's position
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE) {
|
||||||
if (focusedElement !== null) {
|
if (focusedElement && !isWithinHorizontalBounds()) {
|
||||||
birdY = getFocusedElementY() - 0.5;
|
focusOnGround();
|
||||||
if (!isWithinHorizontalBounds()) {
|
|
||||||
focusOnGround();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
birdY = getFocusedY();
|
||||||
} else if (currentState === States.FLYING) {
|
} else if (currentState === States.FLYING) {
|
||||||
// Fly to target location (even if in the air)
|
// Fly to target location (even if in the air)
|
||||||
if (updateParabolicPath(FLY_SPEED)) {
|
if (updateParabolicPath(FLY_SPEED)) {
|
||||||
@@ -1425,18 +1433,13 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (focusedElement === null) {
|
const oldTargetY = targetY;
|
||||||
if (Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
|
targetY = getFocusedY();
|
||||||
// Fly to an element if the user is AFK
|
// Adjust startY to account for scrolling
|
||||||
focusOnElement();
|
startY += targetY - oldTargetY;
|
||||||
lastActionTimestamp = Date.now();
|
if (targetY < 0 || targetY > window.innerHeight) {
|
||||||
}
|
// Fly to ground if the focused element moves out of bounds
|
||||||
} else if (focusedElement !== null) {
|
focusOnGround();
|
||||||
targetY = getFocusedElementY();
|
|
||||||
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);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
@@ -2012,34 +2015,37 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getFocusedElementRandomX() {
|
function getFocusedElementRandomX() {
|
||||||
if (focusedElement === null) {
|
return Math.random() * (focusedBounds.right - focusedBounds.left) + focusedBounds.left;
|
||||||
return Math.random() * window.innerWidth;
|
|
||||||
}
|
|
||||||
const rect = focusedElement.getBoundingClientRect();
|
|
||||||
return Math.random() * (rect.right - rect.left) + rect.left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isWithinHorizontalBounds() {
|
function isWithinHorizontalBounds() {
|
||||||
if (focusedElement === null) {
|
return birdX >= focusedBounds.left && birdX <= focusedBounds.right;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const rect = focusedElement.getBoundingClientRect();
|
|
||||||
return birdX >= rect.left && birdX <= rect.right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFocusedElementY() {
|
function getFocusedY() {
|
||||||
if (focusedElement === null) {
|
return getFullWindowHeight() - focusedBounds.top;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const rect = focusedElement.getBoundingClientRect();
|
|
||||||
return window.innerHeight - rect.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() {
|
function focusOnGround() {
|
||||||
if (focusedElement === null) {
|
console.log("Focusing on ground");
|
||||||
return;
|
|
||||||
}
|
|
||||||
focusedElement = null;
|
focusedElement = null;
|
||||||
|
focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() };
|
||||||
flyTo(Math.random() * window.innerWidth, 0);
|
flyTo(Math.random() * window.innerWidth, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2061,7 +2067,23 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
}
|
}
|
||||||
const randomElement = largeElements[Math.floor(Math.random() * largeElements.length)];
|
const randomElement = largeElements[Math.floor(Math.random() * largeElements.length)];
|
||||||
focusedElement = randomElement;
|
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: getFullWindowHeight() };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rect = focusedElement.getBoundingClientRect();
|
||||||
|
focusedBounds = {
|
||||||
|
left: rect.left,
|
||||||
|
right: rect.right,
|
||||||
|
top: rect.top
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCanvasWidth() {
|
function getCanvasWidth() {
|
||||||
@@ -2077,25 +2099,14 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currentState === States.IDLE) {
|
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);
|
setState(States.HOP);
|
||||||
setAnimation(Animations.FLYING);
|
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;
|
targetX = birdX - HOP_DISTANCE;
|
||||||
} else {
|
} else {
|
||||||
targetX = birdX + HOP_DISTANCE;
|
targetX = birdX + HOP_DISTANCE;
|
||||||
}
|
}
|
||||||
targetY = y;
|
targetY = getFocusedY();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2122,6 +2133,13 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
setAnimation(Animations.FLYING);
|
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
|
* Set the current animation and reset the animation timer
|
||||||
* @param {Anim} animation
|
* @param {Anim} animation
|
||||||
@@ -2143,6 +2161,12 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
if (state === States.IDLE) {
|
if (state === States.IDLE) {
|
||||||
setAnimation(Animations.BOB);
|
setAnimation(Animations.BOB);
|
||||||
}
|
}
|
||||||
|
if (isAbsolute()) {
|
||||||
|
canvas.classList.add("birb-absolute");
|
||||||
|
} else {
|
||||||
|
canvas.classList.remove("birb-absolute");
|
||||||
|
}
|
||||||
|
setY(birdY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2157,7 +2181,15 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
|
|||||||
* @param {number} y
|
* @param {number} y
|
||||||
*/
|
*/
|
||||||
function setY(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`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,10 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.birb-absolute {
|
||||||
|
position: absolute !important;
|
||||||
|
}
|
||||||
|
|
||||||
.birb-decoration {
|
.birb-decoration {
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
Reference in New Issue
Block a user