Allow bird to follow sticky notes

This commit is contained in:
Idrees Hassan
2025-10-28 17:15:19 -04:00
parent c832011011
commit 314ded2562
29 changed files with 224 additions and 64 deletions

View File

@@ -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<Settings>} */
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
/**