Allow bird to follow sticky notes
74
dist/birb.js
vendored
@@ -1482,7 +1482,7 @@
|
|||||||
|
|
||||||
// Focus element constraints
|
// Focus element constraints
|
||||||
const MIN_FOCUS_ELEMENT_WIDTH = 100;
|
const MIN_FOCUS_ELEMENT_WIDTH = 100;
|
||||||
const MIN_FOCUS_ELEMENT_TOP = 80;
|
const MIN_FOCUS_ELEMENT_TOP = 40;
|
||||||
|
|
||||||
/** @type {Partial<Settings>} */
|
/** @type {Partial<Settings>} */
|
||||||
let userSettings = {};
|
let userSettings = {};
|
||||||
@@ -1606,7 +1606,7 @@
|
|||||||
insertModal(`${birdBirb()} Mode`, message);
|
insertModal(`${birdBirb()} Mode`, message);
|
||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem("2025.10.26.568", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.10.26.568"); }, false),
|
new MenuItem("2025.10.28.45", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.10.28.45"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
const styleElement = document.createElement("style");
|
||||||
@@ -1853,6 +1853,8 @@
|
|||||||
}, URL_CHECK_INTERVAL);
|
}, URL_CHECK_INTERVAL);
|
||||||
|
|
||||||
setInterval(update, UPDATE_INTERVAL);
|
setInterval(update, UPDATE_INTERVAL);
|
||||||
|
|
||||||
|
focusOnElement(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
@@ -1906,7 +1908,7 @@
|
|||||||
// Update the bird's position
|
// Update the bird's position
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE) {
|
||||||
if (focusedElement && !isWithinHorizontalBounds()) {
|
if (focusedElement && !isWithinHorizontalBounds()) {
|
||||||
focusOnGround();
|
flySomewhere();
|
||||||
}
|
}
|
||||||
birdY = getFocusedY();
|
birdY = getFocusedY();
|
||||||
} else if (currentState === States.FLYING) {
|
} else if (currentState === States.FLYING) {
|
||||||
@@ -1921,8 +1923,8 @@
|
|||||||
// Adjust startY to account for scrolling
|
// Adjust startY to account for scrolling
|
||||||
startY += targetY - oldTargetY;
|
startY += targetY - oldTargetY;
|
||||||
if (targetY < 0 || targetY > window.innerHeight) {
|
if (targetY < 0 || targetY > window.innerHeight) {
|
||||||
// Fly to ground if the focused element moves out of bounds
|
// Fly to another element or the ground if the focused element moves out of bounds
|
||||||
focusOnGround();
|
flySomewhere();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (birb.draw(SPECIES[currentSpecies])) {
|
if (birb.draw(SPECIES[currentSpecies])) {
|
||||||
@@ -2253,15 +2255,34 @@
|
|||||||
return document.documentElement.clientHeight;
|
return document.documentElement.clientHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fly to either an element or the ground
|
||||||
|
*/
|
||||||
|
function flySomewhere() {
|
||||||
|
// On mobile, always prefer to focus on an element
|
||||||
|
// If not mobile, 50% chance to focus on ground
|
||||||
|
// if ((!isMobile() && coinFlip()) || !focusOnElement()) {
|
||||||
|
// focusOnGround();
|
||||||
|
// }
|
||||||
|
if (!focusOnElement()) {
|
||||||
|
focusOnGround();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function focusOnGround() {
|
function focusOnGround() {
|
||||||
focusedElement = null;
|
focusedElement = null;
|
||||||
focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() };
|
focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() };
|
||||||
flyTo(Math.random() * window.innerWidth, 0);
|
flyTo(Math.random() * window.innerWidth, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusOnElement() {
|
/**
|
||||||
|
* Focus on an element within the viewport
|
||||||
|
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
||||||
|
* @returns Whether an element to focus on was found
|
||||||
|
*/
|
||||||
|
function focusOnElement(teleport = false) {
|
||||||
if (frozen) {
|
if (frozen) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
const elements = document.querySelectorAll("img, video, .birb-sticky-note");
|
const elements = document.querySelectorAll("img, video, .birb-sticky-note");
|
||||||
const inWindow = Array.from(elements).filter((img) => {
|
const inWindow = Array.from(elements).filter((img) => {
|
||||||
@@ -2271,20 +2292,35 @@
|
|||||||
/** @type {HTMLElement[]} */
|
/** @type {HTMLElement[]} */
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
const largeElements = Array.from(inWindow).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH);
|
const largeElements = Array.from(inWindow).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH);
|
||||||
if (largeElements.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Ensure the bird doesn't land on fixed or sticky elements
|
// Ensure the bird doesn't land on fixed or sticky elements
|
||||||
const nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
const style = window.getComputedStyle(el);
|
const style = window.getComputedStyle(el);
|
||||||
return style.position !== "fixed" && style.position !== "sticky";
|
return style.position !== "fixed" && style.position !== "sticky";
|
||||||
});
|
});
|
||||||
|
if (nonFixedElements.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const randomElement = nonFixedElements[Math.floor(Math.random() * nonFixedElements.length)];
|
const randomElement = nonFixedElements[Math.floor(Math.random() * nonFixedElements.length)];
|
||||||
focusedElement = randomElement;
|
focusedElement = randomElement;
|
||||||
log("Focusing on element: ", focusedElement);
|
log("Focusing on element: ", focusedElement);
|
||||||
updateFocusedElementBounds();
|
updateFocusedElementBounds();
|
||||||
|
if (teleport) {
|
||||||
|
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
|
} else {
|
||||||
flyTo(getFocusedElementRandomX(), getFocusedY());
|
flyTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
}
|
}
|
||||||
|
return randomElement !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} x
|
||||||
|
* @param {number} y
|
||||||
|
*/
|
||||||
|
function teleportTo(x, y) {
|
||||||
|
birdX = x;
|
||||||
|
birdY = y;
|
||||||
|
setState(States.IDLE);
|
||||||
|
}
|
||||||
|
|
||||||
function updateFocusedElementBounds() {
|
function updateFocusedElementBounds() {
|
||||||
if (focusedElement === null) {
|
if (focusedElement === null) {
|
||||||
@@ -2294,7 +2330,23 @@
|
|||||||
}
|
}
|
||||||
let { left, right, top } = focusedElement.getBoundingClientRect();
|
let { left, right, top } = focusedElement.getBoundingClientRect();
|
||||||
if (focusedElement.classList.contains("birb-sticky-note")) {
|
if (focusedElement.classList.contains("birb-sticky-note")) {
|
||||||
top -= 4 * UI_CSS_SCALE;
|
top -= 4.5 * UI_CSS_SCALE;
|
||||||
|
if (focusedBounds.left !== left) {
|
||||||
|
// Sticky note has moved
|
||||||
|
const oldWidth = focusedBounds.right - focusedBounds.left;
|
||||||
|
const newWidth = right - left;
|
||||||
|
if (oldWidth === newWidth) {
|
||||||
|
// Move bird along with note
|
||||||
|
if (currentState === States.IDLE) {
|
||||||
|
birdX += left - focusedBounds.left;
|
||||||
|
} else if (currentState === States.HOP) {
|
||||||
|
startX += left - focusedBounds.left;
|
||||||
|
startY += top - focusedBounds.top;
|
||||||
|
targetX += left - focusedBounds.left;
|
||||||
|
targetY += top - focusedBounds.top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
focusedBounds = { left, right, top };
|
focusedBounds = { left, right, top };
|
||||||
}
|
}
|
||||||
|
|||||||
76
dist/birb.user.js
vendored
@@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Pocket Bird
|
// @name Pocket Bird
|
||||||
// @namespace https://idreesinc.com
|
// @namespace https://idreesinc.com
|
||||||
// @version 2025.10.26.568
|
// @version 2025.10.28.45
|
||||||
// @description birb
|
// @description birb
|
||||||
// @author Idrees
|
// @author Idrees
|
||||||
// @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js
|
// @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js
|
||||||
@@ -1496,7 +1496,7 @@
|
|||||||
|
|
||||||
// Focus element constraints
|
// Focus element constraints
|
||||||
const MIN_FOCUS_ELEMENT_WIDTH = 100;
|
const MIN_FOCUS_ELEMENT_WIDTH = 100;
|
||||||
const MIN_FOCUS_ELEMENT_TOP = 80;
|
const MIN_FOCUS_ELEMENT_TOP = 40;
|
||||||
|
|
||||||
/** @type {Partial<Settings>} */
|
/** @type {Partial<Settings>} */
|
||||||
let userSettings = {};
|
let userSettings = {};
|
||||||
@@ -1620,7 +1620,7 @@
|
|||||||
insertModal(`${birdBirb()} Mode`, message);
|
insertModal(`${birdBirb()} Mode`, message);
|
||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem("2025.10.26.568", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.10.26.568"); }, false),
|
new MenuItem("2025.10.28.45", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.10.28.45"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
const styleElement = document.createElement("style");
|
||||||
@@ -1867,6 +1867,8 @@
|
|||||||
}, URL_CHECK_INTERVAL);
|
}, URL_CHECK_INTERVAL);
|
||||||
|
|
||||||
setInterval(update, UPDATE_INTERVAL);
|
setInterval(update, UPDATE_INTERVAL);
|
||||||
|
|
||||||
|
focusOnElement(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
@@ -1920,7 +1922,7 @@
|
|||||||
// Update the bird's position
|
// Update the bird's position
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE) {
|
||||||
if (focusedElement && !isWithinHorizontalBounds()) {
|
if (focusedElement && !isWithinHorizontalBounds()) {
|
||||||
focusOnGround();
|
flySomewhere();
|
||||||
}
|
}
|
||||||
birdY = getFocusedY();
|
birdY = getFocusedY();
|
||||||
} else if (currentState === States.FLYING) {
|
} else if (currentState === States.FLYING) {
|
||||||
@@ -1935,8 +1937,8 @@
|
|||||||
// Adjust startY to account for scrolling
|
// Adjust startY to account for scrolling
|
||||||
startY += targetY - oldTargetY;
|
startY += targetY - oldTargetY;
|
||||||
if (targetY < 0 || targetY > window.innerHeight) {
|
if (targetY < 0 || targetY > window.innerHeight) {
|
||||||
// Fly to ground if the focused element moves out of bounds
|
// Fly to another element or the ground if the focused element moves out of bounds
|
||||||
focusOnGround();
|
flySomewhere();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (birb.draw(SPECIES[currentSpecies])) {
|
if (birb.draw(SPECIES[currentSpecies])) {
|
||||||
@@ -2267,15 +2269,34 @@
|
|||||||
return document.documentElement.clientHeight;
|
return document.documentElement.clientHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fly to either an element or the ground
|
||||||
|
*/
|
||||||
|
function flySomewhere() {
|
||||||
|
// On mobile, always prefer to focus on an element
|
||||||
|
// If not mobile, 50% chance to focus on ground
|
||||||
|
// if ((!isMobile() && coinFlip()) || !focusOnElement()) {
|
||||||
|
// focusOnGround();
|
||||||
|
// }
|
||||||
|
if (!focusOnElement()) {
|
||||||
|
focusOnGround();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function focusOnGround() {
|
function focusOnGround() {
|
||||||
focusedElement = null;
|
focusedElement = null;
|
||||||
focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() };
|
focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() };
|
||||||
flyTo(Math.random() * window.innerWidth, 0);
|
flyTo(Math.random() * window.innerWidth, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusOnElement() {
|
/**
|
||||||
|
* Focus on an element within the viewport
|
||||||
|
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
||||||
|
* @returns Whether an element to focus on was found
|
||||||
|
*/
|
||||||
|
function focusOnElement(teleport = false) {
|
||||||
if (frozen) {
|
if (frozen) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
const elements = document.querySelectorAll("img, video, .birb-sticky-note");
|
const elements = document.querySelectorAll("img, video, .birb-sticky-note");
|
||||||
const inWindow = Array.from(elements).filter((img) => {
|
const inWindow = Array.from(elements).filter((img) => {
|
||||||
@@ -2285,20 +2306,35 @@
|
|||||||
/** @type {HTMLElement[]} */
|
/** @type {HTMLElement[]} */
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
const largeElements = Array.from(inWindow).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH);
|
const largeElements = Array.from(inWindow).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH);
|
||||||
if (largeElements.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Ensure the bird doesn't land on fixed or sticky elements
|
// Ensure the bird doesn't land on fixed or sticky elements
|
||||||
const nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
const style = window.getComputedStyle(el);
|
const style = window.getComputedStyle(el);
|
||||||
return style.position !== "fixed" && style.position !== "sticky";
|
return style.position !== "fixed" && style.position !== "sticky";
|
||||||
});
|
});
|
||||||
|
if (nonFixedElements.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const randomElement = nonFixedElements[Math.floor(Math.random() * nonFixedElements.length)];
|
const randomElement = nonFixedElements[Math.floor(Math.random() * nonFixedElements.length)];
|
||||||
focusedElement = randomElement;
|
focusedElement = randomElement;
|
||||||
log("Focusing on element: ", focusedElement);
|
log("Focusing on element: ", focusedElement);
|
||||||
updateFocusedElementBounds();
|
updateFocusedElementBounds();
|
||||||
|
if (teleport) {
|
||||||
|
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
|
} else {
|
||||||
flyTo(getFocusedElementRandomX(), getFocusedY());
|
flyTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
}
|
}
|
||||||
|
return randomElement !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} x
|
||||||
|
* @param {number} y
|
||||||
|
*/
|
||||||
|
function teleportTo(x, y) {
|
||||||
|
birdX = x;
|
||||||
|
birdY = y;
|
||||||
|
setState(States.IDLE);
|
||||||
|
}
|
||||||
|
|
||||||
function updateFocusedElementBounds() {
|
function updateFocusedElementBounds() {
|
||||||
if (focusedElement === null) {
|
if (focusedElement === null) {
|
||||||
@@ -2308,7 +2344,23 @@
|
|||||||
}
|
}
|
||||||
let { left, right, top } = focusedElement.getBoundingClientRect();
|
let { left, right, top } = focusedElement.getBoundingClientRect();
|
||||||
if (focusedElement.classList.contains("birb-sticky-note")) {
|
if (focusedElement.classList.contains("birb-sticky-note")) {
|
||||||
top -= 4 * UI_CSS_SCALE;
|
top -= 4.5 * UI_CSS_SCALE;
|
||||||
|
if (focusedBounds.left !== left) {
|
||||||
|
// Sticky note has moved
|
||||||
|
const oldWidth = focusedBounds.right - focusedBounds.left;
|
||||||
|
const newWidth = right - left;
|
||||||
|
if (oldWidth === newWidth) {
|
||||||
|
// Move bird along with note
|
||||||
|
if (currentState === States.IDLE) {
|
||||||
|
birdX += left - focusedBounds.left;
|
||||||
|
} else if (currentState === States.HOP) {
|
||||||
|
startX += left - focusedBounds.left;
|
||||||
|
startY += top - focusedBounds.top;
|
||||||
|
targetX += left - focusedBounds.left;
|
||||||
|
targetY += top - focusedBounds.top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
focusedBounds = { left, right, top };
|
focusedBounds = { left, right, top };
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 324 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 302 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 651 B After Width: | Height: | Size: 635 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 829 B |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 848 B |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 944 B |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 914 B |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 1018 B |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 881 B |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 953 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 829 B |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 936 B |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 856 B |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 324 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 1014 B |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 1018 B |
BIN
images/icons/transparent/icon-transparent.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Pocket Bird",
|
"name": "Pocket Bird",
|
||||||
"description": "It's a bird, in your browser. What more could you want?",
|
"description": "It's a bird, in your browser. What more could you want?",
|
||||||
"version": "2025.10.26.568",
|
"version": "2025.10.28.45",
|
||||||
"homepage_url": "https://idreesinc.com",
|
"homepage_url": "https://idreesinc.com",
|
||||||
"icons": {
|
"icons": {
|
||||||
"48": "images/icons/transparent/48x48x1.png",
|
"48": "images/icons/transparent/48x48x1.png",
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ const PET_FEATHER_BOOST = 2;
|
|||||||
|
|
||||||
// Focus element constraints
|
// Focus element constraints
|
||||||
const MIN_FOCUS_ELEMENT_WIDTH = 100;
|
const MIN_FOCUS_ELEMENT_WIDTH = 100;
|
||||||
const MIN_FOCUS_ELEMENT_TOP = 80;
|
const MIN_FOCUS_ELEMENT_TOP = 40;
|
||||||
|
|
||||||
/** @type {Partial<Settings>} */
|
/** @type {Partial<Settings>} */
|
||||||
let userSettings = {};
|
let userSettings = {};
|
||||||
@@ -472,6 +472,8 @@ Promise.all([
|
|||||||
}, URL_CHECK_INTERVAL);
|
}, URL_CHECK_INTERVAL);
|
||||||
|
|
||||||
setInterval(update, UPDATE_INTERVAL);
|
setInterval(update, UPDATE_INTERVAL);
|
||||||
|
|
||||||
|
focusOnElement(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
@@ -525,7 +527,7 @@ Promise.all([
|
|||||||
// Update the bird's position
|
// Update the bird's position
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE) {
|
||||||
if (focusedElement && !isWithinHorizontalBounds()) {
|
if (focusedElement && !isWithinHorizontalBounds()) {
|
||||||
focusOnGround();
|
flySomewhere();
|
||||||
}
|
}
|
||||||
birdY = getFocusedY();
|
birdY = getFocusedY();
|
||||||
} else if (currentState === States.FLYING) {
|
} else if (currentState === States.FLYING) {
|
||||||
@@ -540,8 +542,8 @@ Promise.all([
|
|||||||
// Adjust startY to account for scrolling
|
// Adjust startY to account for scrolling
|
||||||
startY += targetY - oldTargetY;
|
startY += targetY - oldTargetY;
|
||||||
if (targetY < 0 || targetY > window.innerHeight) {
|
if (targetY < 0 || targetY > window.innerHeight) {
|
||||||
// Fly to ground if the focused element moves out of bounds
|
// Fly to another element or the ground if the focused element moves out of bounds
|
||||||
focusOnGround();
|
flySomewhere();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (birb.draw(SPECIES[currentSpecies])) {
|
if (birb.draw(SPECIES[currentSpecies])) {
|
||||||
@@ -876,15 +878,34 @@ Promise.all([
|
|||||||
return document.documentElement.clientHeight;
|
return document.documentElement.clientHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fly to either an element or the ground
|
||||||
|
*/
|
||||||
|
function flySomewhere() {
|
||||||
|
// On mobile, always prefer to focus on an element
|
||||||
|
// If not mobile, 50% chance to focus on ground
|
||||||
|
// if ((!isMobile() && coinFlip()) || !focusOnElement()) {
|
||||||
|
// focusOnGround();
|
||||||
|
// }
|
||||||
|
if (!focusOnElement()) {
|
||||||
|
focusOnGround();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function focusOnGround() {
|
function focusOnGround() {
|
||||||
focusedElement = null;
|
focusedElement = null;
|
||||||
focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() };
|
focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() };
|
||||||
flyTo(Math.random() * window.innerWidth, 0);
|
flyTo(Math.random() * window.innerWidth, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusOnElement() {
|
/**
|
||||||
|
* Focus on an element within the viewport
|
||||||
|
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
||||||
|
* @returns Whether an element to focus on was found
|
||||||
|
*/
|
||||||
|
function focusOnElement(teleport = false) {
|
||||||
if (frozen) {
|
if (frozen) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
const elements = document.querySelectorAll("img, video, .birb-sticky-note");
|
const elements = document.querySelectorAll("img, video, .birb-sticky-note");
|
||||||
const inWindow = Array.from(elements).filter((img) => {
|
const inWindow = Array.from(elements).filter((img) => {
|
||||||
@@ -894,20 +915,35 @@ Promise.all([
|
|||||||
/** @type {HTMLElement[]} */
|
/** @type {HTMLElement[]} */
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
const largeElements = Array.from(inWindow).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH);
|
const largeElements = Array.from(inWindow).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH);
|
||||||
if (largeElements.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Ensure the bird doesn't land on fixed or sticky elements
|
// Ensure the bird doesn't land on fixed or sticky elements
|
||||||
const nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
const style = window.getComputedStyle(el);
|
const style = window.getComputedStyle(el);
|
||||||
return style.position !== "fixed" && style.position !== "sticky";
|
return style.position !== "fixed" && style.position !== "sticky";
|
||||||
});
|
});
|
||||||
|
if (nonFixedElements.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const randomElement = nonFixedElements[Math.floor(Math.random() * nonFixedElements.length)];
|
const randomElement = nonFixedElements[Math.floor(Math.random() * nonFixedElements.length)];
|
||||||
focusedElement = randomElement;
|
focusedElement = randomElement;
|
||||||
log("Focusing on element: ", focusedElement);
|
log("Focusing on element: ", focusedElement);
|
||||||
updateFocusedElementBounds();
|
updateFocusedElementBounds();
|
||||||
|
if (teleport) {
|
||||||
|
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
|
} else {
|
||||||
flyTo(getFocusedElementRandomX(), getFocusedY());
|
flyTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
}
|
}
|
||||||
|
return randomElement !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} x
|
||||||
|
* @param {number} y
|
||||||
|
*/
|
||||||
|
function teleportTo(x, y) {
|
||||||
|
birdX = x;
|
||||||
|
birdY = y;
|
||||||
|
setState(States.IDLE);
|
||||||
|
}
|
||||||
|
|
||||||
function updateFocusedElementBounds() {
|
function updateFocusedElementBounds() {
|
||||||
if (focusedElement === null) {
|
if (focusedElement === null) {
|
||||||
@@ -917,7 +953,23 @@ Promise.all([
|
|||||||
}
|
}
|
||||||
let { left, right, top } = focusedElement.getBoundingClientRect();
|
let { left, right, top } = focusedElement.getBoundingClientRect();
|
||||||
if (focusedElement.classList.contains("birb-sticky-note")) {
|
if (focusedElement.classList.contains("birb-sticky-note")) {
|
||||||
top -= 4 * UI_CSS_SCALE;
|
top -= 4.5 * UI_CSS_SCALE;
|
||||||
|
if (focusedBounds.left !== left) {
|
||||||
|
// Sticky note has moved
|
||||||
|
const oldWidth = focusedBounds.right - focusedBounds.left;
|
||||||
|
const newWidth = right - left;
|
||||||
|
if (oldWidth === newWidth) {
|
||||||
|
// Move bird along with note
|
||||||
|
if (currentState === States.IDLE) {
|
||||||
|
birdX += left - focusedBounds.left;
|
||||||
|
} else if (currentState === States.HOP) {
|
||||||
|
startX += left - focusedBounds.left;
|
||||||
|
startY += top - focusedBounds.top;
|
||||||
|
targetX += left - focusedBounds.left;
|
||||||
|
targetY += top - focusedBounds.top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
focusedBounds = { left, right, top };
|
focusedBounds = { left, right, top };
|
||||||
}
|
}
|
||||||
@@ -983,6 +1035,10 @@ Promise.all([
|
|||||||
birb.setY(birdY);
|
birb.setY(birdY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function coinFlip() {
|
||||||
|
return Math.random() < 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||