diff --git a/dist/birb.js b/dist/birb.js index 359d9a5..fdbf6c4 100644 --- a/dist/birb.js +++ b/dist/birb.js @@ -1482,7 +1482,7 @@ // Focus element constraints const MIN_FOCUS_ELEMENT_WIDTH = 100; - const MIN_FOCUS_ELEMENT_TOP = 80; + const MIN_FOCUS_ELEMENT_TOP = 40; /** @type {Partial} */ let userSettings = {}; @@ -1606,7 +1606,7 @@ insertModal(`${birdBirb()} Mode`, message); }), 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"); @@ -1853,6 +1853,8 @@ }, URL_CHECK_INTERVAL); setInterval(update, UPDATE_INTERVAL); + + focusOnElement(true); } function update() { @@ -1906,7 +1908,7 @@ // Update the bird's position if (currentState === States.IDLE) { if (focusedElement && !isWithinHorizontalBounds()) { - focusOnGround(); + flySomewhere(); } birdY = getFocusedY(); } else if (currentState === States.FLYING) { @@ -1921,8 +1923,8 @@ // 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(); + // Fly to another element or the ground if the focused element moves out of bounds + flySomewhere(); } if (birb.draw(SPECIES[currentSpecies])) { @@ -1952,19 +1954,19 @@ */ function createWindow(id, title, contentElement, onClose) { const window = makeElement("birb-window", undefined, id); - + const header = makeElement("birb-window-header"); const titleElement = makeElement("birb-window-title"); titleElement.textContent = title; const closeButton = makeElement("birb-window-close"); closeButton.textContent = "x"; - + header.appendChild(titleElement); header.appendChild(closeButton); - + const contentWrapper = makeElement("birb-window-content"); contentWrapper.appendChild(contentElement); - + window.appendChild(header); window.appendChild(contentWrapper); @@ -2123,20 +2125,20 @@ const generateDescription = (/** @type {string} */ speciesId) => { const type = SPECIES[speciesId]; const unlocked = unlockedSpecies.includes(speciesId); - + const boldName = document.createElement("b"); boldName.textContent = type.name; - + const spacer = document.createElement("div"); spacer.style.height = "0.3em"; - + const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description); - + const fragment = document.createDocumentFragment(); fragment.appendChild(boldName); fragment.appendChild(spacer); fragment.appendChild(descText); - + return fragment; }; @@ -2253,15 +2255,34 @@ 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() { focusedElement = null; focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() }; 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) { - return; + return false; } const elements = document.querySelectorAll("img, video, .birb-sticky-note"); const inWindow = Array.from(elements).filter((img) => { @@ -2271,19 +2292,34 @@ /** @type {HTMLElement[]} */ // @ts-expect-error 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 const nonFixedElements = largeElements.filter((el) => { const style = window.getComputedStyle(el); return style.position !== "fixed" && style.position !== "sticky"; }); + if (nonFixedElements.length === 0) { + return false; + } const randomElement = nonFixedElements[Math.floor(Math.random() * nonFixedElements.length)]; focusedElement = randomElement; log("Focusing on element: ", focusedElement); updateFocusedElementBounds(); - flyTo(getFocusedElementRandomX(), getFocusedY()); + if (teleport) { + teleportTo(getFocusedElementRandomX(), getFocusedY()); + } else { + 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() { @@ -2294,7 +2330,23 @@ } let { left, right, top } = focusedElement.getBoundingClientRect(); 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 }; } diff --git a/dist/birb.user.js b/dist/birb.user.js index 1aa8fb0..fc7845f 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.26.568 +// @version 2025.10.28.45 // @description birb // @author Idrees // @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js @@ -1496,7 +1496,7 @@ // Focus element constraints const MIN_FOCUS_ELEMENT_WIDTH = 100; - const MIN_FOCUS_ELEMENT_TOP = 80; + const MIN_FOCUS_ELEMENT_TOP = 40; /** @type {Partial} */ let userSettings = {}; @@ -1620,7 +1620,7 @@ insertModal(`${birdBirb()} Mode`, message); }), 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"); @@ -1867,6 +1867,8 @@ }, URL_CHECK_INTERVAL); setInterval(update, UPDATE_INTERVAL); + + focusOnElement(true); } function update() { @@ -1920,7 +1922,7 @@ // Update the bird's position if (currentState === States.IDLE) { if (focusedElement && !isWithinHorizontalBounds()) { - focusOnGround(); + flySomewhere(); } birdY = getFocusedY(); } else if (currentState === States.FLYING) { @@ -1935,8 +1937,8 @@ // 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(); + // Fly to another element or the ground if the focused element moves out of bounds + flySomewhere(); } if (birb.draw(SPECIES[currentSpecies])) { @@ -1966,19 +1968,19 @@ */ function createWindow(id, title, contentElement, onClose) { const window = makeElement("birb-window", undefined, id); - + const header = makeElement("birb-window-header"); const titleElement = makeElement("birb-window-title"); titleElement.textContent = title; const closeButton = makeElement("birb-window-close"); closeButton.textContent = "x"; - + header.appendChild(titleElement); header.appendChild(closeButton); - + const contentWrapper = makeElement("birb-window-content"); contentWrapper.appendChild(contentElement); - + window.appendChild(header); window.appendChild(contentWrapper); @@ -2137,20 +2139,20 @@ const generateDescription = (/** @type {string} */ speciesId) => { const type = SPECIES[speciesId]; const unlocked = unlockedSpecies.includes(speciesId); - + const boldName = document.createElement("b"); boldName.textContent = type.name; - + const spacer = document.createElement("div"); spacer.style.height = "0.3em"; - + const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description); - + const fragment = document.createDocumentFragment(); fragment.appendChild(boldName); fragment.appendChild(spacer); fragment.appendChild(descText); - + return fragment; }; @@ -2267,15 +2269,34 @@ 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() { focusedElement = null; focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() }; 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) { - return; + return false; } const elements = document.querySelectorAll("img, video, .birb-sticky-note"); const inWindow = Array.from(elements).filter((img) => { @@ -2285,19 +2306,34 @@ /** @type {HTMLElement[]} */ // @ts-expect-error 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 const nonFixedElements = largeElements.filter((el) => { const style = window.getComputedStyle(el); return style.position !== "fixed" && style.position !== "sticky"; }); + if (nonFixedElements.length === 0) { + return false; + } const randomElement = nonFixedElements[Math.floor(Math.random() * nonFixedElements.length)]; focusedElement = randomElement; log("Focusing on element: ", focusedElement); updateFocusedElementBounds(); - flyTo(getFocusedElementRandomX(), getFocusedY()); + if (teleport) { + teleportTo(getFocusedElementRandomX(), getFocusedY()); + } else { + 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() { @@ -2308,7 +2344,23 @@ } let { left, right, top } = focusedElement.getBoundingClientRect(); 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 }; } diff --git a/images/icons/transparent/1024x1024x1.png b/images/icons/transparent/1024x1024x1.png index 83a8c9d..e915b5c 100644 Binary files a/images/icons/transparent/1024x1024x1.png and b/images/icons/transparent/1024x1024x1.png differ diff --git a/images/icons/transparent/1024x768x1.png b/images/icons/transparent/1024x768x1.png index 265ffb0..c2dad99 100644 Binary files a/images/icons/transparent/1024x768x1.png and b/images/icons/transparent/1024x768x1.png differ diff --git a/images/icons/transparent/128x128x1.png b/images/icons/transparent/128x128x1.png index 1582c70..0fd3b1d 100644 Binary files a/images/icons/transparent/128x128x1.png and b/images/icons/transparent/128x128x1.png differ diff --git a/images/icons/transparent/128x128x2.png b/images/icons/transparent/128x128x2.png index 1628361..62e461d 100644 Binary files a/images/icons/transparent/128x128x2.png and b/images/icons/transparent/128x128x2.png differ diff --git a/images/icons/transparent/16x16x1.png b/images/icons/transparent/16x16x1.png index bbdbfc1..479ed9a 100644 Binary files a/images/icons/transparent/16x16x1.png and b/images/icons/transparent/16x16x1.png differ diff --git a/images/icons/transparent/16x16x2.png b/images/icons/transparent/16x16x2.png index cedb0da..ab53237 100644 Binary files a/images/icons/transparent/16x16x2.png and b/images/icons/transparent/16x16x2.png differ diff --git a/images/icons/transparent/256x256x1.png b/images/icons/transparent/256x256x1.png index 1628361..62e461d 100644 Binary files a/images/icons/transparent/256x256x1.png and b/images/icons/transparent/256x256x1.png differ diff --git a/images/icons/transparent/256x256x2.png b/images/icons/transparent/256x256x2.png index fbde92a..aa5c5da 100644 Binary files a/images/icons/transparent/256x256x2.png and b/images/icons/transparent/256x256x2.png differ diff --git a/images/icons/transparent/27x20x2.png b/images/icons/transparent/27x20x2.png index 1d18a2d..bfaf3f1 100644 Binary files a/images/icons/transparent/27x20x2.png and b/images/icons/transparent/27x20x2.png differ diff --git a/images/icons/transparent/27x20x3.png b/images/icons/transparent/27x20x3.png index 970600a..85446fa 100644 Binary files a/images/icons/transparent/27x20x3.png and b/images/icons/transparent/27x20x3.png differ diff --git a/images/icons/transparent/29x29x2.png b/images/icons/transparent/29x29x2.png index e0162a5..a983d88 100644 Binary files a/images/icons/transparent/29x29x2.png and b/images/icons/transparent/29x29x2.png differ diff --git a/images/icons/transparent/29x29x3.png b/images/icons/transparent/29x29x3.png index e20f51a..ab38db4 100644 Binary files a/images/icons/transparent/29x29x3.png and b/images/icons/transparent/29x29x3.png differ diff --git a/images/icons/transparent/32x24x2.png b/images/icons/transparent/32x24x2.png index c6aa289..b33086f 100644 Binary files a/images/icons/transparent/32x24x2.png and b/images/icons/transparent/32x24x2.png differ diff --git a/images/icons/transparent/32x24x3.png b/images/icons/transparent/32x24x3.png index 571c67a..310e291 100644 Binary files a/images/icons/transparent/32x24x3.png and b/images/icons/transparent/32x24x3.png differ diff --git a/images/icons/transparent/32x32x1.png b/images/icons/transparent/32x32x1.png index cedb0da..ab53237 100644 Binary files a/images/icons/transparent/32x32x1.png and b/images/icons/transparent/32x32x1.png differ diff --git a/images/icons/transparent/32x32x2.png b/images/icons/transparent/32x32x2.png index 68ab1b4..899855d 100644 Binary files a/images/icons/transparent/32x32x2.png and b/images/icons/transparent/32x32x2.png differ diff --git a/images/icons/transparent/48x48x1.png b/images/icons/transparent/48x48x1.png index bba995f..e007b96 100644 Binary files a/images/icons/transparent/48x48x1.png and b/images/icons/transparent/48x48x1.png differ diff --git a/images/icons/transparent/512x512x1.png b/images/icons/transparent/512x512x1.png index fbde92a..aa5c5da 100644 Binary files a/images/icons/transparent/512x512x1.png and b/images/icons/transparent/512x512x1.png differ diff --git a/images/icons/transparent/512x512x2.png b/images/icons/transparent/512x512x2.png index 83a8c9d..e915b5c 100644 Binary files a/images/icons/transparent/512x512x2.png and b/images/icons/transparent/512x512x2.png differ diff --git a/images/icons/transparent/60x45x2.png b/images/icons/transparent/60x45x2.png index 4e656a7..1f035ac 100644 Binary files a/images/icons/transparent/60x45x2.png and b/images/icons/transparent/60x45x2.png differ diff --git a/images/icons/transparent/60x45x3.png b/images/icons/transparent/60x45x3.png index 198e8ef..2c1cead 100644 Binary files a/images/icons/transparent/60x45x3.png and b/images/icons/transparent/60x45x3.png differ diff --git a/images/icons/transparent/67x50x2.png b/images/icons/transparent/67x50x2.png index d458660..bbf433c 100644 Binary files a/images/icons/transparent/67x50x2.png and b/images/icons/transparent/67x50x2.png differ diff --git a/images/icons/transparent/74x55x2.png b/images/icons/transparent/74x55x2.png index 9309030..7d1a1d0 100644 Binary files a/images/icons/transparent/74x55x2.png and b/images/icons/transparent/74x55x2.png differ diff --git a/images/icons/transparent/96x96x1.png b/images/icons/transparent/96x96x1.png index 9054b9b..98f536b 100644 Binary files a/images/icons/transparent/96x96x1.png and b/images/icons/transparent/96x96x1.png differ diff --git a/images/icons/transparent/icon-transparent.png b/images/icons/transparent/icon-transparent.png new file mode 100644 index 0000000..bb3dc2f Binary files /dev/null and b/images/icons/transparent/icon-transparent.png differ diff --git a/manifest.json b/manifest.json index b81936c..9169f84 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "Pocket Bird", "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", "icons": { "48": "images/icons/transparent/48x48x1.png", diff --git a/src/application.js b/src/application.js index 225a5cb..1e099d5 100644 --- a/src/application.js +++ b/src/application.js @@ -101,7 +101,7 @@ const PET_FEATHER_BOOST = 2; // Focus element constraints const MIN_FOCUS_ELEMENT_WIDTH = 100; -const MIN_FOCUS_ELEMENT_TOP = 80; +const MIN_FOCUS_ELEMENT_TOP = 40; /** @type {Partial} */ let userSettings = {}; @@ -472,6 +472,8 @@ Promise.all([ }, URL_CHECK_INTERVAL); setInterval(update, UPDATE_INTERVAL); + + focusOnElement(true); } function update() { @@ -525,7 +527,7 @@ Promise.all([ // Update the bird's position if (currentState === States.IDLE) { if (focusedElement && !isWithinHorizontalBounds()) { - focusOnGround(); + flySomewhere(); } birdY = getFocusedY(); } else if (currentState === States.FLYING) { @@ -540,8 +542,8 @@ Promise.all([ // 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(); + // Fly to another element or the ground if the focused element moves out of bounds + flySomewhere(); } if (birb.draw(SPECIES[currentSpecies])) { @@ -571,19 +573,19 @@ Promise.all([ */ function createWindow(id, title, contentElement, onClose) { const window = makeElement("birb-window", undefined, id); - + const header = makeElement("birb-window-header"); const titleElement = makeElement("birb-window-title"); titleElement.textContent = title; const closeButton = makeElement("birb-window-close"); closeButton.textContent = "x"; - + header.appendChild(titleElement); header.appendChild(closeButton); - + const contentWrapper = makeElement("birb-window-content"); contentWrapper.appendChild(contentElement); - + window.appendChild(header); window.appendChild(contentWrapper); @@ -746,20 +748,20 @@ Promise.all([ const generateDescription = (/** @type {string} */ speciesId) => { const type = SPECIES[speciesId]; const unlocked = unlockedSpecies.includes(speciesId); - + const boldName = document.createElement("b"); boldName.textContent = type.name; - + const spacer = document.createElement("div"); spacer.style.height = "0.3em"; - + const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description); - + const fragment = document.createDocumentFragment(); fragment.appendChild(boldName); fragment.appendChild(spacer); fragment.appendChild(descText); - + return fragment; }; @@ -876,15 +878,34 @@ Promise.all([ 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() { focusedElement = null; focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() }; 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) { - return; + return false; } const elements = document.querySelectorAll("img, video, .birb-sticky-note"); const inWindow = Array.from(elements).filter((img) => { @@ -894,19 +915,34 @@ Promise.all([ /** @type {HTMLElement[]} */ // @ts-expect-error 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 const nonFixedElements = largeElements.filter((el) => { const style = window.getComputedStyle(el); return style.position !== "fixed" && style.position !== "sticky"; }); + if (nonFixedElements.length === 0) { + return false; + } const randomElement = nonFixedElements[Math.floor(Math.random() * nonFixedElements.length)]; focusedElement = randomElement; log("Focusing on element: ", focusedElement); updateFocusedElementBounds(); - flyTo(getFocusedElementRandomX(), getFocusedY()); + if (teleport) { + teleportTo(getFocusedElementRandomX(), getFocusedY()); + } else { + 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() { @@ -917,7 +953,23 @@ Promise.all([ } let { left, right, top } = focusedElement.getBoundingClientRect(); 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 }; } @@ -983,6 +1035,10 @@ Promise.all([ birb.setY(birdY); } + function coinFlip() { + return Math.random() < 0.5; + } + // Helper functions /**