From 66284b0af8bdc4e59cc78b7a87d460871ce39f7a Mon Sep 17 00:00:00 2001 From: Idrees Hassan Date: Tue, 28 Oct 2025 23:11:46 -0400 Subject: [PATCH] Fix bird not flying to right location when iOS address bar is minimized --- dist/birb.js | 62 ++++++++++++++++++++++++-------------------- dist/birb.user.js | 64 +++++++++++++++++++++++++--------------------- manifest.json | 2 +- src/application.js | 39 +++++++++------------------- src/birb.js | 5 ++-- src/shared.js | 21 +++++++++++++++ 6 files changed, 106 insertions(+), 87 deletions(-) diff --git a/dist/birb.js b/dist/birb.js index 77b4e56..193c63c 100644 --- a/dist/birb.js +++ b/dist/birb.js @@ -187,6 +187,27 @@ return layer; } + /** + * The height of the inner browser window + * Will be the same as getFixedWindowHeight() on most browsers + * On iOS, it will vary to be the height excluding the current address bar size (potentially greater than fixed height) + */ + function getWindowHeight() { + // Necessary because iOS 26 Safari is terrible and won't render + // fixed/sticky elements behind the address bar + return window.innerHeight; + } + + /** + * The fixed height of the inner browser window + * Will be the same as getWindowHeight() on most browsers + * On iOS, it will always be the height of the window when the address bar is fully expanded + * @returns The true height of the inner browser window + */ + function getFixedWindowHeight() { + return document.documentElement.clientHeight; + } + /** Indicators for parts of the base bird sprite sheet */ const Sprite = { THEME_HIGHLIGHT: "theme-highlight", @@ -752,7 +773,8 @@ let bottom; if (this.isAbsolutePositioned) { // Position is absolute, convert from fixed - bottom = y - window.scrollY; + // Account for address bar shrinkage on iOS + bottom = y - window.scrollY - (getWindowHeight() - getFixedWindowHeight()); } else { // Position is fixed bottom = y; @@ -1652,7 +1674,7 @@ insertModal(`${birdBirb()} Mode`, message); }), new Separator(), - new MenuItem("2025.10.28.95", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.10.28.95"); }, false), + new MenuItem("2025.10.28.152", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.10.28.152"); }, false), ]; const styleElement = document.createElement("style"); @@ -1968,7 +1990,7 @@ targetY = getFocusedY(); // Adjust startY to account for scrolling startY += targetY - oldTargetY; - if (targetY < 0 || targetY > window.innerHeight) { + if (targetY < 0 || targetY > getWindowHeight()) { // Fly to another element or the ground if the focused element moves out of bounds flySomewhere(); } @@ -2098,8 +2120,8 @@ return; } const y = parseInt(feather.style.top || "0") + FEATHER_FALL_SPEED; - feather.style.top = `${Math.min(y, window.innerHeight - feather.offsetHeight)}px`; - if (y < window.innerHeight - feather.offsetHeight) { + feather.style.top = `${Math.min(y, getWindowHeight() - feather.offsetHeight)}px`; + if (y < getWindowHeight() - feather.offsetHeight) { feather.style.left = `${Math.sin(3.14 * 2 * (ticks / 120)) * 25}px`; } } @@ -2109,7 +2131,7 @@ */ function centerElement(element) { element.style.left = `${window.innerWidth / 2 - element.offsetWidth / 2}px`; - element.style.top = `${window.innerHeight / 2 - element.offsetHeight / 2}px`; + element.style.top = `${getWindowHeight() / 2 - element.offsetHeight / 2}px`; } /** @@ -2141,7 +2163,7 @@ // Right side x -= (menu.offsetWidth + offset) * UI_CSS_SCALE; } - if (y > window.innerHeight / 2) { + if (y > getWindowHeight() / 2) { // Top side y -= (menu.offsetHeight + offset + 10) * UI_CSS_SCALE; } else { @@ -2256,7 +2278,7 @@ const dy = targetY - startY; const distance = Math.sqrt(dx * dx + dy * dy); const time = Date.now() - stateStart; - if (distance > Math.max(window.innerWidth, window.innerHeight) / 2) { + if (distance > Math.max(window.innerWidth, getWindowHeight()) / 2) { speed *= 1.3; } const amount = Math.min(1, time / (distance / speed)); @@ -2282,23 +2304,7 @@ } function getFocusedY() { - 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; + return getWindowHeight() - focusedBounds.top; } /** @@ -2317,7 +2323,7 @@ function focusOnGround() { focusedElement = null; - focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() }; + updateFocusedElementBounds(); flyTo(Math.random() * window.innerWidth, 0); } @@ -2333,7 +2339,7 @@ const elements = document.querySelectorAll("img, video, .birb-sticky-note"); const inWindow = Array.from(elements).filter((img) => { const rect = img.getBoundingClientRect(); - return rect.left >= 0 && rect.top >= MIN_FOCUS_ELEMENT_TOP && rect.right <= window.innerWidth && rect.top <= window.innerHeight; + return rect.left >= 0 && rect.top >= MIN_FOCUS_ELEMENT_TOP && rect.right <= window.innerWidth && rect.top <= getWindowHeight(); }); /** @type {HTMLElement[]} */ // @ts-expect-error @@ -2371,7 +2377,7 @@ function updateFocusedElementBounds() { if (focusedElement === null) { // Update ground location to bottom of window - focusedBounds = { left: 0, right: window.innerWidth, top: getFullWindowHeight() }; + focusedBounds = { left: 0, right: window.innerWidth, top: getWindowHeight() }; return; } let { left, right, top } = focusedElement.getBoundingClientRect(); diff --git a/dist/birb.user.js b/dist/birb.user.js index 412cb27..6aa4f48 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.28.95 +// @version 2025.10.28.152 // @description birb // @author Idrees // @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js @@ -201,6 +201,27 @@ return layer; } + /** + * The height of the inner browser window + * Will be the same as getFixedWindowHeight() on most browsers + * On iOS, it will vary to be the height excluding the current address bar size (potentially greater than fixed height) + */ + function getWindowHeight() { + // Necessary because iOS 26 Safari is terrible and won't render + // fixed/sticky elements behind the address bar + return window.innerHeight; + } + + /** + * The fixed height of the inner browser window + * Will be the same as getWindowHeight() on most browsers + * On iOS, it will always be the height of the window when the address bar is fully expanded + * @returns The true height of the inner browser window + */ + function getFixedWindowHeight() { + return document.documentElement.clientHeight; + } + /** Indicators for parts of the base bird sprite sheet */ const Sprite = { THEME_HIGHLIGHT: "theme-highlight", @@ -766,7 +787,8 @@ let bottom; if (this.isAbsolutePositioned) { // Position is absolute, convert from fixed - bottom = y - window.scrollY; + // Account for address bar shrinkage on iOS + bottom = y - window.scrollY - (getWindowHeight() - getFixedWindowHeight()); } else { // Position is fixed bottom = y; @@ -1666,7 +1688,7 @@ insertModal(`${birdBirb()} Mode`, message); }), new Separator(), - new MenuItem("2025.10.28.95", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.10.28.95"); }, false), + new MenuItem("2025.10.28.152", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.10.28.152"); }, false), ]; const styleElement = document.createElement("style"); @@ -1982,7 +2004,7 @@ targetY = getFocusedY(); // Adjust startY to account for scrolling startY += targetY - oldTargetY; - if (targetY < 0 || targetY > window.innerHeight) { + if (targetY < 0 || targetY > getWindowHeight()) { // Fly to another element or the ground if the focused element moves out of bounds flySomewhere(); } @@ -2112,8 +2134,8 @@ return; } const y = parseInt(feather.style.top || "0") + FEATHER_FALL_SPEED; - feather.style.top = `${Math.min(y, window.innerHeight - feather.offsetHeight)}px`; - if (y < window.innerHeight - feather.offsetHeight) { + feather.style.top = `${Math.min(y, getWindowHeight() - feather.offsetHeight)}px`; + if (y < getWindowHeight() - feather.offsetHeight) { feather.style.left = `${Math.sin(3.14 * 2 * (ticks / 120)) * 25}px`; } } @@ -2123,7 +2145,7 @@ */ function centerElement(element) { element.style.left = `${window.innerWidth / 2 - element.offsetWidth / 2}px`; - element.style.top = `${window.innerHeight / 2 - element.offsetHeight / 2}px`; + element.style.top = `${getWindowHeight() / 2 - element.offsetHeight / 2}px`; } /** @@ -2155,7 +2177,7 @@ // Right side x -= (menu.offsetWidth + offset) * UI_CSS_SCALE; } - if (y > window.innerHeight / 2) { + if (y > getWindowHeight() / 2) { // Top side y -= (menu.offsetHeight + offset + 10) * UI_CSS_SCALE; } else { @@ -2270,7 +2292,7 @@ const dy = targetY - startY; const distance = Math.sqrt(dx * dx + dy * dy); const time = Date.now() - stateStart; - if (distance > Math.max(window.innerWidth, window.innerHeight) / 2) { + if (distance > Math.max(window.innerWidth, getWindowHeight()) / 2) { speed *= 1.3; } const amount = Math.min(1, time / (distance / speed)); @@ -2296,23 +2318,7 @@ } function getFocusedY() { - 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; + return getWindowHeight() - focusedBounds.top; } /** @@ -2331,7 +2337,7 @@ function focusOnGround() { focusedElement = null; - focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() }; + updateFocusedElementBounds(); flyTo(Math.random() * window.innerWidth, 0); } @@ -2347,7 +2353,7 @@ const elements = document.querySelectorAll("img, video, .birb-sticky-note"); const inWindow = Array.from(elements).filter((img) => { const rect = img.getBoundingClientRect(); - return rect.left >= 0 && rect.top >= MIN_FOCUS_ELEMENT_TOP && rect.right <= window.innerWidth && rect.top <= window.innerHeight; + return rect.left >= 0 && rect.top >= MIN_FOCUS_ELEMENT_TOP && rect.right <= window.innerWidth && rect.top <= getWindowHeight(); }); /** @type {HTMLElement[]} */ // @ts-expect-error @@ -2385,7 +2391,7 @@ function updateFocusedElementBounds() { if (focusedElement === null) { // Update ground location to bottom of window - focusedBounds = { left: 0, right: window.innerWidth, top: getFullWindowHeight() }; + focusedBounds = { left: 0, right: window.innerWidth, top: getWindowHeight() }; return; } let { left, right, top } = focusedElement.getBoundingClientRect(); diff --git a/manifest.json b/manifest.json index dcadeb0..8a01caa 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.28.95", + "version": "2025.10.28.152", "homepage_url": "https://idreesinc.com", "icons": { "48": "images/icons/transparent/48x48x1.png", diff --git a/src/application.js b/src/application.js index 2e62b05..6591412 100644 --- a/src/application.js +++ b/src/application.js @@ -15,7 +15,8 @@ import { log, debug, error, - getLayer + getLayer, + getWindowHeight } from './shared.js'; import { Sprite, @@ -541,7 +542,7 @@ Promise.all([ targetY = getFocusedY(); // Adjust startY to account for scrolling startY += targetY - oldTargetY; - if (targetY < 0 || targetY > window.innerHeight) { + if (targetY < 0 || targetY > getWindowHeight()) { // Fly to another element or the ground if the focused element moves out of bounds flySomewhere(); } @@ -674,8 +675,8 @@ Promise.all([ return; } const y = parseInt(feather.style.top || "0") + FEATHER_FALL_SPEED; - feather.style.top = `${Math.min(y, window.innerHeight - feather.offsetHeight)}px`; - if (y < window.innerHeight - feather.offsetHeight) { + feather.style.top = `${Math.min(y, getWindowHeight() - feather.offsetHeight)}px`; + if (y < getWindowHeight() - feather.offsetHeight) { feather.style.left = `${Math.sin(3.14 * 2 * (ticks / 120)) * 25}px`; } } @@ -685,7 +686,7 @@ Promise.all([ */ function centerElement(element) { element.style.left = `${window.innerWidth / 2 - element.offsetWidth / 2}px`; - element.style.top = `${window.innerHeight / 2 - element.offsetHeight / 2}px`; + element.style.top = `${getWindowHeight() / 2 - element.offsetHeight / 2}px`; } /** @@ -717,7 +718,7 @@ Promise.all([ // Right side x -= (menu.offsetWidth + offset) * UI_CSS_SCALE; } - if (y > window.innerHeight / 2) { + if (y > getWindowHeight() / 2) { // Top side y -= (menu.offsetHeight + offset + 10) * UI_CSS_SCALE; } else { @@ -833,7 +834,7 @@ Promise.all([ const dy = targetY - startY; const distance = Math.sqrt(dx * dx + dy * dy); const time = Date.now() - stateStart; - if (distance > Math.max(window.innerWidth, window.innerHeight) / 2) { + if (distance > Math.max(window.innerWidth, getWindowHeight()) / 2) { speed *= 1.3; } const amount = Math.min(1, time / (distance / speed)); @@ -859,23 +860,7 @@ Promise.all([ } function getFocusedY() { - 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; + return getWindowHeight() - focusedBounds.top; } /** @@ -894,7 +879,7 @@ Promise.all([ function focusOnGround() { focusedElement = null; - focusedBounds = { left: 0, right: window.innerWidth, top: getSafeWindowHeight() }; + updateFocusedElementBounds(); flyTo(Math.random() * window.innerWidth, 0); } @@ -910,7 +895,7 @@ Promise.all([ const elements = document.querySelectorAll("img, video, .birb-sticky-note"); const inWindow = Array.from(elements).filter((img) => { const rect = img.getBoundingClientRect(); - return rect.left >= 0 && rect.top >= MIN_FOCUS_ELEMENT_TOP && rect.right <= window.innerWidth && rect.top <= window.innerHeight; + return rect.left >= 0 && rect.top >= MIN_FOCUS_ELEMENT_TOP && rect.right <= window.innerWidth && rect.top <= getWindowHeight(); }); /** @type {HTMLElement[]} */ // @ts-expect-error @@ -948,7 +933,7 @@ Promise.all([ function updateFocusedElementBounds() { if (focusedElement === null) { // Update ground location to bottom of window - focusedBounds = { left: 0, right: window.innerWidth, top: getFullWindowHeight() }; + focusedBounds = { left: 0, right: window.innerWidth, top: getWindowHeight() }; return; } let { left, right, top } = focusedElement.getBoundingClientRect(); diff --git a/src/birb.js b/src/birb.js index afc86f8..a2baeb6 100644 --- a/src/birb.js +++ b/src/birb.js @@ -1,4 +1,4 @@ -import { Directions, getLayer } from './shared.js'; +import { Directions, getLayer, getWindowHeight, getFixedWindowHeight } from './shared.js'; import Layer from './layer.js'; import Frame from './frame.js'; import Anim from './anim.js'; @@ -201,7 +201,8 @@ export class Birb { let bottom; if (this.isAbsolutePositioned) { // Position is absolute, convert from fixed - bottom = y - window.scrollY; + // Account for address bar shrinkage on iOS + bottom = y - window.scrollY - (getWindowHeight() - getFixedWindowHeight()); } else { // Position is fixed bottom = y; diff --git a/src/shared.js b/src/shared.js index 30fe9aa..80c9889 100644 --- a/src/shared.js +++ b/src/shared.js @@ -182,4 +182,25 @@ export function getLayer(spriteSheet, spriteIndex, width) { layer.push(spriteSheet[y].slice(spriteIndex * width, (spriteIndex + 1) * width)); } return layer; +} + +/** + * The height of the inner browser window + * Will be the same as getFixedWindowHeight() on most browsers + * On iOS, it will vary to be the height excluding the current address bar size (potentially greater than fixed height) + */ +export function getWindowHeight() { + // Necessary because iOS 26 Safari is terrible and won't render + // fixed/sticky elements behind the address bar + return window.innerHeight; +} + +/** + * The fixed height of the inner browser window + * Will be the same as getWindowHeight() on most browsers + * On iOS, it will always be the height of the window when the address bar is fully expanded + * @returns The true height of the inner browser window + */ +export function getFixedWindowHeight() { + return document.documentElement.clientHeight; } \ No newline at end of file