Start work on absolute positioning

This commit is contained in:
Idrees Hassan
2025-09-16 19:47:11 -04:00
parent a94cc92c52
commit 6bb587c96f
5 changed files with 712 additions and 595 deletions

432
birb.js
View File

@@ -676,7 +676,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
let ticks = 0;
// Bird's current position
let birdY = 0;
let birdX = 40;
let birdX = 0;
// Bird's starting position (when flying)
let startX = 0;
let startY = 0;
@@ -695,6 +695,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
/** @type {StickyNote[]} */
let stickyNotes = [];
/**
* @returns {boolean} Whether the script is running in a userscript extension context
*/
@@ -798,6 +799,207 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
return settings().birbMode ? "Birb" : "Bird";
}
function init() {
if (window !== window.top) {
// Skip installation if within an iframe
return;
}
// Preload font
const MONOCRAFT_SRC = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
const fontLink = document.createElement("link");
fontLink.rel = "stylesheet";
fontLink.href = `url(${MONOCRAFT_SRC}) format('opentype')`;
document.head.appendChild(fontLink);
// Add stylesheet font-face
const fontFace = `
@font-face {
font-family: 'Monocraft';
src: url(${MONOCRAFT_SRC}) format('opentype');
font-weight: normal;
font-style: normal;
}
`;
const fontStyle = document.createElement("style");
fontStyle.innerHTML = fontFace;
document.head.appendChild(fontStyle);
load();
styleElement.innerHTML = STYLESHEET;
document.head.appendChild(styleElement);
canvas.id = "birb";
canvas.width = birbFrames.base.getPixels()[0].length * CANVAS_PIXEL_SIZE;
canvas.height = SPRITE_HEIGHT * CANVAS_PIXEL_SIZE;
document.body.appendChild(canvas);
/** @type {NodeJS.Timeout} */
let scrollTimeout;
window.addEventListener("scroll", () => {
// TODO: Only do this if focused on the ground
if (focusedElement === null && currentState !== States.FLYING) {
canvas.style.transition = "opacity 0.2s";
canvas.style.opacity = "0";
}
lastActionTimestamp = Date.now();
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
canvas.style.transition = "opacity 0.4s";
canvas.style.opacity = "1";
}, 100);
});
onClick(document, (e) => {
lastActionTimestamp = Date.now();
if (e.target instanceof Node && document.querySelector("#" + MENU_EXIT_ID)?.contains(e.target)) {
removeMenu();
}
});
onClick(canvas, () => {
insertMenu();
});
canvas.addEventListener("mouseover", () => {
lastActionTimestamp = Date.now();
if (currentState === States.IDLE) {
petStack.push(Date.now());
if (petStack.length > 10) {
petStack.shift();
}
const pets = petStack.filter((time) => Date.now() - time < 1000).length;
if (pets >= 3) {
setAnimation(Animations.HEART);
// Clear the stack
petStack = [];
}
}
});
drawStickyNotes();
let lastUrl = (window.location.href ?? "").split("?")[0];
setInterval(() => {
const currentUrl = (window.location.href ?? "").split("?")[0];
if (currentUrl !== lastUrl) {
log("URL changed, updating sticky notes");
lastUrl = currentUrl;
drawStickyNotes();
}
}, 500);
setInterval(update, 1000 / 60);
birdY = getWindowBottom();
// TODO: For testing only
hop();
}
function drawStickyNotes() {
// Remove all existing sticky notes
const existingNotes = document.querySelectorAll(".birb-sticky-note");
existingNotes.forEach(note => note.remove());
// Render all sticky notes
for (let stickyNote of stickyNotes) {
if (isStickyNoteApplicable(stickyNote)) {
renderStickyNote(stickyNote);
}
}
}
/**
* Run the bird's behavior logic
*/
function update() {
ticks++;
// Hide bird if the browser is fullscreen
if (document.fullscreenElement) {
hideBirb();
// Won't be restored on fullscreen exit
}
if (currentState === States.IDLE) {
if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART && !isMenuOpen()) {
hop();
} else if (Math.random() < 1 / (60 * 20) && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
focusOnElement();
lastActionTimestamp = Date.now();
}
}
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // 1 every 2 hours (ticks * seconds * minutes * hours)
// Double the chance of a feather if recently pet
let petMod = Date.now() - lastPetTimestamp < 1000 * 60 * 5 ? 2 : 1;
if (visible && Math.random() < FEATHER_CHANCE * petMod) {
lastPetTimestamp = 0;
activateFeather();
}
updateFeather();
}
/**
* Render the bird in the dom and update its position if necessary
*/
function draw() {
requestAnimationFrame(draw);
if (!visible) {
return;
}
// Update the bird's position
if (currentState === States.IDLE) {
if (focusedElement !== null) {
birdY = getFocusedElementY();
if (!isWithinHorizontalBounds(birdX)) {
focusOnGround();
}
} else {
// Ground the bird
birdY = getWindowBottom();
}
} else if (currentState === States.FLYING) {
// Fly to target location (even if in the air)
if (updateParabolicPath(FLY_SPEED)) {
setState(States.IDLE);
}
} else if (currentState === States.HOP) {
if (updateParabolicPath(HOP_SPEED)) {
setState(States.IDLE);
}
}
if (focusedElement === null) {
if (Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
// Fly to an element if the user is AFK
// focusOnElement();
// lastActionTimestamp = Date.now();
}
} else if (focusedElement !== null) {
targetY = getFocusedElementY();
if (targetY < window.scrollY || targetY > window.scrollY + window.innerHeight) {
// Fly to ground if the focused element moves out of bounds
focusOnGround();
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (currentAnimation.draw(ctx, direction, animStart, species[currentSpecies])) {
setAnimation(Animations.STILL);
}
// Update HTML element position
setX(birdX);
setY(birdY);
}
init();
draw();
function newStickyNote() {
const id = Date.now().toString();
const site = window.location.href;
@@ -920,185 +1122,6 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
return true;
}
function init() {
if (window !== window.top) {
// Skip installation if within an iframe
return;
}
// Preload font
const MONOCRAFT_SRC = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
const fontLink = document.createElement("link");
fontLink.rel = "stylesheet";
fontLink.href = `url(${MONOCRAFT_SRC}) format('opentype')`;
document.head.appendChild(fontLink);
// Add stylesheet font-face
const fontFace = `
@font-face {
font-family: 'Monocraft';
src: url(${MONOCRAFT_SRC}) format('opentype');
font-weight: normal;
font-style: normal;
}
`;
const fontStyle = document.createElement("style");
fontStyle.innerHTML = fontFace;
document.head.appendChild(fontStyle);
load();
styleElement.innerHTML = STYLESHEET;
document.head.appendChild(styleElement);
canvas.id = "birb";
canvas.width = birbFrames.base.getPixels()[0].length * CANVAS_PIXEL_SIZE;
canvas.height = SPRITE_HEIGHT * CANVAS_PIXEL_SIZE;
document.body.appendChild(canvas);
window.addEventListener("scroll", () => {
lastActionTimestamp = Date.now();
// Can't keep up with scrolling on mobile devices so fly down instead
if (isMobile()) {
// focusOnGround();
}
});
onClick(document, (e) => {
lastActionTimestamp = Date.now();
if (e.target instanceof Node && document.querySelector("#" + MENU_EXIT_ID)?.contains(e.target)) {
removeMenu();
}
});
onClick(canvas, () => {
insertMenu();
});
canvas.addEventListener("mouseover", () => {
lastActionTimestamp = Date.now();
if (currentState === States.IDLE) {
petStack.push(Date.now());
if (petStack.length > 10) {
petStack.shift();
}
const pets = petStack.filter((time) => Date.now() - time < 1000).length;
if (pets >= 3) {
setAnimation(Animations.HEART);
// Clear the stack
petStack = [];
}
}
});
drawStickyNotes();
let lastUrl = (window.location.href ?? "").split("?")[0];
setInterval(() => {
const currentUrl = (window.location.href ?? "").split("?")[0];
if (currentUrl !== lastUrl) {
log("URL changed, updating sticky notes");
lastUrl = currentUrl;
drawStickyNotes();
}
}, 500);
setInterval(update, 1000 / 60);
}
function drawStickyNotes() {
// Remove all existing sticky notes
const existingNotes = document.querySelectorAll(".birb-sticky-note");
existingNotes.forEach(note => note.remove());
// Render all sticky notes
for (let stickyNote of stickyNotes) {
if (isStickyNoteApplicable(stickyNote)) {
renderStickyNote(stickyNote);
}
}
}
function update() {
ticks++;
// Hide bird if the browser is fullscreen
if (document.fullscreenElement) {
hideBirb();
// Won't be restored on fullscreen exit
}
if (currentState === States.IDLE) {
if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART && !isMenuOpen()) {
hop();
} else if (focusedElement !== null && Math.random() < 1 / (60 * 20) && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
focusOnElement();
lastActionTimestamp = Date.now();
}
} else if (currentState === States.HOP) {
if (updateParabolicPath(HOP_SPEED)) {
setState(States.IDLE);
}
}
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // 1 every 2 hours (ticks * seconds * minutes * hours)
// Double the chance of a feather if recently pet
let petMod = Date.now() - lastPetTimestamp < 1000 * 60 * 5 ? 2 : 1;
if (visible && Math.random() < FEATHER_CHANCE * petMod) {
lastPetTimestamp = 0;
activateFeather();
}
updateFeather();
}
function draw() {
requestAnimationFrame(draw);
if (!visible) {
return;
}
// Update the bird's position
if (currentState === States.IDLE) {
if (focusedElement !== null) {
birdY = getFocusedElementY() - 0.5;
if (!isWithinHorizontalBounds()) {
focusOnGround();
}
}
} else if (currentState === States.FLYING) {
// Fly to target location (even if in the air)
if (updateParabolicPath(FLY_SPEED)) {
setState(States.IDLE);
}
}
if (focusedElement === null) {
if (Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
// Fly to an element if the user is AFK
focusOnElement();
lastActionTimestamp = Date.now();
}
} else if (focusedElement !== null) {
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);
if (currentAnimation.draw(ctx, direction, animStart, species[currentSpecies])) {
setAnimation(Animations.STILL);
}
// Update HTML element position
setX(birdX);
setY(birdY);
}
init();
draw();
/**
* Create an HTML element with the specified parameters
* @param {string} className
@@ -1663,37 +1686,44 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
return Math.random() * window.innerWidth;
}
const rect = focusedElement.getBoundingClientRect();
return Math.random() * (rect.right - rect.left) + rect.left;
}
function isWithinHorizontalBounds() {
if (focusedElement === null) {
return true;
}
const rect = focusedElement.getBoundingClientRect();
return birdX >= rect.left && birdX <= rect.right;
return Math.random() * (rect.right - rect.left) + rect.left + window.scrollX;
}
function getFocusedElementY() {
if (focusedElement === null) {
return 0;
return getWindowBottom();
}
const rect = focusedElement.getBoundingClientRect();
return window.innerHeight - rect.top;
return rect.top + window.scrollY;
}
/**
* @param {number} x
* @returns {boolean} Whether the x coordinate is within the horizontal bounds of the focused element
*/
function isWithinHorizontalBounds(x) {
if (focusedElement === null) {
return true;
}
const rect = focusedElement.getBoundingClientRect();
return x >= rect.left && x <= rect.right;
}
function focusOnGround() {
if (focusedElement === null) {
// Already focused on ground
return;
}
console.log("Focusing on ground");
focusedElement = null;
flyTo(Math.random() * window.innerWidth, 0);
flyTo(Math.random() * window.innerWidth, getWindowBottom());
}
function focusOnElement() {
if (frozen) {
return;
}
console.log("Focusing on element");
const elements = document.querySelectorAll("img, video");
const inWindow = Array.from(elements).filter((img) => {
const rect = img.getBoundingClientRect();
@@ -1719,6 +1749,10 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
return canvas.height * BIRB_CSS_SCALE
}
function getWindowBottom() {
return window.scrollY + window.innerHeight;
}
function hop() {
if (frozen) {
return;
@@ -1727,7 +1761,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
// Determine bounds for hopping
let minX = 0;
let maxX = window.innerWidth;
let y = 0;
let y = getWindowBottom();
if (focusedElement !== null) {
// Hop on the element
const rect = focusedElement.getBoundingClientRect();
@@ -1743,6 +1777,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
targetX = birdX + HOP_DISTANCE;
}
targetY = y;
console.log("hopping from", birdX, birdY, "to", targetX, targetY);
}
}
@@ -1763,6 +1798,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
* @param {number} y
*/
function flyTo(x, y) {
console.log("Flying to", x, y);
targetX = x;
targetY = y;
setState(States.FLYING);
@@ -1783,6 +1819,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
* @param {string} state
*/
function setState(state) {
console.log("State:", state);
stateStart = Date.now();
startX = birdX;
startY = birdY;
@@ -1793,18 +1830,21 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
}
/**
* Set the bird element's X position, with the element origin at the center of the bird
* @param {number} x
*/
function setX(x) {
let mod = getCanvasWidth() / -2 - (WINDOW_PIXEL_SIZE * (direction === Directions.RIGHT ? 2 : -2));
const mod = getCanvasWidth() / -2 - (WINDOW_PIXEL_SIZE * (direction === Directions.RIGHT ? 2 : -2));
canvas.style.left = `${x + mod}px`;
}
/**
* Set the bird element's Y position, with the element origin at the bottom of the bird
* @param {number} y
*/
function setY(y) {
canvas.style.bottom = `${y}px`;
const mod = getCanvasHeight() + WINDOW_PIXEL_SIZE;
canvas.style.top = `${y - mod}px`;
}
});
@@ -1833,7 +1873,7 @@ function parabolicLerp(startX, startY, endX, endY, amount, intensity = 1.2) {
const distance = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
const midX = startX + Math.cos(angle) * distance / 2;
const midY = startY + Math.sin(angle) * distance / 2 + distance / 4 * intensity;
const midY = startY + Math.sin(angle) * distance / 2 - distance / 4 * intensity;
const t = amount;
const x = (1 - t) ** 2 * startX + 2 * (1 - t) * t * midX + t ** 2 * endX;
const y = (1 - t) ** 2 * startY + 2 * (1 - t) * t * midY + t ** 2 * endY;

435
dist/birb.js vendored
View File

@@ -66,8 +66,7 @@ const STYLESHEET = `:root {
#birb {
image-rendering: pixelated;
position: fixed;
bottom: 0;
position: absolute;
transform: scale(var(--birb-scale)) !important;
transform-origin: bottom;
z-index: 2147483638 !important;
@@ -1015,7 +1014,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
let ticks = 0;
// Bird's current position
let birdY = 0;
let birdX = 40;
let birdX = 0;
// Bird's starting position (when flying)
let startX = 0;
let startY = 0;
@@ -1034,6 +1033,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
/** @type {StickyNote[]} */
let stickyNotes = [];
/**
* @returns {boolean} Whether the script is running in a userscript extension context
*/
@@ -1137,6 +1137,207 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
return settings().birbMode ? "Birb" : "Bird";
}
function init() {
if (window !== window.top) {
// Skip installation if within an iframe
return;
}
// Preload font
const MONOCRAFT_SRC = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
const fontLink = document.createElement("link");
fontLink.rel = "stylesheet";
fontLink.href = `url(${MONOCRAFT_SRC}) format('opentype')`;
document.head.appendChild(fontLink);
// Add stylesheet font-face
const fontFace = `
@font-face {
font-family: 'Monocraft';
src: url(${MONOCRAFT_SRC}) format('opentype');
font-weight: normal;
font-style: normal;
}
`;
const fontStyle = document.createElement("style");
fontStyle.innerHTML = fontFace;
document.head.appendChild(fontStyle);
load();
styleElement.innerHTML = STYLESHEET;
document.head.appendChild(styleElement);
canvas.id = "birb";
canvas.width = birbFrames.base.getPixels()[0].length * CANVAS_PIXEL_SIZE;
canvas.height = SPRITE_HEIGHT * CANVAS_PIXEL_SIZE;
document.body.appendChild(canvas);
/** @type {NodeJS.Timeout} */
let scrollTimeout;
window.addEventListener("scroll", () => {
// TODO: Only do this if focused on the ground
if (focusedElement === null && currentState !== States.FLYING) {
canvas.style.transition = "opacity 0.2s";
canvas.style.opacity = "0";
}
lastActionTimestamp = Date.now();
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
canvas.style.transition = "opacity 0.4s";
canvas.style.opacity = "1";
}, 100);
});
onClick(document, (e) => {
lastActionTimestamp = Date.now();
if (e.target instanceof Node && document.querySelector("#" + MENU_EXIT_ID)?.contains(e.target)) {
removeMenu();
}
});
onClick(canvas, () => {
insertMenu();
});
canvas.addEventListener("mouseover", () => {
lastActionTimestamp = Date.now();
if (currentState === States.IDLE) {
petStack.push(Date.now());
if (petStack.length > 10) {
petStack.shift();
}
const pets = petStack.filter((time) => Date.now() - time < 1000).length;
if (pets >= 3) {
setAnimation(Animations.HEART);
// Clear the stack
petStack = [];
}
}
});
drawStickyNotes();
let lastUrl = (window.location.href ?? "").split("?")[0];
setInterval(() => {
const currentUrl = (window.location.href ?? "").split("?")[0];
if (currentUrl !== lastUrl) {
log("URL changed, updating sticky notes");
lastUrl = currentUrl;
drawStickyNotes();
}
}, 500);
setInterval(update, 1000 / 60);
birdY = getWindowBottom();
// TODO: For testing only
hop();
}
function drawStickyNotes() {
// Remove all existing sticky notes
const existingNotes = document.querySelectorAll(".birb-sticky-note");
existingNotes.forEach(note => note.remove());
// Render all sticky notes
for (let stickyNote of stickyNotes) {
if (isStickyNoteApplicable(stickyNote)) {
renderStickyNote(stickyNote);
}
}
}
/**
* Run the bird's behavior logic
*/
function update() {
ticks++;
// Hide bird if the browser is fullscreen
if (document.fullscreenElement) {
hideBirb();
// Won't be restored on fullscreen exit
}
if (currentState === States.IDLE) {
if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART && !isMenuOpen()) {
hop();
} else if (Math.random() < 1 / (60 * 20) && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
focusOnElement();
lastActionTimestamp = Date.now();
}
}
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // 1 every 2 hours (ticks * seconds * minutes * hours)
// Double the chance of a feather if recently pet
let petMod = Date.now() - lastPetTimestamp < 1000 * 60 * 5 ? 2 : 1;
if (visible && Math.random() < FEATHER_CHANCE * petMod) {
lastPetTimestamp = 0;
activateFeather();
}
updateFeather();
}
/**
* Render the bird in the dom and update its position if necessary
*/
function draw() {
requestAnimationFrame(draw);
if (!visible) {
return;
}
// Update the bird's position
if (currentState === States.IDLE) {
if (focusedElement !== null) {
birdY = getFocusedElementY();
if (!isWithinHorizontalBounds(birdX)) {
focusOnGround();
}
} else {
// Ground the bird
birdY = getWindowBottom();
}
} else if (currentState === States.FLYING) {
// Fly to target location (even if in the air)
if (updateParabolicPath(FLY_SPEED)) {
setState(States.IDLE);
}
} else if (currentState === States.HOP) {
if (updateParabolicPath(HOP_SPEED)) {
setState(States.IDLE);
}
}
if (focusedElement === null) {
if (Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
// Fly to an element if the user is AFK
// focusOnElement();
// lastActionTimestamp = Date.now();
}
} else if (focusedElement !== null) {
targetY = getFocusedElementY();
if (targetY < window.scrollY || targetY > window.scrollY + window.innerHeight) {
// Fly to ground if the focused element moves out of bounds
focusOnGround();
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (currentAnimation.draw(ctx, direction, animStart, species[currentSpecies])) {
setAnimation(Animations.STILL);
}
// Update HTML element position
setX(birdX);
setY(birdY);
}
init();
draw();
function newStickyNote() {
const id = Date.now().toString();
const site = window.location.href;
@@ -1259,185 +1460,6 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
return true;
}
function init() {
if (window !== window.top) {
// Skip installation if within an iframe
return;
}
// Preload font
const MONOCRAFT_SRC = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
const fontLink = document.createElement("link");
fontLink.rel = "stylesheet";
fontLink.href = `url(${MONOCRAFT_SRC}) format('opentype')`;
document.head.appendChild(fontLink);
// Add stylesheet font-face
const fontFace = `
@font-face {
font-family: 'Monocraft';
src: url(${MONOCRAFT_SRC}) format('opentype');
font-weight: normal;
font-style: normal;
}
`;
const fontStyle = document.createElement("style");
fontStyle.innerHTML = fontFace;
document.head.appendChild(fontStyle);
load();
styleElement.innerHTML = STYLESHEET;
document.head.appendChild(styleElement);
canvas.id = "birb";
canvas.width = birbFrames.base.getPixels()[0].length * CANVAS_PIXEL_SIZE;
canvas.height = SPRITE_HEIGHT * CANVAS_PIXEL_SIZE;
document.body.appendChild(canvas);
window.addEventListener("scroll", () => {
lastActionTimestamp = Date.now();
// Can't keep up with scrolling on mobile devices so fly down instead
if (isMobile()) {
// focusOnGround();
}
});
onClick(document, (e) => {
lastActionTimestamp = Date.now();
if (e.target instanceof Node && document.querySelector("#" + MENU_EXIT_ID)?.contains(e.target)) {
removeMenu();
}
});
onClick(canvas, () => {
insertMenu();
});
canvas.addEventListener("mouseover", () => {
lastActionTimestamp = Date.now();
if (currentState === States.IDLE) {
petStack.push(Date.now());
if (petStack.length > 10) {
petStack.shift();
}
const pets = petStack.filter((time) => Date.now() - time < 1000).length;
if (pets >= 3) {
setAnimation(Animations.HEART);
// Clear the stack
petStack = [];
}
}
});
drawStickyNotes();
let lastUrl = (window.location.href ?? "").split("?")[0];
setInterval(() => {
const currentUrl = (window.location.href ?? "").split("?")[0];
if (currentUrl !== lastUrl) {
log("URL changed, updating sticky notes");
lastUrl = currentUrl;
drawStickyNotes();
}
}, 500);
setInterval(update, 1000 / 60);
}
function drawStickyNotes() {
// Remove all existing sticky notes
const existingNotes = document.querySelectorAll(".birb-sticky-note");
existingNotes.forEach(note => note.remove());
// Render all sticky notes
for (let stickyNote of stickyNotes) {
if (isStickyNoteApplicable(stickyNote)) {
renderStickyNote(stickyNote);
}
}
}
function update() {
ticks++;
// Hide bird if the browser is fullscreen
if (document.fullscreenElement) {
hideBirb();
// Won't be restored on fullscreen exit
}
if (currentState === States.IDLE) {
if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART && !isMenuOpen()) {
hop();
} else if (focusedElement !== null && Math.random() < 1 / (60 * 20) && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
focusOnElement();
lastActionTimestamp = Date.now();
}
} else if (currentState === States.HOP) {
if (updateParabolicPath(HOP_SPEED)) {
setState(States.IDLE);
}
}
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // 1 every 2 hours (ticks * seconds * minutes * hours)
// Double the chance of a feather if recently pet
let petMod = Date.now() - lastPetTimestamp < 1000 * 60 * 5 ? 2 : 1;
if (visible && Math.random() < FEATHER_CHANCE * petMod) {
lastPetTimestamp = 0;
activateFeather();
}
updateFeather();
}
function draw() {
requestAnimationFrame(draw);
if (!visible) {
return;
}
// Update the bird's position
if (currentState === States.IDLE) {
if (focusedElement !== null) {
birdY = getFocusedElementY() - 0.5;
if (!isWithinHorizontalBounds()) {
focusOnGround();
}
}
} else if (currentState === States.FLYING) {
// Fly to target location (even if in the air)
if (updateParabolicPath(FLY_SPEED)) {
setState(States.IDLE);
}
}
if (focusedElement === null) {
if (Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
// Fly to an element if the user is AFK
focusOnElement();
lastActionTimestamp = Date.now();
}
} else if (focusedElement !== null) {
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);
if (currentAnimation.draw(ctx, direction, animStart, species[currentSpecies])) {
setAnimation(Animations.STILL);
}
// Update HTML element position
setX(birdX);
setY(birdY);
}
init();
draw();
/**
* Create an HTML element with the specified parameters
* @param {string} className
@@ -2002,37 +2024,44 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
return Math.random() * window.innerWidth;
}
const rect = focusedElement.getBoundingClientRect();
return Math.random() * (rect.right - rect.left) + rect.left;
}
function isWithinHorizontalBounds() {
if (focusedElement === null) {
return true;
}
const rect = focusedElement.getBoundingClientRect();
return birdX >= rect.left && birdX <= rect.right;
return Math.random() * (rect.right - rect.left) + rect.left + window.scrollX;
}
function getFocusedElementY() {
if (focusedElement === null) {
return 0;
return getWindowBottom();
}
const rect = focusedElement.getBoundingClientRect();
return window.innerHeight - rect.top;
return rect.top + window.scrollY;
}
/**
* @param {number} x
* @returns {boolean} Whether the x coordinate is within the horizontal bounds of the focused element
*/
function isWithinHorizontalBounds(x) {
if (focusedElement === null) {
return true;
}
const rect = focusedElement.getBoundingClientRect();
return x >= rect.left && x <= rect.right;
}
function focusOnGround() {
if (focusedElement === null) {
// Already focused on ground
return;
}
console.log("Focusing on ground");
focusedElement = null;
flyTo(Math.random() * window.innerWidth, 0);
flyTo(Math.random() * window.innerWidth, getWindowBottom());
}
function focusOnElement() {
if (frozen) {
return;
}
console.log("Focusing on element");
const elements = document.querySelectorAll("img, video");
const inWindow = Array.from(elements).filter((img) => {
const rect = img.getBoundingClientRect();
@@ -2058,6 +2087,10 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
return canvas.height * BIRB_CSS_SCALE
}
function getWindowBottom() {
return window.scrollY + window.innerHeight;
}
function hop() {
if (frozen) {
return;
@@ -2066,7 +2099,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
// Determine bounds for hopping
let minX = 0;
let maxX = window.innerWidth;
let y = 0;
let y = getWindowBottom();
if (focusedElement !== null) {
// Hop on the element
const rect = focusedElement.getBoundingClientRect();
@@ -2082,6 +2115,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
targetX = birdX + HOP_DISTANCE;
}
targetY = y;
console.log("hopping from", birdX, birdY, "to", targetX, targetY);
}
}
@@ -2102,6 +2136,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
* @param {number} y
*/
function flyTo(x, y) {
console.log("Flying to", x, y);
targetX = x;
targetY = y;
setState(States.FLYING);
@@ -2122,6 +2157,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
* @param {string} state
*/
function setState(state) {
console.log("State:", state);
stateStart = Date.now();
startX = birdX;
startY = birdY;
@@ -2132,18 +2168,21 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
}
/**
* Set the bird element's X position, with the element origin at the center of the bird
* @param {number} x
*/
function setX(x) {
let mod = getCanvasWidth() / -2 - (WINDOW_PIXEL_SIZE * (direction === Directions.RIGHT ? 2 : -2));
const mod = getCanvasWidth() / -2 - (WINDOW_PIXEL_SIZE * (direction === Directions.RIGHT ? 2 : -2));
canvas.style.left = `${x + mod}px`;
}
/**
* Set the bird element's Y position, with the element origin at the bottom of the bird
* @param {number} y
*/
function setY(y) {
canvas.style.bottom = `${y}px`;
const mod = getCanvasHeight() + WINDOW_PIXEL_SIZE;
canvas.style.top = `${y - mod}px`;
}
});
@@ -2172,7 +2211,7 @@ function parabolicLerp(startX, startY, endX, endY, amount, intensity = 1.2) {
const distance = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
const midX = startX + Math.cos(angle) * distance / 2;
const midY = startY + Math.sin(angle) * distance / 2 + distance / 4 * intensity;
const midY = startY + Math.sin(angle) * distance / 2 - distance / 4 * intensity;
const t = amount;
const x = (1 - t) ** 2 * startX + 2 * (1 - t) * t * midX + t ** 2 * endX;
const y = (1 - t) ** 2 * startY + 2 * (1 - t) * t * midY + t ** 2 * endY;

435
dist/birb.user.js vendored
View File

@@ -80,8 +80,7 @@ const STYLESHEET = `:root {
#birb {
image-rendering: pixelated;
position: fixed;
bottom: 0;
position: absolute;
transform: scale(var(--birb-scale)) !important;
transform-origin: bottom;
z-index: 2147483638 !important;
@@ -1029,7 +1028,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
let ticks = 0;
// Bird's current position
let birdY = 0;
let birdX = 40;
let birdX = 0;
// Bird's starting position (when flying)
let startX = 0;
let startY = 0;
@@ -1048,6 +1047,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
/** @type {StickyNote[]} */
let stickyNotes = [];
/**
* @returns {boolean} Whether the script is running in a userscript extension context
*/
@@ -1151,6 +1151,207 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
return settings().birbMode ? "Birb" : "Bird";
}
function init() {
if (window !== window.top) {
// Skip installation if within an iframe
return;
}
// Preload font
const MONOCRAFT_SRC = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
const fontLink = document.createElement("link");
fontLink.rel = "stylesheet";
fontLink.href = `url(${MONOCRAFT_SRC}) format('opentype')`;
document.head.appendChild(fontLink);
// Add stylesheet font-face
const fontFace = `
@font-face {
font-family: 'Monocraft';
src: url(${MONOCRAFT_SRC}) format('opentype');
font-weight: normal;
font-style: normal;
}
`;
const fontStyle = document.createElement("style");
fontStyle.innerHTML = fontFace;
document.head.appendChild(fontStyle);
load();
styleElement.innerHTML = STYLESHEET;
document.head.appendChild(styleElement);
canvas.id = "birb";
canvas.width = birbFrames.base.getPixels()[0].length * CANVAS_PIXEL_SIZE;
canvas.height = SPRITE_HEIGHT * CANVAS_PIXEL_SIZE;
document.body.appendChild(canvas);
/** @type {NodeJS.Timeout} */
let scrollTimeout;
window.addEventListener("scroll", () => {
// TODO: Only do this if focused on the ground
if (focusedElement === null && currentState !== States.FLYING) {
canvas.style.transition = "opacity 0.2s";
canvas.style.opacity = "0";
}
lastActionTimestamp = Date.now();
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
canvas.style.transition = "opacity 0.4s";
canvas.style.opacity = "1";
}, 100);
});
onClick(document, (e) => {
lastActionTimestamp = Date.now();
if (e.target instanceof Node && document.querySelector("#" + MENU_EXIT_ID)?.contains(e.target)) {
removeMenu();
}
});
onClick(canvas, () => {
insertMenu();
});
canvas.addEventListener("mouseover", () => {
lastActionTimestamp = Date.now();
if (currentState === States.IDLE) {
petStack.push(Date.now());
if (petStack.length > 10) {
petStack.shift();
}
const pets = petStack.filter((time) => Date.now() - time < 1000).length;
if (pets >= 3) {
setAnimation(Animations.HEART);
// Clear the stack
petStack = [];
}
}
});
drawStickyNotes();
let lastUrl = (window.location.href ?? "").split("?")[0];
setInterval(() => {
const currentUrl = (window.location.href ?? "").split("?")[0];
if (currentUrl !== lastUrl) {
log("URL changed, updating sticky notes");
lastUrl = currentUrl;
drawStickyNotes();
}
}, 500);
setInterval(update, 1000 / 60);
birdY = getWindowBottom();
// TODO: For testing only
hop();
}
function drawStickyNotes() {
// Remove all existing sticky notes
const existingNotes = document.querySelectorAll(".birb-sticky-note");
existingNotes.forEach(note => note.remove());
// Render all sticky notes
for (let stickyNote of stickyNotes) {
if (isStickyNoteApplicable(stickyNote)) {
renderStickyNote(stickyNote);
}
}
}
/**
* Run the bird's behavior logic
*/
function update() {
ticks++;
// Hide bird if the browser is fullscreen
if (document.fullscreenElement) {
hideBirb();
// Won't be restored on fullscreen exit
}
if (currentState === States.IDLE) {
if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART && !isMenuOpen()) {
hop();
} else if (Math.random() < 1 / (60 * 20) && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
focusOnElement();
lastActionTimestamp = Date.now();
}
}
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // 1 every 2 hours (ticks * seconds * minutes * hours)
// Double the chance of a feather if recently pet
let petMod = Date.now() - lastPetTimestamp < 1000 * 60 * 5 ? 2 : 1;
if (visible && Math.random() < FEATHER_CHANCE * petMod) {
lastPetTimestamp = 0;
activateFeather();
}
updateFeather();
}
/**
* Render the bird in the dom and update its position if necessary
*/
function draw() {
requestAnimationFrame(draw);
if (!visible) {
return;
}
// Update the bird's position
if (currentState === States.IDLE) {
if (focusedElement !== null) {
birdY = getFocusedElementY();
if (!isWithinHorizontalBounds(birdX)) {
focusOnGround();
}
} else {
// Ground the bird
birdY = getWindowBottom();
}
} else if (currentState === States.FLYING) {
// Fly to target location (even if in the air)
if (updateParabolicPath(FLY_SPEED)) {
setState(States.IDLE);
}
} else if (currentState === States.HOP) {
if (updateParabolicPath(HOP_SPEED)) {
setState(States.IDLE);
}
}
if (focusedElement === null) {
if (Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
// Fly to an element if the user is AFK
// focusOnElement();
// lastActionTimestamp = Date.now();
}
} else if (focusedElement !== null) {
targetY = getFocusedElementY();
if (targetY < window.scrollY || targetY > window.scrollY + window.innerHeight) {
// Fly to ground if the focused element moves out of bounds
focusOnGround();
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (currentAnimation.draw(ctx, direction, animStart, species[currentSpecies])) {
setAnimation(Animations.STILL);
}
// Update HTML element position
setX(birdX);
setY(birdY);
}
init();
draw();
function newStickyNote() {
const id = Date.now().toString();
const site = window.location.href;
@@ -1273,185 +1474,6 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
return true;
}
function init() {
if (window !== window.top) {
// Skip installation if within an iframe
return;
}
// Preload font
const MONOCRAFT_SRC = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
const fontLink = document.createElement("link");
fontLink.rel = "stylesheet";
fontLink.href = `url(${MONOCRAFT_SRC}) format('opentype')`;
document.head.appendChild(fontLink);
// Add stylesheet font-face
const fontFace = `
@font-face {
font-family: 'Monocraft';
src: url(${MONOCRAFT_SRC}) format('opentype');
font-weight: normal;
font-style: normal;
}
`;
const fontStyle = document.createElement("style");
fontStyle.innerHTML = fontFace;
document.head.appendChild(fontStyle);
load();
styleElement.innerHTML = STYLESHEET;
document.head.appendChild(styleElement);
canvas.id = "birb";
canvas.width = birbFrames.base.getPixels()[0].length * CANVAS_PIXEL_SIZE;
canvas.height = SPRITE_HEIGHT * CANVAS_PIXEL_SIZE;
document.body.appendChild(canvas);
window.addEventListener("scroll", () => {
lastActionTimestamp = Date.now();
// Can't keep up with scrolling on mobile devices so fly down instead
if (isMobile()) {
// focusOnGround();
}
});
onClick(document, (e) => {
lastActionTimestamp = Date.now();
if (e.target instanceof Node && document.querySelector("#" + MENU_EXIT_ID)?.contains(e.target)) {
removeMenu();
}
});
onClick(canvas, () => {
insertMenu();
});
canvas.addEventListener("mouseover", () => {
lastActionTimestamp = Date.now();
if (currentState === States.IDLE) {
petStack.push(Date.now());
if (petStack.length > 10) {
petStack.shift();
}
const pets = petStack.filter((time) => Date.now() - time < 1000).length;
if (pets >= 3) {
setAnimation(Animations.HEART);
// Clear the stack
petStack = [];
}
}
});
drawStickyNotes();
let lastUrl = (window.location.href ?? "").split("?")[0];
setInterval(() => {
const currentUrl = (window.location.href ?? "").split("?")[0];
if (currentUrl !== lastUrl) {
log("URL changed, updating sticky notes");
lastUrl = currentUrl;
drawStickyNotes();
}
}, 500);
setInterval(update, 1000 / 60);
}
function drawStickyNotes() {
// Remove all existing sticky notes
const existingNotes = document.querySelectorAll(".birb-sticky-note");
existingNotes.forEach(note => note.remove());
// Render all sticky notes
for (let stickyNote of stickyNotes) {
if (isStickyNoteApplicable(stickyNote)) {
renderStickyNote(stickyNote);
}
}
}
function update() {
ticks++;
// Hide bird if the browser is fullscreen
if (document.fullscreenElement) {
hideBirb();
// Won't be restored on fullscreen exit
}
if (currentState === States.IDLE) {
if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART && !isMenuOpen()) {
hop();
} else if (focusedElement !== null && Math.random() < 1 / (60 * 20) && Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
focusOnElement();
lastActionTimestamp = Date.now();
}
} else if (currentState === States.HOP) {
if (updateParabolicPath(HOP_SPEED)) {
setState(States.IDLE);
}
}
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // 1 every 2 hours (ticks * seconds * minutes * hours)
// Double the chance of a feather if recently pet
let petMod = Date.now() - lastPetTimestamp < 1000 * 60 * 5 ? 2 : 1;
if (visible && Math.random() < FEATHER_CHANCE * petMod) {
lastPetTimestamp = 0;
activateFeather();
}
updateFeather();
}
function draw() {
requestAnimationFrame(draw);
if (!visible) {
return;
}
// Update the bird's position
if (currentState === States.IDLE) {
if (focusedElement !== null) {
birdY = getFocusedElementY() - 0.5;
if (!isWithinHorizontalBounds()) {
focusOnGround();
}
}
} else if (currentState === States.FLYING) {
// Fly to target location (even if in the air)
if (updateParabolicPath(FLY_SPEED)) {
setState(States.IDLE);
}
}
if (focusedElement === null) {
if (Date.now() - lastActionTimestamp > AFK_TIME && !isMenuOpen()) {
// Fly to an element if the user is AFK
focusOnElement();
lastActionTimestamp = Date.now();
}
} else if (focusedElement !== null) {
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);
if (currentAnimation.draw(ctx, direction, animStart, species[currentSpecies])) {
setAnimation(Animations.STILL);
}
// Update HTML element position
setX(birdX);
setY(birdY);
}
init();
draw();
/**
* Create an HTML element with the specified parameters
* @param {string} className
@@ -2016,37 +2038,44 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
return Math.random() * window.innerWidth;
}
const rect = focusedElement.getBoundingClientRect();
return Math.random() * (rect.right - rect.left) + rect.left;
}
function isWithinHorizontalBounds() {
if (focusedElement === null) {
return true;
}
const rect = focusedElement.getBoundingClientRect();
return birdX >= rect.left && birdX <= rect.right;
return Math.random() * (rect.right - rect.left) + rect.left + window.scrollX;
}
function getFocusedElementY() {
if (focusedElement === null) {
return 0;
return getWindowBottom();
}
const rect = focusedElement.getBoundingClientRect();
return window.innerHeight - rect.top;
return rect.top + window.scrollY;
}
/**
* @param {number} x
* @returns {boolean} Whether the x coordinate is within the horizontal bounds of the focused element
*/
function isWithinHorizontalBounds(x) {
if (focusedElement === null) {
return true;
}
const rect = focusedElement.getBoundingClientRect();
return x >= rect.left && x <= rect.right;
}
function focusOnGround() {
if (focusedElement === null) {
// Already focused on ground
return;
}
console.log("Focusing on ground");
focusedElement = null;
flyTo(Math.random() * window.innerWidth, 0);
flyTo(Math.random() * window.innerWidth, getWindowBottom());
}
function focusOnElement() {
if (frozen) {
return;
}
console.log("Focusing on element");
const elements = document.querySelectorAll("img, video");
const inWindow = Array.from(elements).filter((img) => {
const rect = img.getBoundingClientRect();
@@ -2072,6 +2101,10 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
return canvas.height * BIRB_CSS_SCALE
}
function getWindowBottom() {
return window.scrollY + window.innerHeight;
}
function hop() {
if (frozen) {
return;
@@ -2080,7 +2113,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
// Determine bounds for hopping
let minX = 0;
let maxX = window.innerWidth;
let y = 0;
let y = getWindowBottom();
if (focusedElement !== null) {
// Hop on the element
const rect = focusedElement.getBoundingClientRect();
@@ -2096,6 +2129,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
targetX = birdX + HOP_DISTANCE;
}
targetY = y;
console.log("hopping from", birdX, birdY, "to", targetX, targetY);
}
}
@@ -2116,6 +2150,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
* @param {number} y
*/
function flyTo(x, y) {
console.log("Flying to", x, y);
targetX = x;
targetY = y;
setState(States.FLYING);
@@ -2136,6 +2171,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
* @param {string} state
*/
function setState(state) {
console.log("State:", state);
stateStart = Date.now();
startX = birdX;
startY = birdY;
@@ -2146,18 +2182,21 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI
}
/**
* Set the bird element's X position, with the element origin at the center of the bird
* @param {number} x
*/
function setX(x) {
let mod = getCanvasWidth() / -2 - (WINDOW_PIXEL_SIZE * (direction === Directions.RIGHT ? 2 : -2));
const mod = getCanvasWidth() / -2 - (WINDOW_PIXEL_SIZE * (direction === Directions.RIGHT ? 2 : -2));
canvas.style.left = `${x + mod}px`;
}
/**
* Set the bird element's Y position, with the element origin at the bottom of the bird
* @param {number} y
*/
function setY(y) {
canvas.style.bottom = `${y}px`;
const mod = getCanvasHeight() + WINDOW_PIXEL_SIZE;
canvas.style.top = `${y - mod}px`;
}
});
@@ -2186,7 +2225,7 @@ function parabolicLerp(startX, startY, endX, endY, amount, intensity = 1.2) {
const distance = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
const midX = startX + Math.cos(angle) * distance / 2;
const midY = startY + Math.sin(angle) * distance / 2 + distance / 4 * intensity;
const midY = startY + Math.sin(angle) * distance / 2 - distance / 4 * intensity;
const t = amount;
const x = (1 - t) ** 2 * startX + 2 * (1 - t) * t * midX + t ** 2 * endX;
const y = (1 - t) ** 2 * startY + 2 * (1 - t) * t * midY + t ** 2 * endY;

View File

@@ -12,7 +12,7 @@
}
#spacer {
height: 100vh;
height: 200vh;
}
</style>
</head>

View File

@@ -13,8 +13,7 @@
#birb {
image-rendering: pixelated;
position: fixed;
bottom: 0;
position: absolute;
transform: scale(var(--birb-scale)) !important;
transform-origin: bottom;
z-index: 2147483638 !important;