mirror of
https://github.com/NohamR/Pocket-Bird.git
synced 2026-05-24 19:59:36 +00:00
Add editor to this repository
This commit is contained in:
BIN
dist/extension.zip
vendored
BIN
dist/extension.zip
vendored
Binary file not shown.
117
dist/extension/birb.js
vendored
117
dist/extension/birb.js
vendored
@@ -471,6 +471,62 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a sprite sheet image and convert it to a 2D array of palette color names
|
||||
* @param {string} src - URL or data URI of the sprite sheet image
|
||||
* @param {boolean} [templateColors] - Whether to map pixel colors to palette names
|
||||
* @returns {Promise<string[][]>}
|
||||
*/
|
||||
function loadSpriteSheetPixels(src, templateColors = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = src;
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
reject(new Error('Failed to get canvas context'));
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
const pixels = imageData.data;
|
||||
const hexArray = [];
|
||||
for (let y = 0; y < img.height; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < img.width; x++) {
|
||||
const index = (y * img.width + x) * 4;
|
||||
const r = pixels[index];
|
||||
const g = pixels[index + 1];
|
||||
const b = pixels[index + 2];
|
||||
const a = pixels[index + 3];
|
||||
if (a === 0) {
|
||||
row.push(PALETTE.TRANSPARENT);
|
||||
continue;
|
||||
}
|
||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||
if (!templateColors) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||
}
|
||||
hexArray.push(row);
|
||||
}
|
||||
resolve(hexArray);
|
||||
};
|
||||
img.onerror = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {Record<string, BirdType>} */
|
||||
const SPECIES = Object.fromEntries(
|
||||
Object.entries(species).map(([id, data]) => [
|
||||
@@ -2100,7 +2156,7 @@
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}`;
|
||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABElJREFUeJztnT9rFEEYh3+TWATE7hDcsxW7CBb6ASIWaXJXpRS0UAhYCIrkAwRJISgIBq20sjrTWJnKxjTpLIKtd4ocWOSENOa1uJ11bm7n9u683ZnJ/R44Mtnb23f2dt5nZ/bfAYQQQgghZL5QvitAwkdExPWeUoptiEQLG28E+BSQjt2s1wEArXZ7oFxFHQghc4qkNJJEGkkyVB4lx1nFbySJfF5eFtnYGCqXHZ+QMlnwXQFSTLNex+NaDa1mc6hcJfufPuFxrZaVCYkdCjAifAmo1W7jSbc7MO1Jt5sNgQkhpBTMIfDn5eXsVdUQOK8OVcYmhASA5FB1bJ8CMutA+REyZ4hI/+C/8bfq+L4F5EP+hJTJGd8ViI39K1e8xFVKKRERn5ee8HIXQuYU3fPRvT/2ggiJH+7RJ8CUHntD1WPvdLgNyP/CBkSiQMtPO09EKhcgBTyn5J0B5RCQeEB22g0B4O0EUBo76PbPPB2fwguhzT2v+ULBPaqzhAImALDTbuiilxNASqmsDqH1/uy88JGnMTLyLLD5ZRqND3qaiJjzlNIg7KGPMR0iIqE1RFIed+vvvW5rnwIeBzMV1rc72TT6z41TgKZ4DtfvAA+A7Ruvs/d32s2B4zFlyCgEAROi8S3gEYhSKpPeaefCopLvf2Qm28K5EBGRhbPJwLTbzzr4cG8Bqy9PAAC9X98BAO8eJQN7mVnIaEjAGBTwtdUm7tbf63lnFpeQyBCX+L5+O8LB88teThiVxYVFJasvT/Dh3gJmIcGRAkQqoDuv8rvQWoBIJQhDRrnBJtgIvgVMwmZ9uyPvHiXzvp0L5RfqcD0UnENgfeeB631TfrCOOeQxzTD55HdnSMBafnZsezjuWqdxY5NwON5dkaW1PaXLALC05ld+vgW8vt3xemDPFT/tCEWTZyMrmgoLx7sr2Pj5EQDw4vwNAMCtw7djBfj67QgApuqK6/h5PVBbwBrdE81ZFkAJRss/8e1Vvv3yBVx9PUxcAjLzrWQROXufMUlwbAHaLK3t4er9Q1y6eC73s9aGmEpAvgVMiCYU8WmOd1fEzAE73yoQkLjyfy4ECEOCedjiwxS9L98CJiRUjndXpPelh40zLWBw5FNV+3YKEBFJsFCASI/r2RLqfekBAGqb+67P/gsypXR8C5iQkOluXc8a980fb7LpB88ve5egeWIy5LwrrFiehLT88qht7s9sxX0LmJDQMSWIfj5U3dZFd0JsEWoJhpx/YwkQqYS6W9dHzjtL+ZnxfQmYEDIWAgC2CGMQYOEDUfXlMOmKOCVYpnh0bPT3eM75KD9CvKDQH3r3k89xWCpqzKdhtJ4+zJ6KoctlPqDAutE7N37ZdSCEFGM/MSf0fJzoZzGVUmg9fZj9b5ZRYq9LL3dUfPb8CAmD9GYEIIJ8nOg3QcyhqF2ugqL4oX/ZhJx2YsvBia/Lcy6oghX3HZ8Qcrr4C5ZW3dG6vIHpAAAAAElFTkSuQmCC";
|
||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABGxJREFUeJztnb9vE0kYht9xkA4RIZ1ObrKmPUyVSDRcH0RBAXbBmSZCOk46pEgUSFiIPwAhFwgiIRGRBq7yNSZ3UjoqmqRJRxGlxQ5IFlckRZCOfFfYs4w3+8Mm3p1Z+32kKJP12t/YO9+z3+yuNwAhhBBCCJkulO0OEPcREYl6TCnFMURyCwdvDrApIB27WioBAFrt9kA7iz4QQqYU6VPxPKl43rF2nBzHFb/iebI5Py+yvHysnXZ8QtKkYLsDJJlqqYQHxSJa1eqxdpZsvXuHB8Wi3yYk71CAOcKWgFrtNh53uwPLHne7/hSYEEJSwZwCb87P+z9ZTYHD+pBlbEKIA0gIWce2KSCzD5QfIVOGiPQO/hu/s45vW0A25E9Impyy3YG8sbWwYCWuUkqJiNi89ISXuxAypejKR1d/rIIIyT/co4+AKT1WQ9kT3OlwG5CTwgFEcoGWn3aeiGQuQAp4Sgk7A8opILGArLYrAsDaCaB+bKfHP/N0eBIvhDb3vOYPEr6jOk4oYAIAq+2Kblo5AaSU8vvgWvUXzAsbeZpHYs8Cmx+mMfigl4mIuU4qAyI49TGWQ0TEtYFI0uOP0hur29qmgIfBTIVao+Mvo/+iiRSgKZ6d2m3gHnD+6MdvK7QrA8dj0pCRCwImRGNbwDGIUsqX3qQzN6Nk76uMZVvEVoCFWQ/qzBwu/LMBADhaegpv7SauvjgCNoBaYw9ISUZhAm5cXvMfX21XUxcwITlAosS3+2E/886kzdyMkqsvjrBxpzAWCUa+gCmg2y/DS+iDf/f8drPu6edFBxtBUCIihVlvYNlvzzrYuFPoCdiI36x7A3Epwsmn1uhIs+5N+3aOld/2ShmuTtddIfbD6VdVkE838PvffwEA1q79CgC4+epZ6HO0CENeqxdwSDnZFjAhcdgWcK3RiT2wl7YAo+L38zA3eTaUAA/XF/HDLz8BAL5sfgYA3Nr5c6gAugzfXimPfO2WTQET4jJRAjLzLWURRVafeZLg0N8F1uLTNOseLt7dwc/nzoauH9gQJzoT9WXzsy++YD+CBDeK7gfPhpFJ4lV5aaAICeZbFgLa/bAfmf954btvhnC4vojT18vA3Z3Qx8PEd5Lqy6aACXGR5/9VsXyqBWQsviRqjQ6adU9c6EsSiVNg9Kunw/XFgccO3h8AAIoPt6Ke+y3Id4rPnIKHcfr6W1zMSMCEuEb30SV/cF/5+Npfvr1SzmqcS1QRYp6YdDnvEjsWJiEtvzCKD7fG9sZtC5gQ1zEliF4+ZD3WRRchQRFqCbqcf0MJEH0JdR9dil13nPIz49sSMCFkKAQAgiLMgwATjwHqG3H230ikBNMUj46N3h4vcj3KjxArKPSm3r3kizgslWvMu2G0ntz374qh22neoCDwRe/Q+Gn3gRCSTPCOOa7n40j/FlMphdaT+/7fZhspVl36dePis/IjxA36X00FcpCPI10GY05Fg+0sSIrv+odNyKSTtxwcqbNx5WwWb9x2fELIZPE/Any0lmed9+8AAAAASUVORK5CYII=";
|
||||
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
||||
const HATS_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAAAMCAYAAACjpxUSAAAAAXNSR0IArs4c6QAAA29JREFUWIXtl11oW2UYx3+v3ZCuc1ktIpvowrCBjmkdtl6ouBthWMeGTkQxuA9QKCiym20OBfVG3YUVOobYIszLyXY16HYxvOicns3NRoR267ZG04aOdc3Jx8lJsujjRZOzeE5y+raETjR/CJyc9/98vv/zvOdAAw000EADDWhB3e0E6gURkfK1Uuo/U9dS4567GFs0OHqOymJIKud/JeoV5/8AjyBaV7bJhrWdEmxtl3pumgv195tUEO1wRAGwdzg4F6whCm14BHFvU/NCfYjrp4WWF05TV2EEBIKjfFN4CUpi6Fy3mf0/hfys3Hnr1rDQenW4futLJmiPIO5vaSNrZ3XtZebJ5/k51M13wY3IhQA6hXf1GgQCqxaTb1U47wwBYc8DJ/h0YhOd6zaz65GjtK18yNf2Un/Yyavv1Wd0wkksFsMwDAzDQKfeQmSQxNkvAETMqnwZvvgrNYTpXvPYLuaBrIVl7htlMUQT4wt2drinFbkAqjspfi+sZ187SPvXn+u6dRdZ1a9SSpWPhoPrR4ARdslR9m38ft4Al/rDZFImFyOJeXOJxWLE43HGx7X6I4XIIFY6RcacRkwg2oGYo6JW1+yPHO47gplI8sEn788vttFDpNIWtpXEti1CPQM1ex9sbZcVzSugtM8Ve+zwPYI4/9R6bs3Okk88CsATV6/qFO5AdSd9C+jqNdj0bROUjg1raIufeCR84EcArv8+TTGf4vyJnb5iU0px8r1tjI1dRimFiFT76nAafeXUhHNzjXe90k6Mjh1ce/ZtJuNTACRu5+gnxLtcqZaTAJzbdryCH+IdcxS12pv3WHSKgeND5P+YwEwkSc6m2f36Wzz9ysuMRaeq9gZgePsxUnkb888imcl05VrNHvmdAI5RWT1ZO0smlwJgxr5RletOys+vm9/Va2AVmvjlzf20fbYPa2iLL/+5HX1YmQT54nIAfjvzoR9fF1JxVJBJmc51eUrsPfaDO44zHcooT4lwOOzhFiKDAFjp1J045rRz/fDWQx6bj/q+Ij85STH/FzO3bpLNZVnz+GN8+fEBr/9rg1CYnZsOudvY6ZtzKyVWqGfAbcOL7W9IJmuRy88JIkcGgMjMOYe31N/rugKab1QuOm8RkdLEWGgMLf6/0L8APHjfWpqXtVB5ZNhFixvp+D+M/gZZI68eaJ1OpQAAAABJRU5ErkJggg==";
|
||||
|
||||
@@ -2236,7 +2292,7 @@
|
||||
}),
|
||||
new Separator(),
|
||||
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
||||
new MenuItem("2026.3.8", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.8"); }, false),
|
||||
new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false),
|
||||
];
|
||||
|
||||
/** @type {Birb} */
|
||||
@@ -3205,63 +3261,6 @@
|
||||
draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the sprite sheet and return the pixel-map template
|
||||
* @param {string} dataUri
|
||||
* @param {boolean} [templateColors]
|
||||
* @returns {Promise<string[][]>}
|
||||
*/
|
||||
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = dataUri;
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
reject(new Error('Failed to get canvas context'));
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
const pixels = imageData.data;
|
||||
const hexArray = [];
|
||||
for (let y = 0; y < img.height; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < img.width; x++) {
|
||||
const index = (y * img.width + x) * 4;
|
||||
const r = pixels[index];
|
||||
const g = pixels[index + 1];
|
||||
const b = pixels[index + 2];
|
||||
const a = pixels[index + 3];
|
||||
if (a === 0) {
|
||||
row.push(PALETTE.TRANSPARENT);
|
||||
continue;
|
||||
}
|
||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||
if (!templateColors) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||
// Return the color as-is if not found in the map
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||
}
|
||||
hexArray.push(row);
|
||||
}
|
||||
resolve(hexArray);
|
||||
};
|
||||
img.onerror = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
initializeApplication(new BrowserExtensionContext());
|
||||
|
||||
})();
|
||||
|
||||
2
dist/extension/manifest.json
vendored
2
dist/extension/manifest.json
vendored
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"name": "Pocket Bird",
|
||||
"description": "It's a pet bird in your browser, what more could you want?",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.11",
|
||||
"homepage_url": "https://idreesinc.com",
|
||||
"icons": {
|
||||
"48": "images/icons/transparent/48x48x1.png",
|
||||
|
||||
119
dist/obsidian/main.js
vendored
119
dist/obsidian/main.js
vendored
@@ -1,7 +1,7 @@
|
||||
const { Plugin, Notice } = require('obsidian');
|
||||
module.exports = class PocketBird extends Plugin {
|
||||
onload() {
|
||||
console.log("Loading Pocket Bird version 2026.3.8...");
|
||||
console.log("Loading Pocket Bird version 2026.3.11...");
|
||||
const OBSIDIAN_PLUGIN = this;
|
||||
(function () {
|
||||
'use strict';
|
||||
@@ -476,6 +476,62 @@ module.exports = class PocketBird extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a sprite sheet image and convert it to a 2D array of palette color names
|
||||
* @param {string} src - URL or data URI of the sprite sheet image
|
||||
* @param {boolean} [templateColors] - Whether to map pixel colors to palette names
|
||||
* @returns {Promise<string[][]>}
|
||||
*/
|
||||
function loadSpriteSheetPixels(src, templateColors = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = src;
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
reject(new Error('Failed to get canvas context'));
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
const pixels = imageData.data;
|
||||
const hexArray = [];
|
||||
for (let y = 0; y < img.height; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < img.width; x++) {
|
||||
const index = (y * img.width + x) * 4;
|
||||
const r = pixels[index];
|
||||
const g = pixels[index + 1];
|
||||
const b = pixels[index + 2];
|
||||
const a = pixels[index + 3];
|
||||
if (a === 0) {
|
||||
row.push(PALETTE.TRANSPARENT);
|
||||
continue;
|
||||
}
|
||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||
if (!templateColors) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||
}
|
||||
hexArray.push(row);
|
||||
}
|
||||
resolve(hexArray);
|
||||
};
|
||||
img.onerror = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {Record<string, BirdType>} */
|
||||
const SPECIES = Object.fromEntries(
|
||||
Object.entries(species).map(([id, data]) => [
|
||||
@@ -2133,7 +2189,7 @@ module.exports = class PocketBird extends Plugin {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}`;
|
||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABElJREFUeJztnT9rFEEYh3+TWATE7hDcsxW7CBb6ASIWaXJXpRS0UAhYCIrkAwRJISgIBq20sjrTWJnKxjTpLIKtd4ocWOSENOa1uJ11bm7n9u683ZnJ/R44Mtnb23f2dt5nZ/bfAYQQQgghZL5QvitAwkdExPWeUoptiEQLG28E+BSQjt2s1wEArXZ7oFxFHQghc4qkNJJEGkkyVB4lx1nFbySJfF5eFtnYGCqXHZ+QMlnwXQFSTLNex+NaDa1mc6hcJfufPuFxrZaVCYkdCjAifAmo1W7jSbc7MO1Jt5sNgQkhpBTMIfDn5eXsVdUQOK8OVcYmhASA5FB1bJ8CMutA+REyZ4hI/+C/8bfq+L4F5EP+hJTJGd8ViI39K1e8xFVKKRERn5ee8HIXQuYU3fPRvT/2ggiJH+7RJ8CUHntD1WPvdLgNyP/CBkSiQMtPO09EKhcgBTyn5J0B5RCQeEB22g0B4O0EUBo76PbPPB2fwguhzT2v+ULBPaqzhAImALDTbuiilxNASqmsDqH1/uy88JGnMTLyLLD5ZRqND3qaiJjzlNIg7KGPMR0iIqE1RFIed+vvvW5rnwIeBzMV1rc72TT6z41TgKZ4DtfvAA+A7Ruvs/d32s2B4zFlyCgEAROi8S3gEYhSKpPeaefCopLvf2Qm28K5EBGRhbPJwLTbzzr4cG8Bqy9PAAC9X98BAO8eJQN7mVnIaEjAGBTwtdUm7tbf63lnFpeQyBCX+L5+O8LB88teThiVxYVFJasvT/Dh3gJmIcGRAkQqoDuv8rvQWoBIJQhDRrnBJtgIvgVMwmZ9uyPvHiXzvp0L5RfqcD0UnENgfeeB631TfrCOOeQxzTD55HdnSMBafnZsezjuWqdxY5NwON5dkaW1PaXLALC05ld+vgW8vt3xemDPFT/tCEWTZyMrmgoLx7sr2Pj5EQDw4vwNAMCtw7djBfj67QgApuqK6/h5PVBbwBrdE81ZFkAJRss/8e1Vvv3yBVx9PUxcAjLzrWQROXufMUlwbAHaLK3t4er9Q1y6eC73s9aGmEpAvgVMiCYU8WmOd1fEzAE73yoQkLjyfy4ECEOCedjiwxS9L98CJiRUjndXpPelh40zLWBw5FNV+3YKEBFJsFCASI/r2RLqfekBAGqb+67P/gsypXR8C5iQkOluXc8a980fb7LpB88ve5egeWIy5LwrrFiehLT88qht7s9sxX0LmJDQMSWIfj5U3dZFd0JsEWoJhpx/YwkQqYS6W9dHzjtL+ZnxfQmYEDIWAgC2CGMQYOEDUfXlMOmKOCVYpnh0bPT3eM75KD9CvKDQH3r3k89xWCpqzKdhtJ4+zJ6KoctlPqDAutE7N37ZdSCEFGM/MSf0fJzoZzGVUmg9fZj9b5ZRYq9LL3dUfPb8CAmD9GYEIIJ8nOg3QcyhqF2ugqL4oX/ZhJx2YsvBia/Lcy6oghX3HZ8Qcrr4C5ZW3dG6vIHpAAAAAElFTkSuQmCC";
|
||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABGxJREFUeJztnb9vE0kYht9xkA4RIZ1ObrKmPUyVSDRcH0RBAXbBmSZCOk46pEgUSFiIPwAhFwgiIRGRBq7yNSZ3UjoqmqRJRxGlxQ5IFlckRZCOfFfYs4w3+8Mm3p1Z+32kKJP12t/YO9+z3+yuNwAhhBBCCJkulO0OEPcREYl6TCnFMURyCwdvDrApIB27WioBAFrt9kA7iz4QQqYU6VPxPKl43rF2nBzHFb/iebI5Py+yvHysnXZ8QtKkYLsDJJlqqYQHxSJa1eqxdpZsvXuHB8Wi3yYk71CAOcKWgFrtNh53uwPLHne7/hSYEEJSwZwCb87P+z9ZTYHD+pBlbEKIA0gIWce2KSCzD5QfIVOGiPQO/hu/s45vW0A25E9Impyy3YG8sbWwYCWuUkqJiNi89ISXuxAypejKR1d/rIIIyT/co4+AKT1WQ9kT3OlwG5CTwgFEcoGWn3aeiGQuQAp4Sgk7A8opILGArLYrAsDaCaB+bKfHP/N0eBIvhDb3vOYPEr6jOk4oYAIAq+2Kblo5AaSU8vvgWvUXzAsbeZpHYs8Cmx+mMfigl4mIuU4qAyI49TGWQ0TEtYFI0uOP0hur29qmgIfBTIVao+Mvo/+iiRSgKZ6d2m3gHnD+6MdvK7QrA8dj0pCRCwImRGNbwDGIUsqX3qQzN6Nk76uMZVvEVoCFWQ/qzBwu/LMBADhaegpv7SauvjgCNoBaYw9ISUZhAm5cXvMfX21XUxcwITlAosS3+2E/886kzdyMkqsvjrBxpzAWCUa+gCmg2y/DS+iDf/f8drPu6edFBxtBUCIihVlvYNlvzzrYuFPoCdiI36x7A3Epwsmn1uhIs+5N+3aOld/2ShmuTtddIfbD6VdVkE838PvffwEA1q79CgC4+epZ6HO0CENeqxdwSDnZFjAhcdgWcK3RiT2wl7YAo+L38zA3eTaUAA/XF/HDLz8BAL5sfgYA3Nr5c6gAugzfXimPfO2WTQET4jJRAjLzLWURRVafeZLg0N8F1uLTNOseLt7dwc/nzoauH9gQJzoT9WXzsy++YD+CBDeK7gfPhpFJ4lV5aaAICeZbFgLa/bAfmf954btvhnC4vojT18vA3Z3Qx8PEd5Lqy6aACXGR5/9VsXyqBWQsviRqjQ6adU9c6EsSiVNg9Kunw/XFgccO3h8AAIoPt6Ke+y3Id4rPnIKHcfr6W1zMSMCEuEb30SV/cF/5+Npfvr1SzmqcS1QRYp6YdDnvEjsWJiEtvzCKD7fG9sZtC5gQ1zEliF4+ZD3WRRchQRFqCbqcf0MJEH0JdR9dil13nPIz49sSMCFkKAQAgiLMgwATjwHqG3H230ikBNMUj46N3h4vcj3KjxArKPSm3r3kizgslWvMu2G0ntz374qh22neoCDwRe/Q+Gn3gRCSTPCOOa7n40j/FlMphdaT+/7fZhspVl36dePis/IjxA36X00FcpCPI10GY05Fg+0sSIrv+odNyKSTtxwcqbNx5WwWb9x2fELIZPE/Any0lmed9+8AAAAASUVORK5CYII=";
|
||||
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
||||
const HATS_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAAAMCAYAAACjpxUSAAAAAXNSR0IArs4c6QAAA29JREFUWIXtl11oW2UYx3+v3ZCuc1ktIpvowrCBjmkdtl6ouBthWMeGTkQxuA9QKCiym20OBfVG3YUVOobYIszLyXY16HYxvOicns3NRoR267ZG04aOdc3Jx8lJsujjRZOzeE5y+raETjR/CJyc9/98vv/zvOdAAw000EADDWhB3e0E6gURkfK1Uuo/U9dS4567GFs0OHqOymJIKud/JeoV5/8AjyBaV7bJhrWdEmxtl3pumgv195tUEO1wRAGwdzg4F6whCm14BHFvU/NCfYjrp4WWF05TV2EEBIKjfFN4CUpi6Fy3mf0/hfys3Hnr1rDQenW4futLJmiPIO5vaSNrZ3XtZebJ5/k51M13wY3IhQA6hXf1GgQCqxaTb1U47wwBYc8DJ/h0YhOd6zaz65GjtK18yNf2Un/Yyavv1Wd0wkksFsMwDAzDQKfeQmSQxNkvAETMqnwZvvgrNYTpXvPYLuaBrIVl7htlMUQT4wt2drinFbkAqjspfi+sZ187SPvXn+u6dRdZ1a9SSpWPhoPrR4ARdslR9m38ft4Al/rDZFImFyOJeXOJxWLE43HGx7X6I4XIIFY6RcacRkwg2oGYo6JW1+yPHO47gplI8sEn788vttFDpNIWtpXEti1CPQM1ex9sbZcVzSugtM8Ve+zwPYI4/9R6bs3Okk88CsATV6/qFO5AdSd9C+jqNdj0bROUjg1raIufeCR84EcArv8+TTGf4vyJnb5iU0px8r1tjI1dRimFiFT76nAafeXUhHNzjXe90k6Mjh1ce/ZtJuNTACRu5+gnxLtcqZaTAJzbdryCH+IdcxS12pv3WHSKgeND5P+YwEwkSc6m2f36Wzz9ysuMRaeq9gZgePsxUnkb888imcl05VrNHvmdAI5RWT1ZO0smlwJgxr5RletOys+vm9/Va2AVmvjlzf20fbYPa2iLL/+5HX1YmQT54nIAfjvzoR9fF1JxVJBJmc51eUrsPfaDO44zHcooT4lwOOzhFiKDAFjp1J045rRz/fDWQx6bj/q+Ij85STH/FzO3bpLNZVnz+GN8+fEBr/9rg1CYnZsOudvY6ZtzKyVWqGfAbcOL7W9IJmuRy88JIkcGgMjMOYe31N/rugKab1QuOm8RkdLEWGgMLf6/0L8APHjfWpqXtVB5ZNhFixvp+D+M/gZZI68eaJ1OpQAAAABJRU5ErkJggg==";
|
||||
|
||||
@@ -2269,7 +2325,7 @@ module.exports = class PocketBird extends Plugin {
|
||||
}),
|
||||
new Separator(),
|
||||
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
||||
new MenuItem("2026.3.8", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.8"); }, false),
|
||||
new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false),
|
||||
];
|
||||
|
||||
/** @type {Birb} */
|
||||
@@ -3238,63 +3294,6 @@ module.exports = class PocketBird extends Plugin {
|
||||
draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the sprite sheet and return the pixel-map template
|
||||
* @param {string} dataUri
|
||||
* @param {boolean} [templateColors]
|
||||
* @returns {Promise<string[][]>}
|
||||
*/
|
||||
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = dataUri;
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
reject(new Error('Failed to get canvas context'));
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
const pixels = imageData.data;
|
||||
const hexArray = [];
|
||||
for (let y = 0; y < img.height; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < img.width; x++) {
|
||||
const index = (y * img.width + x) * 4;
|
||||
const r = pixels[index];
|
||||
const g = pixels[index + 1];
|
||||
const b = pixels[index + 2];
|
||||
const a = pixels[index + 3];
|
||||
if (a === 0) {
|
||||
row.push(PALETTE.TRANSPARENT);
|
||||
continue;
|
||||
}
|
||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||
if (!templateColors) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||
// Return the color as-is if not found in the map
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||
}
|
||||
hexArray.push(row);
|
||||
}
|
||||
resolve(hexArray);
|
||||
};
|
||||
img.onerror = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
initializeApplication(new ObsidianContext());
|
||||
|
||||
})();
|
||||
|
||||
2
dist/obsidian/manifest.json
vendored
2
dist/obsidian/manifest.json
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "pocket-bird",
|
||||
"name": "Pocket Bird",
|
||||
"version": "2026.3.8",
|
||||
"version": "2026.3.11",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Add a pet bird to fly around your notes and keep you company!",
|
||||
"author": "Idrees Hassan",
|
||||
|
||||
119
dist/userscript/birb.user.js
vendored
119
dist/userscript/birb.user.js
vendored
@@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name Pocket Bird
|
||||
// @namespace https://idreesinc.com
|
||||
// @version 2026.3.8
|
||||
// @version 2026.3.11
|
||||
// @description It's a pet bird in your browser, what more could you want?
|
||||
// @author Idrees
|
||||
// @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js
|
||||
@@ -485,6 +485,62 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a sprite sheet image and convert it to a 2D array of palette color names
|
||||
* @param {string} src - URL or data URI of the sprite sheet image
|
||||
* @param {boolean} [templateColors] - Whether to map pixel colors to palette names
|
||||
* @returns {Promise<string[][]>}
|
||||
*/
|
||||
function loadSpriteSheetPixels(src, templateColors = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = src;
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
reject(new Error('Failed to get canvas context'));
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
const pixels = imageData.data;
|
||||
const hexArray = [];
|
||||
for (let y = 0; y < img.height; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < img.width; x++) {
|
||||
const index = (y * img.width + x) * 4;
|
||||
const r = pixels[index];
|
||||
const g = pixels[index + 1];
|
||||
const b = pixels[index + 2];
|
||||
const a = pixels[index + 3];
|
||||
if (a === 0) {
|
||||
row.push(PALETTE.TRANSPARENT);
|
||||
continue;
|
||||
}
|
||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||
if (!templateColors) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||
}
|
||||
hexArray.push(row);
|
||||
}
|
||||
resolve(hexArray);
|
||||
};
|
||||
img.onerror = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {Record<string, BirdType>} */
|
||||
const SPECIES = Object.fromEntries(
|
||||
Object.entries(species).map(([id, data]) => [
|
||||
@@ -2095,7 +2151,7 @@
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}`;
|
||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABElJREFUeJztnT9rFEEYh3+TWATE7hDcsxW7CBb6ASIWaXJXpRS0UAhYCIrkAwRJISgIBq20sjrTWJnKxjTpLIKtd4ocWOSENOa1uJ11bm7n9u683ZnJ/R44Mtnb23f2dt5nZ/bfAYQQQgghZL5QvitAwkdExPWeUoptiEQLG28E+BSQjt2s1wEArXZ7oFxFHQghc4qkNJJEGkkyVB4lx1nFbySJfF5eFtnYGCqXHZ+QMlnwXQFSTLNex+NaDa1mc6hcJfufPuFxrZaVCYkdCjAifAmo1W7jSbc7MO1Jt5sNgQkhpBTMIfDn5eXsVdUQOK8OVcYmhASA5FB1bJ8CMutA+REyZ4hI/+C/8bfq+L4F5EP+hJTJGd8ViI39K1e8xFVKKRERn5ee8HIXQuYU3fPRvT/2ggiJH+7RJ8CUHntD1WPvdLgNyP/CBkSiQMtPO09EKhcgBTyn5J0B5RCQeEB22g0B4O0EUBo76PbPPB2fwguhzT2v+ULBPaqzhAImALDTbuiilxNASqmsDqH1/uy88JGnMTLyLLD5ZRqND3qaiJjzlNIg7KGPMR0iIqE1RFIed+vvvW5rnwIeBzMV1rc72TT6z41TgKZ4DtfvAA+A7Ruvs/d32s2B4zFlyCgEAROi8S3gEYhSKpPeaefCopLvf2Qm28K5EBGRhbPJwLTbzzr4cG8Bqy9PAAC9X98BAO8eJQN7mVnIaEjAGBTwtdUm7tbf63lnFpeQyBCX+L5+O8LB88teThiVxYVFJasvT/Dh3gJmIcGRAkQqoDuv8rvQWoBIJQhDRrnBJtgIvgVMwmZ9uyPvHiXzvp0L5RfqcD0UnENgfeeB631TfrCOOeQxzTD55HdnSMBafnZsezjuWqdxY5NwON5dkaW1PaXLALC05ld+vgW8vt3xemDPFT/tCEWTZyMrmgoLx7sr2Pj5EQDw4vwNAMCtw7djBfj67QgApuqK6/h5PVBbwBrdE81ZFkAJRss/8e1Vvv3yBVx9PUxcAjLzrWQROXufMUlwbAHaLK3t4er9Q1y6eC73s9aGmEpAvgVMiCYU8WmOd1fEzAE73yoQkLjyfy4ECEOCedjiwxS9L98CJiRUjndXpPelh40zLWBw5FNV+3YKEBFJsFCASI/r2RLqfekBAGqb+67P/gsypXR8C5iQkOluXc8a980fb7LpB88ve5egeWIy5LwrrFiehLT88qht7s9sxX0LmJDQMSWIfj5U3dZFd0JsEWoJhpx/YwkQqYS6W9dHzjtL+ZnxfQmYEDIWAgC2CGMQYOEDUfXlMOmKOCVYpnh0bPT3eM75KD9CvKDQH3r3k89xWCpqzKdhtJ4+zJ6KoctlPqDAutE7N37ZdSCEFGM/MSf0fJzoZzGVUmg9fZj9b5ZRYq9LL3dUfPb8CAmD9GYEIIJ8nOg3QcyhqF2ugqL4oX/ZhJx2YsvBia/Lcy6oghX3HZ8Qcrr4C5ZW3dG6vIHpAAAAAElFTkSuQmCC";
|
||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABGxJREFUeJztnb9vE0kYht9xkA4RIZ1ObrKmPUyVSDRcH0RBAXbBmSZCOk46pEgUSFiIPwAhFwgiIRGRBq7yNSZ3UjoqmqRJRxGlxQ5IFlckRZCOfFfYs4w3+8Mm3p1Z+32kKJP12t/YO9+z3+yuNwAhhBBCCJkulO0OEPcREYl6TCnFMURyCwdvDrApIB27WioBAFrt9kA7iz4QQqYU6VPxPKl43rF2nBzHFb/iebI5Py+yvHysnXZ8QtKkYLsDJJlqqYQHxSJa1eqxdpZsvXuHB8Wi3yYk71CAOcKWgFrtNh53uwPLHne7/hSYEEJSwZwCb87P+z9ZTYHD+pBlbEKIA0gIWce2KSCzD5QfIVOGiPQO/hu/s45vW0A25E9Impyy3YG8sbWwYCWuUkqJiNi89ISXuxAypejKR1d/rIIIyT/co4+AKT1WQ9kT3OlwG5CTwgFEcoGWn3aeiGQuQAp4Sgk7A8opILGArLYrAsDaCaB+bKfHP/N0eBIvhDb3vOYPEr6jOk4oYAIAq+2Kblo5AaSU8vvgWvUXzAsbeZpHYs8Cmx+mMfigl4mIuU4qAyI49TGWQ0TEtYFI0uOP0hur29qmgIfBTIVao+Mvo/+iiRSgKZ6d2m3gHnD+6MdvK7QrA8dj0pCRCwImRGNbwDGIUsqX3qQzN6Nk76uMZVvEVoCFWQ/qzBwu/LMBADhaegpv7SauvjgCNoBaYw9ISUZhAm5cXvMfX21XUxcwITlAosS3+2E/886kzdyMkqsvjrBxpzAWCUa+gCmg2y/DS+iDf/f8drPu6edFBxtBUCIihVlvYNlvzzrYuFPoCdiI36x7A3Epwsmn1uhIs+5N+3aOld/2ShmuTtddIfbD6VdVkE838PvffwEA1q79CgC4+epZ6HO0CENeqxdwSDnZFjAhcdgWcK3RiT2wl7YAo+L38zA3eTaUAA/XF/HDLz8BAL5sfgYA3Nr5c6gAugzfXimPfO2WTQET4jJRAjLzLWURRVafeZLg0N8F1uLTNOseLt7dwc/nzoauH9gQJzoT9WXzsy++YD+CBDeK7gfPhpFJ4lV5aaAICeZbFgLa/bAfmf954btvhnC4vojT18vA3Z3Qx8PEd5Lqy6aACXGR5/9VsXyqBWQsviRqjQ6adU9c6EsSiVNg9Kunw/XFgccO3h8AAIoPt6Ke+y3Id4rPnIKHcfr6W1zMSMCEuEb30SV/cF/5+Npfvr1SzmqcS1QRYp6YdDnvEjsWJiEtvzCKD7fG9sZtC5gQ1zEliF4+ZD3WRRchQRFqCbqcf0MJEH0JdR9dil13nPIz49sSMCFkKAQAgiLMgwATjwHqG3H230ikBNMUj46N3h4vcj3KjxArKPSm3r3kizgslWvMu2G0ntz374qh22neoCDwRe/Q+Gn3gRCSTPCOOa7n40j/FlMphdaT+/7fZhspVl36dePis/IjxA36X00FcpCPI10GY05Fg+0sSIrv+odNyKSTtxwcqbNx5WwWb9x2fELIZPE/Any0lmed9+8AAAAASUVORK5CYII=";
|
||||
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
||||
const HATS_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAAAMCAYAAACjpxUSAAAAAXNSR0IArs4c6QAAA29JREFUWIXtl11oW2UYx3+v3ZCuc1ktIpvowrCBjmkdtl6ouBthWMeGTkQxuA9QKCiym20OBfVG3YUVOobYIszLyXY16HYxvOicns3NRoR267ZG04aOdc3Jx8lJsujjRZOzeE5y+raETjR/CJyc9/98vv/zvOdAAw000EADDWhB3e0E6gURkfK1Uuo/U9dS4567GFs0OHqOymJIKud/JeoV5/8AjyBaV7bJhrWdEmxtl3pumgv195tUEO1wRAGwdzg4F6whCm14BHFvU/NCfYjrp4WWF05TV2EEBIKjfFN4CUpi6Fy3mf0/hfys3Hnr1rDQenW4futLJmiPIO5vaSNrZ3XtZebJ5/k51M13wY3IhQA6hXf1GgQCqxaTb1U47wwBYc8DJ/h0YhOd6zaz65GjtK18yNf2Un/Yyavv1Wd0wkksFsMwDAzDQKfeQmSQxNkvAETMqnwZvvgrNYTpXvPYLuaBrIVl7htlMUQT4wt2drinFbkAqjspfi+sZ187SPvXn+u6dRdZ1a9SSpWPhoPrR4ARdslR9m38ft4Al/rDZFImFyOJeXOJxWLE43HGx7X6I4XIIFY6RcacRkwg2oGYo6JW1+yPHO47gplI8sEn788vttFDpNIWtpXEti1CPQM1ex9sbZcVzSugtM8Ve+zwPYI4/9R6bs3Okk88CsATV6/qFO5AdSd9C+jqNdj0bROUjg1raIufeCR84EcArv8+TTGf4vyJnb5iU0px8r1tjI1dRimFiFT76nAafeXUhHNzjXe90k6Mjh1ce/ZtJuNTACRu5+gnxLtcqZaTAJzbdryCH+IdcxS12pv3WHSKgeND5P+YwEwkSc6m2f36Wzz9ysuMRaeq9gZgePsxUnkb888imcl05VrNHvmdAI5RWT1ZO0smlwJgxr5RletOys+vm9/Va2AVmvjlzf20fbYPa2iLL/+5HX1YmQT54nIAfjvzoR9fF1JxVJBJmc51eUrsPfaDO44zHcooT4lwOOzhFiKDAFjp1J045rRz/fDWQx6bj/q+Ij85STH/FzO3bpLNZVnz+GN8+fEBr/9rg1CYnZsOudvY6ZtzKyVWqGfAbcOL7W9IJmuRy88JIkcGgMjMOYe31N/rugKab1QuOm8RkdLEWGgMLf6/0L8APHjfWpqXtVB5ZNhFixvp+D+M/gZZI68eaJ1OpQAAAABJRU5ErkJggg==";
|
||||
|
||||
@@ -2231,7 +2287,7 @@
|
||||
}),
|
||||
new Separator(),
|
||||
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
||||
new MenuItem("2026.3.8", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.8"); }, false),
|
||||
new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false),
|
||||
];
|
||||
|
||||
/** @type {Birb} */
|
||||
@@ -3200,63 +3256,6 @@
|
||||
draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the sprite sheet and return the pixel-map template
|
||||
* @param {string} dataUri
|
||||
* @param {boolean} [templateColors]
|
||||
* @returns {Promise<string[][]>}
|
||||
*/
|
||||
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = dataUri;
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
reject(new Error('Failed to get canvas context'));
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
const pixels = imageData.data;
|
||||
const hexArray = [];
|
||||
for (let y = 0; y < img.height; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < img.width; x++) {
|
||||
const index = (y * img.width + x) * 4;
|
||||
const r = pixels[index];
|
||||
const g = pixels[index + 1];
|
||||
const b = pixels[index + 2];
|
||||
const a = pixels[index + 3];
|
||||
if (a === 0) {
|
||||
row.push(PALETTE.TRANSPARENT);
|
||||
continue;
|
||||
}
|
||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||
if (!templateColors) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||
// Return the color as-is if not found in the map
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||
}
|
||||
hexArray.push(row);
|
||||
}
|
||||
resolve(hexArray);
|
||||
};
|
||||
img.onerror = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
initializeApplication(new UserScriptContext());
|
||||
|
||||
})();
|
||||
|
||||
117
dist/web/birb.embed.js
vendored
117
dist/web/birb.embed.js
vendored
@@ -471,6 +471,62 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a sprite sheet image and convert it to a 2D array of palette color names
|
||||
* @param {string} src - URL or data URI of the sprite sheet image
|
||||
* @param {boolean} [templateColors] - Whether to map pixel colors to palette names
|
||||
* @returns {Promise<string[][]>}
|
||||
*/
|
||||
function loadSpriteSheetPixels(src, templateColors = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = src;
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
reject(new Error('Failed to get canvas context'));
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
const pixels = imageData.data;
|
||||
const hexArray = [];
|
||||
for (let y = 0; y < img.height; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < img.width; x++) {
|
||||
const index = (y * img.width + x) * 4;
|
||||
const r = pixels[index];
|
||||
const g = pixels[index + 1];
|
||||
const b = pixels[index + 2];
|
||||
const a = pixels[index + 3];
|
||||
if (a === 0) {
|
||||
row.push(PALETTE.TRANSPARENT);
|
||||
continue;
|
||||
}
|
||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||
if (!templateColors) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||
}
|
||||
hexArray.push(row);
|
||||
}
|
||||
resolve(hexArray);
|
||||
};
|
||||
img.onerror = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {Record<string, BirdType>} */
|
||||
const SPECIES = Object.fromEntries(
|
||||
Object.entries(species).map(([id, data]) => [
|
||||
@@ -2075,7 +2131,7 @@
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}`;
|
||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABElJREFUeJztnT9rFEEYh3+TWATE7hDcsxW7CBb6ASIWaXJXpRS0UAhYCIrkAwRJISgIBq20sjrTWJnKxjTpLIKtd4ocWOSENOa1uJ11bm7n9u683ZnJ/R44Mtnb23f2dt5nZ/bfAYQQQgghZL5QvitAwkdExPWeUoptiEQLG28E+BSQjt2s1wEArXZ7oFxFHQghc4qkNJJEGkkyVB4lx1nFbySJfF5eFtnYGCqXHZ+QMlnwXQFSTLNex+NaDa1mc6hcJfufPuFxrZaVCYkdCjAifAmo1W7jSbc7MO1Jt5sNgQkhpBTMIfDn5eXsVdUQOK8OVcYmhASA5FB1bJ8CMutA+REyZ4hI/+C/8bfq+L4F5EP+hJTJGd8ViI39K1e8xFVKKRERn5ee8HIXQuYU3fPRvT/2ggiJH+7RJ8CUHntD1WPvdLgNyP/CBkSiQMtPO09EKhcgBTyn5J0B5RCQeEB22g0B4O0EUBo76PbPPB2fwguhzT2v+ULBPaqzhAImALDTbuiilxNASqmsDqH1/uy88JGnMTLyLLD5ZRqND3qaiJjzlNIg7KGPMR0iIqE1RFIed+vvvW5rnwIeBzMV1rc72TT6z41TgKZ4DtfvAA+A7Ruvs/d32s2B4zFlyCgEAROi8S3gEYhSKpPeaefCopLvf2Qm28K5EBGRhbPJwLTbzzr4cG8Bqy9PAAC9X98BAO8eJQN7mVnIaEjAGBTwtdUm7tbf63lnFpeQyBCX+L5+O8LB88teThiVxYVFJasvT/Dh3gJmIcGRAkQqoDuv8rvQWoBIJQhDRrnBJtgIvgVMwmZ9uyPvHiXzvp0L5RfqcD0UnENgfeeB631TfrCOOeQxzTD55HdnSMBafnZsezjuWqdxY5NwON5dkaW1PaXLALC05ld+vgW8vt3xemDPFT/tCEWTZyMrmgoLx7sr2Pj5EQDw4vwNAMCtw7djBfj67QgApuqK6/h5PVBbwBrdE81ZFkAJRss/8e1Vvv3yBVx9PUxcAjLzrWQROXufMUlwbAHaLK3t4er9Q1y6eC73s9aGmEpAvgVMiCYU8WmOd1fEzAE73yoQkLjyfy4ECEOCedjiwxS9L98CJiRUjndXpPelh40zLWBw5FNV+3YKEBFJsFCASI/r2RLqfekBAGqb+67P/gsypXR8C5iQkOluXc8a980fb7LpB88ve5egeWIy5LwrrFiehLT88qht7s9sxX0LmJDQMSWIfj5U3dZFd0JsEWoJhpx/YwkQqYS6W9dHzjtL+ZnxfQmYEDIWAgC2CGMQYOEDUfXlMOmKOCVYpnh0bPT3eM75KD9CvKDQH3r3k89xWCpqzKdhtJ4+zJ6KoctlPqDAutE7N37ZdSCEFGM/MSf0fJzoZzGVUmg9fZj9b5ZRYq9LL3dUfPb8CAmD9GYEIIJ8nOg3QcyhqF2ugqL4oX/ZhJx2YsvBia/Lcy6oghX3HZ8Qcrr4C5ZW3dG6vIHpAAAAAElFTkSuQmCC";
|
||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABGxJREFUeJztnb9vE0kYht9xkA4RIZ1ObrKmPUyVSDRcH0RBAXbBmSZCOk46pEgUSFiIPwAhFwgiIRGRBq7yNSZ3UjoqmqRJRxGlxQ5IFlckRZCOfFfYs4w3+8Mm3p1Z+32kKJP12t/YO9+z3+yuNwAhhBBCCJkulO0OEPcREYl6TCnFMURyCwdvDrApIB27WioBAFrt9kA7iz4QQqYU6VPxPKl43rF2nBzHFb/iebI5Py+yvHysnXZ8QtKkYLsDJJlqqYQHxSJa1eqxdpZsvXuHB8Wi3yYk71CAOcKWgFrtNh53uwPLHne7/hSYEEJSwZwCb87P+z9ZTYHD+pBlbEKIA0gIWce2KSCzD5QfIVOGiPQO/hu/s45vW0A25E9Impyy3YG8sbWwYCWuUkqJiNi89ISXuxAypejKR1d/rIIIyT/co4+AKT1WQ9kT3OlwG5CTwgFEcoGWn3aeiGQuQAp4Sgk7A8opILGArLYrAsDaCaB+bKfHP/N0eBIvhDb3vOYPEr6jOk4oYAIAq+2Kblo5AaSU8vvgWvUXzAsbeZpHYs8Cmx+mMfigl4mIuU4qAyI49TGWQ0TEtYFI0uOP0hur29qmgIfBTIVao+Mvo/+iiRSgKZ6d2m3gHnD+6MdvK7QrA8dj0pCRCwImRGNbwDGIUsqX3qQzN6Nk76uMZVvEVoCFWQ/qzBwu/LMBADhaegpv7SauvjgCNoBaYw9ISUZhAm5cXvMfX21XUxcwITlAosS3+2E/886kzdyMkqsvjrBxpzAWCUa+gCmg2y/DS+iDf/f8drPu6edFBxtBUCIihVlvYNlvzzrYuFPoCdiI36x7A3Epwsmn1uhIs+5N+3aOld/2ShmuTtddIfbD6VdVkE838PvffwEA1q79CgC4+epZ6HO0CENeqxdwSDnZFjAhcdgWcK3RiT2wl7YAo+L38zA3eTaUAA/XF/HDLz8BAL5sfgYA3Nr5c6gAugzfXimPfO2WTQET4jJRAjLzLWURRVafeZLg0N8F1uLTNOseLt7dwc/nzoauH9gQJzoT9WXzsy++YD+CBDeK7gfPhpFJ4lV5aaAICeZbFgLa/bAfmf954btvhnC4vojT18vA3Z3Qx8PEd5Lqy6aACXGR5/9VsXyqBWQsviRqjQ6adU9c6EsSiVNg9Kunw/XFgccO3h8AAIoPt6Ke+y3Id4rPnIKHcfr6W1zMSMCEuEb30SV/cF/5+Npfvr1SzmqcS1QRYp6YdDnvEjsWJiEtvzCKD7fG9sZtC5gQ1zEliF4+ZD3WRRchQRFqCbqcf0MJEH0JdR9dil13nPIz49sSMCFkKAQAgiLMgwATjwHqG3H230ikBNMUj46N3h4vcj3KjxArKPSm3r3kizgslWvMu2G0ntz374qh22neoCDwRe/Q+Gn3gRCSTPCOOa7n40j/FlMphdaT+/7fZhspVl36dePis/IjxA36X00FcpCPI10GY05Fg+0sSIrv+odNyKSTtxwcqbNx5WwWb9x2fELIZPE/Any0lmed9+8AAAAASUVORK5CYII=";
|
||||
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
||||
const HATS_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAAAMCAYAAACjpxUSAAAAAXNSR0IArs4c6QAAA29JREFUWIXtl11oW2UYx3+v3ZCuc1ktIpvowrCBjmkdtl6ouBthWMeGTkQxuA9QKCiym20OBfVG3YUVOobYIszLyXY16HYxvOicns3NRoR267ZG04aOdc3Jx8lJsujjRZOzeE5y+raETjR/CJyc9/98vv/zvOdAAw000EADDWhB3e0E6gURkfK1Uuo/U9dS4567GFs0OHqOymJIKud/JeoV5/8AjyBaV7bJhrWdEmxtl3pumgv195tUEO1wRAGwdzg4F6whCm14BHFvU/NCfYjrp4WWF05TV2EEBIKjfFN4CUpi6Fy3mf0/hfys3Hnr1rDQenW4futLJmiPIO5vaSNrZ3XtZebJ5/k51M13wY3IhQA6hXf1GgQCqxaTb1U47wwBYc8DJ/h0YhOd6zaz65GjtK18yNf2Un/Yyavv1Wd0wkksFsMwDAzDQKfeQmSQxNkvAETMqnwZvvgrNYTpXvPYLuaBrIVl7htlMUQT4wt2drinFbkAqjspfi+sZ187SPvXn+u6dRdZ1a9SSpWPhoPrR4ARdslR9m38ft4Al/rDZFImFyOJeXOJxWLE43HGx7X6I4XIIFY6RcacRkwg2oGYo6JW1+yPHO47gplI8sEn788vttFDpNIWtpXEti1CPQM1ex9sbZcVzSugtM8Ve+zwPYI4/9R6bs3Okk88CsATV6/qFO5AdSd9C+jqNdj0bROUjg1raIufeCR84EcArv8+TTGf4vyJnb5iU0px8r1tjI1dRimFiFT76nAafeXUhHNzjXe90k6Mjh1ce/ZtJuNTACRu5+gnxLtcqZaTAJzbdryCH+IdcxS12pv3WHSKgeND5P+YwEwkSc6m2f36Wzz9ysuMRaeq9gZgePsxUnkb888imcl05VrNHvmdAI5RWT1ZO0smlwJgxr5RletOys+vm9/Va2AVmvjlzf20fbYPa2iLL/+5HX1YmQT54nIAfjvzoR9fF1JxVJBJmc51eUrsPfaDO44zHcooT4lwOOzhFiKDAFjp1J045rRz/fDWQx6bj/q+Ij85STH/FzO3bpLNZVnz+GN8+fEBr/9rg1CYnZsOudvY6ZtzKyVWqGfAbcOL7W9IJmuRy88JIkcGgMjMOYe31N/rugKab1QuOm8RkdLEWGgMLf6/0L8APHjfWpqXtVB5ZNhFixvp+D+M/gZZI68eaJ1OpQAAAABJRU5ErkJggg==";
|
||||
|
||||
@@ -2211,7 +2267,7 @@
|
||||
}),
|
||||
new Separator(),
|
||||
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
||||
new MenuItem("2026.3.8", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.8"); }, false),
|
||||
new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false),
|
||||
];
|
||||
|
||||
/** @type {Birb} */
|
||||
@@ -3180,63 +3236,6 @@
|
||||
draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the sprite sheet and return the pixel-map template
|
||||
* @param {string} dataUri
|
||||
* @param {boolean} [templateColors]
|
||||
* @returns {Promise<string[][]>}
|
||||
*/
|
||||
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = dataUri;
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
reject(new Error('Failed to get canvas context'));
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
const pixels = imageData.data;
|
||||
const hexArray = [];
|
||||
for (let y = 0; y < img.height; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < img.width; x++) {
|
||||
const index = (y * img.width + x) * 4;
|
||||
const r = pixels[index];
|
||||
const g = pixels[index + 1];
|
||||
const b = pixels[index + 2];
|
||||
const a = pixels[index + 3];
|
||||
if (a === 0) {
|
||||
row.push(PALETTE.TRANSPARENT);
|
||||
continue;
|
||||
}
|
||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||
if (!templateColors) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||
// Return the color as-is if not found in the map
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||
}
|
||||
hexArray.push(row);
|
||||
}
|
||||
resolve(hexArray);
|
||||
};
|
||||
img.onerror = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
initializeApplication(new LocalContext());
|
||||
|
||||
})();
|
||||
|
||||
117
dist/web/birb.js
vendored
117
dist/web/birb.js
vendored
@@ -471,6 +471,62 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a sprite sheet image and convert it to a 2D array of palette color names
|
||||
* @param {string} src - URL or data URI of the sprite sheet image
|
||||
* @param {boolean} [templateColors] - Whether to map pixel colors to palette names
|
||||
* @returns {Promise<string[][]>}
|
||||
*/
|
||||
function loadSpriteSheetPixels(src, templateColors = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = src;
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
reject(new Error('Failed to get canvas context'));
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
const pixels = imageData.data;
|
||||
const hexArray = [];
|
||||
for (let y = 0; y < img.height; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < img.width; x++) {
|
||||
const index = (y * img.width + x) * 4;
|
||||
const r = pixels[index];
|
||||
const g = pixels[index + 1];
|
||||
const b = pixels[index + 2];
|
||||
const a = pixels[index + 3];
|
||||
if (a === 0) {
|
||||
row.push(PALETTE.TRANSPARENT);
|
||||
continue;
|
||||
}
|
||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||
if (!templateColors) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||
}
|
||||
hexArray.push(row);
|
||||
}
|
||||
resolve(hexArray);
|
||||
};
|
||||
img.onerror = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {Record<string, BirdType>} */
|
||||
const SPECIES = Object.fromEntries(
|
||||
Object.entries(species).map(([id, data]) => [
|
||||
@@ -2075,7 +2131,7 @@
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}`;
|
||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABElJREFUeJztnT9rFEEYh3+TWATE7hDcsxW7CBb6ASIWaXJXpRS0UAhYCIrkAwRJISgIBq20sjrTWJnKxjTpLIKtd4ocWOSENOa1uJ11bm7n9u683ZnJ/R44Mtnb23f2dt5nZ/bfAYQQQgghZL5QvitAwkdExPWeUoptiEQLG28E+BSQjt2s1wEArXZ7oFxFHQghc4qkNJJEGkkyVB4lx1nFbySJfF5eFtnYGCqXHZ+QMlnwXQFSTLNex+NaDa1mc6hcJfufPuFxrZaVCYkdCjAifAmo1W7jSbc7MO1Jt5sNgQkhpBTMIfDn5eXsVdUQOK8OVcYmhASA5FB1bJ8CMutA+REyZ4hI/+C/8bfq+L4F5EP+hJTJGd8ViI39K1e8xFVKKRERn5ee8HIXQuYU3fPRvT/2ggiJH+7RJ8CUHntD1WPvdLgNyP/CBkSiQMtPO09EKhcgBTyn5J0B5RCQeEB22g0B4O0EUBo76PbPPB2fwguhzT2v+ULBPaqzhAImALDTbuiilxNASqmsDqH1/uy88JGnMTLyLLD5ZRqND3qaiJjzlNIg7KGPMR0iIqE1RFIed+vvvW5rnwIeBzMV1rc72TT6z41TgKZ4DtfvAA+A7Ruvs/d32s2B4zFlyCgEAROi8S3gEYhSKpPeaefCopLvf2Qm28K5EBGRhbPJwLTbzzr4cG8Bqy9PAAC9X98BAO8eJQN7mVnIaEjAGBTwtdUm7tbf63lnFpeQyBCX+L5+O8LB88teThiVxYVFJasvT/Dh3gJmIcGRAkQqoDuv8rvQWoBIJQhDRrnBJtgIvgVMwmZ9uyPvHiXzvp0L5RfqcD0UnENgfeeB631TfrCOOeQxzTD55HdnSMBafnZsezjuWqdxY5NwON5dkaW1PaXLALC05ld+vgW8vt3xemDPFT/tCEWTZyMrmgoLx7sr2Pj5EQDw4vwNAMCtw7djBfj67QgApuqK6/h5PVBbwBrdE81ZFkAJRss/8e1Vvv3yBVx9PUxcAjLzrWQROXufMUlwbAHaLK3t4er9Q1y6eC73s9aGmEpAvgVMiCYU8WmOd1fEzAE73yoQkLjyfy4ECEOCedjiwxS9L98CJiRUjndXpPelh40zLWBw5FNV+3YKEBFJsFCASI/r2RLqfekBAGqb+67P/gsypXR8C5iQkOluXc8a980fb7LpB88ve5egeWIy5LwrrFiehLT88qht7s9sxX0LmJDQMSWIfj5U3dZFd0JsEWoJhpx/YwkQqYS6W9dHzjtL+ZnxfQmYEDIWAgC2CGMQYOEDUfXlMOmKOCVYpnh0bPT3eM75KD9CvKDQH3r3k89xWCpqzKdhtJ4+zJ6KoctlPqDAutE7N37ZdSCEFGM/MSf0fJzoZzGVUmg9fZj9b5ZRYq9LL3dUfPb8CAmD9GYEIIJ8nOg3QcyhqF2ugqL4oX/ZhJx2YsvBia/Lcy6oghX3HZ8Qcrr4C5ZW3dG6vIHpAAAAAElFTkSuQmCC";
|
||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABGxJREFUeJztnb9vE0kYht9xkA4RIZ1ObrKmPUyVSDRcH0RBAXbBmSZCOk46pEgUSFiIPwAhFwgiIRGRBq7yNSZ3UjoqmqRJRxGlxQ5IFlckRZCOfFfYs4w3+8Mm3p1Z+32kKJP12t/YO9+z3+yuNwAhhBBCCJkulO0OEPcREYl6TCnFMURyCwdvDrApIB27WioBAFrt9kA7iz4QQqYU6VPxPKl43rF2nBzHFb/iebI5Py+yvHysnXZ8QtKkYLsDJJlqqYQHxSJa1eqxdpZsvXuHB8Wi3yYk71CAOcKWgFrtNh53uwPLHne7/hSYEEJSwZwCb87P+z9ZTYHD+pBlbEKIA0gIWce2KSCzD5QfIVOGiPQO/hu/s45vW0A25E9Impyy3YG8sbWwYCWuUkqJiNi89ISXuxAypejKR1d/rIIIyT/co4+AKT1WQ9kT3OlwG5CTwgFEcoGWn3aeiGQuQAp4Sgk7A8opILGArLYrAsDaCaB+bKfHP/N0eBIvhDb3vOYPEr6jOk4oYAIAq+2Kblo5AaSU8vvgWvUXzAsbeZpHYs8Cmx+mMfigl4mIuU4qAyI49TGWQ0TEtYFI0uOP0hur29qmgIfBTIVao+Mvo/+iiRSgKZ6d2m3gHnD+6MdvK7QrA8dj0pCRCwImRGNbwDGIUsqX3qQzN6Nk76uMZVvEVoCFWQ/qzBwu/LMBADhaegpv7SauvjgCNoBaYw9ISUZhAm5cXvMfX21XUxcwITlAosS3+2E/886kzdyMkqsvjrBxpzAWCUa+gCmg2y/DS+iDf/f8drPu6edFBxtBUCIihVlvYNlvzzrYuFPoCdiI36x7A3Epwsmn1uhIs+5N+3aOld/2ShmuTtddIfbD6VdVkE838PvffwEA1q79CgC4+epZ6HO0CENeqxdwSDnZFjAhcdgWcK3RiT2wl7YAo+L38zA3eTaUAA/XF/HDLz8BAL5sfgYA3Nr5c6gAugzfXimPfO2WTQET4jJRAjLzLWURRVafeZLg0N8F1uLTNOseLt7dwc/nzoauH9gQJzoT9WXzsy++YD+CBDeK7gfPhpFJ4lV5aaAICeZbFgLa/bAfmf954btvhnC4vojT18vA3Z3Qx8PEd5Lqy6aACXGR5/9VsXyqBWQsviRqjQ6adU9c6EsSiVNg9Kunw/XFgccO3h8AAIoPt6Ke+y3Id4rPnIKHcfr6W1zMSMCEuEb30SV/cF/5+Npfvr1SzmqcS1QRYp6YdDnvEjsWJiEtvzCKD7fG9sZtC5gQ1zEliF4+ZD3WRRchQRFqCbqcf0MJEH0JdR9dil13nPIz49sSMCFkKAQAgiLMgwATjwHqG3H230ikBNMUj46N3h4vcj3KjxArKPSm3r3kizgslWvMu2G0ntz374qh22neoCDwRe/Q+Gn3gRCSTPCOOa7n40j/FlMphdaT+/7fZhspVl36dePis/IjxA36X00FcpCPI10GY05Fg+0sSIrv+odNyKSTtxwcqbNx5WwWb9x2fELIZPE/Any0lmed9+8AAAAASUVORK5CYII=";
|
||||
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
||||
const HATS_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIQAAAAMCAYAAACjpxUSAAAAAXNSR0IArs4c6QAAA29JREFUWIXtl11oW2UYx3+v3ZCuc1ktIpvowrCBjmkdtl6ouBthWMeGTkQxuA9QKCiym20OBfVG3YUVOobYIszLyXY16HYxvOicns3NRoR267ZG04aOdc3Jx8lJsujjRZOzeE5y+raETjR/CJyc9/98vv/zvOdAAw000EADDWhB3e0E6gURkfK1Uuo/U9dS4567GFs0OHqOymJIKud/JeoV5/8AjyBaV7bJhrWdEmxtl3pumgv195tUEO1wRAGwdzg4F6whCm14BHFvU/NCfYjrp4WWF05TV2EEBIKjfFN4CUpi6Fy3mf0/hfys3Hnr1rDQenW4futLJmiPIO5vaSNrZ3XtZebJ5/k51M13wY3IhQA6hXf1GgQCqxaTb1U47wwBYc8DJ/h0YhOd6zaz65GjtK18yNf2Un/Yyavv1Wd0wkksFsMwDAzDQKfeQmSQxNkvAETMqnwZvvgrNYTpXvPYLuaBrIVl7htlMUQT4wt2drinFbkAqjspfi+sZ187SPvXn+u6dRdZ1a9SSpWPhoPrR4ARdslR9m38ft4Al/rDZFImFyOJeXOJxWLE43HGx7X6I4XIIFY6RcacRkwg2oGYo6JW1+yPHO47gplI8sEn788vttFDpNIWtpXEti1CPQM1ex9sbZcVzSugtM8Ve+zwPYI4/9R6bs3Okk88CsATV6/qFO5AdSd9C+jqNdj0bROUjg1raIufeCR84EcArv8+TTGf4vyJnb5iU0px8r1tjI1dRimFiFT76nAafeXUhHNzjXe90k6Mjh1ce/ZtJuNTACRu5+gnxLtcqZaTAJzbdryCH+IdcxS12pv3WHSKgeND5P+YwEwkSc6m2f36Wzz9ysuMRaeq9gZgePsxUnkb888imcl05VrNHvmdAI5RWT1ZO0smlwJgxr5RletOys+vm9/Va2AVmvjlzf20fbYPa2iLL/+5HX1YmQT54nIAfjvzoR9fF1JxVJBJmc51eUrsPfaDO44zHcooT4lwOOzhFiKDAFjp1J045rRz/fDWQx6bj/q+Ij85STH/FzO3bpLNZVnz+GN8+fEBr/9rg1CYnZsOudvY6ZtzKyVWqGfAbcOL7W9IJmuRy88JIkcGgMjMOYe31N/rugKab1QuOm8RkdLEWGgMLf6/0L8APHjfWpqXtVB5ZNhFixvp+D+M/gZZI68eaJ1OpQAAAABJRU5ErkJggg==";
|
||||
|
||||
@@ -2211,7 +2267,7 @@
|
||||
}),
|
||||
new Separator(),
|
||||
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
||||
new MenuItem("2026.3.8", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.8"); }, false),
|
||||
new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false),
|
||||
];
|
||||
|
||||
/** @type {Birb} */
|
||||
@@ -3180,63 +3236,6 @@
|
||||
draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the sprite sheet and return the pixel-map template
|
||||
* @param {string} dataUri
|
||||
* @param {boolean} [templateColors]
|
||||
* @returns {Promise<string[][]>}
|
||||
*/
|
||||
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = dataUri;
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
reject(new Error('Failed to get canvas context'));
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
const pixels = imageData.data;
|
||||
const hexArray = [];
|
||||
for (let y = 0; y < img.height; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < img.width; x++) {
|
||||
const index = (y * img.width + x) * 4;
|
||||
const r = pixels[index];
|
||||
const g = pixels[index + 1];
|
||||
const b = pixels[index + 2];
|
||||
const a = pixels[index + 3];
|
||||
if (a === 0) {
|
||||
row.push(PALETTE.TRANSPARENT);
|
||||
continue;
|
||||
}
|
||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||
if (!templateColors) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||
// Return the color as-is if not found in the map
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||
}
|
||||
hexArray.push(row);
|
||||
}
|
||||
resolve(hexArray);
|
||||
};
|
||||
img.onerror = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
initializeApplication(new LocalContext());
|
||||
|
||||
})();
|
||||
|
||||
321
editor/editor.js
Normal file
321
editor/editor.js
Normal file
@@ -0,0 +1,321 @@
|
||||
// @ts-check
|
||||
import { SPRITE_SHEET_COLOR_MAP, PALETTE, loadSpriteSheetPixels } from '../src/animation/sprites.js';
|
||||
import Layer, { TAG } from '../src/animation/layer.js';
|
||||
import Frame from '../src/animation/frame.js';
|
||||
import { Directions, getLayerPixels } from '../src/shared.js';
|
||||
import species from '../src/species.js';
|
||||
|
||||
const COLOR_MAP = SPRITE_SHEET_COLOR_MAP;
|
||||
const SPRITE_PATH = "../sprites/birb.png";
|
||||
const SPRITE_SIZE = 32;
|
||||
/** @type {Array<{tag: string, label: string}>} */
|
||||
const AVAILABLE_TAGS = [
|
||||
{ tag: TAG.TUFT, label: "Tuft" },
|
||||
];
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
const DEFAULT_OVERRIDES = {
|
||||
"hood": "face",
|
||||
"nose": "face"
|
||||
};
|
||||
const IGNORED_PARTS = new Set(
|
||||
["transparent", "border", "heart", "heart-border", "heart-shine", "feather-spine"]
|
||||
);
|
||||
|
||||
/** @type {HTMLCanvasElement} */
|
||||
// @ts-ignore
|
||||
const canvas = document.getElementById("preview");
|
||||
/** @type {CanvasRenderingContext2D} */
|
||||
// @ts-ignore
|
||||
const ctx = canvas.getContext("2d");
|
||||
/** @type {HTMLElement} */
|
||||
// @ts-ignore
|
||||
const editor = document.getElementById("editor");
|
||||
const colorPickerInput = document.createElement("input");
|
||||
/** @type {HTMLElement} */
|
||||
// @ts-ignore
|
||||
const jsonElement = document.getElementById("json");
|
||||
|
||||
/** @type {string|null} */
|
||||
let selectedPart = null;
|
||||
/** @type {HTMLElement|null} */
|
||||
let selectedColorElement = null;
|
||||
/** @type {Record<string, HTMLElement>} */
|
||||
const colorElements = {};
|
||||
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
let currentSpecies = { ...species.bluebird.colors };
|
||||
let colorHistory = [{ ...currentSpecies }];
|
||||
let historyIndex = 0;
|
||||
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const currentTags = new Set();
|
||||
|
||||
/** @type {Frame|null} */
|
||||
let baseFrame = null;
|
||||
const spriteCanvas = document.createElement('canvas');
|
||||
spriteCanvas.width = canvas.width;
|
||||
spriteCanvas.height = canvas.height;
|
||||
/** @type {CanvasRenderingContext2D} */
|
||||
// @ts-ignore
|
||||
const spriteCtx = spriteCanvas.getContext('2d');
|
||||
|
||||
function drawBackground() {
|
||||
const patternSize = 2;
|
||||
const colors = ["#edf0f4", "#dadbe0"];
|
||||
for (let y = 0; y < canvas.height; y += patternSize) {
|
||||
for (let x = 0; x < canvas.width; x += patternSize) {
|
||||
ctx.fillStyle = ((x / patternSize + y / patternSize) % 2 === 0) ? colors[0] : colors[1];
|
||||
ctx.fillRect(x, y, patternSize, patternSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the full palette color scheme from the current species settings
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
function buildColorScheme() {
|
||||
/** @type {Record<string, string>} */
|
||||
const scheme = {};
|
||||
for (const paletteName of Object.values(PALETTE)) {
|
||||
scheme[paletteName] = getColor(paletteName);
|
||||
}
|
||||
return scheme;
|
||||
}
|
||||
|
||||
function draw() {
|
||||
if (!baseFrame) {
|
||||
return;
|
||||
}
|
||||
drawBackground();
|
||||
baseFrame.draw(spriteCtx, Directions.RIGHT, 1, buildColorScheme(), [...currentTags]);
|
||||
ctx.drawImage(spriteCanvas, 0, 0);
|
||||
}
|
||||
|
||||
function updateColors() {
|
||||
const lastColors = colorHistory[historyIndex];
|
||||
let changed = false;
|
||||
for (const part of Object.keys(currentSpecies)) {
|
||||
if (currentSpecies[part] !== lastColors[part]) {
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!changed) {
|
||||
for (const part of Object.keys(lastColors)) {
|
||||
if (!(part in currentSpecies)) {
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
colorHistory = colorHistory.slice(0, historyIndex + 1);
|
||||
colorHistory.push({ ...currentSpecies });
|
||||
historyIndex++;
|
||||
}
|
||||
updateJson();
|
||||
draw();
|
||||
}
|
||||
|
||||
function loadEditor() {
|
||||
for (const [color, part] of Object.entries(COLOR_MAP)) {
|
||||
if (IGNORED_PARTS.has(part)) {
|
||||
continue;
|
||||
}
|
||||
const item = createColorItem(part, getColor(part) || color);
|
||||
editor.appendChild(item);
|
||||
}
|
||||
// for (const { tag, label } of AVAILABLE_TAGS) {
|
||||
// editor.appendChild(createTagItem(tag, label));
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} part
|
||||
* @return {string}
|
||||
*/
|
||||
function getColor(part) {
|
||||
if (currentSpecies[part]) {
|
||||
return currentSpecies[part];
|
||||
}
|
||||
if (DEFAULT_OVERRIDES[part]) {
|
||||
return getColor(DEFAULT_OVERRIDES[part]);
|
||||
}
|
||||
for (const [color, partName] of Object.entries(COLOR_MAP)) {
|
||||
if (partName === part) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
function createColorPicker() {
|
||||
colorPickerInput.type = "text";
|
||||
colorPickerInput.id = "coloris-proxy";
|
||||
colorPickerInput.setAttribute("data-coloris", "");
|
||||
document.body.appendChild(colorPickerInput);
|
||||
|
||||
colorPickerInput.addEventListener("input", () => {
|
||||
if (selectedColorElement && selectedPart !== null) {
|
||||
const newColor = colorPickerInput.value;
|
||||
selectedColorElement.style.backgroundColor = newColor;
|
||||
currentSpecies[selectedPart] = newColor;
|
||||
draw();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("mouseup", () => {
|
||||
if (selectedPart !== null && !jsonElement.contains(document.activeElement)) {
|
||||
updateColors();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} label
|
||||
* @param {string} color
|
||||
* @returns {HTMLDivElement}
|
||||
*/
|
||||
function createColorItem(label, color) {
|
||||
const item = document.createElement("div");
|
||||
item.classList.add("editor-item");
|
||||
|
||||
const colorElement = document.createElement("div");
|
||||
colorElement.classList.add("color");
|
||||
colorElement.style.backgroundColor = color;
|
||||
colorElements[label] = colorElement;
|
||||
item.appendChild(colorElement);
|
||||
if (color !== "transparent") {
|
||||
colorElement.addEventListener("click", () => {
|
||||
selectedPart = label;
|
||||
selectedColorElement = colorElement;
|
||||
const rect = colorElement.getBoundingClientRect();
|
||||
colorPickerInput.style.left = rect.left + "px";
|
||||
colorPickerInput.style.top = (rect.bottom + window.scrollY) + "px";
|
||||
|
||||
colorPickerInput.value = currentSpecies[label] || color;
|
||||
colorPickerInput.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
});
|
||||
} else {
|
||||
colorElement.classList.add("color--transparent");
|
||||
}
|
||||
const labelElement = document.createElement("div");
|
||||
const labelText = label.replaceAll("-", " ").toUpperCase();
|
||||
labelElement.classList.add("label");
|
||||
labelElement.textContent = labelText;
|
||||
labelElement.title = "Click to remove from species";
|
||||
labelElement.addEventListener("click", () => {
|
||||
delete currentSpecies[label];
|
||||
colorElement.style.backgroundColor = getColor(label);
|
||||
updateColors();
|
||||
refreshEditorColors();
|
||||
});
|
||||
item.appendChild(labelElement);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} tag
|
||||
* @param {string} label
|
||||
* @returns {HTMLDivElement}
|
||||
*/
|
||||
function createTagItem(tag, label) {
|
||||
const item = document.createElement("div");
|
||||
item.classList.add("tag-item");
|
||||
|
||||
const toggle = document.createElement("button");
|
||||
toggle.classList.add("tag-toggle");
|
||||
toggle.textContent = "✓";
|
||||
toggle.addEventListener("click", () => {
|
||||
if (currentTags.has(tag)) {
|
||||
currentTags.delete(tag);
|
||||
toggle.classList.remove("tag-toggle--active");
|
||||
} else {
|
||||
currentTags.add(tag);
|
||||
toggle.classList.add("tag-toggle--active");
|
||||
}
|
||||
draw();
|
||||
});
|
||||
item.appendChild(toggle);
|
||||
|
||||
const labelElement = document.createElement("div");
|
||||
labelElement.classList.add("label");
|
||||
labelElement.textContent = label.toUpperCase();
|
||||
item.appendChild(labelElement);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
function refreshEditorColors() {
|
||||
for (const [, part] of Object.entries(COLOR_MAP)) {
|
||||
const el = colorElements[part];
|
||||
if (el && !el.classList.contains("color--transparent")) {
|
||||
el.style.backgroundColor = getColor(part);
|
||||
}
|
||||
}
|
||||
if (selectedColorElement && selectedPart !== null) {
|
||||
colorPickerInput.value = currentSpecies[selectedPart] || "";
|
||||
}
|
||||
}
|
||||
|
||||
function updateJson() {
|
||||
jsonElement.textContent = JSON.stringify(currentSpecies, null, 2);
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (!(e.metaKey || e.ctrlKey)) {
|
||||
return;
|
||||
}
|
||||
if (e.key === "z" && !e.shiftKey) {
|
||||
if (historyIndex > 0) {
|
||||
historyIndex--;
|
||||
currentSpecies = { ...colorHistory[historyIndex] };
|
||||
refreshEditorColors();
|
||||
updateJson();
|
||||
draw();
|
||||
e.preventDefault();
|
||||
}
|
||||
} else if ((e.key === "z" && e.shiftKey) || e.key === "y") {
|
||||
if (historyIndex < colorHistory.length - 1) {
|
||||
historyIndex++;
|
||||
currentSpecies = { ...colorHistory[historyIndex] };
|
||||
refreshEditorColors();
|
||||
updateJson();
|
||||
draw();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
jsonElement.addEventListener("input", () => {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonElement.textContent || "");
|
||||
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
||||
currentSpecies = parsed;
|
||||
refreshEditorColors();
|
||||
draw();
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
});
|
||||
|
||||
jsonElement.addEventListener("blur", () => {
|
||||
updateColors();
|
||||
});
|
||||
|
||||
createColorPicker();
|
||||
loadEditor();
|
||||
|
||||
(async () => {
|
||||
const pixels = await loadSpriteSheetPixels(SPRITE_PATH);
|
||||
baseFrame = new Frame([
|
||||
new Layer(getLayerPixels(pixels, 0, SPRITE_SIZE)),
|
||||
new Layer(getLayerPixels(pixels, 5, SPRITE_SIZE), TAG.TUFT),
|
||||
]);
|
||||
updateColors();
|
||||
})();
|
||||
24
editor/index.html
Normal file
24
editor/index.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Birb Editor</title>
|
||||
<link rel="stylesheet" href="stylesheet.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mdbassit/Coloris@latest/dist/coloris.min.css"/>
|
||||
<script src="https://cdn.jsdelivr.net/gh/mdbassit/Coloris@latest/dist/coloris.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="horizontal-container">
|
||||
<canvas id="preview" width="32px" height="32px"></canvas>
|
||||
<div id="editor"></div>
|
||||
<pre id="json" contenteditable="true"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="editor.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
151
editor/stylesheet.css
Normal file
151
editor/stylesheet.css
Normal file
@@ -0,0 +1,151 @@
|
||||
@import url(https://cdn.jsdelivr.net/npm/firacode@6.2.0/distr/fira_code.css);
|
||||
|
||||
body {
|
||||
background: linear-gradient(to top, #D2DAE9, white);
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.horizontal-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
#preview {
|
||||
width: 480px;
|
||||
height: 480px;
|
||||
image-rendering: pixelated;
|
||||
filter: drop-shadow(0px 0px 40px rgba(0, 0, 0, 0.1));
|
||||
box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 40px;
|
||||
}
|
||||
|
||||
#editor {
|
||||
width: 460px;
|
||||
height: 480px;
|
||||
background: #ffffff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 30px 20px;
|
||||
column-count: 2;
|
||||
}
|
||||
|
||||
#json {
|
||||
width: 200px;
|
||||
height: 480px;
|
||||
background: #ffffff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
gap: 20px;
|
||||
text-align: left;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.editor-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 18px;
|
||||
font-family: 'Fira Code', monospace;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.color {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
|
||||
background: red;
|
||||
transition: transform 0.1s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.color:hover {
|
||||
transform: scale(1.15);
|
||||
transition: 0.1s ease-in;
|
||||
}
|
||||
|
||||
.color--transparent {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#coloris-proxy {
|
||||
position: fixed;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
border: none;
|
||||
padding: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
height: 38px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.tag-toggle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
background: #edf0f4;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
color: transparent;
|
||||
transition: background 0.15s, color 0.15s, transform 0.1s;
|
||||
}
|
||||
|
||||
.tag-toggle:hover {
|
||||
transform: scale(1.15);
|
||||
transition: 0.1s ease-in;
|
||||
}
|
||||
|
||||
.tag-toggle--active {
|
||||
background: #639bff;
|
||||
color: white;
|
||||
}
|
||||
@@ -79,6 +79,62 @@ export class BirdType {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a sprite sheet image and convert it to a 2D array of palette color names
|
||||
* @param {string} src - URL or data URI of the sprite sheet image
|
||||
* @param {boolean} [templateColors] - Whether to map pixel colors to palette names
|
||||
* @returns {Promise<string[][]>}
|
||||
*/
|
||||
export function loadSpriteSheetPixels(src, templateColors = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = src;
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
reject(new Error('Failed to get canvas context'));
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
const pixels = imageData.data;
|
||||
const hexArray = [];
|
||||
for (let y = 0; y < img.height; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < img.width; x++) {
|
||||
const index = (y * img.width + x) * 4;
|
||||
const r = pixels[index];
|
||||
const g = pixels[index + 1];
|
||||
const b = pixels[index + 2];
|
||||
const a = pixels[index + 3];
|
||||
if (a === 0) {
|
||||
row.push(PALETTE.TRANSPARENT);
|
||||
continue;
|
||||
}
|
||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||
if (!templateColors) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||
}
|
||||
hexArray.push(row);
|
||||
}
|
||||
resolve(hexArray);
|
||||
};
|
||||
img.onerror = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {Record<string, BirdType>} */
|
||||
export const SPECIES = Object.fromEntries(
|
||||
Object.entries(species).map(([id, data]) => [
|
||||
|
||||
@@ -25,7 +25,8 @@ import {
|
||||
import {
|
||||
PALETTE,
|
||||
SPRITE_SHEET_COLOR_MAP,
|
||||
SPECIES
|
||||
SPECIES,
|
||||
loadSpriteSheetPixels,
|
||||
} from './animation/sprites.js';
|
||||
import {
|
||||
StickyNote,
|
||||
@@ -1202,59 +1203,3 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
||||
draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the sprite sheet and return the pixel-map template
|
||||
* @param {string} dataUri
|
||||
* @param {boolean} [templateColors]
|
||||
* @returns {Promise<string[][]>}
|
||||
*/
|
||||
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = dataUri;
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
reject(new Error('Failed to get canvas context'));
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
const pixels = imageData.data;
|
||||
const hexArray = [];
|
||||
for (let y = 0; y < img.height; y++) {
|
||||
const row = [];
|
||||
for (let x = 0; x < img.width; x++) {
|
||||
const index = (y * img.width + x) * 4;
|
||||
const r = pixels[index];
|
||||
const g = pixels[index + 1];
|
||||
const b = pixels[index + 2];
|
||||
const a = pixels[index + 3];
|
||||
if (a === 0) {
|
||||
row.push(PALETTE.TRANSPARENT);
|
||||
continue;
|
||||
}
|
||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||
if (!templateColors) {
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||
// Return the color as-is if not found in the map
|
||||
row.push(hex);
|
||||
continue;
|
||||
}
|
||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||
}
|
||||
hexArray.push(row);
|
||||
}
|
||||
resolve(hexArray);
|
||||
};
|
||||
img.onerror = (err) => {
|
||||
reject(err);
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user