Merge pull request #9 from IdreesInc/new-birds
New birds and new logic!
4
.gitignore
vendored
@@ -4,3 +4,7 @@
|
|||||||
obsidian-test.sh
|
obsidian-test.sh
|
||||||
build-cache.json
|
build-cache.json
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
|
aseprite/birb-test.aseprite
|
||||||
|
aseprite/wren.aseprite
|
||||||
|
aseprite/birb-no-shoulder.aseprite
|
||||||
|
aseprite/birb-fat.aseprite
|
||||||
|
|||||||
BIN
dist/extension.zip
vendored
546
dist/extension/birb.js
vendored
@@ -237,6 +237,8 @@
|
|||||||
"bluebird": {
|
"bluebird": {
|
||||||
"name": "Eastern Bluebird",
|
"name": "Eastern Bluebird",
|
||||||
"description": "Native to North American and very social, though can be timid around people.",
|
"description": "Native to North American and very social, though can be timid around people.",
|
||||||
|
"latinName": "Sialia sialis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Eastern_bluebird",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#639bff",
|
"face": "#639bff",
|
||||||
@@ -249,6 +251,8 @@
|
|||||||
"shimaEnaga": {
|
"shimaEnaga": {
|
||||||
"name": "Shima Enaga",
|
"name": "Shima Enaga",
|
||||||
"description": "Small, fluffy birds found in the snowy regions of Japan, these birds are highly sought after by ornithologists and nature photographers.",
|
"description": "Small, fluffy birds found in the snowy regions of Japan, these birds are highly sought after by ornithologists and nature photographers.",
|
||||||
|
"latinName": "Aegithalos caudatus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Long-tailed_tit",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#ffffff",
|
"face": "#ffffff",
|
||||||
@@ -262,6 +266,8 @@
|
|||||||
"tuftedTitmouse": {
|
"tuftedTitmouse": {
|
||||||
"name": "Tufted Titmouse",
|
"name": "Tufted Titmouse",
|
||||||
"description": "Native to the eastern United States, full of personality, and notably my wife's favorite bird.",
|
"description": "Native to the eastern United States, full of personality, and notably my wife's favorite bird.",
|
||||||
|
"latinName": "Baeolophus bicolor",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Tufted_titmouse",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#c7cad7",
|
"face": "#c7cad7",
|
||||||
@@ -278,6 +284,8 @@
|
|||||||
"europeanRobin": {
|
"europeanRobin": {
|
||||||
"name": "European Robin",
|
"name": "European Robin",
|
||||||
"description": "Native to western Europe, this is the quintessential robin. Quite friendly, you'll often find them searching for worms.",
|
"description": "Native to western Europe, this is the quintessential robin. Quite friendly, you'll often find them searching for worms.",
|
||||||
|
"latinName": "Erithacus rubecula",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/European_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#ffaf34",
|
"face": "#ffaf34",
|
||||||
@@ -292,6 +300,8 @@
|
|||||||
"redCardinal": {
|
"redCardinal": {
|
||||||
"name": "Red Cardinal",
|
"name": "Red Cardinal",
|
||||||
"description": "Native to the eastern United States, this strikingly red bird is hard to miss.",
|
"description": "Native to the eastern United States, this strikingly red bird is hard to miss.",
|
||||||
|
"latinName": "Cardinalis cardinalis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_cardinal",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#d93619",
|
"beak": "#d93619",
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
@@ -311,6 +321,8 @@
|
|||||||
"americanGoldfinch": {
|
"americanGoldfinch": {
|
||||||
"name": "American Goldfinch",
|
"name": "American Goldfinch",
|
||||||
"description": "Coloured a brilliant yellow, this bird feeds almost entirely on the seeds of plants such as thistle, sunflowers, and coneflowers.",
|
"description": "Coloured a brilliant yellow, this bird feeds almost entirely on the seeds of plants such as thistle, sunflowers, and coneflowers.",
|
||||||
|
"latinName": "Spinus tristis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/American_goldfinch",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#ffaf34",
|
"beak": "#ffaf34",
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
@@ -327,6 +339,8 @@
|
|||||||
"barnSwallow": {
|
"barnSwallow": {
|
||||||
"name": "Barn Swallow",
|
"name": "Barn Swallow",
|
||||||
"description": "Agile birds that often roost in man-made structures, these birds are known to build nests near Ospreys for protection.",
|
"description": "Agile birds that often roost in man-made structures, these birds are known to build nests near Ospreys for protection.",
|
||||||
|
"latinName": "Hirundo rustica",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Barn_swallow",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#db7c4d",
|
"face": "#db7c4d",
|
||||||
@@ -340,6 +354,8 @@
|
|||||||
"mistletoebird": {
|
"mistletoebird": {
|
||||||
"name": "Mistletoebird",
|
"name": "Mistletoebird",
|
||||||
"description": "Native to Australia, these birds eat mainly mistletoe and in turn spread the seeds far and wide.",
|
"description": "Native to Australia, these birds eat mainly mistletoe and in turn spread the seeds far and wide.",
|
||||||
|
"latinName": "Dicaeum hirundinaceum",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Mistletoebird",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#6c6a7c",
|
"foot": "#6c6a7c",
|
||||||
"face": "#352e6d",
|
"face": "#352e6d",
|
||||||
@@ -349,22 +365,11 @@
|
|||||||
"wing-edge": "#282065"
|
"wing-edge": "#282065"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redAvadavat": {
|
|
||||||
"name": "Red Avadavat",
|
|
||||||
"description": "Native to India and southeast Asia, these birds are also known as Strawberry Finches due to their speckled plumage.",
|
|
||||||
"colors": {
|
|
||||||
"beak": "#f71919",
|
|
||||||
"foot": "#af7575",
|
|
||||||
"face": "#cb092b",
|
|
||||||
"belly": "#ae1724",
|
|
||||||
"underbelly": "#831b24",
|
|
||||||
"wing": "#7e3030",
|
|
||||||
"wing-edge": "#490f0f"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scarletRobin": {
|
"scarletRobin": {
|
||||||
"name": "Scarlet Robin",
|
"name": "Scarlet Robin",
|
||||||
"description": "Native to Australia, this striking robin can be found in Eucalyptus forests.",
|
"description": "Native to Australia, this striking robin can be found in Eucalyptus forests.",
|
||||||
|
"latinName": "Petroica boodang",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Scarlet_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#494949",
|
"foot": "#494949",
|
||||||
"face": "#3d3d3d",
|
"face": "#3d3d3d",
|
||||||
@@ -372,12 +377,15 @@
|
|||||||
"underbelly": "#dcdcdc",
|
"underbelly": "#dcdcdc",
|
||||||
"wing": "#2b2b2b",
|
"wing": "#2b2b2b",
|
||||||
"wing-edge": "#ebebeb",
|
"wing-edge": "#ebebeb",
|
||||||
|
"nose": "#ebebeb",
|
||||||
"theme-highlight": "#fc5633"
|
"theme-highlight": "#fc5633"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"americanRobin": {
|
"americanRobin": {
|
||||||
"name": "American Robin",
|
"name": "American Robin",
|
||||||
"description": "While not a true robin, this social North American bird is so named due to its orange coloring. It seems unbothered by nearby humans.",
|
"description": "While not a true robin, this social North American bird is so named due to its orange coloring. It seems unbothered by nearby humans.",
|
||||||
|
"latinName": "Turdus migratorius",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/American_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#e89f30",
|
"beak": "#e89f30",
|
||||||
"foot": "#9f8075",
|
"foot": "#9f8075",
|
||||||
@@ -392,6 +400,8 @@
|
|||||||
"carolinaWren": {
|
"carolinaWren": {
|
||||||
"name": "Carolina Wren",
|
"name": "Carolina Wren",
|
||||||
"description": "Native to the eastern United States, these little birds are known for their curious and energetic nature.",
|
"description": "Native to the eastern United States, these little birds are known for their curious and energetic nature.",
|
||||||
|
"latinName": "Thryothorus ludovicianus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Carolina_wren",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#edc7a9",
|
"face": "#edc7a9",
|
||||||
@@ -402,14 +412,249 @@
|
|||||||
"wing": "#c58a5b",
|
"wing": "#c58a5b",
|
||||||
"wing-edge": "#866348"
|
"wing-edge": "#866348"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"blackCappedChickadee": {
|
||||||
|
"name": "Black-capped Chickadee",
|
||||||
|
"description": "Native to North America, these small and curious birds are known for their distinctive call from which they get their name.",
|
||||||
|
"latinName": "Poecile atricapillus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Black-capped_chickadee",
|
||||||
|
"colors": {
|
||||||
|
"hood": "#363636",
|
||||||
|
"cheek": "#363636",
|
||||||
|
"eyebrow": "#363636",
|
||||||
|
"nose": "#363636",
|
||||||
|
"collar": "#363636",
|
||||||
|
"belly": "#d6d4cf",
|
||||||
|
"underbelly": "#cfc5b4",
|
||||||
|
"face": "#eaeaea",
|
||||||
|
"wing": "#8f8e9a",
|
||||||
|
"wing-edge": "#706f7d",
|
||||||
|
"scruff": "#8f8e9a",
|
||||||
|
"foot": "#535259"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"blueJay": {
|
||||||
|
"name": "Blue Jay",
|
||||||
|
"description": "This loud and rambunctious bird is native to North America and is known for challenging anything in its path.",
|
||||||
|
"latinName": "Cyanocitta cristata",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Blue_jay",
|
||||||
|
"colors": {
|
||||||
|
"foot": "#5a626b",
|
||||||
|
"face": "#ebf2ff",
|
||||||
|
"belly": "#e5ecfa",
|
||||||
|
"underbelly": "#c4cbd6",
|
||||||
|
"wing": "#5890ff",
|
||||||
|
"wing-edge": "#3a77e8",
|
||||||
|
"hood": "#6391e8",
|
||||||
|
"nose": "#6391e8",
|
||||||
|
"collar": "#2e3136",
|
||||||
|
"scruff": "#6391e8"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"tuft"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"darkEyedJunco": {
|
||||||
|
"name": "Dark-eyed Junco",
|
||||||
|
"description": "Native across North America, these social birds will often be seen hopping along the ground in winter.",
|
||||||
|
"latinName": "Junco hyemalis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Dark-eyed_junco",
|
||||||
|
"colors": {
|
||||||
|
"face": "#55565e",
|
||||||
|
"wing": "#5c5f69",
|
||||||
|
"wing-edge": "#444547",
|
||||||
|
"belly": "#6c7180",
|
||||||
|
"underbelly": "#b8bbcc",
|
||||||
|
"foot": "#87776d",
|
||||||
|
"beak": "#ab8a98"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"houseFinch": {
|
||||||
|
"name": "House Finch",
|
||||||
|
"description": "Native to North America, these highly social birds sing cheerful songs and are often seen at bird feeders.",
|
||||||
|
"latinName": "Haemorhous mexicanus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/House_finch",
|
||||||
|
"colors": {
|
||||||
|
"face": "#cc3a3f",
|
||||||
|
"wing": "#ae8e78",
|
||||||
|
"wing-edge": "#8f6c54",
|
||||||
|
"belly": "#d97c77",
|
||||||
|
"underbelly": "#c5a489",
|
||||||
|
"foot": "#705b4c",
|
||||||
|
"beak": "#cf8479",
|
||||||
|
"hood": "#b02f35",
|
||||||
|
"nose": "#ab2b31",
|
||||||
|
"theme-highlight": "#ef444d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pigeon": {
|
||||||
|
"name": "Rock Pigeon",
|
||||||
|
"description": "Descended from the Rock Dove, these once domesticated birds are often found in cities worldwide. Quite friendly and intelligent, they were favored companions of Nikola Tesla.",
|
||||||
|
"latinName": "Columba livia",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Rock_dove",
|
||||||
|
"colors": {
|
||||||
|
"foot": "#ef6e5b",
|
||||||
|
"face": "#5a6c91",
|
||||||
|
"wing-edge": "#65686e",
|
||||||
|
"nose": "#ebebeb",
|
||||||
|
"belly": "#977699",
|
||||||
|
"underbelly": "#b0b3ba",
|
||||||
|
"wing": "#c7cbd4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redAvadavat": {
|
||||||
|
"name": "Red Avadavat",
|
||||||
|
"description": "Native to India and southeast Asia, these birds are also known as Strawberry Finches due to their speckled plumage.",
|
||||||
|
"latinName": "Amandava amandava",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_avadavat",
|
||||||
|
"colors": {
|
||||||
|
"beak": "#f71919",
|
||||||
|
"foot": "#af7575",
|
||||||
|
"face": "#cb092b",
|
||||||
|
"belly": "#ae1724",
|
||||||
|
"underbelly": "#831b24",
|
||||||
|
"wing": "#7e3030",
|
||||||
|
"wing-edge": "#490f0f",
|
||||||
|
"wing-spots": "#e8e4e4",
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"pinkRobin": {
|
||||||
|
"name": "Pink Robin",
|
||||||
|
"description": "Native to Australia, these bubblegum-pink puffballs are quieter than most, instead relying on their vibrant colours to attract partners.",
|
||||||
|
"latinName": "Petroica rodinogaster",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Pink_robin",
|
||||||
|
"colors": {
|
||||||
|
"face": "#403a46",
|
||||||
|
"wing": "#38333d",
|
||||||
|
"wing-edge": "#252325",
|
||||||
|
"underbelly": "#ff7eb8",
|
||||||
|
"belly": "#ff6eaf",
|
||||||
|
"foot": "#3c393c",
|
||||||
|
"theme-highlight": "#ff82ba"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"spangledCotinga": {
|
||||||
|
"name": "Spangled Cotinga",
|
||||||
|
"description": "This South American bird can be found in the Amazon rainforest, flashing its iridescent turquoise feathers high above in the canopy.",
|
||||||
|
"latinName": "Cotinga cayana",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Spangled_cotinga",
|
||||||
|
"colors": {
|
||||||
|
"face": "#62eafe",
|
||||||
|
"chin": "#a12457",
|
||||||
|
"collar": "#a12457",
|
||||||
|
"belly": "#62eafe",
|
||||||
|
"underbelly": "#5cd8ea",
|
||||||
|
"wing": "#227c89",
|
||||||
|
"wing-edge": "#13353a",
|
||||||
|
"foot": "#68696b",
|
||||||
|
"collar-scruff": "#62eafe"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"elegantEuphonia": {
|
||||||
|
"name": "Elegant Euphonia",
|
||||||
|
"description": "This vividly coloured finch is found throughout Central America and is known for the distinctive blue hood that crowns its head.",
|
||||||
|
"latinName": "Chlorophonia elegantissima",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Elegant_euphonia",
|
||||||
|
"colors": {
|
||||||
|
"wing": "#2d31a1",
|
||||||
|
"wing-edge": "#191c6d",
|
||||||
|
"face": "#1f2392",
|
||||||
|
"hood": "#6bc6ed",
|
||||||
|
"nose-tip": "#fd7e1d",
|
||||||
|
"foot": "#555650",
|
||||||
|
"belly": "#ff952b",
|
||||||
|
"underbelly": "#fd7e1d",
|
||||||
|
"temple": "#57c8fa",
|
||||||
|
"upper-corner-eye": "#57c8fa",
|
||||||
|
"upper-eyelid": "#57c8fa",
|
||||||
|
"collar-scruff": "#57c8fa",
|
||||||
|
"scruff": "#57c8fa",
|
||||||
|
"beak": "#252c31",
|
||||||
|
"collar": "#191c6d"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"paintedBunting": {
|
||||||
|
"name": "Painted Bunting",
|
||||||
|
"description": "A remarkably colourful bird, this North American species is quite difficult to observe despite its vivid palette due to its shy nature and vulnerable habitat.",
|
||||||
|
"latinName": "Passerina ciris",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Painted_bunting",
|
||||||
|
"colors": {
|
||||||
|
"face": "#5567f0",
|
||||||
|
"underbelly": "#f16534",
|
||||||
|
"belly": "#ef3b3b",
|
||||||
|
"wing": "#a3e65a",
|
||||||
|
"wing-edge": "#91cc50",
|
||||||
|
"shoulder": "#f6fe40",
|
||||||
|
"foot": "#767980"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"redWarbler": {
|
||||||
|
"name": "Red Warbler",
|
||||||
|
"description": "Endemic to the highlands of Mexico, this bird has the rare distinction of being one of the very few toxic birds in the world.",
|
||||||
|
"latinName": "Cardellina rubra",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_warbler",
|
||||||
|
"colors": {
|
||||||
|
"face": "#e80a28",
|
||||||
|
"belly": "#d90921",
|
||||||
|
"underbelly": "#c70c18",
|
||||||
|
"wing": "#ba121d",
|
||||||
|
"wing-edge": "#5b3535",
|
||||||
|
"foot": "#5e4645",
|
||||||
|
"behind-eye": "#deedff",
|
||||||
|
"temple": "#e8f0fa",
|
||||||
|
"corner-eye": "#d5e4f5",
|
||||||
|
"lower-eyelid": "#e34a61",
|
||||||
|
"beak": "#873535",
|
||||||
|
"cheek": "#db1734"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"cubanTody": {
|
||||||
|
"name": "Cuban Tody",
|
||||||
|
"description": "As the name suggests, this little green bird is only found on the island of Cuba and is known for being particularly round.",
|
||||||
|
"latinName": "Todus multicolor",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Cuban_tody",
|
||||||
|
"colors": {
|
||||||
|
"beak": "#f16f54",
|
||||||
|
"face": "#5fdf44",
|
||||||
|
"chin": "#f12d3e",
|
||||||
|
"collar": "#f12d3e",
|
||||||
|
"belly": "#f6f5e4",
|
||||||
|
"collar-scruff": "#a3ebff",
|
||||||
|
"underbelly": "#eae9d2",
|
||||||
|
"wing": "#11c751",
|
||||||
|
"wing-edge": "#156631",
|
||||||
|
"foot": "#ac7055",
|
||||||
|
"scruff": "#11c751",
|
||||||
|
"theme-highlight": "#4adc67"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"violetBackedStarling": {
|
||||||
|
"name": "Violet-backed Starling",
|
||||||
|
"description": "Native to Sub-Saharan Africa, these small starlings are known for being the most vividly purple birds in the world.",
|
||||||
|
"latinName": "Cinnyricinclus leucogaster",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Violet-backed_starling",
|
||||||
|
"colors": {
|
||||||
|
"face": "#9c3af2",
|
||||||
|
"wing": "#8f37ed",
|
||||||
|
"wing-edge": "#7029b8",
|
||||||
|
"belly": "#ffffff",
|
||||||
|
"underbelly": "#f2f2f2",
|
||||||
|
"foot": "#736a66",
|
||||||
|
"collar": "#aa60e6"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const PALETTE = Object.freeze(/** @type {const} */ ({
|
||||||
* Palette color names
|
|
||||||
* @type {Record<string, string>}
|
|
||||||
*/
|
|
||||||
const PALETTE = {
|
|
||||||
THEME_HIGHLIGHT: "theme-highlight",
|
THEME_HIGHLIGHT: "theme-highlight",
|
||||||
TRANSPARENT: "transparent",
|
TRANSPARENT: "transparent",
|
||||||
OUTLINE: "outline",
|
OUTLINE: "outline",
|
||||||
@@ -420,23 +665,36 @@
|
|||||||
FACE: "face",
|
FACE: "face",
|
||||||
HOOD: "hood",
|
HOOD: "hood",
|
||||||
EYEBROW: "eyebrow",
|
EYEBROW: "eyebrow",
|
||||||
|
UPPER_EYELID: "upper-eyelid",
|
||||||
|
UPPER_CORNER_EYE: "upper-corner-eye",
|
||||||
|
BEHIND_EYE: "behind-eye",
|
||||||
|
CORNER_EYE: "corner-eye",
|
||||||
|
TEMPLE: "temple",
|
||||||
|
LOWER_EYELID: "lower-eyelid",
|
||||||
NOSE: "nose",
|
NOSE: "nose",
|
||||||
|
NOSE_TIP: "nose-tip",
|
||||||
CHEEK: "cheek",
|
CHEEK: "cheek",
|
||||||
SCRUFF: "scruff",
|
SCRUFF: "scruff",
|
||||||
|
CHIN: "chin",
|
||||||
COLLAR: "collar",
|
COLLAR: "collar",
|
||||||
|
COLLAR_SCRUFF: "collar-scruff",
|
||||||
BELLY: "belly",
|
BELLY: "belly",
|
||||||
UNDERBELLY: "underbelly",
|
UNDERBELLY: "underbelly",
|
||||||
WING: "wing",
|
WING: "wing",
|
||||||
|
SHOULDER: "shoulder",
|
||||||
|
WING_SPOTS: "wing-spots",
|
||||||
WING_EDGE: "wing-edge",
|
WING_EDGE: "wing-edge",
|
||||||
HEART: "heart",
|
HEART: "heart",
|
||||||
HEART_BORDER: "heart-border",
|
HEART_BORDER: "heart-border",
|
||||||
HEART_SHINE: "heart-shine",
|
HEART_SHINE: "heart-shine",
|
||||||
FEATHER_SPINE: "feather-spine",
|
FEATHER_SPINE: "feather-spine",
|
||||||
};
|
}));
|
||||||
|
|
||||||
|
/** @typedef {typeof PALETTE[keyof typeof PALETTE]} PaletteColor */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping of sprite sheet colors to palette colors
|
* Mapping of sprite sheet colors to palette colors
|
||||||
* @type {Record<string, string>}
|
* @type {Record<string, PaletteColor>}
|
||||||
*/
|
*/
|
||||||
const SPRITE_SHEET_COLOR_MAP = {
|
const SPRITE_SHEET_COLOR_MAP = {
|
||||||
"transparent": PALETTE.TRANSPARENT,
|
"transparent": PALETTE.TRANSPARENT,
|
||||||
@@ -449,13 +707,24 @@
|
|||||||
"#639bff": PALETTE.FACE,
|
"#639bff": PALETTE.FACE,
|
||||||
"#99e550": PALETTE.HOOD,
|
"#99e550": PALETTE.HOOD,
|
||||||
"#ff5573": PALETTE.EYEBROW,
|
"#ff5573": PALETTE.EYEBROW,
|
||||||
|
"#ff768e": PALETTE.UPPER_EYELID,
|
||||||
|
"#ff90a4": PALETTE.UPPER_CORNER_EYE,
|
||||||
|
"#ff2c88": PALETTE.BEHIND_EYE,
|
||||||
|
"#e34f9c": PALETTE.CORNER_EYE,
|
||||||
|
"#b53477": PALETTE.TEMPLE,
|
||||||
|
"#ae65f1": PALETTE.LOWER_EYELID,
|
||||||
"#d95763": PALETTE.NOSE,
|
"#d95763": PALETTE.NOSE,
|
||||||
|
"#b93844": PALETTE.NOSE_TIP,
|
||||||
"#ff67a9": PALETTE.CHEEK,
|
"#ff67a9": PALETTE.CHEEK,
|
||||||
"#c5e550": PALETTE.SCRUFF,
|
"#c5e550": PALETTE.SCRUFF,
|
||||||
|
"#b87af1": PALETTE.CHIN,
|
||||||
"#ffe955": PALETTE.COLLAR,
|
"#ffe955": PALETTE.COLLAR,
|
||||||
|
"#f8ff55": PALETTE.COLLAR_SCRUFF,
|
||||||
"#f8b143": PALETTE.BELLY,
|
"#f8b143": PALETTE.BELLY,
|
||||||
"#ec8637": PALETTE.UNDERBELLY,
|
"#ec8637": PALETTE.UNDERBELLY,
|
||||||
"#578ae6": PALETTE.WING,
|
"#578ae6": PALETTE.WING,
|
||||||
|
"#55d1f3": PALETTE.SHOULDER,
|
||||||
|
"#90b0e8": PALETTE.WING_SPOTS,
|
||||||
"#326ed9": PALETTE.WING_EDGE,
|
"#326ed9": PALETTE.WING_EDGE,
|
||||||
"#c82e2e": PALETTE.HEART,
|
"#c82e2e": PALETTE.HEART,
|
||||||
"#501a1a": PALETTE.HEART_BORDER,
|
"#501a1a": PALETTE.HEART_BORDER,
|
||||||
@@ -463,16 +732,52 @@
|
|||||||
"#373737": PALETTE.FEATHER_SPINE,
|
"#373737": PALETTE.FEATHER_SPINE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Partial<Record<PaletteColor, PaletteColor>>}
|
||||||
|
*/
|
||||||
|
({
|
||||||
|
[PALETTE.HOOD]: PALETTE.FACE,
|
||||||
|
[PALETTE.EYEBROW]: PALETTE.FACE,
|
||||||
|
[PALETTE.UPPER_EYELID]: PALETTE.EYEBROW,
|
||||||
|
[PALETTE.UPPER_CORNER_EYE]: PALETTE.EYEBROW,
|
||||||
|
[PALETTE.BEHIND_EYE]: PALETTE.FACE,
|
||||||
|
[PALETTE.CORNER_EYE]: PALETTE.FACE,
|
||||||
|
[PALETTE.TEMPLE]: PALETTE.FACE,
|
||||||
|
[PALETTE.LOWER_EYELID]: PALETTE.FACE,
|
||||||
|
[PALETTE.NOSE]: PALETTE.FACE,
|
||||||
|
[PALETTE.NOSE_TIP]: PALETTE.NOSE,
|
||||||
|
[PALETTE.CHEEK]: PALETTE.FACE,
|
||||||
|
[PALETTE.SCRUFF]: PALETTE.FACE,
|
||||||
|
[PALETTE.CHIN]: PALETTE.FACE,
|
||||||
|
[PALETTE.COLLAR]: PALETTE.FACE,
|
||||||
|
[PALETTE.COLLAR_SCRUFF]: PALETTE.COLLAR,
|
||||||
|
[PALETTE.WING_SPOTS]: PALETTE.WING,
|
||||||
|
[PALETTE.SHOULDER]: PALETTE.WING,
|
||||||
|
});
|
||||||
|
|
||||||
|
const RARITY = Object.freeze(/** @type {const} */ ({
|
||||||
|
COMMON: "common",
|
||||||
|
UNCOMMON: "uncommon"
|
||||||
|
}));
|
||||||
|
|
||||||
|
/** @typedef {typeof RARITY[keyof typeof RARITY]} Rarity */
|
||||||
|
|
||||||
class BirdType {
|
class BirdType {
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {string} description
|
* @param {string} description
|
||||||
|
* @param {string} latinName
|
||||||
|
* @param {string} url
|
||||||
* @param {Record<string, string>} colors
|
* @param {Record<string, string>} colors
|
||||||
* @param {string[]} [tags]
|
* @param {string[]} [tags]
|
||||||
|
* @param {Rarity} [rarity]
|
||||||
*/
|
*/
|
||||||
constructor(name, description, colors, tags = []) {
|
constructor(name, description, latinName, url, colors, tags = [], rarity = RARITY.COMMON) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
this.latinName = latinName;
|
||||||
|
this.url = url;
|
||||||
const defaultColors = {
|
const defaultColors = {
|
||||||
[PALETTE.TRANSPARENT]: "transparent",
|
[PALETTE.TRANSPARENT]: "transparent",
|
||||||
[PALETTE.OUTLINE]: "#000000",
|
[PALETTE.OUTLINE]: "#000000",
|
||||||
@@ -484,15 +789,27 @@
|
|||||||
[PALETTE.HEART_SHINE]: "#ff6b6b",
|
[PALETTE.HEART_SHINE]: "#ff6b6b",
|
||||||
[PALETTE.FEATHER_SPINE]: "#373737",
|
[PALETTE.FEATHER_SPINE]: "#373737",
|
||||||
[PALETTE.HOOD]: colors.face,
|
[PALETTE.HOOD]: colors.face,
|
||||||
[PALETTE.EYEBROW]: colors.face,
|
[PALETTE.EYEBROW]: colors.face,
|
||||||
|
[PALETTE.UPPER_EYELID]: colors.eyebrow || colors.face,
|
||||||
|
[PALETTE.UPPER_CORNER_EYE]: colors.eyebrow || colors.face,
|
||||||
|
[PALETTE.BEHIND_EYE]: colors.face,
|
||||||
|
[PALETTE.CORNER_EYE]: colors.face,
|
||||||
|
[PALETTE.TEMPLE]: colors.face,
|
||||||
|
[PALETTE.LOWER_EYELID]: colors.face,
|
||||||
[PALETTE.NOSE]: colors.face,
|
[PALETTE.NOSE]: colors.face,
|
||||||
|
[PALETTE.NOSE_TIP]: colors.nose || colors.face,
|
||||||
[PALETTE.CHEEK]: colors.face,
|
[PALETTE.CHEEK]: colors.face,
|
||||||
[PALETTE.SCRUFF]: colors.face,
|
[PALETTE.SCRUFF]: colors.face,
|
||||||
|
[PALETTE.CHIN]: colors.face,
|
||||||
[PALETTE.COLLAR]: colors.face,
|
[PALETTE.COLLAR]: colors.face,
|
||||||
|
[PALETTE.COLLAR_SCRUFF]: colors.collar || colors.face,
|
||||||
|
[PALETTE.SHOULDER]: colors.wing,
|
||||||
};
|
};
|
||||||
/** @type {Record<string, string>} */
|
/** @type {Record<string, string>} */
|
||||||
this.colors = { ...defaultColors, ...colors, [PALETTE.THEME_HIGHLIGHT]: colors[PALETTE.THEME_HIGHLIGHT] ?? colors.hood ?? colors.face };
|
this.colors = { ...defaultColors, ...colors, [PALETTE.THEME_HIGHLIGHT]: colors[PALETTE.THEME_HIGHLIGHT] ?? colors.hood ?? colors.face };
|
||||||
this.tags = tags;
|
this.tags = tags;
|
||||||
|
/** @type {Rarity} */
|
||||||
|
this.rarity = rarity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,7 +873,7 @@
|
|||||||
const SPECIES = Object.fromEntries(
|
const SPECIES = Object.fromEntries(
|
||||||
Object.entries(species).map(([id, data]) => [
|
Object.entries(species).map(([id, data]) => [
|
||||||
id,
|
id,
|
||||||
new BirdType(data.name, data.description, data.colors, data.tags ?? []),
|
new BirdType(data.name, data.description, data.latinName, data.url, data.colors, data.tags, data.rarity)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1253,37 +1570,42 @@
|
|||||||
audioContext;
|
audioContext;
|
||||||
|
|
||||||
chirp() {
|
chirp() {
|
||||||
if (!this.audioContext) {
|
const count = Math.floor(1 + Math.random() * 1.5);
|
||||||
this.audioContext = new AudioContext();
|
for (let i = 0; i < count; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.audioContext) {
|
||||||
|
this.audioContext = new AudioContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TIMES = [0, 0.06, 0.10, 0.15];
|
||||||
|
const FREQUENCIES = [2200,
|
||||||
|
3500 + Math.random() * 600 * count,
|
||||||
|
2100 + Math.random() * 200 * count,
|
||||||
|
1600 + Math.random() * 400 * count];
|
||||||
|
const VOLUMES = [0.00005, 0.165, 0.165, 0.0001];
|
||||||
|
|
||||||
|
const oscillator = this.audioContext.createOscillator();
|
||||||
|
oscillator.type = "sine";
|
||||||
|
const gain = this.audioContext.createGain();
|
||||||
|
oscillator.connect(gain);
|
||||||
|
gain.connect(this.audioContext.destination);
|
||||||
|
|
||||||
|
const now = this.audioContext.currentTime;
|
||||||
|
for (let i = 0; i < TIMES.length; i++) {
|
||||||
|
const time = TIMES[i] + now;
|
||||||
|
if (i === 0) {
|
||||||
|
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
|
||||||
|
gain.gain.setValueAtTime(VOLUMES[i], time);
|
||||||
|
} else {
|
||||||
|
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oscillator.start(now);
|
||||||
|
oscillator.stop(now + TIMES[TIMES.length - 1]);
|
||||||
|
}, i * 120);
|
||||||
}
|
}
|
||||||
|
|
||||||
const TIMES = [0, 0.06, 0.10, 0.15];
|
|
||||||
const FREQUENCIES = [2200,
|
|
||||||
3500 + Math.random() * 600,
|
|
||||||
2100 + Math.random() * 200,
|
|
||||||
1600 + Math.random() * 400];
|
|
||||||
const VOLUMES = [0.0001, 0.2, 0.2, 0.0001];
|
|
||||||
|
|
||||||
const oscillator = this.audioContext.createOscillator();
|
|
||||||
oscillator.type = "sine";
|
|
||||||
const gain = this.audioContext.createGain();
|
|
||||||
oscillator.connect(gain);
|
|
||||||
gain.connect(this.audioContext.destination);
|
|
||||||
|
|
||||||
const now = this.audioContext.currentTime;
|
|
||||||
for (let i = 0; i < TIMES.length; i++) {
|
|
||||||
const time = TIMES[i] + now;
|
|
||||||
if (i === 0) {
|
|
||||||
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
|
|
||||||
gain.gain.setValueAtTime(VOLUMES[i], time);
|
|
||||||
} else {
|
|
||||||
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
|
|
||||||
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
oscillator.start(now);
|
|
||||||
oscillator.stop(now + TIMES[TIMES.length - 1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2047,7 +2369,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#birb-field-guide .birb-grid-content {
|
#birb-field-guide .birb-grid-content {
|
||||||
grid-template-rows: repeat(3, auto);
|
grid-template-columns: repeat(4, auto);
|
||||||
}
|
}
|
||||||
|
|
||||||
#birb-wardrobe .birb-grid-content {
|
#birb-wardrobe .birb-grid-content {
|
||||||
@@ -2057,7 +2379,7 @@
|
|||||||
|
|
||||||
.birb-grid-content {
|
.birb-grid-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: row;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
@@ -2089,7 +2411,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.birb-grid-item, .birb-field-guide-description, .birb-message-content {
|
.birb-grid-item, .birb-field-guide-description, .birb-message-content {
|
||||||
border: var(--birb-border-size) solid rgb(255, 207, 144);
|
border: var(--birb-border-size) solid #ffcf90;
|
||||||
box-shadow: 0 0 0 var(--birb-border-size) white;
|
box-shadow: 0 0 0 var(--birb-border-size) white;
|
||||||
background: rgba(255, 221, 177, 0.5);
|
background: rgba(255, 221, 177, 0.5);
|
||||||
}
|
}
|
||||||
@@ -2108,6 +2430,15 @@
|
|||||||
background: var(--birb-mix-color);
|
background: var(--birb-mix-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.birb-field-guide-section-label {
|
||||||
|
padding-top: 4px;
|
||||||
|
/* padding-left: calc(10px + var(--birb-border-size) / 2); */
|
||||||
|
color: #876c4e;
|
||||||
|
text-align: center;
|
||||||
|
/* Italics */
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.birb-field-guide-description {
|
.birb-field-guide-description {
|
||||||
max-width: calc(100% - 20px);
|
max-width: calc(100% - 20px);
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
@@ -2119,7 +2450,14 @@
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: rgb(124, 108, 75);
|
color: #7c6c4b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.birb-field-guide-latin-name {
|
||||||
|
text-decoration: underline;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: bold;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
#birb-feather {
|
#birb-feather {
|
||||||
@@ -2132,7 +2470,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: rgb(124, 108, 75);
|
color: #7c6c4b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.birb-sticky-note {
|
.birb-sticky-note {
|
||||||
@@ -2181,7 +2519,7 @@
|
|||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}`;
|
}`;
|
||||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABFFJREFUeJztnb9rE2EYx79vK1gUQSRLL64apxZcdC84dNBk0LiIoIJCwUGwlP4BUjqIFpSKLioOdYlV6ObUpV26ORRX0yoEHezQgvZxSN7rm+v9SGzu3vea7wdC31wued7k3ueT5727XAFCCCGEENJfKNsdIO4jIhL1mFKKY4jkFg7eHGBTQDp2pVgEANTq9bZ2Fn0ghPQp0qLseVL2vH3tODn2Kn7Z82RlZERkYmJfO+34hKTJgO0OkGQqxSKmCgXUKpV97SxZXV7GVKHgtwnJOxRgjrAloFq9jplGo23ZTKPhT4EJISQVzCnwysiIf8tqChzWhyxjE0IcQELIOrZNAZl9oPwI6TNEpLnz3/ibdXzbArIhf0LS5IjtDuSN1dFRK3GVUkpExOapJzzdhZA+RVc+uvpjFURI/uE3eheY0mM1lD3BLx1uA3JQOIBILtDy084TkcwFSAH3KWFHQDkFJBaQF/WyALB2AKgV2+nxzzztnMQToc1vXvOGhN+o9hIKmADAi3pZN60cAFJK+X1wrfoL5oWNPM0jsUeBzQ/TGHzQy0TEXCeVARGc+hjLISLi2kAk6XG3+MHqtrYp4E4wU6E6u+Evo/+iiRSgKZ716m3gAXB29+TeCvVy2/6YNGTkgoAJ0dgWcAyilPKld9gZHlSy+Vd6si1iK8CB4x7UsWGc+7QEANi98QTeq+sYn98FloDq7CaQkoxcEDAhOUCixPf12+/MO5M2w4NKxud3sXRvoCcSjHwBU0C3X+4voW+NVzD37rl/f2HS08+LDtaFoEREBo57bcvaBAxg69emH9uMSxGSPiFWfmtzJbg6XXeF2A+nVVVBflzFnY/vAQCvLl8DAFx//TT0OVqEIa/VDNihnGwLmBCXqc5uxO7YS1uAUfFbeZibPOtIgNuLYzh68RQAYGflJwDg5vrbjgLoMnxtrtT1uVs2BUyIy0QJyMy3lEUUWX3mSYId/xZYi0+zMOnh/P11nDl9InT9wIY40JGonZWfvviC/QgS3Ci6HzwaRg4Tr0s32oqQYL5lIaCv335H5n9e+O+LIWwvjmHoSgm4vx76eJj4DlJ92RQwIS7y7E8FE0dqQMbiS6I6u4GFSU9c6EsSiVNgtKqn7cWxtse2vmwBAArTq1HP3Qvyn+Izp+BhDF35jPMZCZgQ12g8uuAP7kvf3/jL1+ZKWY1ziSpCzAOTLuddYsfCJKTlF0ZherVnb9y2gAlxHVOCaOZD1mNddBESFKGWoMv515EA0ZJQ49GF2HV7KT8zvi0BE0I6QgAgKMI8CDBxH6C+EGfrjURKME3x6NhofuNFrkf5EWIFhebUu5l8Ebulco15NYza44f+VTF0O80LFAR+6B0aP+0+EEKSCV4xx/V87OrfYiqlUHv80L9vtpFi1aVfNy4+Kz9C3KD101QgB/nY1Wkw5lQ02M6CpPiuf9iEHHbyloNddTaunM3ijduOTwg5XPwDDgmrxnQErq8AAAAASUVORK5CYII=";
|
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABORJREFUeJztnU9IHFccx79vE6g0BEK7BJzNwR6apReFQElKjpZaeol7EHuRQhFaECIEIkF6LGkQGqyQ0EBa+u8iHjZCCfXQUw7qRVDwYKQEirtt0yVtMRaFdn857L7x7Tizf3Rn3oz7/cDi8+3s/t44733m9+afACGEEEII6SyU7QaQ+CMiEvSeUop9iCQWdt4EYFNAOnYukwEA5AuFmnIUbSCEdChSZdBxZNBxDpTrybFd8QcdR5Z6e0XGxg6Uw45PSJikbDeANCaXyeBGOo18LnegHCXLjx7hRjrtlglJOhRggrAloHyhgFulUk3drVLJnQITQkgomFPgpd5e9xXVFNivDVHGJoTEAPEh6tg2BWS2gfIjpMMQkcrBf+Nn1PFtC8iG/AkJk5O2G5A0lvv6rMRVSikREZuXnvByF0I6FJ356OyPWRAhyYd79BYwpcdsKHq8Ox1uA3JU2IFIItDy084TkcgFSAF3KH5nQDkFJBaQe4VBAWDtBFA1dqz7P8dp8zS8ENrc85ovNLhHtZ1QwAQA7hUGddHKCSCllNuGuGV/3nFhY5wmkbpngc0/ptH5oOtEBCIiYXYG79THqA89NokXH2UeWN3WNgXcDOZQGJ4qunX0XzCBAjTFs3DpbfRcexXny2eA1zKAcxYo1EoQIewV4yBgQjS2BVwHUUq50jvudJ9Q8tv/0pZtUTcDTJ1yoF7uxrtr68DaOsoj03A+fx/FNz7DuR+yGFrdrjko3U4RxkHAhCQACRLf5tZ25I0Jm+4TSt77soyHH6faIsHALzAF9NPkP279v38Cbz6dw693f8R06Xu3fq7vtP5ccLAWBCUikjrl1NSVR6bh3K8I+GHXX/jmq09841KEpEOoK7+VmSziOl2PC4EZoL7zAAAGxkex8MV9AEDu01HsnQWure3L7/KTHaSMYw5+HGa6Wt4p1gh4AcCdD//G1tM5nLm7BJSAodXtmrhmRui3Ts3GJiTODE8VrR7YG1rdduOnFvYzzdkJB0mSbt2GVoWF3fl+vHTpFQDA3tIzAMBIzwNcfrKDxcflA59763zKrddp+MpMtuVrt3R8+WPIFfDAeEXAI2tfu8uZ7ahuAL/vqqwwJUiOAUECNMdbyCIKzD6TJMGm7wXW4tPM9Z3GL1c38Pq5yhTUlN7i47J3QxzpTNTe0jMMjI/WtqNnX3yLxrLDU0VfAfNsGDlOfJsdwQcb+7Mw73iLQkCbW9vu+E8qh34Ywu58P7quZIGrGwCAza3a9/3Ed5Tsy6aACYkjd/7LYexkHohYfI0YnipidsKROLSlEQ2nwKhmT7vz/TXvPV9/DgBITy4HfXY/yCHFZ07B/ei68jMuVAXspd0CJiRulG5edDv3O79/59avzGSj6udywUhCTGYnnEQcdmrYMD8Jafn5kZ5cbtuK2xYwIXHHlCAq4yHqvi46CfGKUEswzuOvKQGiKqHSzYt1l22n/Mz4tgRMCGkKAQCvCJMgwIbHAPXlMNUVCZRgmOLRsVHZ4wUuR/kRYgWFytS7MvgCDkslGvNpGPnb192nYuhymA8o8Nzo7Rs/7DYQQhrjfWJO3MdjS/8WUymF/O3r7u9mGSFmXfp768Vn5kdIPKjejAAkYDy2dBmMORX1lqOgUfy4/7EJOe4kbQy21Nh66WwUK247PiHkePECZQPi+PbreqwAAAAASUVORK5CYII=";
|
||||||
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
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==";
|
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,6 +2549,7 @@
|
|||||||
const HOP_CHANCE = 1 / (60 * 2.5); // Every 2.5 seconds
|
const HOP_CHANCE = 1 / (60 * 2.5); // Every 2.5 seconds
|
||||||
const FOCUS_SWITCH_CHANCE = 1 / (60 * 20); // Every 20 seconds
|
const FOCUS_SWITCH_CHANCE = 1 / (60 * 20); // Every 20 seconds
|
||||||
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // Every 2 hours
|
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // Every 2 hours
|
||||||
|
const UNCOMMON_FEATHER_CHANCE = 0.15; // 15% of feathers are uncommon
|
||||||
const HAT_CHANCE = 1 / (60 * 60 * 25); // Every 25 minutes
|
const HAT_CHANCE = 1 / (60 * 60 * 25); // Every 25 minutes
|
||||||
|
|
||||||
// Feathers
|
// Feathers
|
||||||
@@ -2317,7 +2656,7 @@
|
|||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
||||||
new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false),
|
new MenuItem("2026.3.29", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.29"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @type {Birb} */
|
/** @type {Birb} */
|
||||||
@@ -2507,7 +2846,9 @@
|
|||||||
|
|
||||||
setInterval(update, UPDATE_INTERVAL);
|
setInterval(update, UPDATE_INTERVAL);
|
||||||
|
|
||||||
focusOnElement(true);
|
flyToElement(true);
|
||||||
|
// TODO: Remove
|
||||||
|
insertFieldGuide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
@@ -2526,11 +2867,11 @@
|
|||||||
// Idle for a while, do something
|
// Idle for a while, do something
|
||||||
if (focusedElement === null) {
|
if (focusedElement === null) {
|
||||||
// Fly to an element
|
// Fly to an element
|
||||||
focusOnElement();
|
flyToElement();
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
} else if (Math.random() < FOCUS_SWITCH_CHANCE) {
|
} else if (Math.random() < FOCUS_SWITCH_CHANCE) {
|
||||||
// Fly to another element if idle for a longer while
|
// Fly to another element if idle for a longer while
|
||||||
focusOnElement();
|
flyToElement();
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2566,7 +2907,7 @@
|
|||||||
// Update the bird's position
|
// Update the bird's position
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE) {
|
||||||
if (focusedElement && !isWithinHorizontalBounds()) {
|
if (focusedElement && !isWithinHorizontalBounds()) {
|
||||||
flySomewhere();
|
flyToElement();
|
||||||
}
|
}
|
||||||
birdY = getFocusedY();
|
birdY = getFocusedY();
|
||||||
} else if (currentState === States.FLYING) {
|
} else if (currentState === States.FLYING) {
|
||||||
@@ -2582,7 +2923,7 @@
|
|||||||
startY += targetY - oldTargetY;
|
startY += targetY - oldTargetY;
|
||||||
if (targetY < 0 || targetY > getWindowHeight()) {
|
if (targetY < 0 || targetY > getWindowHeight()) {
|
||||||
// Fly to another element or the ground if the focused element moves out of bounds
|
// Fly to another element or the ground if the focused element moves out of bounds
|
||||||
flySomewhere();
|
flyToElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (birb.draw(SPECIES[currentSpecies], currentHat)) {
|
if (birb.draw(SPECIES[currentSpecies], currentHat)) {
|
||||||
@@ -2660,7 +3001,8 @@
|
|||||||
if (document.querySelector("#" + FEATHER_ID)) {
|
if (document.querySelector("#" + FEATHER_ID)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const speciesToUnlock = Object.keys(SPECIES).filter((species) => !unlockedSpecies.includes(species));
|
const rarity = Math.random() < UNCOMMON_FEATHER_CHANCE ? RARITY.UNCOMMON : RARITY.COMMON;
|
||||||
|
const speciesToUnlock = Object.keys(SPECIES).filter((species) => !unlockedSpecies.includes(species) && SPECIES[species].rarity === rarity);
|
||||||
if (speciesToUnlock.length === 0) {
|
if (speciesToUnlock.length === 0) {
|
||||||
// No more species to unlock
|
// No more species to unlock
|
||||||
return;
|
return;
|
||||||
@@ -2855,9 +3197,23 @@
|
|||||||
removeWardrobe();
|
removeWardrobe();
|
||||||
|
|
||||||
const contentContainer = document.createElement("div");
|
const contentContainer = document.createElement("div");
|
||||||
const content = makeElement("birb-grid-content");
|
const familiarBirds = makeElement("birb-grid-content");
|
||||||
|
const uncommonBirds = makeElement("birb-grid-content");
|
||||||
|
|
||||||
|
const familiarLabel = document.createElement("div");
|
||||||
|
familiarLabel.className = "birb-field-guide-section-label";
|
||||||
|
familiarLabel.textContent = `----- Familiar ${birdBirb()}s -----`;
|
||||||
|
|
||||||
|
const uncommonLabel = document.createElement("div");
|
||||||
|
uncommonLabel.className = "birb-field-guide-section-label";
|
||||||
|
uncommonLabel.textContent = `----- Uncommon ${birdBirb()}s -----`;
|
||||||
|
uncommonLabel.title = "Arbitrarily classified birds that are a little harder to find, but worth the wait!";
|
||||||
|
|
||||||
const description = makeElement("birb-field-guide-description");
|
const description = makeElement("birb-field-guide-description");
|
||||||
contentContainer.appendChild(content);
|
contentContainer.appendChild(familiarLabel);
|
||||||
|
contentContainer.appendChild(familiarBirds);
|
||||||
|
contentContainer.appendChild(uncommonLabel);
|
||||||
|
contentContainer.appendChild(uncommonBirds);
|
||||||
contentContainer.appendChild(description);
|
contentContainer.appendChild(description);
|
||||||
|
|
||||||
const fieldGuide = createWindow(
|
const fieldGuide = createWindow(
|
||||||
@@ -2873,14 +3229,26 @@
|
|||||||
const boldName = document.createElement("b");
|
const boldName = document.createElement("b");
|
||||||
boldName.textContent = type.name;
|
boldName.textContent = type.name;
|
||||||
|
|
||||||
const spacer = document.createElement("div");
|
|
||||||
spacer.style.height = "0.3em";
|
const spacerOne = document.createElement("div");
|
||||||
|
spacerOne.style.height = "0.3em";
|
||||||
|
|
||||||
|
const latinName = document.createElement("a");
|
||||||
|
latinName.className = "birb-field-guide-latin-name";
|
||||||
|
latinName.textContent = type.latinName;
|
||||||
|
latinName.href = type.url;
|
||||||
|
latinName.target = "_blank";
|
||||||
|
|
||||||
|
const spacerTwo = document.createElement("div");
|
||||||
|
spacerTwo.style.height = "0.4em";
|
||||||
|
|
||||||
const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description);
|
const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description);
|
||||||
|
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
fragment.appendChild(boldName);
|
fragment.appendChild(boldName);
|
||||||
fragment.appendChild(spacer);
|
fragment.appendChild(spacerOne);
|
||||||
|
fragment.appendChild(latinName);
|
||||||
|
fragment.appendChild(spacerTwo);
|
||||||
fragment.appendChild(descText);
|
fragment.appendChild(descText);
|
||||||
|
|
||||||
return fragment;
|
return fragment;
|
||||||
@@ -2902,7 +3270,11 @@
|
|||||||
}
|
}
|
||||||
birb.getFrames().base.draw(speciesCtx, Directions.RIGHT, CANVAS_PIXEL_SIZE, type.colors, type.tags);
|
birb.getFrames().base.draw(speciesCtx, Directions.RIGHT, CANVAS_PIXEL_SIZE, type.colors, type.tags);
|
||||||
speciesElement.appendChild(speciesCanvas);
|
speciesElement.appendChild(speciesCanvas);
|
||||||
content.appendChild(speciesElement);
|
let section = familiarBirds;
|
||||||
|
if (type.rarity === RARITY.UNCOMMON) {
|
||||||
|
section = uncommonBirds;
|
||||||
|
}
|
||||||
|
section.appendChild(speciesElement);
|
||||||
if (unlocked) {
|
if (unlocked) {
|
||||||
onClick(speciesElement, () => {
|
onClick(speciesElement, () => {
|
||||||
switchSpecies(id);
|
switchSpecies(id);
|
||||||
@@ -3084,26 +3456,6 @@
|
|||||||
return getWindowHeight() - focusedBounds.top;
|
return getWindowHeight() - focusedBounds.top;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fly to either an element or the ground
|
|
||||||
*/
|
|
||||||
function flySomewhere() {
|
|
||||||
// On mobile, always prefer to focus on an element
|
|
||||||
// If not mobile, 50% chance to focus on ground
|
|
||||||
// if ((!isMobile() && coinFlip()) || !focusOnElement()) {
|
|
||||||
// focusOnGround();
|
|
||||||
// }
|
|
||||||
if (!focusOnElement()) {
|
|
||||||
focusOnGround();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusOnGround() {
|
|
||||||
focusedElement = null;
|
|
||||||
updateFocusedElementBounds();
|
|
||||||
flyTo(Math.random() * window.innerWidth, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {HTMLElement|null} The random element, or null if no valid element was found
|
* @returns {HTMLElement|null} The random element, or null if no valid element was found
|
||||||
*/
|
*/
|
||||||
@@ -3135,20 +3487,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Focus on an element within the viewport
|
* Fly to an element within the viewport
|
||||||
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
||||||
* @returns Whether an element to focus on was found
|
* @returns Whether an element to fly to was found (null if flying to the ground)
|
||||||
*/
|
*/
|
||||||
function focusOnElement(teleport = false) {
|
function flyToElement(teleport = false) {
|
||||||
if (frozen) {
|
if (frozen) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const previousElement = focusedElement;
|
||||||
focusedElement = getRandomValidElement();
|
focusedElement = getRandomValidElement();
|
||||||
log("Focusing on element: ", focusedElement);
|
|
||||||
updateFocusedElementBounds();
|
updateFocusedElementBounds();
|
||||||
if (teleport) {
|
if (teleport) {
|
||||||
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
} else {
|
} else if (focusedElement !== previousElement) {
|
||||||
flyTo(getFocusedElementRandomX(), getFocusedY());
|
flyTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
}
|
}
|
||||||
return focusedElement !== null;
|
return focusedElement !== null;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
dist/extension/images/icons/transparent/16x16x1.png
vendored
|
Before Width: | Height: | Size: 635 B After Width: | Height: | Size: 662 B |
BIN
dist/extension/images/icons/transparent/16x16x2.png
vendored
|
Before Width: | Height: | Size: 829 B After Width: | Height: | Size: 848 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
BIN
dist/extension/images/icons/transparent/27x20x2.png
vendored
|
Before Width: | Height: | Size: 848 B After Width: | Height: | Size: 881 B |
BIN
dist/extension/images/icons/transparent/27x20x3.png
vendored
|
Before Width: | Height: | Size: 944 B After Width: | Height: | Size: 996 B |
BIN
dist/extension/images/icons/transparent/29x29x2.png
vendored
|
Before Width: | Height: | Size: 914 B After Width: | Height: | Size: 933 B |
BIN
dist/extension/images/icons/transparent/29x29x3.png
vendored
|
Before Width: | Height: | Size: 1018 B After Width: | Height: | Size: 1.0 KiB |
BIN
dist/extension/images/icons/transparent/32x24x2.png
vendored
|
Before Width: | Height: | Size: 881 B After Width: | Height: | Size: 933 B |
BIN
dist/extension/images/icons/transparent/32x24x3.png
vendored
|
Before Width: | Height: | Size: 953 B After Width: | Height: | Size: 1008 B |
BIN
dist/extension/images/icons/transparent/32x32x1.png
vendored
|
Before Width: | Height: | Size: 829 B After Width: | Height: | Size: 848 B |
BIN
dist/extension/images/icons/transparent/32x32x2.png
vendored
|
Before Width: | Height: | Size: 936 B After Width: | Height: | Size: 957 B |
BIN
dist/extension/images/icons/transparent/48x48x1.png
vendored
|
Before Width: | Height: | Size: 856 B After Width: | Height: | Size: 866 B |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
BIN
dist/extension/images/icons/transparent/60x45x2.png
vendored
|
Before Width: | Height: | Size: 1014 B After Width: | Height: | Size: 1.0 KiB |
BIN
dist/extension/images/icons/transparent/60x45x3.png
vendored
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
dist/extension/images/icons/transparent/67x50x2.png
vendored
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
dist/extension/images/icons/transparent/74x55x2.png
vendored
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
dist/extension/images/icons/transparent/96x96x1.png
vendored
|
Before Width: | Height: | Size: 1018 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
2
dist/extension/manifest.json
vendored
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Pocket Bird",
|
"name": "Pocket Bird",
|
||||||
"description": "It's a pet bird in your browser, what more could you want?",
|
"description": "It's a pet bird in your browser, what more could you want?",
|
||||||
"version": "2026.3.11",
|
"version": "2026.3.29",
|
||||||
"homepage_url": "https://idreesinc.com",
|
"homepage_url": "https://idreesinc.com",
|
||||||
"icons": {
|
"icons": {
|
||||||
"48": "images/icons/transparent/48x48x1.png",
|
"48": "images/icons/transparent/48x48x1.png",
|
||||||
|
|||||||
548
dist/obsidian/main.js
vendored
@@ -1,7 +1,7 @@
|
|||||||
const { Plugin, Notice } = require('obsidian');
|
const { Plugin, Notice } = require('obsidian');
|
||||||
module.exports = class PocketBird extends Plugin {
|
module.exports = class PocketBird extends Plugin {
|
||||||
onload() {
|
onload() {
|
||||||
console.log("Loading Pocket Bird version 2026.3.11...");
|
console.log("Loading Pocket Bird version 2026.3.29...");
|
||||||
const OBSIDIAN_PLUGIN = this;
|
const OBSIDIAN_PLUGIN = this;
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
@@ -242,6 +242,8 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
"bluebird": {
|
"bluebird": {
|
||||||
"name": "Eastern Bluebird",
|
"name": "Eastern Bluebird",
|
||||||
"description": "Native to North American and very social, though can be timid around people.",
|
"description": "Native to North American and very social, though can be timid around people.",
|
||||||
|
"latinName": "Sialia sialis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Eastern_bluebird",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#639bff",
|
"face": "#639bff",
|
||||||
@@ -254,6 +256,8 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
"shimaEnaga": {
|
"shimaEnaga": {
|
||||||
"name": "Shima Enaga",
|
"name": "Shima Enaga",
|
||||||
"description": "Small, fluffy birds found in the snowy regions of Japan, these birds are highly sought after by ornithologists and nature photographers.",
|
"description": "Small, fluffy birds found in the snowy regions of Japan, these birds are highly sought after by ornithologists and nature photographers.",
|
||||||
|
"latinName": "Aegithalos caudatus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Long-tailed_tit",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#ffffff",
|
"face": "#ffffff",
|
||||||
@@ -267,6 +271,8 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
"tuftedTitmouse": {
|
"tuftedTitmouse": {
|
||||||
"name": "Tufted Titmouse",
|
"name": "Tufted Titmouse",
|
||||||
"description": "Native to the eastern United States, full of personality, and notably my wife's favorite bird.",
|
"description": "Native to the eastern United States, full of personality, and notably my wife's favorite bird.",
|
||||||
|
"latinName": "Baeolophus bicolor",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Tufted_titmouse",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#c7cad7",
|
"face": "#c7cad7",
|
||||||
@@ -283,6 +289,8 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
"europeanRobin": {
|
"europeanRobin": {
|
||||||
"name": "European Robin",
|
"name": "European Robin",
|
||||||
"description": "Native to western Europe, this is the quintessential robin. Quite friendly, you'll often find them searching for worms.",
|
"description": "Native to western Europe, this is the quintessential robin. Quite friendly, you'll often find them searching for worms.",
|
||||||
|
"latinName": "Erithacus rubecula",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/European_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#ffaf34",
|
"face": "#ffaf34",
|
||||||
@@ -297,6 +305,8 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
"redCardinal": {
|
"redCardinal": {
|
||||||
"name": "Red Cardinal",
|
"name": "Red Cardinal",
|
||||||
"description": "Native to the eastern United States, this strikingly red bird is hard to miss.",
|
"description": "Native to the eastern United States, this strikingly red bird is hard to miss.",
|
||||||
|
"latinName": "Cardinalis cardinalis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_cardinal",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#d93619",
|
"beak": "#d93619",
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
@@ -316,6 +326,8 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
"americanGoldfinch": {
|
"americanGoldfinch": {
|
||||||
"name": "American Goldfinch",
|
"name": "American Goldfinch",
|
||||||
"description": "Coloured a brilliant yellow, this bird feeds almost entirely on the seeds of plants such as thistle, sunflowers, and coneflowers.",
|
"description": "Coloured a brilliant yellow, this bird feeds almost entirely on the seeds of plants such as thistle, sunflowers, and coneflowers.",
|
||||||
|
"latinName": "Spinus tristis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/American_goldfinch",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#ffaf34",
|
"beak": "#ffaf34",
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
@@ -332,6 +344,8 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
"barnSwallow": {
|
"barnSwallow": {
|
||||||
"name": "Barn Swallow",
|
"name": "Barn Swallow",
|
||||||
"description": "Agile birds that often roost in man-made structures, these birds are known to build nests near Ospreys for protection.",
|
"description": "Agile birds that often roost in man-made structures, these birds are known to build nests near Ospreys for protection.",
|
||||||
|
"latinName": "Hirundo rustica",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Barn_swallow",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#db7c4d",
|
"face": "#db7c4d",
|
||||||
@@ -345,6 +359,8 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
"mistletoebird": {
|
"mistletoebird": {
|
||||||
"name": "Mistletoebird",
|
"name": "Mistletoebird",
|
||||||
"description": "Native to Australia, these birds eat mainly mistletoe and in turn spread the seeds far and wide.",
|
"description": "Native to Australia, these birds eat mainly mistletoe and in turn spread the seeds far and wide.",
|
||||||
|
"latinName": "Dicaeum hirundinaceum",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Mistletoebird",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#6c6a7c",
|
"foot": "#6c6a7c",
|
||||||
"face": "#352e6d",
|
"face": "#352e6d",
|
||||||
@@ -354,22 +370,11 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
"wing-edge": "#282065"
|
"wing-edge": "#282065"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redAvadavat": {
|
|
||||||
"name": "Red Avadavat",
|
|
||||||
"description": "Native to India and southeast Asia, these birds are also known as Strawberry Finches due to their speckled plumage.",
|
|
||||||
"colors": {
|
|
||||||
"beak": "#f71919",
|
|
||||||
"foot": "#af7575",
|
|
||||||
"face": "#cb092b",
|
|
||||||
"belly": "#ae1724",
|
|
||||||
"underbelly": "#831b24",
|
|
||||||
"wing": "#7e3030",
|
|
||||||
"wing-edge": "#490f0f"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scarletRobin": {
|
"scarletRobin": {
|
||||||
"name": "Scarlet Robin",
|
"name": "Scarlet Robin",
|
||||||
"description": "Native to Australia, this striking robin can be found in Eucalyptus forests.",
|
"description": "Native to Australia, this striking robin can be found in Eucalyptus forests.",
|
||||||
|
"latinName": "Petroica boodang",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Scarlet_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#494949",
|
"foot": "#494949",
|
||||||
"face": "#3d3d3d",
|
"face": "#3d3d3d",
|
||||||
@@ -377,12 +382,15 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
"underbelly": "#dcdcdc",
|
"underbelly": "#dcdcdc",
|
||||||
"wing": "#2b2b2b",
|
"wing": "#2b2b2b",
|
||||||
"wing-edge": "#ebebeb",
|
"wing-edge": "#ebebeb",
|
||||||
|
"nose": "#ebebeb",
|
||||||
"theme-highlight": "#fc5633"
|
"theme-highlight": "#fc5633"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"americanRobin": {
|
"americanRobin": {
|
||||||
"name": "American Robin",
|
"name": "American Robin",
|
||||||
"description": "While not a true robin, this social North American bird is so named due to its orange coloring. It seems unbothered by nearby humans.",
|
"description": "While not a true robin, this social North American bird is so named due to its orange coloring. It seems unbothered by nearby humans.",
|
||||||
|
"latinName": "Turdus migratorius",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/American_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#e89f30",
|
"beak": "#e89f30",
|
||||||
"foot": "#9f8075",
|
"foot": "#9f8075",
|
||||||
@@ -397,6 +405,8 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
"carolinaWren": {
|
"carolinaWren": {
|
||||||
"name": "Carolina Wren",
|
"name": "Carolina Wren",
|
||||||
"description": "Native to the eastern United States, these little birds are known for their curious and energetic nature.",
|
"description": "Native to the eastern United States, these little birds are known for their curious and energetic nature.",
|
||||||
|
"latinName": "Thryothorus ludovicianus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Carolina_wren",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#edc7a9",
|
"face": "#edc7a9",
|
||||||
@@ -407,14 +417,249 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
"wing": "#c58a5b",
|
"wing": "#c58a5b",
|
||||||
"wing-edge": "#866348"
|
"wing-edge": "#866348"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"blackCappedChickadee": {
|
||||||
|
"name": "Black-capped Chickadee",
|
||||||
|
"description": "Native to North America, these small and curious birds are known for their distinctive call from which they get their name.",
|
||||||
|
"latinName": "Poecile atricapillus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Black-capped_chickadee",
|
||||||
|
"colors": {
|
||||||
|
"hood": "#363636",
|
||||||
|
"cheek": "#363636",
|
||||||
|
"eyebrow": "#363636",
|
||||||
|
"nose": "#363636",
|
||||||
|
"collar": "#363636",
|
||||||
|
"belly": "#d6d4cf",
|
||||||
|
"underbelly": "#cfc5b4",
|
||||||
|
"face": "#eaeaea",
|
||||||
|
"wing": "#8f8e9a",
|
||||||
|
"wing-edge": "#706f7d",
|
||||||
|
"scruff": "#8f8e9a",
|
||||||
|
"foot": "#535259"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"blueJay": {
|
||||||
|
"name": "Blue Jay",
|
||||||
|
"description": "This loud and rambunctious bird is native to North America and is known for challenging anything in its path.",
|
||||||
|
"latinName": "Cyanocitta cristata",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Blue_jay",
|
||||||
|
"colors": {
|
||||||
|
"foot": "#5a626b",
|
||||||
|
"face": "#ebf2ff",
|
||||||
|
"belly": "#e5ecfa",
|
||||||
|
"underbelly": "#c4cbd6",
|
||||||
|
"wing": "#5890ff",
|
||||||
|
"wing-edge": "#3a77e8",
|
||||||
|
"hood": "#6391e8",
|
||||||
|
"nose": "#6391e8",
|
||||||
|
"collar": "#2e3136",
|
||||||
|
"scruff": "#6391e8"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"tuft"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"darkEyedJunco": {
|
||||||
|
"name": "Dark-eyed Junco",
|
||||||
|
"description": "Native across North America, these social birds will often be seen hopping along the ground in winter.",
|
||||||
|
"latinName": "Junco hyemalis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Dark-eyed_junco",
|
||||||
|
"colors": {
|
||||||
|
"face": "#55565e",
|
||||||
|
"wing": "#5c5f69",
|
||||||
|
"wing-edge": "#444547",
|
||||||
|
"belly": "#6c7180",
|
||||||
|
"underbelly": "#b8bbcc",
|
||||||
|
"foot": "#87776d",
|
||||||
|
"beak": "#ab8a98"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"houseFinch": {
|
||||||
|
"name": "House Finch",
|
||||||
|
"description": "Native to North America, these highly social birds sing cheerful songs and are often seen at bird feeders.",
|
||||||
|
"latinName": "Haemorhous mexicanus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/House_finch",
|
||||||
|
"colors": {
|
||||||
|
"face": "#cc3a3f",
|
||||||
|
"wing": "#ae8e78",
|
||||||
|
"wing-edge": "#8f6c54",
|
||||||
|
"belly": "#d97c77",
|
||||||
|
"underbelly": "#c5a489",
|
||||||
|
"foot": "#705b4c",
|
||||||
|
"beak": "#cf8479",
|
||||||
|
"hood": "#b02f35",
|
||||||
|
"nose": "#ab2b31",
|
||||||
|
"theme-highlight": "#ef444d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pigeon": {
|
||||||
|
"name": "Rock Pigeon",
|
||||||
|
"description": "Descended from the Rock Dove, these once domesticated birds are often found in cities worldwide. Quite friendly and intelligent, they were favored companions of Nikola Tesla.",
|
||||||
|
"latinName": "Columba livia",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Rock_dove",
|
||||||
|
"colors": {
|
||||||
|
"foot": "#ef6e5b",
|
||||||
|
"face": "#5a6c91",
|
||||||
|
"wing-edge": "#65686e",
|
||||||
|
"nose": "#ebebeb",
|
||||||
|
"belly": "#977699",
|
||||||
|
"underbelly": "#b0b3ba",
|
||||||
|
"wing": "#c7cbd4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redAvadavat": {
|
||||||
|
"name": "Red Avadavat",
|
||||||
|
"description": "Native to India and southeast Asia, these birds are also known as Strawberry Finches due to their speckled plumage.",
|
||||||
|
"latinName": "Amandava amandava",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_avadavat",
|
||||||
|
"colors": {
|
||||||
|
"beak": "#f71919",
|
||||||
|
"foot": "#af7575",
|
||||||
|
"face": "#cb092b",
|
||||||
|
"belly": "#ae1724",
|
||||||
|
"underbelly": "#831b24",
|
||||||
|
"wing": "#7e3030",
|
||||||
|
"wing-edge": "#490f0f",
|
||||||
|
"wing-spots": "#e8e4e4",
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"pinkRobin": {
|
||||||
|
"name": "Pink Robin",
|
||||||
|
"description": "Native to Australia, these bubblegum-pink puffballs are quieter than most, instead relying on their vibrant colours to attract partners.",
|
||||||
|
"latinName": "Petroica rodinogaster",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Pink_robin",
|
||||||
|
"colors": {
|
||||||
|
"face": "#403a46",
|
||||||
|
"wing": "#38333d",
|
||||||
|
"wing-edge": "#252325",
|
||||||
|
"underbelly": "#ff7eb8",
|
||||||
|
"belly": "#ff6eaf",
|
||||||
|
"foot": "#3c393c",
|
||||||
|
"theme-highlight": "#ff82ba"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"spangledCotinga": {
|
||||||
|
"name": "Spangled Cotinga",
|
||||||
|
"description": "This South American bird can be found in the Amazon rainforest, flashing its iridescent turquoise feathers high above in the canopy.",
|
||||||
|
"latinName": "Cotinga cayana",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Spangled_cotinga",
|
||||||
|
"colors": {
|
||||||
|
"face": "#62eafe",
|
||||||
|
"chin": "#a12457",
|
||||||
|
"collar": "#a12457",
|
||||||
|
"belly": "#62eafe",
|
||||||
|
"underbelly": "#5cd8ea",
|
||||||
|
"wing": "#227c89",
|
||||||
|
"wing-edge": "#13353a",
|
||||||
|
"foot": "#68696b",
|
||||||
|
"collar-scruff": "#62eafe"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"elegantEuphonia": {
|
||||||
|
"name": "Elegant Euphonia",
|
||||||
|
"description": "This vividly coloured finch is found throughout Central America and is known for the distinctive blue hood that crowns its head.",
|
||||||
|
"latinName": "Chlorophonia elegantissima",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Elegant_euphonia",
|
||||||
|
"colors": {
|
||||||
|
"wing": "#2d31a1",
|
||||||
|
"wing-edge": "#191c6d",
|
||||||
|
"face": "#1f2392",
|
||||||
|
"hood": "#6bc6ed",
|
||||||
|
"nose-tip": "#fd7e1d",
|
||||||
|
"foot": "#555650",
|
||||||
|
"belly": "#ff952b",
|
||||||
|
"underbelly": "#fd7e1d",
|
||||||
|
"temple": "#57c8fa",
|
||||||
|
"upper-corner-eye": "#57c8fa",
|
||||||
|
"upper-eyelid": "#57c8fa",
|
||||||
|
"collar-scruff": "#57c8fa",
|
||||||
|
"scruff": "#57c8fa",
|
||||||
|
"beak": "#252c31",
|
||||||
|
"collar": "#191c6d"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"paintedBunting": {
|
||||||
|
"name": "Painted Bunting",
|
||||||
|
"description": "A remarkably colourful bird, this North American species is quite difficult to observe despite its vivid palette due to its shy nature and vulnerable habitat.",
|
||||||
|
"latinName": "Passerina ciris",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Painted_bunting",
|
||||||
|
"colors": {
|
||||||
|
"face": "#5567f0",
|
||||||
|
"underbelly": "#f16534",
|
||||||
|
"belly": "#ef3b3b",
|
||||||
|
"wing": "#a3e65a",
|
||||||
|
"wing-edge": "#91cc50",
|
||||||
|
"shoulder": "#f6fe40",
|
||||||
|
"foot": "#767980"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"redWarbler": {
|
||||||
|
"name": "Red Warbler",
|
||||||
|
"description": "Endemic to the highlands of Mexico, this bird has the rare distinction of being one of the very few toxic birds in the world.",
|
||||||
|
"latinName": "Cardellina rubra",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_warbler",
|
||||||
|
"colors": {
|
||||||
|
"face": "#e80a28",
|
||||||
|
"belly": "#d90921",
|
||||||
|
"underbelly": "#c70c18",
|
||||||
|
"wing": "#ba121d",
|
||||||
|
"wing-edge": "#5b3535",
|
||||||
|
"foot": "#5e4645",
|
||||||
|
"behind-eye": "#deedff",
|
||||||
|
"temple": "#e8f0fa",
|
||||||
|
"corner-eye": "#d5e4f5",
|
||||||
|
"lower-eyelid": "#e34a61",
|
||||||
|
"beak": "#873535",
|
||||||
|
"cheek": "#db1734"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"cubanTody": {
|
||||||
|
"name": "Cuban Tody",
|
||||||
|
"description": "As the name suggests, this little green bird is only found on the island of Cuba and is known for being particularly round.",
|
||||||
|
"latinName": "Todus multicolor",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Cuban_tody",
|
||||||
|
"colors": {
|
||||||
|
"beak": "#f16f54",
|
||||||
|
"face": "#5fdf44",
|
||||||
|
"chin": "#f12d3e",
|
||||||
|
"collar": "#f12d3e",
|
||||||
|
"belly": "#f6f5e4",
|
||||||
|
"collar-scruff": "#a3ebff",
|
||||||
|
"underbelly": "#eae9d2",
|
||||||
|
"wing": "#11c751",
|
||||||
|
"wing-edge": "#156631",
|
||||||
|
"foot": "#ac7055",
|
||||||
|
"scruff": "#11c751",
|
||||||
|
"theme-highlight": "#4adc67"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"violetBackedStarling": {
|
||||||
|
"name": "Violet-backed Starling",
|
||||||
|
"description": "Native to Sub-Saharan Africa, these small starlings are known for being the most vividly purple birds in the world.",
|
||||||
|
"latinName": "Cinnyricinclus leucogaster",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Violet-backed_starling",
|
||||||
|
"colors": {
|
||||||
|
"face": "#9c3af2",
|
||||||
|
"wing": "#8f37ed",
|
||||||
|
"wing-edge": "#7029b8",
|
||||||
|
"belly": "#ffffff",
|
||||||
|
"underbelly": "#f2f2f2",
|
||||||
|
"foot": "#736a66",
|
||||||
|
"collar": "#aa60e6"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const PALETTE = Object.freeze(/** @type {const} */ ({
|
||||||
* Palette color names
|
|
||||||
* @type {Record<string, string>}
|
|
||||||
*/
|
|
||||||
const PALETTE = {
|
|
||||||
THEME_HIGHLIGHT: "theme-highlight",
|
THEME_HIGHLIGHT: "theme-highlight",
|
||||||
TRANSPARENT: "transparent",
|
TRANSPARENT: "transparent",
|
||||||
OUTLINE: "outline",
|
OUTLINE: "outline",
|
||||||
@@ -425,23 +670,36 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
FACE: "face",
|
FACE: "face",
|
||||||
HOOD: "hood",
|
HOOD: "hood",
|
||||||
EYEBROW: "eyebrow",
|
EYEBROW: "eyebrow",
|
||||||
|
UPPER_EYELID: "upper-eyelid",
|
||||||
|
UPPER_CORNER_EYE: "upper-corner-eye",
|
||||||
|
BEHIND_EYE: "behind-eye",
|
||||||
|
CORNER_EYE: "corner-eye",
|
||||||
|
TEMPLE: "temple",
|
||||||
|
LOWER_EYELID: "lower-eyelid",
|
||||||
NOSE: "nose",
|
NOSE: "nose",
|
||||||
|
NOSE_TIP: "nose-tip",
|
||||||
CHEEK: "cheek",
|
CHEEK: "cheek",
|
||||||
SCRUFF: "scruff",
|
SCRUFF: "scruff",
|
||||||
|
CHIN: "chin",
|
||||||
COLLAR: "collar",
|
COLLAR: "collar",
|
||||||
|
COLLAR_SCRUFF: "collar-scruff",
|
||||||
BELLY: "belly",
|
BELLY: "belly",
|
||||||
UNDERBELLY: "underbelly",
|
UNDERBELLY: "underbelly",
|
||||||
WING: "wing",
|
WING: "wing",
|
||||||
|
SHOULDER: "shoulder",
|
||||||
|
WING_SPOTS: "wing-spots",
|
||||||
WING_EDGE: "wing-edge",
|
WING_EDGE: "wing-edge",
|
||||||
HEART: "heart",
|
HEART: "heart",
|
||||||
HEART_BORDER: "heart-border",
|
HEART_BORDER: "heart-border",
|
||||||
HEART_SHINE: "heart-shine",
|
HEART_SHINE: "heart-shine",
|
||||||
FEATHER_SPINE: "feather-spine",
|
FEATHER_SPINE: "feather-spine",
|
||||||
};
|
}));
|
||||||
|
|
||||||
|
/** @typedef {typeof PALETTE[keyof typeof PALETTE]} PaletteColor */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping of sprite sheet colors to palette colors
|
* Mapping of sprite sheet colors to palette colors
|
||||||
* @type {Record<string, string>}
|
* @type {Record<string, PaletteColor>}
|
||||||
*/
|
*/
|
||||||
const SPRITE_SHEET_COLOR_MAP = {
|
const SPRITE_SHEET_COLOR_MAP = {
|
||||||
"transparent": PALETTE.TRANSPARENT,
|
"transparent": PALETTE.TRANSPARENT,
|
||||||
@@ -454,13 +712,24 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
"#639bff": PALETTE.FACE,
|
"#639bff": PALETTE.FACE,
|
||||||
"#99e550": PALETTE.HOOD,
|
"#99e550": PALETTE.HOOD,
|
||||||
"#ff5573": PALETTE.EYEBROW,
|
"#ff5573": PALETTE.EYEBROW,
|
||||||
|
"#ff768e": PALETTE.UPPER_EYELID,
|
||||||
|
"#ff90a4": PALETTE.UPPER_CORNER_EYE,
|
||||||
|
"#ff2c88": PALETTE.BEHIND_EYE,
|
||||||
|
"#e34f9c": PALETTE.CORNER_EYE,
|
||||||
|
"#b53477": PALETTE.TEMPLE,
|
||||||
|
"#ae65f1": PALETTE.LOWER_EYELID,
|
||||||
"#d95763": PALETTE.NOSE,
|
"#d95763": PALETTE.NOSE,
|
||||||
|
"#b93844": PALETTE.NOSE_TIP,
|
||||||
"#ff67a9": PALETTE.CHEEK,
|
"#ff67a9": PALETTE.CHEEK,
|
||||||
"#c5e550": PALETTE.SCRUFF,
|
"#c5e550": PALETTE.SCRUFF,
|
||||||
|
"#b87af1": PALETTE.CHIN,
|
||||||
"#ffe955": PALETTE.COLLAR,
|
"#ffe955": PALETTE.COLLAR,
|
||||||
|
"#f8ff55": PALETTE.COLLAR_SCRUFF,
|
||||||
"#f8b143": PALETTE.BELLY,
|
"#f8b143": PALETTE.BELLY,
|
||||||
"#ec8637": PALETTE.UNDERBELLY,
|
"#ec8637": PALETTE.UNDERBELLY,
|
||||||
"#578ae6": PALETTE.WING,
|
"#578ae6": PALETTE.WING,
|
||||||
|
"#55d1f3": PALETTE.SHOULDER,
|
||||||
|
"#90b0e8": PALETTE.WING_SPOTS,
|
||||||
"#326ed9": PALETTE.WING_EDGE,
|
"#326ed9": PALETTE.WING_EDGE,
|
||||||
"#c82e2e": PALETTE.HEART,
|
"#c82e2e": PALETTE.HEART,
|
||||||
"#501a1a": PALETTE.HEART_BORDER,
|
"#501a1a": PALETTE.HEART_BORDER,
|
||||||
@@ -468,16 +737,52 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
"#373737": PALETTE.FEATHER_SPINE,
|
"#373737": PALETTE.FEATHER_SPINE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Partial<Record<PaletteColor, PaletteColor>>}
|
||||||
|
*/
|
||||||
|
({
|
||||||
|
[PALETTE.HOOD]: PALETTE.FACE,
|
||||||
|
[PALETTE.EYEBROW]: PALETTE.FACE,
|
||||||
|
[PALETTE.UPPER_EYELID]: PALETTE.EYEBROW,
|
||||||
|
[PALETTE.UPPER_CORNER_EYE]: PALETTE.EYEBROW,
|
||||||
|
[PALETTE.BEHIND_EYE]: PALETTE.FACE,
|
||||||
|
[PALETTE.CORNER_EYE]: PALETTE.FACE,
|
||||||
|
[PALETTE.TEMPLE]: PALETTE.FACE,
|
||||||
|
[PALETTE.LOWER_EYELID]: PALETTE.FACE,
|
||||||
|
[PALETTE.NOSE]: PALETTE.FACE,
|
||||||
|
[PALETTE.NOSE_TIP]: PALETTE.NOSE,
|
||||||
|
[PALETTE.CHEEK]: PALETTE.FACE,
|
||||||
|
[PALETTE.SCRUFF]: PALETTE.FACE,
|
||||||
|
[PALETTE.CHIN]: PALETTE.FACE,
|
||||||
|
[PALETTE.COLLAR]: PALETTE.FACE,
|
||||||
|
[PALETTE.COLLAR_SCRUFF]: PALETTE.COLLAR,
|
||||||
|
[PALETTE.WING_SPOTS]: PALETTE.WING,
|
||||||
|
[PALETTE.SHOULDER]: PALETTE.WING,
|
||||||
|
});
|
||||||
|
|
||||||
|
const RARITY = Object.freeze(/** @type {const} */ ({
|
||||||
|
COMMON: "common",
|
||||||
|
UNCOMMON: "uncommon"
|
||||||
|
}));
|
||||||
|
|
||||||
|
/** @typedef {typeof RARITY[keyof typeof RARITY]} Rarity */
|
||||||
|
|
||||||
class BirdType {
|
class BirdType {
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {string} description
|
* @param {string} description
|
||||||
|
* @param {string} latinName
|
||||||
|
* @param {string} url
|
||||||
* @param {Record<string, string>} colors
|
* @param {Record<string, string>} colors
|
||||||
* @param {string[]} [tags]
|
* @param {string[]} [tags]
|
||||||
|
* @param {Rarity} [rarity]
|
||||||
*/
|
*/
|
||||||
constructor(name, description, colors, tags = []) {
|
constructor(name, description, latinName, url, colors, tags = [], rarity = RARITY.COMMON) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
this.latinName = latinName;
|
||||||
|
this.url = url;
|
||||||
const defaultColors = {
|
const defaultColors = {
|
||||||
[PALETTE.TRANSPARENT]: "transparent",
|
[PALETTE.TRANSPARENT]: "transparent",
|
||||||
[PALETTE.OUTLINE]: "#000000",
|
[PALETTE.OUTLINE]: "#000000",
|
||||||
@@ -489,15 +794,27 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
[PALETTE.HEART_SHINE]: "#ff6b6b",
|
[PALETTE.HEART_SHINE]: "#ff6b6b",
|
||||||
[PALETTE.FEATHER_SPINE]: "#373737",
|
[PALETTE.FEATHER_SPINE]: "#373737",
|
||||||
[PALETTE.HOOD]: colors.face,
|
[PALETTE.HOOD]: colors.face,
|
||||||
[PALETTE.EYEBROW]: colors.face,
|
[PALETTE.EYEBROW]: colors.face,
|
||||||
|
[PALETTE.UPPER_EYELID]: colors.eyebrow || colors.face,
|
||||||
|
[PALETTE.UPPER_CORNER_EYE]: colors.eyebrow || colors.face,
|
||||||
|
[PALETTE.BEHIND_EYE]: colors.face,
|
||||||
|
[PALETTE.CORNER_EYE]: colors.face,
|
||||||
|
[PALETTE.TEMPLE]: colors.face,
|
||||||
|
[PALETTE.LOWER_EYELID]: colors.face,
|
||||||
[PALETTE.NOSE]: colors.face,
|
[PALETTE.NOSE]: colors.face,
|
||||||
|
[PALETTE.NOSE_TIP]: colors.nose || colors.face,
|
||||||
[PALETTE.CHEEK]: colors.face,
|
[PALETTE.CHEEK]: colors.face,
|
||||||
[PALETTE.SCRUFF]: colors.face,
|
[PALETTE.SCRUFF]: colors.face,
|
||||||
|
[PALETTE.CHIN]: colors.face,
|
||||||
[PALETTE.COLLAR]: colors.face,
|
[PALETTE.COLLAR]: colors.face,
|
||||||
|
[PALETTE.COLLAR_SCRUFF]: colors.collar || colors.face,
|
||||||
|
[PALETTE.SHOULDER]: colors.wing,
|
||||||
};
|
};
|
||||||
/** @type {Record<string, string>} */
|
/** @type {Record<string, string>} */
|
||||||
this.colors = { ...defaultColors, ...colors, [PALETTE.THEME_HIGHLIGHT]: colors[PALETTE.THEME_HIGHLIGHT] ?? colors.hood ?? colors.face };
|
this.colors = { ...defaultColors, ...colors, [PALETTE.THEME_HIGHLIGHT]: colors[PALETTE.THEME_HIGHLIGHT] ?? colors.hood ?? colors.face };
|
||||||
this.tags = tags;
|
this.tags = tags;
|
||||||
|
/** @type {Rarity} */
|
||||||
|
this.rarity = rarity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,7 +878,7 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
const SPECIES = Object.fromEntries(
|
const SPECIES = Object.fromEntries(
|
||||||
Object.entries(species).map(([id, data]) => [
|
Object.entries(species).map(([id, data]) => [
|
||||||
id,
|
id,
|
||||||
new BirdType(data.name, data.description, data.colors, data.tags ?? []),
|
new BirdType(data.name, data.description, data.latinName, data.url, data.colors, data.tags, data.rarity)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1258,37 +1575,42 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
audioContext;
|
audioContext;
|
||||||
|
|
||||||
chirp() {
|
chirp() {
|
||||||
if (!this.audioContext) {
|
const count = Math.floor(1 + Math.random() * 1.5);
|
||||||
this.audioContext = new AudioContext();
|
for (let i = 0; i < count; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.audioContext) {
|
||||||
|
this.audioContext = new AudioContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TIMES = [0, 0.06, 0.10, 0.15];
|
||||||
|
const FREQUENCIES = [2200,
|
||||||
|
3500 + Math.random() * 600 * count,
|
||||||
|
2100 + Math.random() * 200 * count,
|
||||||
|
1600 + Math.random() * 400 * count];
|
||||||
|
const VOLUMES = [0.00005, 0.165, 0.165, 0.0001];
|
||||||
|
|
||||||
|
const oscillator = this.audioContext.createOscillator();
|
||||||
|
oscillator.type = "sine";
|
||||||
|
const gain = this.audioContext.createGain();
|
||||||
|
oscillator.connect(gain);
|
||||||
|
gain.connect(this.audioContext.destination);
|
||||||
|
|
||||||
|
const now = this.audioContext.currentTime;
|
||||||
|
for (let i = 0; i < TIMES.length; i++) {
|
||||||
|
const time = TIMES[i] + now;
|
||||||
|
if (i === 0) {
|
||||||
|
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
|
||||||
|
gain.gain.setValueAtTime(VOLUMES[i], time);
|
||||||
|
} else {
|
||||||
|
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oscillator.start(now);
|
||||||
|
oscillator.stop(now + TIMES[TIMES.length - 1]);
|
||||||
|
}, i * 120);
|
||||||
}
|
}
|
||||||
|
|
||||||
const TIMES = [0, 0.06, 0.10, 0.15];
|
|
||||||
const FREQUENCIES = [2200,
|
|
||||||
3500 + Math.random() * 600,
|
|
||||||
2100 + Math.random() * 200,
|
|
||||||
1600 + Math.random() * 400];
|
|
||||||
const VOLUMES = [0.0001, 0.2, 0.2, 0.0001];
|
|
||||||
|
|
||||||
const oscillator = this.audioContext.createOscillator();
|
|
||||||
oscillator.type = "sine";
|
|
||||||
const gain = this.audioContext.createGain();
|
|
||||||
oscillator.connect(gain);
|
|
||||||
gain.connect(this.audioContext.destination);
|
|
||||||
|
|
||||||
const now = this.audioContext.currentTime;
|
|
||||||
for (let i = 0; i < TIMES.length; i++) {
|
|
||||||
const time = TIMES[i] + now;
|
|
||||||
if (i === 0) {
|
|
||||||
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
|
|
||||||
gain.gain.setValueAtTime(VOLUMES[i], time);
|
|
||||||
} else {
|
|
||||||
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
|
|
||||||
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
oscillator.start(now);
|
|
||||||
oscillator.stop(now + TIMES[TIMES.length - 1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2080,7 +2402,7 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#birb-field-guide .birb-grid-content {
|
#birb-field-guide .birb-grid-content {
|
||||||
grid-template-rows: repeat(3, auto);
|
grid-template-columns: repeat(4, auto);
|
||||||
}
|
}
|
||||||
|
|
||||||
#birb-wardrobe .birb-grid-content {
|
#birb-wardrobe .birb-grid-content {
|
||||||
@@ -2090,7 +2412,7 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
|
|
||||||
.birb-grid-content {
|
.birb-grid-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: row;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
@@ -2122,7 +2444,7 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.birb-grid-item, .birb-field-guide-description, .birb-message-content {
|
.birb-grid-item, .birb-field-guide-description, .birb-message-content {
|
||||||
border: var(--birb-border-size) solid rgb(255, 207, 144);
|
border: var(--birb-border-size) solid #ffcf90;
|
||||||
box-shadow: 0 0 0 var(--birb-border-size) white;
|
box-shadow: 0 0 0 var(--birb-border-size) white;
|
||||||
background: rgba(255, 221, 177, 0.5);
|
background: rgba(255, 221, 177, 0.5);
|
||||||
}
|
}
|
||||||
@@ -2141,6 +2463,15 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
background: var(--birb-mix-color);
|
background: var(--birb-mix-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.birb-field-guide-section-label {
|
||||||
|
padding-top: 4px;
|
||||||
|
/* padding-left: calc(10px + var(--birb-border-size) / 2); */
|
||||||
|
color: #876c4e;
|
||||||
|
text-align: center;
|
||||||
|
/* Italics */
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.birb-field-guide-description {
|
.birb-field-guide-description {
|
||||||
max-width: calc(100% - 20px);
|
max-width: calc(100% - 20px);
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
@@ -2152,7 +2483,14 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: rgb(124, 108, 75);
|
color: #7c6c4b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.birb-field-guide-latin-name {
|
||||||
|
text-decoration: underline;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: bold;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
#birb-feather {
|
#birb-feather {
|
||||||
@@ -2165,7 +2503,7 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: rgb(124, 108, 75);
|
color: #7c6c4b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.birb-sticky-note {
|
.birb-sticky-note {
|
||||||
@@ -2214,7 +2552,7 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}`;
|
}`;
|
||||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABFFJREFUeJztnb9rE2EYx79vK1gUQSRLL64apxZcdC84dNBk0LiIoIJCwUGwlP4BUjqIFpSKLioOdYlV6ObUpV26ORRX0yoEHezQgvZxSN7rm+v9SGzu3vea7wdC31wued7k3ueT5727XAFCCCGEENJfKNsdIO4jIhL1mFKKY4jkFg7eHGBTQDp2pVgEANTq9bZ2Fn0ghPQp0qLseVL2vH3tODn2Kn7Z82RlZERkYmJfO+34hKTJgO0OkGQqxSKmCgXUKpV97SxZXV7GVKHgtwnJOxRgjrAloFq9jplGo23ZTKPhT4EJISQVzCnwysiIf8tqChzWhyxjE0IcQELIOrZNAZl9oPwI6TNEpLnz3/ibdXzbArIhf0LS5IjtDuSN1dFRK3GVUkpExOapJzzdhZA+RVc+uvpjFURI/uE3eheY0mM1lD3BLx1uA3JQOIBILtDy084TkcwFSAH3KWFHQDkFJBaQF/WyALB2AKgV2+nxzzztnMQToc1vXvOGhN+o9hIKmADAi3pZN60cAFJK+X1wrfoL5oWNPM0jsUeBzQ/TGHzQy0TEXCeVARGc+hjLISLi2kAk6XG3+MHqtrYp4E4wU6E6u+Evo/+iiRSgKZ716m3gAXB29+TeCvVy2/6YNGTkgoAJ0dgWcAyilPKld9gZHlSy+Vd6si1iK8CB4x7UsWGc+7QEANi98QTeq+sYn98FloDq7CaQkoxcEDAhOUCixPf12+/MO5M2w4NKxud3sXRvoCcSjHwBU0C3X+4voW+NVzD37rl/f2HS08+LDtaFoEREBo57bcvaBAxg69emH9uMSxGSPiFWfmtzJbg6XXeF2A+nVVVBflzFnY/vAQCvLl8DAFx//TT0OVqEIa/VDNihnGwLmBCXqc5uxO7YS1uAUfFbeZibPOtIgNuLYzh68RQAYGflJwDg5vrbjgLoMnxtrtT1uVs2BUyIy0QJyMy3lEUUWX3mSYId/xZYi0+zMOnh/P11nDl9InT9wIY40JGonZWfvviC/QgS3Ci6HzwaRg4Tr0s32oqQYL5lIaCv335H5n9e+O+LIWwvjmHoSgm4vx76eJj4DlJ92RQwIS7y7E8FE0dqQMbiS6I6u4GFSU9c6EsSiVNgtKqn7cWxtse2vmwBAArTq1HP3Qvyn+Izp+BhDF35jPMZCZgQ12g8uuAP7kvf3/jL1+ZKWY1ziSpCzAOTLuddYsfCJKTlF0ZherVnb9y2gAlxHVOCaOZD1mNddBESFKGWoMv515EA0ZJQ49GF2HV7KT8zvi0BE0I6QgAgKMI8CDBxH6C+EGfrjURKME3x6NhofuNFrkf5EWIFhebUu5l8Ebulco15NYza44f+VTF0O80LFAR+6B0aP+0+EEKSCV4xx/V87OrfYiqlUHv80L9vtpFi1aVfNy4+Kz9C3KD101QgB/nY1Wkw5lQ02M6CpPiuf9iEHHbyloNddTaunM3ijduOTwg5XPwDDgmrxnQErq8AAAAASUVORK5CYII=";
|
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABORJREFUeJztnU9IHFccx79vE6g0BEK7BJzNwR6apReFQElKjpZaeol7EHuRQhFaECIEIkF6LGkQGqyQ0EBa+u8iHjZCCfXQUw7qRVDwYKQEirtt0yVtMRaFdn857L7x7Tizf3Rn3oz7/cDi8+3s/t44733m9+afACGEEEII6SyU7QaQ+CMiEvSeUop9iCQWdt4EYFNAOnYukwEA5AuFmnIUbSCEdChSZdBxZNBxDpTrybFd8QcdR5Z6e0XGxg6Uw45PSJikbDeANCaXyeBGOo18LnegHCXLjx7hRjrtlglJOhRggrAloHyhgFulUk3drVLJnQITQkgomFPgpd5e9xXVFNivDVHGJoTEAPEh6tg2BWS2gfIjpMMQkcrBf+Nn1PFtC8iG/AkJk5O2G5A0lvv6rMRVSikREZuXnvByF0I6FJ356OyPWRAhyYd79BYwpcdsKHq8Ox1uA3JU2IFIItDy084TkcgFSAF3KH5nQDkFJBaQe4VBAWDtBFA1dqz7P8dp8zS8ENrc85ovNLhHtZ1QwAQA7hUGddHKCSCllNuGuGV/3nFhY5wmkbpngc0/ptH5oOtEBCIiYXYG79THqA89NokXH2UeWN3WNgXcDOZQGJ4qunX0XzCBAjTFs3DpbfRcexXny2eA1zKAcxYo1EoQIewV4yBgQjS2BVwHUUq50jvudJ9Q8tv/0pZtUTcDTJ1yoF7uxrtr68DaOsoj03A+fx/FNz7DuR+yGFrdrjko3U4RxkHAhCQACRLf5tZ25I0Jm+4TSt77soyHH6faIsHALzAF9NPkP279v38Cbz6dw693f8R06Xu3fq7vtP5ccLAWBCUikjrl1NSVR6bh3K8I+GHXX/jmq09841KEpEOoK7+VmSziOl2PC4EZoL7zAAAGxkex8MV9AEDu01HsnQWure3L7/KTHaSMYw5+HGa6Wt4p1gh4AcCdD//G1tM5nLm7BJSAodXtmrhmRui3Ts3GJiTODE8VrR7YG1rdduOnFvYzzdkJB0mSbt2GVoWF3fl+vHTpFQDA3tIzAMBIzwNcfrKDxcflA59763zKrddp+MpMtuVrt3R8+WPIFfDAeEXAI2tfu8uZ7ahuAL/vqqwwJUiOAUECNMdbyCIKzD6TJMGm7wXW4tPM9Z3GL1c38Pq5yhTUlN7i47J3QxzpTNTe0jMMjI/WtqNnX3yLxrLDU0VfAfNsGDlOfJsdwQcb+7Mw73iLQkCbW9vu+E8qh34Ywu58P7quZIGrGwCAza3a9/3Ed5Tsy6aACYkjd/7LYexkHohYfI0YnipidsKROLSlEQ2nwKhmT7vz/TXvPV9/DgBITy4HfXY/yCHFZ07B/ei68jMuVAXspd0CJiRulG5edDv3O79/59avzGSj6udywUhCTGYnnEQcdmrYMD8Jafn5kZ5cbtuK2xYwIXHHlCAq4yHqvi46CfGKUEswzuOvKQGiKqHSzYt1l22n/Mz4tgRMCGkKAQCvCJMgwIbHAPXlMNUVCZRgmOLRsVHZ4wUuR/kRYgWFytS7MvgCDkslGvNpGPnb192nYuhymA8o8Nzo7Rs/7DYQQhrjfWJO3MdjS/8WUymF/O3r7u9mGSFmXfp768Vn5kdIPKjejAAkYDy2dBmMORX1lqOgUfy4/7EJOe4kbQy21Nh66WwUK247PiHkePECZQPi+PbreqwAAAAASUVORK5CYII=";
|
||||||
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
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==";
|
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==";
|
||||||
|
|
||||||
@@ -2244,6 +2582,7 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
const HOP_CHANCE = 1 / (60 * 2.5); // Every 2.5 seconds
|
const HOP_CHANCE = 1 / (60 * 2.5); // Every 2.5 seconds
|
||||||
const FOCUS_SWITCH_CHANCE = 1 / (60 * 20); // Every 20 seconds
|
const FOCUS_SWITCH_CHANCE = 1 / (60 * 20); // Every 20 seconds
|
||||||
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // Every 2 hours
|
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // Every 2 hours
|
||||||
|
const UNCOMMON_FEATHER_CHANCE = 0.15; // 15% of feathers are uncommon
|
||||||
const HAT_CHANCE = 1 / (60 * 60 * 25); // Every 25 minutes
|
const HAT_CHANCE = 1 / (60 * 60 * 25); // Every 25 minutes
|
||||||
|
|
||||||
// Feathers
|
// Feathers
|
||||||
@@ -2350,7 +2689,7 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
||||||
new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false),
|
new MenuItem("2026.3.29", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.29"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @type {Birb} */
|
/** @type {Birb} */
|
||||||
@@ -2540,7 +2879,9 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
|
|
||||||
setInterval(update, UPDATE_INTERVAL);
|
setInterval(update, UPDATE_INTERVAL);
|
||||||
|
|
||||||
focusOnElement(true);
|
flyToElement(true);
|
||||||
|
// TODO: Remove
|
||||||
|
insertFieldGuide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
@@ -2559,11 +2900,11 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
// Idle for a while, do something
|
// Idle for a while, do something
|
||||||
if (focusedElement === null) {
|
if (focusedElement === null) {
|
||||||
// Fly to an element
|
// Fly to an element
|
||||||
focusOnElement();
|
flyToElement();
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
} else if (Math.random() < FOCUS_SWITCH_CHANCE) {
|
} else if (Math.random() < FOCUS_SWITCH_CHANCE) {
|
||||||
// Fly to another element if idle for a longer while
|
// Fly to another element if idle for a longer while
|
||||||
focusOnElement();
|
flyToElement();
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2599,7 +2940,7 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
// Update the bird's position
|
// Update the bird's position
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE) {
|
||||||
if (focusedElement && !isWithinHorizontalBounds()) {
|
if (focusedElement && !isWithinHorizontalBounds()) {
|
||||||
flySomewhere();
|
flyToElement();
|
||||||
}
|
}
|
||||||
birdY = getFocusedY();
|
birdY = getFocusedY();
|
||||||
} else if (currentState === States.FLYING) {
|
} else if (currentState === States.FLYING) {
|
||||||
@@ -2615,7 +2956,7 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
startY += targetY - oldTargetY;
|
startY += targetY - oldTargetY;
|
||||||
if (targetY < 0 || targetY > getWindowHeight()) {
|
if (targetY < 0 || targetY > getWindowHeight()) {
|
||||||
// Fly to another element or the ground if the focused element moves out of bounds
|
// Fly to another element or the ground if the focused element moves out of bounds
|
||||||
flySomewhere();
|
flyToElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (birb.draw(SPECIES[currentSpecies], currentHat)) {
|
if (birb.draw(SPECIES[currentSpecies], currentHat)) {
|
||||||
@@ -2693,7 +3034,8 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
if (document.querySelector("#" + FEATHER_ID)) {
|
if (document.querySelector("#" + FEATHER_ID)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const speciesToUnlock = Object.keys(SPECIES).filter((species) => !unlockedSpecies.includes(species));
|
const rarity = Math.random() < UNCOMMON_FEATHER_CHANCE ? RARITY.UNCOMMON : RARITY.COMMON;
|
||||||
|
const speciesToUnlock = Object.keys(SPECIES).filter((species) => !unlockedSpecies.includes(species) && SPECIES[species].rarity === rarity);
|
||||||
if (speciesToUnlock.length === 0) {
|
if (speciesToUnlock.length === 0) {
|
||||||
// No more species to unlock
|
// No more species to unlock
|
||||||
return;
|
return;
|
||||||
@@ -2888,9 +3230,23 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
removeWardrobe();
|
removeWardrobe();
|
||||||
|
|
||||||
const contentContainer = document.createElement("div");
|
const contentContainer = document.createElement("div");
|
||||||
const content = makeElement("birb-grid-content");
|
const familiarBirds = makeElement("birb-grid-content");
|
||||||
|
const uncommonBirds = makeElement("birb-grid-content");
|
||||||
|
|
||||||
|
const familiarLabel = document.createElement("div");
|
||||||
|
familiarLabel.className = "birb-field-guide-section-label";
|
||||||
|
familiarLabel.textContent = `----- Familiar ${birdBirb()}s -----`;
|
||||||
|
|
||||||
|
const uncommonLabel = document.createElement("div");
|
||||||
|
uncommonLabel.className = "birb-field-guide-section-label";
|
||||||
|
uncommonLabel.textContent = `----- Uncommon ${birdBirb()}s -----`;
|
||||||
|
uncommonLabel.title = "Arbitrarily classified birds that are a little harder to find, but worth the wait!";
|
||||||
|
|
||||||
const description = makeElement("birb-field-guide-description");
|
const description = makeElement("birb-field-guide-description");
|
||||||
contentContainer.appendChild(content);
|
contentContainer.appendChild(familiarLabel);
|
||||||
|
contentContainer.appendChild(familiarBirds);
|
||||||
|
contentContainer.appendChild(uncommonLabel);
|
||||||
|
contentContainer.appendChild(uncommonBirds);
|
||||||
contentContainer.appendChild(description);
|
contentContainer.appendChild(description);
|
||||||
|
|
||||||
const fieldGuide = createWindow(
|
const fieldGuide = createWindow(
|
||||||
@@ -2906,14 +3262,26 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
const boldName = document.createElement("b");
|
const boldName = document.createElement("b");
|
||||||
boldName.textContent = type.name;
|
boldName.textContent = type.name;
|
||||||
|
|
||||||
const spacer = document.createElement("div");
|
|
||||||
spacer.style.height = "0.3em";
|
const spacerOne = document.createElement("div");
|
||||||
|
spacerOne.style.height = "0.3em";
|
||||||
|
|
||||||
|
const latinName = document.createElement("a");
|
||||||
|
latinName.className = "birb-field-guide-latin-name";
|
||||||
|
latinName.textContent = type.latinName;
|
||||||
|
latinName.href = type.url;
|
||||||
|
latinName.target = "_blank";
|
||||||
|
|
||||||
|
const spacerTwo = document.createElement("div");
|
||||||
|
spacerTwo.style.height = "0.4em";
|
||||||
|
|
||||||
const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description);
|
const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description);
|
||||||
|
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
fragment.appendChild(boldName);
|
fragment.appendChild(boldName);
|
||||||
fragment.appendChild(spacer);
|
fragment.appendChild(spacerOne);
|
||||||
|
fragment.appendChild(latinName);
|
||||||
|
fragment.appendChild(spacerTwo);
|
||||||
fragment.appendChild(descText);
|
fragment.appendChild(descText);
|
||||||
|
|
||||||
return fragment;
|
return fragment;
|
||||||
@@ -2935,7 +3303,11 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
}
|
}
|
||||||
birb.getFrames().base.draw(speciesCtx, Directions.RIGHT, CANVAS_PIXEL_SIZE, type.colors, type.tags);
|
birb.getFrames().base.draw(speciesCtx, Directions.RIGHT, CANVAS_PIXEL_SIZE, type.colors, type.tags);
|
||||||
speciesElement.appendChild(speciesCanvas);
|
speciesElement.appendChild(speciesCanvas);
|
||||||
content.appendChild(speciesElement);
|
let section = familiarBirds;
|
||||||
|
if (type.rarity === RARITY.UNCOMMON) {
|
||||||
|
section = uncommonBirds;
|
||||||
|
}
|
||||||
|
section.appendChild(speciesElement);
|
||||||
if (unlocked) {
|
if (unlocked) {
|
||||||
onClick(speciesElement, () => {
|
onClick(speciesElement, () => {
|
||||||
switchSpecies(id);
|
switchSpecies(id);
|
||||||
@@ -3117,26 +3489,6 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
return getWindowHeight() - focusedBounds.top;
|
return getWindowHeight() - focusedBounds.top;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fly to either an element or the ground
|
|
||||||
*/
|
|
||||||
function flySomewhere() {
|
|
||||||
// On mobile, always prefer to focus on an element
|
|
||||||
// If not mobile, 50% chance to focus on ground
|
|
||||||
// if ((!isMobile() && coinFlip()) || !focusOnElement()) {
|
|
||||||
// focusOnGround();
|
|
||||||
// }
|
|
||||||
if (!focusOnElement()) {
|
|
||||||
focusOnGround();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusOnGround() {
|
|
||||||
focusedElement = null;
|
|
||||||
updateFocusedElementBounds();
|
|
||||||
flyTo(Math.random() * window.innerWidth, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {HTMLElement|null} The random element, or null if no valid element was found
|
* @returns {HTMLElement|null} The random element, or null if no valid element was found
|
||||||
*/
|
*/
|
||||||
@@ -3168,20 +3520,20 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Focus on an element within the viewport
|
* Fly to an element within the viewport
|
||||||
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
||||||
* @returns Whether an element to focus on was found
|
* @returns Whether an element to fly to was found (null if flying to the ground)
|
||||||
*/
|
*/
|
||||||
function focusOnElement(teleport = false) {
|
function flyToElement(teleport = false) {
|
||||||
if (frozen) {
|
if (frozen) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const previousElement = focusedElement;
|
||||||
focusedElement = getRandomValidElement();
|
focusedElement = getRandomValidElement();
|
||||||
log("Focusing on element: ", focusedElement);
|
|
||||||
updateFocusedElementBounds();
|
updateFocusedElementBounds();
|
||||||
if (teleport) {
|
if (teleport) {
|
||||||
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
} else {
|
} else if (focusedElement !== previousElement) {
|
||||||
flyTo(getFocusedElementRandomX(), getFocusedY());
|
flyTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
}
|
}
|
||||||
return focusedElement !== null;
|
return focusedElement !== null;
|
||||||
|
|||||||
2
dist/obsidian/manifest.json
vendored
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "pocket-bird",
|
"id": "pocket-bird",
|
||||||
"name": "Pocket Bird",
|
"name": "Pocket Bird",
|
||||||
"version": "2026.3.11",
|
"version": "2026.3.29",
|
||||||
"minAppVersion": "0.15.0",
|
"minAppVersion": "0.15.0",
|
||||||
"description": "Add a pet bird to fly around your notes and keep you company!",
|
"description": "Add a pet bird to fly around your notes and keep you company!",
|
||||||
"author": "Idrees Hassan",
|
"author": "Idrees Hassan",
|
||||||
|
|||||||
548
dist/userscript/birb.user.js
vendored
@@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Pocket Bird
|
// @name Pocket Bird
|
||||||
// @namespace https://idreesinc.com
|
// @namespace https://idreesinc.com
|
||||||
// @version 2026.3.11
|
// @version 2026.3.29
|
||||||
// @description It's a pet bird in your browser, what more could you want?
|
// @description It's a pet bird in your browser, what more could you want?
|
||||||
// @author Idrees
|
// @author Idrees
|
||||||
// @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js
|
// @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js
|
||||||
@@ -251,6 +251,8 @@
|
|||||||
"bluebird": {
|
"bluebird": {
|
||||||
"name": "Eastern Bluebird",
|
"name": "Eastern Bluebird",
|
||||||
"description": "Native to North American and very social, though can be timid around people.",
|
"description": "Native to North American and very social, though can be timid around people.",
|
||||||
|
"latinName": "Sialia sialis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Eastern_bluebird",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#639bff",
|
"face": "#639bff",
|
||||||
@@ -263,6 +265,8 @@
|
|||||||
"shimaEnaga": {
|
"shimaEnaga": {
|
||||||
"name": "Shima Enaga",
|
"name": "Shima Enaga",
|
||||||
"description": "Small, fluffy birds found in the snowy regions of Japan, these birds are highly sought after by ornithologists and nature photographers.",
|
"description": "Small, fluffy birds found in the snowy regions of Japan, these birds are highly sought after by ornithologists and nature photographers.",
|
||||||
|
"latinName": "Aegithalos caudatus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Long-tailed_tit",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#ffffff",
|
"face": "#ffffff",
|
||||||
@@ -276,6 +280,8 @@
|
|||||||
"tuftedTitmouse": {
|
"tuftedTitmouse": {
|
||||||
"name": "Tufted Titmouse",
|
"name": "Tufted Titmouse",
|
||||||
"description": "Native to the eastern United States, full of personality, and notably my wife's favorite bird.",
|
"description": "Native to the eastern United States, full of personality, and notably my wife's favorite bird.",
|
||||||
|
"latinName": "Baeolophus bicolor",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Tufted_titmouse",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#c7cad7",
|
"face": "#c7cad7",
|
||||||
@@ -292,6 +298,8 @@
|
|||||||
"europeanRobin": {
|
"europeanRobin": {
|
||||||
"name": "European Robin",
|
"name": "European Robin",
|
||||||
"description": "Native to western Europe, this is the quintessential robin. Quite friendly, you'll often find them searching for worms.",
|
"description": "Native to western Europe, this is the quintessential robin. Quite friendly, you'll often find them searching for worms.",
|
||||||
|
"latinName": "Erithacus rubecula",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/European_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#ffaf34",
|
"face": "#ffaf34",
|
||||||
@@ -306,6 +314,8 @@
|
|||||||
"redCardinal": {
|
"redCardinal": {
|
||||||
"name": "Red Cardinal",
|
"name": "Red Cardinal",
|
||||||
"description": "Native to the eastern United States, this strikingly red bird is hard to miss.",
|
"description": "Native to the eastern United States, this strikingly red bird is hard to miss.",
|
||||||
|
"latinName": "Cardinalis cardinalis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_cardinal",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#d93619",
|
"beak": "#d93619",
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
@@ -325,6 +335,8 @@
|
|||||||
"americanGoldfinch": {
|
"americanGoldfinch": {
|
||||||
"name": "American Goldfinch",
|
"name": "American Goldfinch",
|
||||||
"description": "Coloured a brilliant yellow, this bird feeds almost entirely on the seeds of plants such as thistle, sunflowers, and coneflowers.",
|
"description": "Coloured a brilliant yellow, this bird feeds almost entirely on the seeds of plants such as thistle, sunflowers, and coneflowers.",
|
||||||
|
"latinName": "Spinus tristis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/American_goldfinch",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#ffaf34",
|
"beak": "#ffaf34",
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
@@ -341,6 +353,8 @@
|
|||||||
"barnSwallow": {
|
"barnSwallow": {
|
||||||
"name": "Barn Swallow",
|
"name": "Barn Swallow",
|
||||||
"description": "Agile birds that often roost in man-made structures, these birds are known to build nests near Ospreys for protection.",
|
"description": "Agile birds that often roost in man-made structures, these birds are known to build nests near Ospreys for protection.",
|
||||||
|
"latinName": "Hirundo rustica",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Barn_swallow",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#db7c4d",
|
"face": "#db7c4d",
|
||||||
@@ -354,6 +368,8 @@
|
|||||||
"mistletoebird": {
|
"mistletoebird": {
|
||||||
"name": "Mistletoebird",
|
"name": "Mistletoebird",
|
||||||
"description": "Native to Australia, these birds eat mainly mistletoe and in turn spread the seeds far and wide.",
|
"description": "Native to Australia, these birds eat mainly mistletoe and in turn spread the seeds far and wide.",
|
||||||
|
"latinName": "Dicaeum hirundinaceum",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Mistletoebird",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#6c6a7c",
|
"foot": "#6c6a7c",
|
||||||
"face": "#352e6d",
|
"face": "#352e6d",
|
||||||
@@ -363,22 +379,11 @@
|
|||||||
"wing-edge": "#282065"
|
"wing-edge": "#282065"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redAvadavat": {
|
|
||||||
"name": "Red Avadavat",
|
|
||||||
"description": "Native to India and southeast Asia, these birds are also known as Strawberry Finches due to their speckled plumage.",
|
|
||||||
"colors": {
|
|
||||||
"beak": "#f71919",
|
|
||||||
"foot": "#af7575",
|
|
||||||
"face": "#cb092b",
|
|
||||||
"belly": "#ae1724",
|
|
||||||
"underbelly": "#831b24",
|
|
||||||
"wing": "#7e3030",
|
|
||||||
"wing-edge": "#490f0f"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scarletRobin": {
|
"scarletRobin": {
|
||||||
"name": "Scarlet Robin",
|
"name": "Scarlet Robin",
|
||||||
"description": "Native to Australia, this striking robin can be found in Eucalyptus forests.",
|
"description": "Native to Australia, this striking robin can be found in Eucalyptus forests.",
|
||||||
|
"latinName": "Petroica boodang",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Scarlet_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#494949",
|
"foot": "#494949",
|
||||||
"face": "#3d3d3d",
|
"face": "#3d3d3d",
|
||||||
@@ -386,12 +391,15 @@
|
|||||||
"underbelly": "#dcdcdc",
|
"underbelly": "#dcdcdc",
|
||||||
"wing": "#2b2b2b",
|
"wing": "#2b2b2b",
|
||||||
"wing-edge": "#ebebeb",
|
"wing-edge": "#ebebeb",
|
||||||
|
"nose": "#ebebeb",
|
||||||
"theme-highlight": "#fc5633"
|
"theme-highlight": "#fc5633"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"americanRobin": {
|
"americanRobin": {
|
||||||
"name": "American Robin",
|
"name": "American Robin",
|
||||||
"description": "While not a true robin, this social North American bird is so named due to its orange coloring. It seems unbothered by nearby humans.",
|
"description": "While not a true robin, this social North American bird is so named due to its orange coloring. It seems unbothered by nearby humans.",
|
||||||
|
"latinName": "Turdus migratorius",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/American_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#e89f30",
|
"beak": "#e89f30",
|
||||||
"foot": "#9f8075",
|
"foot": "#9f8075",
|
||||||
@@ -406,6 +414,8 @@
|
|||||||
"carolinaWren": {
|
"carolinaWren": {
|
||||||
"name": "Carolina Wren",
|
"name": "Carolina Wren",
|
||||||
"description": "Native to the eastern United States, these little birds are known for their curious and energetic nature.",
|
"description": "Native to the eastern United States, these little birds are known for their curious and energetic nature.",
|
||||||
|
"latinName": "Thryothorus ludovicianus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Carolina_wren",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#edc7a9",
|
"face": "#edc7a9",
|
||||||
@@ -416,14 +426,249 @@
|
|||||||
"wing": "#c58a5b",
|
"wing": "#c58a5b",
|
||||||
"wing-edge": "#866348"
|
"wing-edge": "#866348"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"blackCappedChickadee": {
|
||||||
|
"name": "Black-capped Chickadee",
|
||||||
|
"description": "Native to North America, these small and curious birds are known for their distinctive call from which they get their name.",
|
||||||
|
"latinName": "Poecile atricapillus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Black-capped_chickadee",
|
||||||
|
"colors": {
|
||||||
|
"hood": "#363636",
|
||||||
|
"cheek": "#363636",
|
||||||
|
"eyebrow": "#363636",
|
||||||
|
"nose": "#363636",
|
||||||
|
"collar": "#363636",
|
||||||
|
"belly": "#d6d4cf",
|
||||||
|
"underbelly": "#cfc5b4",
|
||||||
|
"face": "#eaeaea",
|
||||||
|
"wing": "#8f8e9a",
|
||||||
|
"wing-edge": "#706f7d",
|
||||||
|
"scruff": "#8f8e9a",
|
||||||
|
"foot": "#535259"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"blueJay": {
|
||||||
|
"name": "Blue Jay",
|
||||||
|
"description": "This loud and rambunctious bird is native to North America and is known for challenging anything in its path.",
|
||||||
|
"latinName": "Cyanocitta cristata",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Blue_jay",
|
||||||
|
"colors": {
|
||||||
|
"foot": "#5a626b",
|
||||||
|
"face": "#ebf2ff",
|
||||||
|
"belly": "#e5ecfa",
|
||||||
|
"underbelly": "#c4cbd6",
|
||||||
|
"wing": "#5890ff",
|
||||||
|
"wing-edge": "#3a77e8",
|
||||||
|
"hood": "#6391e8",
|
||||||
|
"nose": "#6391e8",
|
||||||
|
"collar": "#2e3136",
|
||||||
|
"scruff": "#6391e8"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"tuft"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"darkEyedJunco": {
|
||||||
|
"name": "Dark-eyed Junco",
|
||||||
|
"description": "Native across North America, these social birds will often be seen hopping along the ground in winter.",
|
||||||
|
"latinName": "Junco hyemalis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Dark-eyed_junco",
|
||||||
|
"colors": {
|
||||||
|
"face": "#55565e",
|
||||||
|
"wing": "#5c5f69",
|
||||||
|
"wing-edge": "#444547",
|
||||||
|
"belly": "#6c7180",
|
||||||
|
"underbelly": "#b8bbcc",
|
||||||
|
"foot": "#87776d",
|
||||||
|
"beak": "#ab8a98"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"houseFinch": {
|
||||||
|
"name": "House Finch",
|
||||||
|
"description": "Native to North America, these highly social birds sing cheerful songs and are often seen at bird feeders.",
|
||||||
|
"latinName": "Haemorhous mexicanus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/House_finch",
|
||||||
|
"colors": {
|
||||||
|
"face": "#cc3a3f",
|
||||||
|
"wing": "#ae8e78",
|
||||||
|
"wing-edge": "#8f6c54",
|
||||||
|
"belly": "#d97c77",
|
||||||
|
"underbelly": "#c5a489",
|
||||||
|
"foot": "#705b4c",
|
||||||
|
"beak": "#cf8479",
|
||||||
|
"hood": "#b02f35",
|
||||||
|
"nose": "#ab2b31",
|
||||||
|
"theme-highlight": "#ef444d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pigeon": {
|
||||||
|
"name": "Rock Pigeon",
|
||||||
|
"description": "Descended from the Rock Dove, these once domesticated birds are often found in cities worldwide. Quite friendly and intelligent, they were favored companions of Nikola Tesla.",
|
||||||
|
"latinName": "Columba livia",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Rock_dove",
|
||||||
|
"colors": {
|
||||||
|
"foot": "#ef6e5b",
|
||||||
|
"face": "#5a6c91",
|
||||||
|
"wing-edge": "#65686e",
|
||||||
|
"nose": "#ebebeb",
|
||||||
|
"belly": "#977699",
|
||||||
|
"underbelly": "#b0b3ba",
|
||||||
|
"wing": "#c7cbd4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redAvadavat": {
|
||||||
|
"name": "Red Avadavat",
|
||||||
|
"description": "Native to India and southeast Asia, these birds are also known as Strawberry Finches due to their speckled plumage.",
|
||||||
|
"latinName": "Amandava amandava",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_avadavat",
|
||||||
|
"colors": {
|
||||||
|
"beak": "#f71919",
|
||||||
|
"foot": "#af7575",
|
||||||
|
"face": "#cb092b",
|
||||||
|
"belly": "#ae1724",
|
||||||
|
"underbelly": "#831b24",
|
||||||
|
"wing": "#7e3030",
|
||||||
|
"wing-edge": "#490f0f",
|
||||||
|
"wing-spots": "#e8e4e4",
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"pinkRobin": {
|
||||||
|
"name": "Pink Robin",
|
||||||
|
"description": "Native to Australia, these bubblegum-pink puffballs are quieter than most, instead relying on their vibrant colours to attract partners.",
|
||||||
|
"latinName": "Petroica rodinogaster",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Pink_robin",
|
||||||
|
"colors": {
|
||||||
|
"face": "#403a46",
|
||||||
|
"wing": "#38333d",
|
||||||
|
"wing-edge": "#252325",
|
||||||
|
"underbelly": "#ff7eb8",
|
||||||
|
"belly": "#ff6eaf",
|
||||||
|
"foot": "#3c393c",
|
||||||
|
"theme-highlight": "#ff82ba"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"spangledCotinga": {
|
||||||
|
"name": "Spangled Cotinga",
|
||||||
|
"description": "This South American bird can be found in the Amazon rainforest, flashing its iridescent turquoise feathers high above in the canopy.",
|
||||||
|
"latinName": "Cotinga cayana",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Spangled_cotinga",
|
||||||
|
"colors": {
|
||||||
|
"face": "#62eafe",
|
||||||
|
"chin": "#a12457",
|
||||||
|
"collar": "#a12457",
|
||||||
|
"belly": "#62eafe",
|
||||||
|
"underbelly": "#5cd8ea",
|
||||||
|
"wing": "#227c89",
|
||||||
|
"wing-edge": "#13353a",
|
||||||
|
"foot": "#68696b",
|
||||||
|
"collar-scruff": "#62eafe"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"elegantEuphonia": {
|
||||||
|
"name": "Elegant Euphonia",
|
||||||
|
"description": "This vividly coloured finch is found throughout Central America and is known for the distinctive blue hood that crowns its head.",
|
||||||
|
"latinName": "Chlorophonia elegantissima",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Elegant_euphonia",
|
||||||
|
"colors": {
|
||||||
|
"wing": "#2d31a1",
|
||||||
|
"wing-edge": "#191c6d",
|
||||||
|
"face": "#1f2392",
|
||||||
|
"hood": "#6bc6ed",
|
||||||
|
"nose-tip": "#fd7e1d",
|
||||||
|
"foot": "#555650",
|
||||||
|
"belly": "#ff952b",
|
||||||
|
"underbelly": "#fd7e1d",
|
||||||
|
"temple": "#57c8fa",
|
||||||
|
"upper-corner-eye": "#57c8fa",
|
||||||
|
"upper-eyelid": "#57c8fa",
|
||||||
|
"collar-scruff": "#57c8fa",
|
||||||
|
"scruff": "#57c8fa",
|
||||||
|
"beak": "#252c31",
|
||||||
|
"collar": "#191c6d"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"paintedBunting": {
|
||||||
|
"name": "Painted Bunting",
|
||||||
|
"description": "A remarkably colourful bird, this North American species is quite difficult to observe despite its vivid palette due to its shy nature and vulnerable habitat.",
|
||||||
|
"latinName": "Passerina ciris",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Painted_bunting",
|
||||||
|
"colors": {
|
||||||
|
"face": "#5567f0",
|
||||||
|
"underbelly": "#f16534",
|
||||||
|
"belly": "#ef3b3b",
|
||||||
|
"wing": "#a3e65a",
|
||||||
|
"wing-edge": "#91cc50",
|
||||||
|
"shoulder": "#f6fe40",
|
||||||
|
"foot": "#767980"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"redWarbler": {
|
||||||
|
"name": "Red Warbler",
|
||||||
|
"description": "Endemic to the highlands of Mexico, this bird has the rare distinction of being one of the very few toxic birds in the world.",
|
||||||
|
"latinName": "Cardellina rubra",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_warbler",
|
||||||
|
"colors": {
|
||||||
|
"face": "#e80a28",
|
||||||
|
"belly": "#d90921",
|
||||||
|
"underbelly": "#c70c18",
|
||||||
|
"wing": "#ba121d",
|
||||||
|
"wing-edge": "#5b3535",
|
||||||
|
"foot": "#5e4645",
|
||||||
|
"behind-eye": "#deedff",
|
||||||
|
"temple": "#e8f0fa",
|
||||||
|
"corner-eye": "#d5e4f5",
|
||||||
|
"lower-eyelid": "#e34a61",
|
||||||
|
"beak": "#873535",
|
||||||
|
"cheek": "#db1734"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"cubanTody": {
|
||||||
|
"name": "Cuban Tody",
|
||||||
|
"description": "As the name suggests, this little green bird is only found on the island of Cuba and is known for being particularly round.",
|
||||||
|
"latinName": "Todus multicolor",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Cuban_tody",
|
||||||
|
"colors": {
|
||||||
|
"beak": "#f16f54",
|
||||||
|
"face": "#5fdf44",
|
||||||
|
"chin": "#f12d3e",
|
||||||
|
"collar": "#f12d3e",
|
||||||
|
"belly": "#f6f5e4",
|
||||||
|
"collar-scruff": "#a3ebff",
|
||||||
|
"underbelly": "#eae9d2",
|
||||||
|
"wing": "#11c751",
|
||||||
|
"wing-edge": "#156631",
|
||||||
|
"foot": "#ac7055",
|
||||||
|
"scruff": "#11c751",
|
||||||
|
"theme-highlight": "#4adc67"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"violetBackedStarling": {
|
||||||
|
"name": "Violet-backed Starling",
|
||||||
|
"description": "Native to Sub-Saharan Africa, these small starlings are known for being the most vividly purple birds in the world.",
|
||||||
|
"latinName": "Cinnyricinclus leucogaster",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Violet-backed_starling",
|
||||||
|
"colors": {
|
||||||
|
"face": "#9c3af2",
|
||||||
|
"wing": "#8f37ed",
|
||||||
|
"wing-edge": "#7029b8",
|
||||||
|
"belly": "#ffffff",
|
||||||
|
"underbelly": "#f2f2f2",
|
||||||
|
"foot": "#736a66",
|
||||||
|
"collar": "#aa60e6"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const PALETTE = Object.freeze(/** @type {const} */ ({
|
||||||
* Palette color names
|
|
||||||
* @type {Record<string, string>}
|
|
||||||
*/
|
|
||||||
const PALETTE = {
|
|
||||||
THEME_HIGHLIGHT: "theme-highlight",
|
THEME_HIGHLIGHT: "theme-highlight",
|
||||||
TRANSPARENT: "transparent",
|
TRANSPARENT: "transparent",
|
||||||
OUTLINE: "outline",
|
OUTLINE: "outline",
|
||||||
@@ -434,23 +679,36 @@
|
|||||||
FACE: "face",
|
FACE: "face",
|
||||||
HOOD: "hood",
|
HOOD: "hood",
|
||||||
EYEBROW: "eyebrow",
|
EYEBROW: "eyebrow",
|
||||||
|
UPPER_EYELID: "upper-eyelid",
|
||||||
|
UPPER_CORNER_EYE: "upper-corner-eye",
|
||||||
|
BEHIND_EYE: "behind-eye",
|
||||||
|
CORNER_EYE: "corner-eye",
|
||||||
|
TEMPLE: "temple",
|
||||||
|
LOWER_EYELID: "lower-eyelid",
|
||||||
NOSE: "nose",
|
NOSE: "nose",
|
||||||
|
NOSE_TIP: "nose-tip",
|
||||||
CHEEK: "cheek",
|
CHEEK: "cheek",
|
||||||
SCRUFF: "scruff",
|
SCRUFF: "scruff",
|
||||||
|
CHIN: "chin",
|
||||||
COLLAR: "collar",
|
COLLAR: "collar",
|
||||||
|
COLLAR_SCRUFF: "collar-scruff",
|
||||||
BELLY: "belly",
|
BELLY: "belly",
|
||||||
UNDERBELLY: "underbelly",
|
UNDERBELLY: "underbelly",
|
||||||
WING: "wing",
|
WING: "wing",
|
||||||
|
SHOULDER: "shoulder",
|
||||||
|
WING_SPOTS: "wing-spots",
|
||||||
WING_EDGE: "wing-edge",
|
WING_EDGE: "wing-edge",
|
||||||
HEART: "heart",
|
HEART: "heart",
|
||||||
HEART_BORDER: "heart-border",
|
HEART_BORDER: "heart-border",
|
||||||
HEART_SHINE: "heart-shine",
|
HEART_SHINE: "heart-shine",
|
||||||
FEATHER_SPINE: "feather-spine",
|
FEATHER_SPINE: "feather-spine",
|
||||||
};
|
}));
|
||||||
|
|
||||||
|
/** @typedef {typeof PALETTE[keyof typeof PALETTE]} PaletteColor */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping of sprite sheet colors to palette colors
|
* Mapping of sprite sheet colors to palette colors
|
||||||
* @type {Record<string, string>}
|
* @type {Record<string, PaletteColor>}
|
||||||
*/
|
*/
|
||||||
const SPRITE_SHEET_COLOR_MAP = {
|
const SPRITE_SHEET_COLOR_MAP = {
|
||||||
"transparent": PALETTE.TRANSPARENT,
|
"transparent": PALETTE.TRANSPARENT,
|
||||||
@@ -463,13 +721,24 @@
|
|||||||
"#639bff": PALETTE.FACE,
|
"#639bff": PALETTE.FACE,
|
||||||
"#99e550": PALETTE.HOOD,
|
"#99e550": PALETTE.HOOD,
|
||||||
"#ff5573": PALETTE.EYEBROW,
|
"#ff5573": PALETTE.EYEBROW,
|
||||||
|
"#ff768e": PALETTE.UPPER_EYELID,
|
||||||
|
"#ff90a4": PALETTE.UPPER_CORNER_EYE,
|
||||||
|
"#ff2c88": PALETTE.BEHIND_EYE,
|
||||||
|
"#e34f9c": PALETTE.CORNER_EYE,
|
||||||
|
"#b53477": PALETTE.TEMPLE,
|
||||||
|
"#ae65f1": PALETTE.LOWER_EYELID,
|
||||||
"#d95763": PALETTE.NOSE,
|
"#d95763": PALETTE.NOSE,
|
||||||
|
"#b93844": PALETTE.NOSE_TIP,
|
||||||
"#ff67a9": PALETTE.CHEEK,
|
"#ff67a9": PALETTE.CHEEK,
|
||||||
"#c5e550": PALETTE.SCRUFF,
|
"#c5e550": PALETTE.SCRUFF,
|
||||||
|
"#b87af1": PALETTE.CHIN,
|
||||||
"#ffe955": PALETTE.COLLAR,
|
"#ffe955": PALETTE.COLLAR,
|
||||||
|
"#f8ff55": PALETTE.COLLAR_SCRUFF,
|
||||||
"#f8b143": PALETTE.BELLY,
|
"#f8b143": PALETTE.BELLY,
|
||||||
"#ec8637": PALETTE.UNDERBELLY,
|
"#ec8637": PALETTE.UNDERBELLY,
|
||||||
"#578ae6": PALETTE.WING,
|
"#578ae6": PALETTE.WING,
|
||||||
|
"#55d1f3": PALETTE.SHOULDER,
|
||||||
|
"#90b0e8": PALETTE.WING_SPOTS,
|
||||||
"#326ed9": PALETTE.WING_EDGE,
|
"#326ed9": PALETTE.WING_EDGE,
|
||||||
"#c82e2e": PALETTE.HEART,
|
"#c82e2e": PALETTE.HEART,
|
||||||
"#501a1a": PALETTE.HEART_BORDER,
|
"#501a1a": PALETTE.HEART_BORDER,
|
||||||
@@ -477,16 +746,52 @@
|
|||||||
"#373737": PALETTE.FEATHER_SPINE,
|
"#373737": PALETTE.FEATHER_SPINE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Partial<Record<PaletteColor, PaletteColor>>}
|
||||||
|
*/
|
||||||
|
({
|
||||||
|
[PALETTE.HOOD]: PALETTE.FACE,
|
||||||
|
[PALETTE.EYEBROW]: PALETTE.FACE,
|
||||||
|
[PALETTE.UPPER_EYELID]: PALETTE.EYEBROW,
|
||||||
|
[PALETTE.UPPER_CORNER_EYE]: PALETTE.EYEBROW,
|
||||||
|
[PALETTE.BEHIND_EYE]: PALETTE.FACE,
|
||||||
|
[PALETTE.CORNER_EYE]: PALETTE.FACE,
|
||||||
|
[PALETTE.TEMPLE]: PALETTE.FACE,
|
||||||
|
[PALETTE.LOWER_EYELID]: PALETTE.FACE,
|
||||||
|
[PALETTE.NOSE]: PALETTE.FACE,
|
||||||
|
[PALETTE.NOSE_TIP]: PALETTE.NOSE,
|
||||||
|
[PALETTE.CHEEK]: PALETTE.FACE,
|
||||||
|
[PALETTE.SCRUFF]: PALETTE.FACE,
|
||||||
|
[PALETTE.CHIN]: PALETTE.FACE,
|
||||||
|
[PALETTE.COLLAR]: PALETTE.FACE,
|
||||||
|
[PALETTE.COLLAR_SCRUFF]: PALETTE.COLLAR,
|
||||||
|
[PALETTE.WING_SPOTS]: PALETTE.WING,
|
||||||
|
[PALETTE.SHOULDER]: PALETTE.WING,
|
||||||
|
});
|
||||||
|
|
||||||
|
const RARITY = Object.freeze(/** @type {const} */ ({
|
||||||
|
COMMON: "common",
|
||||||
|
UNCOMMON: "uncommon"
|
||||||
|
}));
|
||||||
|
|
||||||
|
/** @typedef {typeof RARITY[keyof typeof RARITY]} Rarity */
|
||||||
|
|
||||||
class BirdType {
|
class BirdType {
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {string} description
|
* @param {string} description
|
||||||
|
* @param {string} latinName
|
||||||
|
* @param {string} url
|
||||||
* @param {Record<string, string>} colors
|
* @param {Record<string, string>} colors
|
||||||
* @param {string[]} [tags]
|
* @param {string[]} [tags]
|
||||||
|
* @param {Rarity} [rarity]
|
||||||
*/
|
*/
|
||||||
constructor(name, description, colors, tags = []) {
|
constructor(name, description, latinName, url, colors, tags = [], rarity = RARITY.COMMON) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
this.latinName = latinName;
|
||||||
|
this.url = url;
|
||||||
const defaultColors = {
|
const defaultColors = {
|
||||||
[PALETTE.TRANSPARENT]: "transparent",
|
[PALETTE.TRANSPARENT]: "transparent",
|
||||||
[PALETTE.OUTLINE]: "#000000",
|
[PALETTE.OUTLINE]: "#000000",
|
||||||
@@ -498,15 +803,27 @@
|
|||||||
[PALETTE.HEART_SHINE]: "#ff6b6b",
|
[PALETTE.HEART_SHINE]: "#ff6b6b",
|
||||||
[PALETTE.FEATHER_SPINE]: "#373737",
|
[PALETTE.FEATHER_SPINE]: "#373737",
|
||||||
[PALETTE.HOOD]: colors.face,
|
[PALETTE.HOOD]: colors.face,
|
||||||
[PALETTE.EYEBROW]: colors.face,
|
[PALETTE.EYEBROW]: colors.face,
|
||||||
|
[PALETTE.UPPER_EYELID]: colors.eyebrow || colors.face,
|
||||||
|
[PALETTE.UPPER_CORNER_EYE]: colors.eyebrow || colors.face,
|
||||||
|
[PALETTE.BEHIND_EYE]: colors.face,
|
||||||
|
[PALETTE.CORNER_EYE]: colors.face,
|
||||||
|
[PALETTE.TEMPLE]: colors.face,
|
||||||
|
[PALETTE.LOWER_EYELID]: colors.face,
|
||||||
[PALETTE.NOSE]: colors.face,
|
[PALETTE.NOSE]: colors.face,
|
||||||
|
[PALETTE.NOSE_TIP]: colors.nose || colors.face,
|
||||||
[PALETTE.CHEEK]: colors.face,
|
[PALETTE.CHEEK]: colors.face,
|
||||||
[PALETTE.SCRUFF]: colors.face,
|
[PALETTE.SCRUFF]: colors.face,
|
||||||
|
[PALETTE.CHIN]: colors.face,
|
||||||
[PALETTE.COLLAR]: colors.face,
|
[PALETTE.COLLAR]: colors.face,
|
||||||
|
[PALETTE.COLLAR_SCRUFF]: colors.collar || colors.face,
|
||||||
|
[PALETTE.SHOULDER]: colors.wing,
|
||||||
};
|
};
|
||||||
/** @type {Record<string, string>} */
|
/** @type {Record<string, string>} */
|
||||||
this.colors = { ...defaultColors, ...colors, [PALETTE.THEME_HIGHLIGHT]: colors[PALETTE.THEME_HIGHLIGHT] ?? colors.hood ?? colors.face };
|
this.colors = { ...defaultColors, ...colors, [PALETTE.THEME_HIGHLIGHT]: colors[PALETTE.THEME_HIGHLIGHT] ?? colors.hood ?? colors.face };
|
||||||
this.tags = tags;
|
this.tags = tags;
|
||||||
|
/** @type {Rarity} */
|
||||||
|
this.rarity = rarity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,7 +887,7 @@
|
|||||||
const SPECIES = Object.fromEntries(
|
const SPECIES = Object.fromEntries(
|
||||||
Object.entries(species).map(([id, data]) => [
|
Object.entries(species).map(([id, data]) => [
|
||||||
id,
|
id,
|
||||||
new BirdType(data.name, data.description, data.colors, data.tags ?? []),
|
new BirdType(data.name, data.description, data.latinName, data.url, data.colors, data.tags, data.rarity)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1267,37 +1584,42 @@
|
|||||||
audioContext;
|
audioContext;
|
||||||
|
|
||||||
chirp() {
|
chirp() {
|
||||||
if (!this.audioContext) {
|
const count = Math.floor(1 + Math.random() * 1.5);
|
||||||
this.audioContext = new AudioContext();
|
for (let i = 0; i < count; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.audioContext) {
|
||||||
|
this.audioContext = new AudioContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TIMES = [0, 0.06, 0.10, 0.15];
|
||||||
|
const FREQUENCIES = [2200,
|
||||||
|
3500 + Math.random() * 600 * count,
|
||||||
|
2100 + Math.random() * 200 * count,
|
||||||
|
1600 + Math.random() * 400 * count];
|
||||||
|
const VOLUMES = [0.00005, 0.165, 0.165, 0.0001];
|
||||||
|
|
||||||
|
const oscillator = this.audioContext.createOscillator();
|
||||||
|
oscillator.type = "sine";
|
||||||
|
const gain = this.audioContext.createGain();
|
||||||
|
oscillator.connect(gain);
|
||||||
|
gain.connect(this.audioContext.destination);
|
||||||
|
|
||||||
|
const now = this.audioContext.currentTime;
|
||||||
|
for (let i = 0; i < TIMES.length; i++) {
|
||||||
|
const time = TIMES[i] + now;
|
||||||
|
if (i === 0) {
|
||||||
|
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
|
||||||
|
gain.gain.setValueAtTime(VOLUMES[i], time);
|
||||||
|
} else {
|
||||||
|
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oscillator.start(now);
|
||||||
|
oscillator.stop(now + TIMES[TIMES.length - 1]);
|
||||||
|
}, i * 120);
|
||||||
}
|
}
|
||||||
|
|
||||||
const TIMES = [0, 0.06, 0.10, 0.15];
|
|
||||||
const FREQUENCIES = [2200,
|
|
||||||
3500 + Math.random() * 600,
|
|
||||||
2100 + Math.random() * 200,
|
|
||||||
1600 + Math.random() * 400];
|
|
||||||
const VOLUMES = [0.0001, 0.2, 0.2, 0.0001];
|
|
||||||
|
|
||||||
const oscillator = this.audioContext.createOscillator();
|
|
||||||
oscillator.type = "sine";
|
|
||||||
const gain = this.audioContext.createGain();
|
|
||||||
oscillator.connect(gain);
|
|
||||||
gain.connect(this.audioContext.destination);
|
|
||||||
|
|
||||||
const now = this.audioContext.currentTime;
|
|
||||||
for (let i = 0; i < TIMES.length; i++) {
|
|
||||||
const time = TIMES[i] + now;
|
|
||||||
if (i === 0) {
|
|
||||||
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
|
|
||||||
gain.gain.setValueAtTime(VOLUMES[i], time);
|
|
||||||
} else {
|
|
||||||
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
|
|
||||||
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
oscillator.start(now);
|
|
||||||
oscillator.stop(now + TIMES[TIMES.length - 1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2042,7 +2364,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#birb-field-guide .birb-grid-content {
|
#birb-field-guide .birb-grid-content {
|
||||||
grid-template-rows: repeat(3, auto);
|
grid-template-columns: repeat(4, auto);
|
||||||
}
|
}
|
||||||
|
|
||||||
#birb-wardrobe .birb-grid-content {
|
#birb-wardrobe .birb-grid-content {
|
||||||
@@ -2052,7 +2374,7 @@
|
|||||||
|
|
||||||
.birb-grid-content {
|
.birb-grid-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: row;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
@@ -2084,7 +2406,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.birb-grid-item, .birb-field-guide-description, .birb-message-content {
|
.birb-grid-item, .birb-field-guide-description, .birb-message-content {
|
||||||
border: var(--birb-border-size) solid rgb(255, 207, 144);
|
border: var(--birb-border-size) solid #ffcf90;
|
||||||
box-shadow: 0 0 0 var(--birb-border-size) white;
|
box-shadow: 0 0 0 var(--birb-border-size) white;
|
||||||
background: rgba(255, 221, 177, 0.5);
|
background: rgba(255, 221, 177, 0.5);
|
||||||
}
|
}
|
||||||
@@ -2103,6 +2425,15 @@
|
|||||||
background: var(--birb-mix-color);
|
background: var(--birb-mix-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.birb-field-guide-section-label {
|
||||||
|
padding-top: 4px;
|
||||||
|
/* padding-left: calc(10px + var(--birb-border-size) / 2); */
|
||||||
|
color: #876c4e;
|
||||||
|
text-align: center;
|
||||||
|
/* Italics */
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.birb-field-guide-description {
|
.birb-field-guide-description {
|
||||||
max-width: calc(100% - 20px);
|
max-width: calc(100% - 20px);
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
@@ -2114,7 +2445,14 @@
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: rgb(124, 108, 75);
|
color: #7c6c4b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.birb-field-guide-latin-name {
|
||||||
|
text-decoration: underline;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: bold;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
#birb-feather {
|
#birb-feather {
|
||||||
@@ -2127,7 +2465,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: rgb(124, 108, 75);
|
color: #7c6c4b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.birb-sticky-note {
|
.birb-sticky-note {
|
||||||
@@ -2176,7 +2514,7 @@
|
|||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}`;
|
}`;
|
||||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABFFJREFUeJztnb9rE2EYx79vK1gUQSRLL64apxZcdC84dNBk0LiIoIJCwUGwlP4BUjqIFpSKLioOdYlV6ObUpV26ORRX0yoEHezQgvZxSN7rm+v9SGzu3vea7wdC31wued7k3ueT5727XAFCCCGEENJfKNsdIO4jIhL1mFKKY4jkFg7eHGBTQDp2pVgEANTq9bZ2Fn0ghPQp0qLseVL2vH3tODn2Kn7Z82RlZERkYmJfO+34hKTJgO0OkGQqxSKmCgXUKpV97SxZXV7GVKHgtwnJOxRgjrAloFq9jplGo23ZTKPhT4EJISQVzCnwysiIf8tqChzWhyxjE0IcQELIOrZNAZl9oPwI6TNEpLnz3/ibdXzbArIhf0LS5IjtDuSN1dFRK3GVUkpExOapJzzdhZA+RVc+uvpjFURI/uE3eheY0mM1lD3BLx1uA3JQOIBILtDy084TkcwFSAH3KWFHQDkFJBaQF/WyALB2AKgV2+nxzzztnMQToc1vXvOGhN+o9hIKmADAi3pZN60cAFJK+X1wrfoL5oWNPM0jsUeBzQ/TGHzQy0TEXCeVARGc+hjLISLi2kAk6XG3+MHqtrYp4E4wU6E6u+Evo/+iiRSgKZ716m3gAXB29+TeCvVy2/6YNGTkgoAJ0dgWcAyilPKld9gZHlSy+Vd6si1iK8CB4x7UsWGc+7QEANi98QTeq+sYn98FloDq7CaQkoxcEDAhOUCixPf12+/MO5M2w4NKxud3sXRvoCcSjHwBU0C3X+4voW+NVzD37rl/f2HS08+LDtaFoEREBo57bcvaBAxg69emH9uMSxGSPiFWfmtzJbg6XXeF2A+nVVVBflzFnY/vAQCvLl8DAFx//TT0OVqEIa/VDNihnGwLmBCXqc5uxO7YS1uAUfFbeZibPOtIgNuLYzh68RQAYGflJwDg5vrbjgLoMnxtrtT1uVs2BUyIy0QJyMy3lEUUWX3mSYId/xZYi0+zMOnh/P11nDl9InT9wIY40JGonZWfvviC/QgS3Ci6HzwaRg4Tr0s32oqQYL5lIaCv335H5n9e+O+LIWwvjmHoSgm4vx76eJj4DlJ92RQwIS7y7E8FE0dqQMbiS6I6u4GFSU9c6EsSiVNgtKqn7cWxtse2vmwBAArTq1HP3Qvyn+Izp+BhDF35jPMZCZgQ12g8uuAP7kvf3/jL1+ZKWY1ziSpCzAOTLuddYsfCJKTlF0ZherVnb9y2gAlxHVOCaOZD1mNddBESFKGWoMv515EA0ZJQ49GF2HV7KT8zvi0BE0I6QgAgKMI8CDBxH6C+EGfrjURKME3x6NhofuNFrkf5EWIFhebUu5l8Ebulco15NYza44f+VTF0O80LFAR+6B0aP+0+EEKSCV4xx/V87OrfYiqlUHv80L9vtpFi1aVfNy4+Kz9C3KD101QgB/nY1Wkw5lQ02M6CpPiuf9iEHHbyloNddTaunM3ijduOTwg5XPwDDgmrxnQErq8AAAAASUVORK5CYII=";
|
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABORJREFUeJztnU9IHFccx79vE6g0BEK7BJzNwR6apReFQElKjpZaeol7EHuRQhFaECIEIkF6LGkQGqyQ0EBa+u8iHjZCCfXQUw7qRVDwYKQEirtt0yVtMRaFdn857L7x7Tizf3Rn3oz7/cDi8+3s/t44733m9+afACGEEEII6SyU7QaQ+CMiEvSeUop9iCQWdt4EYFNAOnYukwEA5AuFmnIUbSCEdChSZdBxZNBxDpTrybFd8QcdR5Z6e0XGxg6Uw45PSJikbDeANCaXyeBGOo18LnegHCXLjx7hRjrtlglJOhRggrAloHyhgFulUk3drVLJnQITQkgomFPgpd5e9xXVFNivDVHGJoTEAPEh6tg2BWS2gfIjpMMQkcrBf+Nn1PFtC8iG/AkJk5O2G5A0lvv6rMRVSikREZuXnvByF0I6FJ356OyPWRAhyYd79BYwpcdsKHq8Ox1uA3JU2IFIItDy084TkcgFSAF3KH5nQDkFJBaQe4VBAWDtBFA1dqz7P8dp8zS8ENrc85ovNLhHtZ1QwAQA7hUGddHKCSCllNuGuGV/3nFhY5wmkbpngc0/ptH5oOtEBCIiYXYG79THqA89NokXH2UeWN3WNgXcDOZQGJ4qunX0XzCBAjTFs3DpbfRcexXny2eA1zKAcxYo1EoQIewV4yBgQjS2BVwHUUq50jvudJ9Q8tv/0pZtUTcDTJ1yoF7uxrtr68DaOsoj03A+fx/FNz7DuR+yGFrdrjko3U4RxkHAhCQACRLf5tZ25I0Jm+4TSt77soyHH6faIsHALzAF9NPkP279v38Cbz6dw693f8R06Xu3fq7vtP5ccLAWBCUikjrl1NSVR6bh3K8I+GHXX/jmq09841KEpEOoK7+VmSziOl2PC4EZoL7zAAAGxkex8MV9AEDu01HsnQWure3L7/KTHaSMYw5+HGa6Wt4p1gh4AcCdD//G1tM5nLm7BJSAodXtmrhmRui3Ts3GJiTODE8VrR7YG1rdduOnFvYzzdkJB0mSbt2GVoWF3fl+vHTpFQDA3tIzAMBIzwNcfrKDxcflA59763zKrddp+MpMtuVrt3R8+WPIFfDAeEXAI2tfu8uZ7ahuAL/vqqwwJUiOAUECNMdbyCIKzD6TJMGm7wXW4tPM9Z3GL1c38Pq5yhTUlN7i47J3QxzpTNTe0jMMjI/WtqNnX3yLxrLDU0VfAfNsGDlOfJsdwQcb+7Mw73iLQkCbW9vu+E8qh34Ywu58P7quZIGrGwCAza3a9/3Ed5Tsy6aACYkjd/7LYexkHohYfI0YnipidsKROLSlEQ2nwKhmT7vz/TXvPV9/DgBITy4HfXY/yCHFZ07B/ei68jMuVAXspd0CJiRulG5edDv3O79/59avzGSj6udywUhCTGYnnEQcdmrYMD8Jafn5kZ5cbtuK2xYwIXHHlCAq4yHqvi46CfGKUEswzuOvKQGiKqHSzYt1l22n/Mz4tgRMCGkKAQCvCJMgwIbHAPXlMNUVCZRgmOLRsVHZ4wUuR/kRYgWFytS7MvgCDkslGvNpGPnb192nYuhymA8o8Nzo7Rs/7DYQQhrjfWJO3MdjS/8WUymF/O3r7u9mGSFmXfp768Vn5kdIPKjejAAkYDy2dBmMORX1lqOgUfy4/7EJOe4kbQy21Nh66WwUK247PiHkePECZQPi+PbreqwAAAAASUVORK5CYII=";
|
||||||
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
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==";
|
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==";
|
||||||
|
|
||||||
@@ -2206,6 +2544,7 @@
|
|||||||
const HOP_CHANCE = 1 / (60 * 2.5); // Every 2.5 seconds
|
const HOP_CHANCE = 1 / (60 * 2.5); // Every 2.5 seconds
|
||||||
const FOCUS_SWITCH_CHANCE = 1 / (60 * 20); // Every 20 seconds
|
const FOCUS_SWITCH_CHANCE = 1 / (60 * 20); // Every 20 seconds
|
||||||
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // Every 2 hours
|
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // Every 2 hours
|
||||||
|
const UNCOMMON_FEATHER_CHANCE = 0.15; // 15% of feathers are uncommon
|
||||||
const HAT_CHANCE = 1 / (60 * 60 * 25); // Every 25 minutes
|
const HAT_CHANCE = 1 / (60 * 60 * 25); // Every 25 minutes
|
||||||
|
|
||||||
// Feathers
|
// Feathers
|
||||||
@@ -2312,7 +2651,7 @@
|
|||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
||||||
new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false),
|
new MenuItem("2026.3.29", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.29"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @type {Birb} */
|
/** @type {Birb} */
|
||||||
@@ -2502,7 +2841,9 @@
|
|||||||
|
|
||||||
setInterval(update, UPDATE_INTERVAL);
|
setInterval(update, UPDATE_INTERVAL);
|
||||||
|
|
||||||
focusOnElement(true);
|
flyToElement(true);
|
||||||
|
// TODO: Remove
|
||||||
|
insertFieldGuide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
@@ -2521,11 +2862,11 @@
|
|||||||
// Idle for a while, do something
|
// Idle for a while, do something
|
||||||
if (focusedElement === null) {
|
if (focusedElement === null) {
|
||||||
// Fly to an element
|
// Fly to an element
|
||||||
focusOnElement();
|
flyToElement();
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
} else if (Math.random() < FOCUS_SWITCH_CHANCE) {
|
} else if (Math.random() < FOCUS_SWITCH_CHANCE) {
|
||||||
// Fly to another element if idle for a longer while
|
// Fly to another element if idle for a longer while
|
||||||
focusOnElement();
|
flyToElement();
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2561,7 +2902,7 @@
|
|||||||
// Update the bird's position
|
// Update the bird's position
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE) {
|
||||||
if (focusedElement && !isWithinHorizontalBounds()) {
|
if (focusedElement && !isWithinHorizontalBounds()) {
|
||||||
flySomewhere();
|
flyToElement();
|
||||||
}
|
}
|
||||||
birdY = getFocusedY();
|
birdY = getFocusedY();
|
||||||
} else if (currentState === States.FLYING) {
|
} else if (currentState === States.FLYING) {
|
||||||
@@ -2577,7 +2918,7 @@
|
|||||||
startY += targetY - oldTargetY;
|
startY += targetY - oldTargetY;
|
||||||
if (targetY < 0 || targetY > getWindowHeight()) {
|
if (targetY < 0 || targetY > getWindowHeight()) {
|
||||||
// Fly to another element or the ground if the focused element moves out of bounds
|
// Fly to another element or the ground if the focused element moves out of bounds
|
||||||
flySomewhere();
|
flyToElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (birb.draw(SPECIES[currentSpecies], currentHat)) {
|
if (birb.draw(SPECIES[currentSpecies], currentHat)) {
|
||||||
@@ -2655,7 +2996,8 @@
|
|||||||
if (document.querySelector("#" + FEATHER_ID)) {
|
if (document.querySelector("#" + FEATHER_ID)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const speciesToUnlock = Object.keys(SPECIES).filter((species) => !unlockedSpecies.includes(species));
|
const rarity = Math.random() < UNCOMMON_FEATHER_CHANCE ? RARITY.UNCOMMON : RARITY.COMMON;
|
||||||
|
const speciesToUnlock = Object.keys(SPECIES).filter((species) => !unlockedSpecies.includes(species) && SPECIES[species].rarity === rarity);
|
||||||
if (speciesToUnlock.length === 0) {
|
if (speciesToUnlock.length === 0) {
|
||||||
// No more species to unlock
|
// No more species to unlock
|
||||||
return;
|
return;
|
||||||
@@ -2850,9 +3192,23 @@
|
|||||||
removeWardrobe();
|
removeWardrobe();
|
||||||
|
|
||||||
const contentContainer = document.createElement("div");
|
const contentContainer = document.createElement("div");
|
||||||
const content = makeElement("birb-grid-content");
|
const familiarBirds = makeElement("birb-grid-content");
|
||||||
|
const uncommonBirds = makeElement("birb-grid-content");
|
||||||
|
|
||||||
|
const familiarLabel = document.createElement("div");
|
||||||
|
familiarLabel.className = "birb-field-guide-section-label";
|
||||||
|
familiarLabel.textContent = `----- Familiar ${birdBirb()}s -----`;
|
||||||
|
|
||||||
|
const uncommonLabel = document.createElement("div");
|
||||||
|
uncommonLabel.className = "birb-field-guide-section-label";
|
||||||
|
uncommonLabel.textContent = `----- Uncommon ${birdBirb()}s -----`;
|
||||||
|
uncommonLabel.title = "Arbitrarily classified birds that are a little harder to find, but worth the wait!";
|
||||||
|
|
||||||
const description = makeElement("birb-field-guide-description");
|
const description = makeElement("birb-field-guide-description");
|
||||||
contentContainer.appendChild(content);
|
contentContainer.appendChild(familiarLabel);
|
||||||
|
contentContainer.appendChild(familiarBirds);
|
||||||
|
contentContainer.appendChild(uncommonLabel);
|
||||||
|
contentContainer.appendChild(uncommonBirds);
|
||||||
contentContainer.appendChild(description);
|
contentContainer.appendChild(description);
|
||||||
|
|
||||||
const fieldGuide = createWindow(
|
const fieldGuide = createWindow(
|
||||||
@@ -2868,14 +3224,26 @@
|
|||||||
const boldName = document.createElement("b");
|
const boldName = document.createElement("b");
|
||||||
boldName.textContent = type.name;
|
boldName.textContent = type.name;
|
||||||
|
|
||||||
const spacer = document.createElement("div");
|
|
||||||
spacer.style.height = "0.3em";
|
const spacerOne = document.createElement("div");
|
||||||
|
spacerOne.style.height = "0.3em";
|
||||||
|
|
||||||
|
const latinName = document.createElement("a");
|
||||||
|
latinName.className = "birb-field-guide-latin-name";
|
||||||
|
latinName.textContent = type.latinName;
|
||||||
|
latinName.href = type.url;
|
||||||
|
latinName.target = "_blank";
|
||||||
|
|
||||||
|
const spacerTwo = document.createElement("div");
|
||||||
|
spacerTwo.style.height = "0.4em";
|
||||||
|
|
||||||
const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description);
|
const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description);
|
||||||
|
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
fragment.appendChild(boldName);
|
fragment.appendChild(boldName);
|
||||||
fragment.appendChild(spacer);
|
fragment.appendChild(spacerOne);
|
||||||
|
fragment.appendChild(latinName);
|
||||||
|
fragment.appendChild(spacerTwo);
|
||||||
fragment.appendChild(descText);
|
fragment.appendChild(descText);
|
||||||
|
|
||||||
return fragment;
|
return fragment;
|
||||||
@@ -2897,7 +3265,11 @@
|
|||||||
}
|
}
|
||||||
birb.getFrames().base.draw(speciesCtx, Directions.RIGHT, CANVAS_PIXEL_SIZE, type.colors, type.tags);
|
birb.getFrames().base.draw(speciesCtx, Directions.RIGHT, CANVAS_PIXEL_SIZE, type.colors, type.tags);
|
||||||
speciesElement.appendChild(speciesCanvas);
|
speciesElement.appendChild(speciesCanvas);
|
||||||
content.appendChild(speciesElement);
|
let section = familiarBirds;
|
||||||
|
if (type.rarity === RARITY.UNCOMMON) {
|
||||||
|
section = uncommonBirds;
|
||||||
|
}
|
||||||
|
section.appendChild(speciesElement);
|
||||||
if (unlocked) {
|
if (unlocked) {
|
||||||
onClick(speciesElement, () => {
|
onClick(speciesElement, () => {
|
||||||
switchSpecies(id);
|
switchSpecies(id);
|
||||||
@@ -3079,26 +3451,6 @@
|
|||||||
return getWindowHeight() - focusedBounds.top;
|
return getWindowHeight() - focusedBounds.top;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fly to either an element or the ground
|
|
||||||
*/
|
|
||||||
function flySomewhere() {
|
|
||||||
// On mobile, always prefer to focus on an element
|
|
||||||
// If not mobile, 50% chance to focus on ground
|
|
||||||
// if ((!isMobile() && coinFlip()) || !focusOnElement()) {
|
|
||||||
// focusOnGround();
|
|
||||||
// }
|
|
||||||
if (!focusOnElement()) {
|
|
||||||
focusOnGround();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusOnGround() {
|
|
||||||
focusedElement = null;
|
|
||||||
updateFocusedElementBounds();
|
|
||||||
flyTo(Math.random() * window.innerWidth, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {HTMLElement|null} The random element, or null if no valid element was found
|
* @returns {HTMLElement|null} The random element, or null if no valid element was found
|
||||||
*/
|
*/
|
||||||
@@ -3130,20 +3482,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Focus on an element within the viewport
|
* Fly to an element within the viewport
|
||||||
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
||||||
* @returns Whether an element to focus on was found
|
* @returns Whether an element to fly to was found (null if flying to the ground)
|
||||||
*/
|
*/
|
||||||
function focusOnElement(teleport = false) {
|
function flyToElement(teleport = false) {
|
||||||
if (frozen) {
|
if (frozen) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const previousElement = focusedElement;
|
||||||
focusedElement = getRandomValidElement();
|
focusedElement = getRandomValidElement();
|
||||||
log("Focusing on element: ", focusedElement);
|
|
||||||
updateFocusedElementBounds();
|
updateFocusedElementBounds();
|
||||||
if (teleport) {
|
if (teleport) {
|
||||||
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
} else {
|
} else if (focusedElement !== previousElement) {
|
||||||
flyTo(getFocusedElementRandomX(), getFocusedY());
|
flyTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
}
|
}
|
||||||
return focusedElement !== null;
|
return focusedElement !== null;
|
||||||
|
|||||||
546
dist/web/birb.embed.js
vendored
@@ -237,6 +237,8 @@
|
|||||||
"bluebird": {
|
"bluebird": {
|
||||||
"name": "Eastern Bluebird",
|
"name": "Eastern Bluebird",
|
||||||
"description": "Native to North American and very social, though can be timid around people.",
|
"description": "Native to North American and very social, though can be timid around people.",
|
||||||
|
"latinName": "Sialia sialis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Eastern_bluebird",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#639bff",
|
"face": "#639bff",
|
||||||
@@ -249,6 +251,8 @@
|
|||||||
"shimaEnaga": {
|
"shimaEnaga": {
|
||||||
"name": "Shima Enaga",
|
"name": "Shima Enaga",
|
||||||
"description": "Small, fluffy birds found in the snowy regions of Japan, these birds are highly sought after by ornithologists and nature photographers.",
|
"description": "Small, fluffy birds found in the snowy regions of Japan, these birds are highly sought after by ornithologists and nature photographers.",
|
||||||
|
"latinName": "Aegithalos caudatus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Long-tailed_tit",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#ffffff",
|
"face": "#ffffff",
|
||||||
@@ -262,6 +266,8 @@
|
|||||||
"tuftedTitmouse": {
|
"tuftedTitmouse": {
|
||||||
"name": "Tufted Titmouse",
|
"name": "Tufted Titmouse",
|
||||||
"description": "Native to the eastern United States, full of personality, and notably my wife's favorite bird.",
|
"description": "Native to the eastern United States, full of personality, and notably my wife's favorite bird.",
|
||||||
|
"latinName": "Baeolophus bicolor",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Tufted_titmouse",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#c7cad7",
|
"face": "#c7cad7",
|
||||||
@@ -278,6 +284,8 @@
|
|||||||
"europeanRobin": {
|
"europeanRobin": {
|
||||||
"name": "European Robin",
|
"name": "European Robin",
|
||||||
"description": "Native to western Europe, this is the quintessential robin. Quite friendly, you'll often find them searching for worms.",
|
"description": "Native to western Europe, this is the quintessential robin. Quite friendly, you'll often find them searching for worms.",
|
||||||
|
"latinName": "Erithacus rubecula",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/European_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#ffaf34",
|
"face": "#ffaf34",
|
||||||
@@ -292,6 +300,8 @@
|
|||||||
"redCardinal": {
|
"redCardinal": {
|
||||||
"name": "Red Cardinal",
|
"name": "Red Cardinal",
|
||||||
"description": "Native to the eastern United States, this strikingly red bird is hard to miss.",
|
"description": "Native to the eastern United States, this strikingly red bird is hard to miss.",
|
||||||
|
"latinName": "Cardinalis cardinalis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_cardinal",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#d93619",
|
"beak": "#d93619",
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
@@ -311,6 +321,8 @@
|
|||||||
"americanGoldfinch": {
|
"americanGoldfinch": {
|
||||||
"name": "American Goldfinch",
|
"name": "American Goldfinch",
|
||||||
"description": "Coloured a brilliant yellow, this bird feeds almost entirely on the seeds of plants such as thistle, sunflowers, and coneflowers.",
|
"description": "Coloured a brilliant yellow, this bird feeds almost entirely on the seeds of plants such as thistle, sunflowers, and coneflowers.",
|
||||||
|
"latinName": "Spinus tristis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/American_goldfinch",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#ffaf34",
|
"beak": "#ffaf34",
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
@@ -327,6 +339,8 @@
|
|||||||
"barnSwallow": {
|
"barnSwallow": {
|
||||||
"name": "Barn Swallow",
|
"name": "Barn Swallow",
|
||||||
"description": "Agile birds that often roost in man-made structures, these birds are known to build nests near Ospreys for protection.",
|
"description": "Agile birds that often roost in man-made structures, these birds are known to build nests near Ospreys for protection.",
|
||||||
|
"latinName": "Hirundo rustica",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Barn_swallow",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#db7c4d",
|
"face": "#db7c4d",
|
||||||
@@ -340,6 +354,8 @@
|
|||||||
"mistletoebird": {
|
"mistletoebird": {
|
||||||
"name": "Mistletoebird",
|
"name": "Mistletoebird",
|
||||||
"description": "Native to Australia, these birds eat mainly mistletoe and in turn spread the seeds far and wide.",
|
"description": "Native to Australia, these birds eat mainly mistletoe and in turn spread the seeds far and wide.",
|
||||||
|
"latinName": "Dicaeum hirundinaceum",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Mistletoebird",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#6c6a7c",
|
"foot": "#6c6a7c",
|
||||||
"face": "#352e6d",
|
"face": "#352e6d",
|
||||||
@@ -349,22 +365,11 @@
|
|||||||
"wing-edge": "#282065"
|
"wing-edge": "#282065"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redAvadavat": {
|
|
||||||
"name": "Red Avadavat",
|
|
||||||
"description": "Native to India and southeast Asia, these birds are also known as Strawberry Finches due to their speckled plumage.",
|
|
||||||
"colors": {
|
|
||||||
"beak": "#f71919",
|
|
||||||
"foot": "#af7575",
|
|
||||||
"face": "#cb092b",
|
|
||||||
"belly": "#ae1724",
|
|
||||||
"underbelly": "#831b24",
|
|
||||||
"wing": "#7e3030",
|
|
||||||
"wing-edge": "#490f0f"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scarletRobin": {
|
"scarletRobin": {
|
||||||
"name": "Scarlet Robin",
|
"name": "Scarlet Robin",
|
||||||
"description": "Native to Australia, this striking robin can be found in Eucalyptus forests.",
|
"description": "Native to Australia, this striking robin can be found in Eucalyptus forests.",
|
||||||
|
"latinName": "Petroica boodang",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Scarlet_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#494949",
|
"foot": "#494949",
|
||||||
"face": "#3d3d3d",
|
"face": "#3d3d3d",
|
||||||
@@ -372,12 +377,15 @@
|
|||||||
"underbelly": "#dcdcdc",
|
"underbelly": "#dcdcdc",
|
||||||
"wing": "#2b2b2b",
|
"wing": "#2b2b2b",
|
||||||
"wing-edge": "#ebebeb",
|
"wing-edge": "#ebebeb",
|
||||||
|
"nose": "#ebebeb",
|
||||||
"theme-highlight": "#fc5633"
|
"theme-highlight": "#fc5633"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"americanRobin": {
|
"americanRobin": {
|
||||||
"name": "American Robin",
|
"name": "American Robin",
|
||||||
"description": "While not a true robin, this social North American bird is so named due to its orange coloring. It seems unbothered by nearby humans.",
|
"description": "While not a true robin, this social North American bird is so named due to its orange coloring. It seems unbothered by nearby humans.",
|
||||||
|
"latinName": "Turdus migratorius",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/American_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#e89f30",
|
"beak": "#e89f30",
|
||||||
"foot": "#9f8075",
|
"foot": "#9f8075",
|
||||||
@@ -392,6 +400,8 @@
|
|||||||
"carolinaWren": {
|
"carolinaWren": {
|
||||||
"name": "Carolina Wren",
|
"name": "Carolina Wren",
|
||||||
"description": "Native to the eastern United States, these little birds are known for their curious and energetic nature.",
|
"description": "Native to the eastern United States, these little birds are known for their curious and energetic nature.",
|
||||||
|
"latinName": "Thryothorus ludovicianus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Carolina_wren",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#edc7a9",
|
"face": "#edc7a9",
|
||||||
@@ -402,14 +412,249 @@
|
|||||||
"wing": "#c58a5b",
|
"wing": "#c58a5b",
|
||||||
"wing-edge": "#866348"
|
"wing-edge": "#866348"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"blackCappedChickadee": {
|
||||||
|
"name": "Black-capped Chickadee",
|
||||||
|
"description": "Native to North America, these small and curious birds are known for their distinctive call from which they get their name.",
|
||||||
|
"latinName": "Poecile atricapillus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Black-capped_chickadee",
|
||||||
|
"colors": {
|
||||||
|
"hood": "#363636",
|
||||||
|
"cheek": "#363636",
|
||||||
|
"eyebrow": "#363636",
|
||||||
|
"nose": "#363636",
|
||||||
|
"collar": "#363636",
|
||||||
|
"belly": "#d6d4cf",
|
||||||
|
"underbelly": "#cfc5b4",
|
||||||
|
"face": "#eaeaea",
|
||||||
|
"wing": "#8f8e9a",
|
||||||
|
"wing-edge": "#706f7d",
|
||||||
|
"scruff": "#8f8e9a",
|
||||||
|
"foot": "#535259"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"blueJay": {
|
||||||
|
"name": "Blue Jay",
|
||||||
|
"description": "This loud and rambunctious bird is native to North America and is known for challenging anything in its path.",
|
||||||
|
"latinName": "Cyanocitta cristata",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Blue_jay",
|
||||||
|
"colors": {
|
||||||
|
"foot": "#5a626b",
|
||||||
|
"face": "#ebf2ff",
|
||||||
|
"belly": "#e5ecfa",
|
||||||
|
"underbelly": "#c4cbd6",
|
||||||
|
"wing": "#5890ff",
|
||||||
|
"wing-edge": "#3a77e8",
|
||||||
|
"hood": "#6391e8",
|
||||||
|
"nose": "#6391e8",
|
||||||
|
"collar": "#2e3136",
|
||||||
|
"scruff": "#6391e8"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"tuft"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"darkEyedJunco": {
|
||||||
|
"name": "Dark-eyed Junco",
|
||||||
|
"description": "Native across North America, these social birds will often be seen hopping along the ground in winter.",
|
||||||
|
"latinName": "Junco hyemalis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Dark-eyed_junco",
|
||||||
|
"colors": {
|
||||||
|
"face": "#55565e",
|
||||||
|
"wing": "#5c5f69",
|
||||||
|
"wing-edge": "#444547",
|
||||||
|
"belly": "#6c7180",
|
||||||
|
"underbelly": "#b8bbcc",
|
||||||
|
"foot": "#87776d",
|
||||||
|
"beak": "#ab8a98"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"houseFinch": {
|
||||||
|
"name": "House Finch",
|
||||||
|
"description": "Native to North America, these highly social birds sing cheerful songs and are often seen at bird feeders.",
|
||||||
|
"latinName": "Haemorhous mexicanus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/House_finch",
|
||||||
|
"colors": {
|
||||||
|
"face": "#cc3a3f",
|
||||||
|
"wing": "#ae8e78",
|
||||||
|
"wing-edge": "#8f6c54",
|
||||||
|
"belly": "#d97c77",
|
||||||
|
"underbelly": "#c5a489",
|
||||||
|
"foot": "#705b4c",
|
||||||
|
"beak": "#cf8479",
|
||||||
|
"hood": "#b02f35",
|
||||||
|
"nose": "#ab2b31",
|
||||||
|
"theme-highlight": "#ef444d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pigeon": {
|
||||||
|
"name": "Rock Pigeon",
|
||||||
|
"description": "Descended from the Rock Dove, these once domesticated birds are often found in cities worldwide. Quite friendly and intelligent, they were favored companions of Nikola Tesla.",
|
||||||
|
"latinName": "Columba livia",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Rock_dove",
|
||||||
|
"colors": {
|
||||||
|
"foot": "#ef6e5b",
|
||||||
|
"face": "#5a6c91",
|
||||||
|
"wing-edge": "#65686e",
|
||||||
|
"nose": "#ebebeb",
|
||||||
|
"belly": "#977699",
|
||||||
|
"underbelly": "#b0b3ba",
|
||||||
|
"wing": "#c7cbd4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redAvadavat": {
|
||||||
|
"name": "Red Avadavat",
|
||||||
|
"description": "Native to India and southeast Asia, these birds are also known as Strawberry Finches due to their speckled plumage.",
|
||||||
|
"latinName": "Amandava amandava",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_avadavat",
|
||||||
|
"colors": {
|
||||||
|
"beak": "#f71919",
|
||||||
|
"foot": "#af7575",
|
||||||
|
"face": "#cb092b",
|
||||||
|
"belly": "#ae1724",
|
||||||
|
"underbelly": "#831b24",
|
||||||
|
"wing": "#7e3030",
|
||||||
|
"wing-edge": "#490f0f",
|
||||||
|
"wing-spots": "#e8e4e4",
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"pinkRobin": {
|
||||||
|
"name": "Pink Robin",
|
||||||
|
"description": "Native to Australia, these bubblegum-pink puffballs are quieter than most, instead relying on their vibrant colours to attract partners.",
|
||||||
|
"latinName": "Petroica rodinogaster",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Pink_robin",
|
||||||
|
"colors": {
|
||||||
|
"face": "#403a46",
|
||||||
|
"wing": "#38333d",
|
||||||
|
"wing-edge": "#252325",
|
||||||
|
"underbelly": "#ff7eb8",
|
||||||
|
"belly": "#ff6eaf",
|
||||||
|
"foot": "#3c393c",
|
||||||
|
"theme-highlight": "#ff82ba"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"spangledCotinga": {
|
||||||
|
"name": "Spangled Cotinga",
|
||||||
|
"description": "This South American bird can be found in the Amazon rainforest, flashing its iridescent turquoise feathers high above in the canopy.",
|
||||||
|
"latinName": "Cotinga cayana",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Spangled_cotinga",
|
||||||
|
"colors": {
|
||||||
|
"face": "#62eafe",
|
||||||
|
"chin": "#a12457",
|
||||||
|
"collar": "#a12457",
|
||||||
|
"belly": "#62eafe",
|
||||||
|
"underbelly": "#5cd8ea",
|
||||||
|
"wing": "#227c89",
|
||||||
|
"wing-edge": "#13353a",
|
||||||
|
"foot": "#68696b",
|
||||||
|
"collar-scruff": "#62eafe"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"elegantEuphonia": {
|
||||||
|
"name": "Elegant Euphonia",
|
||||||
|
"description": "This vividly coloured finch is found throughout Central America and is known for the distinctive blue hood that crowns its head.",
|
||||||
|
"latinName": "Chlorophonia elegantissima",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Elegant_euphonia",
|
||||||
|
"colors": {
|
||||||
|
"wing": "#2d31a1",
|
||||||
|
"wing-edge": "#191c6d",
|
||||||
|
"face": "#1f2392",
|
||||||
|
"hood": "#6bc6ed",
|
||||||
|
"nose-tip": "#fd7e1d",
|
||||||
|
"foot": "#555650",
|
||||||
|
"belly": "#ff952b",
|
||||||
|
"underbelly": "#fd7e1d",
|
||||||
|
"temple": "#57c8fa",
|
||||||
|
"upper-corner-eye": "#57c8fa",
|
||||||
|
"upper-eyelid": "#57c8fa",
|
||||||
|
"collar-scruff": "#57c8fa",
|
||||||
|
"scruff": "#57c8fa",
|
||||||
|
"beak": "#252c31",
|
||||||
|
"collar": "#191c6d"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"paintedBunting": {
|
||||||
|
"name": "Painted Bunting",
|
||||||
|
"description": "A remarkably colourful bird, this North American species is quite difficult to observe despite its vivid palette due to its shy nature and vulnerable habitat.",
|
||||||
|
"latinName": "Passerina ciris",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Painted_bunting",
|
||||||
|
"colors": {
|
||||||
|
"face": "#5567f0",
|
||||||
|
"underbelly": "#f16534",
|
||||||
|
"belly": "#ef3b3b",
|
||||||
|
"wing": "#a3e65a",
|
||||||
|
"wing-edge": "#91cc50",
|
||||||
|
"shoulder": "#f6fe40",
|
||||||
|
"foot": "#767980"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"redWarbler": {
|
||||||
|
"name": "Red Warbler",
|
||||||
|
"description": "Endemic to the highlands of Mexico, this bird has the rare distinction of being one of the very few toxic birds in the world.",
|
||||||
|
"latinName": "Cardellina rubra",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_warbler",
|
||||||
|
"colors": {
|
||||||
|
"face": "#e80a28",
|
||||||
|
"belly": "#d90921",
|
||||||
|
"underbelly": "#c70c18",
|
||||||
|
"wing": "#ba121d",
|
||||||
|
"wing-edge": "#5b3535",
|
||||||
|
"foot": "#5e4645",
|
||||||
|
"behind-eye": "#deedff",
|
||||||
|
"temple": "#e8f0fa",
|
||||||
|
"corner-eye": "#d5e4f5",
|
||||||
|
"lower-eyelid": "#e34a61",
|
||||||
|
"beak": "#873535",
|
||||||
|
"cheek": "#db1734"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"cubanTody": {
|
||||||
|
"name": "Cuban Tody",
|
||||||
|
"description": "As the name suggests, this little green bird is only found on the island of Cuba and is known for being particularly round.",
|
||||||
|
"latinName": "Todus multicolor",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Cuban_tody",
|
||||||
|
"colors": {
|
||||||
|
"beak": "#f16f54",
|
||||||
|
"face": "#5fdf44",
|
||||||
|
"chin": "#f12d3e",
|
||||||
|
"collar": "#f12d3e",
|
||||||
|
"belly": "#f6f5e4",
|
||||||
|
"collar-scruff": "#a3ebff",
|
||||||
|
"underbelly": "#eae9d2",
|
||||||
|
"wing": "#11c751",
|
||||||
|
"wing-edge": "#156631",
|
||||||
|
"foot": "#ac7055",
|
||||||
|
"scruff": "#11c751",
|
||||||
|
"theme-highlight": "#4adc67"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"violetBackedStarling": {
|
||||||
|
"name": "Violet-backed Starling",
|
||||||
|
"description": "Native to Sub-Saharan Africa, these small starlings are known for being the most vividly purple birds in the world.",
|
||||||
|
"latinName": "Cinnyricinclus leucogaster",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Violet-backed_starling",
|
||||||
|
"colors": {
|
||||||
|
"face": "#9c3af2",
|
||||||
|
"wing": "#8f37ed",
|
||||||
|
"wing-edge": "#7029b8",
|
||||||
|
"belly": "#ffffff",
|
||||||
|
"underbelly": "#f2f2f2",
|
||||||
|
"foot": "#736a66",
|
||||||
|
"collar": "#aa60e6"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const PALETTE = Object.freeze(/** @type {const} */ ({
|
||||||
* Palette color names
|
|
||||||
* @type {Record<string, string>}
|
|
||||||
*/
|
|
||||||
const PALETTE = {
|
|
||||||
THEME_HIGHLIGHT: "theme-highlight",
|
THEME_HIGHLIGHT: "theme-highlight",
|
||||||
TRANSPARENT: "transparent",
|
TRANSPARENT: "transparent",
|
||||||
OUTLINE: "outline",
|
OUTLINE: "outline",
|
||||||
@@ -420,23 +665,36 @@
|
|||||||
FACE: "face",
|
FACE: "face",
|
||||||
HOOD: "hood",
|
HOOD: "hood",
|
||||||
EYEBROW: "eyebrow",
|
EYEBROW: "eyebrow",
|
||||||
|
UPPER_EYELID: "upper-eyelid",
|
||||||
|
UPPER_CORNER_EYE: "upper-corner-eye",
|
||||||
|
BEHIND_EYE: "behind-eye",
|
||||||
|
CORNER_EYE: "corner-eye",
|
||||||
|
TEMPLE: "temple",
|
||||||
|
LOWER_EYELID: "lower-eyelid",
|
||||||
NOSE: "nose",
|
NOSE: "nose",
|
||||||
|
NOSE_TIP: "nose-tip",
|
||||||
CHEEK: "cheek",
|
CHEEK: "cheek",
|
||||||
SCRUFF: "scruff",
|
SCRUFF: "scruff",
|
||||||
|
CHIN: "chin",
|
||||||
COLLAR: "collar",
|
COLLAR: "collar",
|
||||||
|
COLLAR_SCRUFF: "collar-scruff",
|
||||||
BELLY: "belly",
|
BELLY: "belly",
|
||||||
UNDERBELLY: "underbelly",
|
UNDERBELLY: "underbelly",
|
||||||
WING: "wing",
|
WING: "wing",
|
||||||
|
SHOULDER: "shoulder",
|
||||||
|
WING_SPOTS: "wing-spots",
|
||||||
WING_EDGE: "wing-edge",
|
WING_EDGE: "wing-edge",
|
||||||
HEART: "heart",
|
HEART: "heart",
|
||||||
HEART_BORDER: "heart-border",
|
HEART_BORDER: "heart-border",
|
||||||
HEART_SHINE: "heart-shine",
|
HEART_SHINE: "heart-shine",
|
||||||
FEATHER_SPINE: "feather-spine",
|
FEATHER_SPINE: "feather-spine",
|
||||||
};
|
}));
|
||||||
|
|
||||||
|
/** @typedef {typeof PALETTE[keyof typeof PALETTE]} PaletteColor */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping of sprite sheet colors to palette colors
|
* Mapping of sprite sheet colors to palette colors
|
||||||
* @type {Record<string, string>}
|
* @type {Record<string, PaletteColor>}
|
||||||
*/
|
*/
|
||||||
const SPRITE_SHEET_COLOR_MAP = {
|
const SPRITE_SHEET_COLOR_MAP = {
|
||||||
"transparent": PALETTE.TRANSPARENT,
|
"transparent": PALETTE.TRANSPARENT,
|
||||||
@@ -449,13 +707,24 @@
|
|||||||
"#639bff": PALETTE.FACE,
|
"#639bff": PALETTE.FACE,
|
||||||
"#99e550": PALETTE.HOOD,
|
"#99e550": PALETTE.HOOD,
|
||||||
"#ff5573": PALETTE.EYEBROW,
|
"#ff5573": PALETTE.EYEBROW,
|
||||||
|
"#ff768e": PALETTE.UPPER_EYELID,
|
||||||
|
"#ff90a4": PALETTE.UPPER_CORNER_EYE,
|
||||||
|
"#ff2c88": PALETTE.BEHIND_EYE,
|
||||||
|
"#e34f9c": PALETTE.CORNER_EYE,
|
||||||
|
"#b53477": PALETTE.TEMPLE,
|
||||||
|
"#ae65f1": PALETTE.LOWER_EYELID,
|
||||||
"#d95763": PALETTE.NOSE,
|
"#d95763": PALETTE.NOSE,
|
||||||
|
"#b93844": PALETTE.NOSE_TIP,
|
||||||
"#ff67a9": PALETTE.CHEEK,
|
"#ff67a9": PALETTE.CHEEK,
|
||||||
"#c5e550": PALETTE.SCRUFF,
|
"#c5e550": PALETTE.SCRUFF,
|
||||||
|
"#b87af1": PALETTE.CHIN,
|
||||||
"#ffe955": PALETTE.COLLAR,
|
"#ffe955": PALETTE.COLLAR,
|
||||||
|
"#f8ff55": PALETTE.COLLAR_SCRUFF,
|
||||||
"#f8b143": PALETTE.BELLY,
|
"#f8b143": PALETTE.BELLY,
|
||||||
"#ec8637": PALETTE.UNDERBELLY,
|
"#ec8637": PALETTE.UNDERBELLY,
|
||||||
"#578ae6": PALETTE.WING,
|
"#578ae6": PALETTE.WING,
|
||||||
|
"#55d1f3": PALETTE.SHOULDER,
|
||||||
|
"#90b0e8": PALETTE.WING_SPOTS,
|
||||||
"#326ed9": PALETTE.WING_EDGE,
|
"#326ed9": PALETTE.WING_EDGE,
|
||||||
"#c82e2e": PALETTE.HEART,
|
"#c82e2e": PALETTE.HEART,
|
||||||
"#501a1a": PALETTE.HEART_BORDER,
|
"#501a1a": PALETTE.HEART_BORDER,
|
||||||
@@ -463,16 +732,52 @@
|
|||||||
"#373737": PALETTE.FEATHER_SPINE,
|
"#373737": PALETTE.FEATHER_SPINE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Partial<Record<PaletteColor, PaletteColor>>}
|
||||||
|
*/
|
||||||
|
({
|
||||||
|
[PALETTE.HOOD]: PALETTE.FACE,
|
||||||
|
[PALETTE.EYEBROW]: PALETTE.FACE,
|
||||||
|
[PALETTE.UPPER_EYELID]: PALETTE.EYEBROW,
|
||||||
|
[PALETTE.UPPER_CORNER_EYE]: PALETTE.EYEBROW,
|
||||||
|
[PALETTE.BEHIND_EYE]: PALETTE.FACE,
|
||||||
|
[PALETTE.CORNER_EYE]: PALETTE.FACE,
|
||||||
|
[PALETTE.TEMPLE]: PALETTE.FACE,
|
||||||
|
[PALETTE.LOWER_EYELID]: PALETTE.FACE,
|
||||||
|
[PALETTE.NOSE]: PALETTE.FACE,
|
||||||
|
[PALETTE.NOSE_TIP]: PALETTE.NOSE,
|
||||||
|
[PALETTE.CHEEK]: PALETTE.FACE,
|
||||||
|
[PALETTE.SCRUFF]: PALETTE.FACE,
|
||||||
|
[PALETTE.CHIN]: PALETTE.FACE,
|
||||||
|
[PALETTE.COLLAR]: PALETTE.FACE,
|
||||||
|
[PALETTE.COLLAR_SCRUFF]: PALETTE.COLLAR,
|
||||||
|
[PALETTE.WING_SPOTS]: PALETTE.WING,
|
||||||
|
[PALETTE.SHOULDER]: PALETTE.WING,
|
||||||
|
});
|
||||||
|
|
||||||
|
const RARITY = Object.freeze(/** @type {const} */ ({
|
||||||
|
COMMON: "common",
|
||||||
|
UNCOMMON: "uncommon"
|
||||||
|
}));
|
||||||
|
|
||||||
|
/** @typedef {typeof RARITY[keyof typeof RARITY]} Rarity */
|
||||||
|
|
||||||
class BirdType {
|
class BirdType {
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {string} description
|
* @param {string} description
|
||||||
|
* @param {string} latinName
|
||||||
|
* @param {string} url
|
||||||
* @param {Record<string, string>} colors
|
* @param {Record<string, string>} colors
|
||||||
* @param {string[]} [tags]
|
* @param {string[]} [tags]
|
||||||
|
* @param {Rarity} [rarity]
|
||||||
*/
|
*/
|
||||||
constructor(name, description, colors, tags = []) {
|
constructor(name, description, latinName, url, colors, tags = [], rarity = RARITY.COMMON) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
this.latinName = latinName;
|
||||||
|
this.url = url;
|
||||||
const defaultColors = {
|
const defaultColors = {
|
||||||
[PALETTE.TRANSPARENT]: "transparent",
|
[PALETTE.TRANSPARENT]: "transparent",
|
||||||
[PALETTE.OUTLINE]: "#000000",
|
[PALETTE.OUTLINE]: "#000000",
|
||||||
@@ -484,15 +789,27 @@
|
|||||||
[PALETTE.HEART_SHINE]: "#ff6b6b",
|
[PALETTE.HEART_SHINE]: "#ff6b6b",
|
||||||
[PALETTE.FEATHER_SPINE]: "#373737",
|
[PALETTE.FEATHER_SPINE]: "#373737",
|
||||||
[PALETTE.HOOD]: colors.face,
|
[PALETTE.HOOD]: colors.face,
|
||||||
[PALETTE.EYEBROW]: colors.face,
|
[PALETTE.EYEBROW]: colors.face,
|
||||||
|
[PALETTE.UPPER_EYELID]: colors.eyebrow || colors.face,
|
||||||
|
[PALETTE.UPPER_CORNER_EYE]: colors.eyebrow || colors.face,
|
||||||
|
[PALETTE.BEHIND_EYE]: colors.face,
|
||||||
|
[PALETTE.CORNER_EYE]: colors.face,
|
||||||
|
[PALETTE.TEMPLE]: colors.face,
|
||||||
|
[PALETTE.LOWER_EYELID]: colors.face,
|
||||||
[PALETTE.NOSE]: colors.face,
|
[PALETTE.NOSE]: colors.face,
|
||||||
|
[PALETTE.NOSE_TIP]: colors.nose || colors.face,
|
||||||
[PALETTE.CHEEK]: colors.face,
|
[PALETTE.CHEEK]: colors.face,
|
||||||
[PALETTE.SCRUFF]: colors.face,
|
[PALETTE.SCRUFF]: colors.face,
|
||||||
|
[PALETTE.CHIN]: colors.face,
|
||||||
[PALETTE.COLLAR]: colors.face,
|
[PALETTE.COLLAR]: colors.face,
|
||||||
|
[PALETTE.COLLAR_SCRUFF]: colors.collar || colors.face,
|
||||||
|
[PALETTE.SHOULDER]: colors.wing,
|
||||||
};
|
};
|
||||||
/** @type {Record<string, string>} */
|
/** @type {Record<string, string>} */
|
||||||
this.colors = { ...defaultColors, ...colors, [PALETTE.THEME_HIGHLIGHT]: colors[PALETTE.THEME_HIGHLIGHT] ?? colors.hood ?? colors.face };
|
this.colors = { ...defaultColors, ...colors, [PALETTE.THEME_HIGHLIGHT]: colors[PALETTE.THEME_HIGHLIGHT] ?? colors.hood ?? colors.face };
|
||||||
this.tags = tags;
|
this.tags = tags;
|
||||||
|
/** @type {Rarity} */
|
||||||
|
this.rarity = rarity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,7 +873,7 @@
|
|||||||
const SPECIES = Object.fromEntries(
|
const SPECIES = Object.fromEntries(
|
||||||
Object.entries(species).map(([id, data]) => [
|
Object.entries(species).map(([id, data]) => [
|
||||||
id,
|
id,
|
||||||
new BirdType(data.name, data.description, data.colors, data.tags ?? []),
|
new BirdType(data.name, data.description, data.latinName, data.url, data.colors, data.tags, data.rarity)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1253,37 +1570,42 @@
|
|||||||
audioContext;
|
audioContext;
|
||||||
|
|
||||||
chirp() {
|
chirp() {
|
||||||
if (!this.audioContext) {
|
const count = Math.floor(1 + Math.random() * 1.5);
|
||||||
this.audioContext = new AudioContext();
|
for (let i = 0; i < count; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.audioContext) {
|
||||||
|
this.audioContext = new AudioContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TIMES = [0, 0.06, 0.10, 0.15];
|
||||||
|
const FREQUENCIES = [2200,
|
||||||
|
3500 + Math.random() * 600 * count,
|
||||||
|
2100 + Math.random() * 200 * count,
|
||||||
|
1600 + Math.random() * 400 * count];
|
||||||
|
const VOLUMES = [0.00005, 0.165, 0.165, 0.0001];
|
||||||
|
|
||||||
|
const oscillator = this.audioContext.createOscillator();
|
||||||
|
oscillator.type = "sine";
|
||||||
|
const gain = this.audioContext.createGain();
|
||||||
|
oscillator.connect(gain);
|
||||||
|
gain.connect(this.audioContext.destination);
|
||||||
|
|
||||||
|
const now = this.audioContext.currentTime;
|
||||||
|
for (let i = 0; i < TIMES.length; i++) {
|
||||||
|
const time = TIMES[i] + now;
|
||||||
|
if (i === 0) {
|
||||||
|
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
|
||||||
|
gain.gain.setValueAtTime(VOLUMES[i], time);
|
||||||
|
} else {
|
||||||
|
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oscillator.start(now);
|
||||||
|
oscillator.stop(now + TIMES[TIMES.length - 1]);
|
||||||
|
}, i * 120);
|
||||||
}
|
}
|
||||||
|
|
||||||
const TIMES = [0, 0.06, 0.10, 0.15];
|
|
||||||
const FREQUENCIES = [2200,
|
|
||||||
3500 + Math.random() * 600,
|
|
||||||
2100 + Math.random() * 200,
|
|
||||||
1600 + Math.random() * 400];
|
|
||||||
const VOLUMES = [0.0001, 0.2, 0.2, 0.0001];
|
|
||||||
|
|
||||||
const oscillator = this.audioContext.createOscillator();
|
|
||||||
oscillator.type = "sine";
|
|
||||||
const gain = this.audioContext.createGain();
|
|
||||||
oscillator.connect(gain);
|
|
||||||
gain.connect(this.audioContext.destination);
|
|
||||||
|
|
||||||
const now = this.audioContext.currentTime;
|
|
||||||
for (let i = 0; i < TIMES.length; i++) {
|
|
||||||
const time = TIMES[i] + now;
|
|
||||||
if (i === 0) {
|
|
||||||
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
|
|
||||||
gain.gain.setValueAtTime(VOLUMES[i], time);
|
|
||||||
} else {
|
|
||||||
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
|
|
||||||
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
oscillator.start(now);
|
|
||||||
oscillator.stop(now + TIMES[TIMES.length - 1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2022,7 +2344,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#birb-field-guide .birb-grid-content {
|
#birb-field-guide .birb-grid-content {
|
||||||
grid-template-rows: repeat(3, auto);
|
grid-template-columns: repeat(4, auto);
|
||||||
}
|
}
|
||||||
|
|
||||||
#birb-wardrobe .birb-grid-content {
|
#birb-wardrobe .birb-grid-content {
|
||||||
@@ -2032,7 +2354,7 @@
|
|||||||
|
|
||||||
.birb-grid-content {
|
.birb-grid-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: row;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
@@ -2064,7 +2386,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.birb-grid-item, .birb-field-guide-description, .birb-message-content {
|
.birb-grid-item, .birb-field-guide-description, .birb-message-content {
|
||||||
border: var(--birb-border-size) solid rgb(255, 207, 144);
|
border: var(--birb-border-size) solid #ffcf90;
|
||||||
box-shadow: 0 0 0 var(--birb-border-size) white;
|
box-shadow: 0 0 0 var(--birb-border-size) white;
|
||||||
background: rgba(255, 221, 177, 0.5);
|
background: rgba(255, 221, 177, 0.5);
|
||||||
}
|
}
|
||||||
@@ -2083,6 +2405,15 @@
|
|||||||
background: var(--birb-mix-color);
|
background: var(--birb-mix-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.birb-field-guide-section-label {
|
||||||
|
padding-top: 4px;
|
||||||
|
/* padding-left: calc(10px + var(--birb-border-size) / 2); */
|
||||||
|
color: #876c4e;
|
||||||
|
text-align: center;
|
||||||
|
/* Italics */
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.birb-field-guide-description {
|
.birb-field-guide-description {
|
||||||
max-width: calc(100% - 20px);
|
max-width: calc(100% - 20px);
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
@@ -2094,7 +2425,14 @@
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: rgb(124, 108, 75);
|
color: #7c6c4b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.birb-field-guide-latin-name {
|
||||||
|
text-decoration: underline;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: bold;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
#birb-feather {
|
#birb-feather {
|
||||||
@@ -2107,7 +2445,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: rgb(124, 108, 75);
|
color: #7c6c4b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.birb-sticky-note {
|
.birb-sticky-note {
|
||||||
@@ -2156,7 +2494,7 @@
|
|||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}`;
|
}`;
|
||||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABFFJREFUeJztnb9rE2EYx79vK1gUQSRLL64apxZcdC84dNBk0LiIoIJCwUGwlP4BUjqIFpSKLioOdYlV6ObUpV26ORRX0yoEHezQgvZxSN7rm+v9SGzu3vea7wdC31wued7k3ueT5727XAFCCCGEENJfKNsdIO4jIhL1mFKKY4jkFg7eHGBTQDp2pVgEANTq9bZ2Fn0ghPQp0qLseVL2vH3tODn2Kn7Z82RlZERkYmJfO+34hKTJgO0OkGQqxSKmCgXUKpV97SxZXV7GVKHgtwnJOxRgjrAloFq9jplGo23ZTKPhT4EJISQVzCnwysiIf8tqChzWhyxjE0IcQELIOrZNAZl9oPwI6TNEpLnz3/ibdXzbArIhf0LS5IjtDuSN1dFRK3GVUkpExOapJzzdhZA+RVc+uvpjFURI/uE3eheY0mM1lD3BLx1uA3JQOIBILtDy084TkcwFSAH3KWFHQDkFJBaQF/WyALB2AKgV2+nxzzztnMQToc1vXvOGhN+o9hIKmADAi3pZN60cAFJK+X1wrfoL5oWNPM0jsUeBzQ/TGHzQy0TEXCeVARGc+hjLISLi2kAk6XG3+MHqtrYp4E4wU6E6u+Evo/+iiRSgKZ716m3gAXB29+TeCvVy2/6YNGTkgoAJ0dgWcAyilPKld9gZHlSy+Vd6si1iK8CB4x7UsWGc+7QEANi98QTeq+sYn98FloDq7CaQkoxcEDAhOUCixPf12+/MO5M2w4NKxud3sXRvoCcSjHwBU0C3X+4voW+NVzD37rl/f2HS08+LDtaFoEREBo57bcvaBAxg69emH9uMSxGSPiFWfmtzJbg6XXeF2A+nVVVBflzFnY/vAQCvLl8DAFx//TT0OVqEIa/VDNihnGwLmBCXqc5uxO7YS1uAUfFbeZibPOtIgNuLYzh68RQAYGflJwDg5vrbjgLoMnxtrtT1uVs2BUyIy0QJyMy3lEUUWX3mSYId/xZYi0+zMOnh/P11nDl9InT9wIY40JGonZWfvviC/QgS3Ci6HzwaRg4Tr0s32oqQYL5lIaCv335H5n9e+O+LIWwvjmHoSgm4vx76eJj4DlJ92RQwIS7y7E8FE0dqQMbiS6I6u4GFSU9c6EsSiVNgtKqn7cWxtse2vmwBAArTq1HP3Qvyn+Izp+BhDF35jPMZCZgQ12g8uuAP7kvf3/jL1+ZKWY1ziSpCzAOTLuddYsfCJKTlF0ZherVnb9y2gAlxHVOCaOZD1mNddBESFKGWoMv515EA0ZJQ49GF2HV7KT8zvi0BE0I6QgAgKMI8CDBxH6C+EGfrjURKME3x6NhofuNFrkf5EWIFhebUu5l8Ebulco15NYza44f+VTF0O80LFAR+6B0aP+0+EEKSCV4xx/V87OrfYiqlUHv80L9vtpFi1aVfNy4+Kz9C3KD101QgB/nY1Wkw5lQ02M6CpPiuf9iEHHbyloNddTaunM3ijduOTwg5XPwDDgmrxnQErq8AAAAASUVORK5CYII=";
|
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABORJREFUeJztnU9IHFccx79vE6g0BEK7BJzNwR6apReFQElKjpZaeol7EHuRQhFaECIEIkF6LGkQGqyQ0EBa+u8iHjZCCfXQUw7qRVDwYKQEirtt0yVtMRaFdn857L7x7Tizf3Rn3oz7/cDi8+3s/t44733m9+afACGEEEII6SyU7QaQ+CMiEvSeUop9iCQWdt4EYFNAOnYukwEA5AuFmnIUbSCEdChSZdBxZNBxDpTrybFd8QcdR5Z6e0XGxg6Uw45PSJikbDeANCaXyeBGOo18LnegHCXLjx7hRjrtlglJOhRggrAloHyhgFulUk3drVLJnQITQkgomFPgpd5e9xXVFNivDVHGJoTEAPEh6tg2BWS2gfIjpMMQkcrBf+Nn1PFtC8iG/AkJk5O2G5A0lvv6rMRVSikREZuXnvByF0I6FJ356OyPWRAhyYd79BYwpcdsKHq8Ox1uA3JU2IFIItDy084TkcgFSAF3KH5nQDkFJBaQe4VBAWDtBFA1dqz7P8dp8zS8ENrc85ovNLhHtZ1QwAQA7hUGddHKCSCllNuGuGV/3nFhY5wmkbpngc0/ptH5oOtEBCIiYXYG79THqA89NokXH2UeWN3WNgXcDOZQGJ4qunX0XzCBAjTFs3DpbfRcexXny2eA1zKAcxYo1EoQIewV4yBgQjS2BVwHUUq50jvudJ9Q8tv/0pZtUTcDTJ1yoF7uxrtr68DaOsoj03A+fx/FNz7DuR+yGFrdrjko3U4RxkHAhCQACRLf5tZ25I0Jm+4TSt77soyHH6faIsHALzAF9NPkP279v38Cbz6dw693f8R06Xu3fq7vtP5ccLAWBCUikjrl1NSVR6bh3K8I+GHXX/jmq09841KEpEOoK7+VmSziOl2PC4EZoL7zAAAGxkex8MV9AEDu01HsnQWure3L7/KTHaSMYw5+HGa6Wt4p1gh4AcCdD//G1tM5nLm7BJSAodXtmrhmRui3Ts3GJiTODE8VrR7YG1rdduOnFvYzzdkJB0mSbt2GVoWF3fl+vHTpFQDA3tIzAMBIzwNcfrKDxcflA59763zKrddp+MpMtuVrt3R8+WPIFfDAeEXAI2tfu8uZ7ahuAL/vqqwwJUiOAUECNMdbyCIKzD6TJMGm7wXW4tPM9Z3GL1c38Pq5yhTUlN7i47J3QxzpTNTe0jMMjI/WtqNnX3yLxrLDU0VfAfNsGDlOfJsdwQcb+7Mw73iLQkCbW9vu+E8qh34Ywu58P7quZIGrGwCAza3a9/3Ed5Tsy6aACYkjd/7LYexkHohYfI0YnipidsKROLSlEQ2nwKhmT7vz/TXvPV9/DgBITy4HfXY/yCHFZ07B/ei68jMuVAXspd0CJiRulG5edDv3O79/59avzGSj6udywUhCTGYnnEQcdmrYMD8Jafn5kZ5cbtuK2xYwIXHHlCAq4yHqvi46CfGKUEswzuOvKQGiKqHSzYt1l22n/Mz4tgRMCGkKAQCvCJMgwIbHAPXlMNUVCZRgmOLRsVHZ4wUuR/kRYgWFytS7MvgCDkslGvNpGPnb192nYuhymA8o8Nzo7Rs/7DYQQhrjfWJO3MdjS/8WUymF/O3r7u9mGSFmXfp768Vn5kdIPKjejAAkYDy2dBmMORX1lqOgUfy4/7EJOe4kbQy21Nh66WwUK247PiHkePECZQPi+PbreqwAAAAASUVORK5CYII=";
|
||||||
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
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==";
|
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==";
|
||||||
|
|
||||||
@@ -2186,6 +2524,7 @@
|
|||||||
const HOP_CHANCE = 1 / (60 * 2.5); // Every 2.5 seconds
|
const HOP_CHANCE = 1 / (60 * 2.5); // Every 2.5 seconds
|
||||||
const FOCUS_SWITCH_CHANCE = 1 / (60 * 20); // Every 20 seconds
|
const FOCUS_SWITCH_CHANCE = 1 / (60 * 20); // Every 20 seconds
|
||||||
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // Every 2 hours
|
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // Every 2 hours
|
||||||
|
const UNCOMMON_FEATHER_CHANCE = 0.15; // 15% of feathers are uncommon
|
||||||
const HAT_CHANCE = 1 / (60 * 60 * 25); // Every 25 minutes
|
const HAT_CHANCE = 1 / (60 * 60 * 25); // Every 25 minutes
|
||||||
|
|
||||||
// Feathers
|
// Feathers
|
||||||
@@ -2292,7 +2631,7 @@
|
|||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
||||||
new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false),
|
new MenuItem("2026.3.29", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.29"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @type {Birb} */
|
/** @type {Birb} */
|
||||||
@@ -2482,7 +2821,9 @@
|
|||||||
|
|
||||||
setInterval(update, UPDATE_INTERVAL);
|
setInterval(update, UPDATE_INTERVAL);
|
||||||
|
|
||||||
focusOnElement(true);
|
flyToElement(true);
|
||||||
|
// TODO: Remove
|
||||||
|
insertFieldGuide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
@@ -2501,11 +2842,11 @@
|
|||||||
// Idle for a while, do something
|
// Idle for a while, do something
|
||||||
if (focusedElement === null) {
|
if (focusedElement === null) {
|
||||||
// Fly to an element
|
// Fly to an element
|
||||||
focusOnElement();
|
flyToElement();
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
} else if (Math.random() < FOCUS_SWITCH_CHANCE) {
|
} else if (Math.random() < FOCUS_SWITCH_CHANCE) {
|
||||||
// Fly to another element if idle for a longer while
|
// Fly to another element if idle for a longer while
|
||||||
focusOnElement();
|
flyToElement();
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2541,7 +2882,7 @@
|
|||||||
// Update the bird's position
|
// Update the bird's position
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE) {
|
||||||
if (focusedElement && !isWithinHorizontalBounds()) {
|
if (focusedElement && !isWithinHorizontalBounds()) {
|
||||||
flySomewhere();
|
flyToElement();
|
||||||
}
|
}
|
||||||
birdY = getFocusedY();
|
birdY = getFocusedY();
|
||||||
} else if (currentState === States.FLYING) {
|
} else if (currentState === States.FLYING) {
|
||||||
@@ -2557,7 +2898,7 @@
|
|||||||
startY += targetY - oldTargetY;
|
startY += targetY - oldTargetY;
|
||||||
if (targetY < 0 || targetY > getWindowHeight()) {
|
if (targetY < 0 || targetY > getWindowHeight()) {
|
||||||
// Fly to another element or the ground if the focused element moves out of bounds
|
// Fly to another element or the ground if the focused element moves out of bounds
|
||||||
flySomewhere();
|
flyToElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (birb.draw(SPECIES[currentSpecies], currentHat)) {
|
if (birb.draw(SPECIES[currentSpecies], currentHat)) {
|
||||||
@@ -2635,7 +2976,8 @@
|
|||||||
if (document.querySelector("#" + FEATHER_ID)) {
|
if (document.querySelector("#" + FEATHER_ID)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const speciesToUnlock = Object.keys(SPECIES).filter((species) => !unlockedSpecies.includes(species));
|
const rarity = Math.random() < UNCOMMON_FEATHER_CHANCE ? RARITY.UNCOMMON : RARITY.COMMON;
|
||||||
|
const speciesToUnlock = Object.keys(SPECIES).filter((species) => !unlockedSpecies.includes(species) && SPECIES[species].rarity === rarity);
|
||||||
if (speciesToUnlock.length === 0) {
|
if (speciesToUnlock.length === 0) {
|
||||||
// No more species to unlock
|
// No more species to unlock
|
||||||
return;
|
return;
|
||||||
@@ -2830,9 +3172,23 @@
|
|||||||
removeWardrobe();
|
removeWardrobe();
|
||||||
|
|
||||||
const contentContainer = document.createElement("div");
|
const contentContainer = document.createElement("div");
|
||||||
const content = makeElement("birb-grid-content");
|
const familiarBirds = makeElement("birb-grid-content");
|
||||||
|
const uncommonBirds = makeElement("birb-grid-content");
|
||||||
|
|
||||||
|
const familiarLabel = document.createElement("div");
|
||||||
|
familiarLabel.className = "birb-field-guide-section-label";
|
||||||
|
familiarLabel.textContent = `----- Familiar ${birdBirb()}s -----`;
|
||||||
|
|
||||||
|
const uncommonLabel = document.createElement("div");
|
||||||
|
uncommonLabel.className = "birb-field-guide-section-label";
|
||||||
|
uncommonLabel.textContent = `----- Uncommon ${birdBirb()}s -----`;
|
||||||
|
uncommonLabel.title = "Arbitrarily classified birds that are a little harder to find, but worth the wait!";
|
||||||
|
|
||||||
const description = makeElement("birb-field-guide-description");
|
const description = makeElement("birb-field-guide-description");
|
||||||
contentContainer.appendChild(content);
|
contentContainer.appendChild(familiarLabel);
|
||||||
|
contentContainer.appendChild(familiarBirds);
|
||||||
|
contentContainer.appendChild(uncommonLabel);
|
||||||
|
contentContainer.appendChild(uncommonBirds);
|
||||||
contentContainer.appendChild(description);
|
contentContainer.appendChild(description);
|
||||||
|
|
||||||
const fieldGuide = createWindow(
|
const fieldGuide = createWindow(
|
||||||
@@ -2848,14 +3204,26 @@
|
|||||||
const boldName = document.createElement("b");
|
const boldName = document.createElement("b");
|
||||||
boldName.textContent = type.name;
|
boldName.textContent = type.name;
|
||||||
|
|
||||||
const spacer = document.createElement("div");
|
|
||||||
spacer.style.height = "0.3em";
|
const spacerOne = document.createElement("div");
|
||||||
|
spacerOne.style.height = "0.3em";
|
||||||
|
|
||||||
|
const latinName = document.createElement("a");
|
||||||
|
latinName.className = "birb-field-guide-latin-name";
|
||||||
|
latinName.textContent = type.latinName;
|
||||||
|
latinName.href = type.url;
|
||||||
|
latinName.target = "_blank";
|
||||||
|
|
||||||
|
const spacerTwo = document.createElement("div");
|
||||||
|
spacerTwo.style.height = "0.4em";
|
||||||
|
|
||||||
const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description);
|
const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description);
|
||||||
|
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
fragment.appendChild(boldName);
|
fragment.appendChild(boldName);
|
||||||
fragment.appendChild(spacer);
|
fragment.appendChild(spacerOne);
|
||||||
|
fragment.appendChild(latinName);
|
||||||
|
fragment.appendChild(spacerTwo);
|
||||||
fragment.appendChild(descText);
|
fragment.appendChild(descText);
|
||||||
|
|
||||||
return fragment;
|
return fragment;
|
||||||
@@ -2877,7 +3245,11 @@
|
|||||||
}
|
}
|
||||||
birb.getFrames().base.draw(speciesCtx, Directions.RIGHT, CANVAS_PIXEL_SIZE, type.colors, type.tags);
|
birb.getFrames().base.draw(speciesCtx, Directions.RIGHT, CANVAS_PIXEL_SIZE, type.colors, type.tags);
|
||||||
speciesElement.appendChild(speciesCanvas);
|
speciesElement.appendChild(speciesCanvas);
|
||||||
content.appendChild(speciesElement);
|
let section = familiarBirds;
|
||||||
|
if (type.rarity === RARITY.UNCOMMON) {
|
||||||
|
section = uncommonBirds;
|
||||||
|
}
|
||||||
|
section.appendChild(speciesElement);
|
||||||
if (unlocked) {
|
if (unlocked) {
|
||||||
onClick(speciesElement, () => {
|
onClick(speciesElement, () => {
|
||||||
switchSpecies(id);
|
switchSpecies(id);
|
||||||
@@ -3059,26 +3431,6 @@
|
|||||||
return getWindowHeight() - focusedBounds.top;
|
return getWindowHeight() - focusedBounds.top;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fly to either an element or the ground
|
|
||||||
*/
|
|
||||||
function flySomewhere() {
|
|
||||||
// On mobile, always prefer to focus on an element
|
|
||||||
// If not mobile, 50% chance to focus on ground
|
|
||||||
// if ((!isMobile() && coinFlip()) || !focusOnElement()) {
|
|
||||||
// focusOnGround();
|
|
||||||
// }
|
|
||||||
if (!focusOnElement()) {
|
|
||||||
focusOnGround();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusOnGround() {
|
|
||||||
focusedElement = null;
|
|
||||||
updateFocusedElementBounds();
|
|
||||||
flyTo(Math.random() * window.innerWidth, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {HTMLElement|null} The random element, or null if no valid element was found
|
* @returns {HTMLElement|null} The random element, or null if no valid element was found
|
||||||
*/
|
*/
|
||||||
@@ -3110,20 +3462,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Focus on an element within the viewport
|
* Fly to an element within the viewport
|
||||||
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
||||||
* @returns Whether an element to focus on was found
|
* @returns Whether an element to fly to was found (null if flying to the ground)
|
||||||
*/
|
*/
|
||||||
function focusOnElement(teleport = false) {
|
function flyToElement(teleport = false) {
|
||||||
if (frozen) {
|
if (frozen) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const previousElement = focusedElement;
|
||||||
focusedElement = getRandomValidElement();
|
focusedElement = getRandomValidElement();
|
||||||
log("Focusing on element: ", focusedElement);
|
|
||||||
updateFocusedElementBounds();
|
updateFocusedElementBounds();
|
||||||
if (teleport) {
|
if (teleport) {
|
||||||
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
} else {
|
} else if (focusedElement !== previousElement) {
|
||||||
flyTo(getFocusedElementRandomX(), getFocusedY());
|
flyTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
}
|
}
|
||||||
return focusedElement !== null;
|
return focusedElement !== null;
|
||||||
|
|||||||
546
dist/web/birb.js
vendored
@@ -237,6 +237,8 @@
|
|||||||
"bluebird": {
|
"bluebird": {
|
||||||
"name": "Eastern Bluebird",
|
"name": "Eastern Bluebird",
|
||||||
"description": "Native to North American and very social, though can be timid around people.",
|
"description": "Native to North American and very social, though can be timid around people.",
|
||||||
|
"latinName": "Sialia sialis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Eastern_bluebird",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#639bff",
|
"face": "#639bff",
|
||||||
@@ -249,6 +251,8 @@
|
|||||||
"shimaEnaga": {
|
"shimaEnaga": {
|
||||||
"name": "Shima Enaga",
|
"name": "Shima Enaga",
|
||||||
"description": "Small, fluffy birds found in the snowy regions of Japan, these birds are highly sought after by ornithologists and nature photographers.",
|
"description": "Small, fluffy birds found in the snowy regions of Japan, these birds are highly sought after by ornithologists and nature photographers.",
|
||||||
|
"latinName": "Aegithalos caudatus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Long-tailed_tit",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#ffffff",
|
"face": "#ffffff",
|
||||||
@@ -262,6 +266,8 @@
|
|||||||
"tuftedTitmouse": {
|
"tuftedTitmouse": {
|
||||||
"name": "Tufted Titmouse",
|
"name": "Tufted Titmouse",
|
||||||
"description": "Native to the eastern United States, full of personality, and notably my wife's favorite bird.",
|
"description": "Native to the eastern United States, full of personality, and notably my wife's favorite bird.",
|
||||||
|
"latinName": "Baeolophus bicolor",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Tufted_titmouse",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#c7cad7",
|
"face": "#c7cad7",
|
||||||
@@ -278,6 +284,8 @@
|
|||||||
"europeanRobin": {
|
"europeanRobin": {
|
||||||
"name": "European Robin",
|
"name": "European Robin",
|
||||||
"description": "Native to western Europe, this is the quintessential robin. Quite friendly, you'll often find them searching for worms.",
|
"description": "Native to western Europe, this is the quintessential robin. Quite friendly, you'll often find them searching for worms.",
|
||||||
|
"latinName": "Erithacus rubecula",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/European_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#ffaf34",
|
"face": "#ffaf34",
|
||||||
@@ -292,6 +300,8 @@
|
|||||||
"redCardinal": {
|
"redCardinal": {
|
||||||
"name": "Red Cardinal",
|
"name": "Red Cardinal",
|
||||||
"description": "Native to the eastern United States, this strikingly red bird is hard to miss.",
|
"description": "Native to the eastern United States, this strikingly red bird is hard to miss.",
|
||||||
|
"latinName": "Cardinalis cardinalis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_cardinal",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#d93619",
|
"beak": "#d93619",
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
@@ -311,6 +321,8 @@
|
|||||||
"americanGoldfinch": {
|
"americanGoldfinch": {
|
||||||
"name": "American Goldfinch",
|
"name": "American Goldfinch",
|
||||||
"description": "Coloured a brilliant yellow, this bird feeds almost entirely on the seeds of plants such as thistle, sunflowers, and coneflowers.",
|
"description": "Coloured a brilliant yellow, this bird feeds almost entirely on the seeds of plants such as thistle, sunflowers, and coneflowers.",
|
||||||
|
"latinName": "Spinus tristis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/American_goldfinch",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#ffaf34",
|
"beak": "#ffaf34",
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
@@ -327,6 +339,8 @@
|
|||||||
"barnSwallow": {
|
"barnSwallow": {
|
||||||
"name": "Barn Swallow",
|
"name": "Barn Swallow",
|
||||||
"description": "Agile birds that often roost in man-made structures, these birds are known to build nests near Ospreys for protection.",
|
"description": "Agile birds that often roost in man-made structures, these birds are known to build nests near Ospreys for protection.",
|
||||||
|
"latinName": "Hirundo rustica",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Barn_swallow",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#db7c4d",
|
"face": "#db7c4d",
|
||||||
@@ -340,6 +354,8 @@
|
|||||||
"mistletoebird": {
|
"mistletoebird": {
|
||||||
"name": "Mistletoebird",
|
"name": "Mistletoebird",
|
||||||
"description": "Native to Australia, these birds eat mainly mistletoe and in turn spread the seeds far and wide.",
|
"description": "Native to Australia, these birds eat mainly mistletoe and in turn spread the seeds far and wide.",
|
||||||
|
"latinName": "Dicaeum hirundinaceum",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Mistletoebird",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#6c6a7c",
|
"foot": "#6c6a7c",
|
||||||
"face": "#352e6d",
|
"face": "#352e6d",
|
||||||
@@ -349,22 +365,11 @@
|
|||||||
"wing-edge": "#282065"
|
"wing-edge": "#282065"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redAvadavat": {
|
|
||||||
"name": "Red Avadavat",
|
|
||||||
"description": "Native to India and southeast Asia, these birds are also known as Strawberry Finches due to their speckled plumage.",
|
|
||||||
"colors": {
|
|
||||||
"beak": "#f71919",
|
|
||||||
"foot": "#af7575",
|
|
||||||
"face": "#cb092b",
|
|
||||||
"belly": "#ae1724",
|
|
||||||
"underbelly": "#831b24",
|
|
||||||
"wing": "#7e3030",
|
|
||||||
"wing-edge": "#490f0f"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scarletRobin": {
|
"scarletRobin": {
|
||||||
"name": "Scarlet Robin",
|
"name": "Scarlet Robin",
|
||||||
"description": "Native to Australia, this striking robin can be found in Eucalyptus forests.",
|
"description": "Native to Australia, this striking robin can be found in Eucalyptus forests.",
|
||||||
|
"latinName": "Petroica boodang",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Scarlet_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#494949",
|
"foot": "#494949",
|
||||||
"face": "#3d3d3d",
|
"face": "#3d3d3d",
|
||||||
@@ -372,12 +377,15 @@
|
|||||||
"underbelly": "#dcdcdc",
|
"underbelly": "#dcdcdc",
|
||||||
"wing": "#2b2b2b",
|
"wing": "#2b2b2b",
|
||||||
"wing-edge": "#ebebeb",
|
"wing-edge": "#ebebeb",
|
||||||
|
"nose": "#ebebeb",
|
||||||
"theme-highlight": "#fc5633"
|
"theme-highlight": "#fc5633"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"americanRobin": {
|
"americanRobin": {
|
||||||
"name": "American Robin",
|
"name": "American Robin",
|
||||||
"description": "While not a true robin, this social North American bird is so named due to its orange coloring. It seems unbothered by nearby humans.",
|
"description": "While not a true robin, this social North American bird is so named due to its orange coloring. It seems unbothered by nearby humans.",
|
||||||
|
"latinName": "Turdus migratorius",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/American_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#e89f30",
|
"beak": "#e89f30",
|
||||||
"foot": "#9f8075",
|
"foot": "#9f8075",
|
||||||
@@ -392,6 +400,8 @@
|
|||||||
"carolinaWren": {
|
"carolinaWren": {
|
||||||
"name": "Carolina Wren",
|
"name": "Carolina Wren",
|
||||||
"description": "Native to the eastern United States, these little birds are known for their curious and energetic nature.",
|
"description": "Native to the eastern United States, these little birds are known for their curious and energetic nature.",
|
||||||
|
"latinName": "Thryothorus ludovicianus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Carolina_wren",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#edc7a9",
|
"face": "#edc7a9",
|
||||||
@@ -402,14 +412,249 @@
|
|||||||
"wing": "#c58a5b",
|
"wing": "#c58a5b",
|
||||||
"wing-edge": "#866348"
|
"wing-edge": "#866348"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"blackCappedChickadee": {
|
||||||
|
"name": "Black-capped Chickadee",
|
||||||
|
"description": "Native to North America, these small and curious birds are known for their distinctive call from which they get their name.",
|
||||||
|
"latinName": "Poecile atricapillus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Black-capped_chickadee",
|
||||||
|
"colors": {
|
||||||
|
"hood": "#363636",
|
||||||
|
"cheek": "#363636",
|
||||||
|
"eyebrow": "#363636",
|
||||||
|
"nose": "#363636",
|
||||||
|
"collar": "#363636",
|
||||||
|
"belly": "#d6d4cf",
|
||||||
|
"underbelly": "#cfc5b4",
|
||||||
|
"face": "#eaeaea",
|
||||||
|
"wing": "#8f8e9a",
|
||||||
|
"wing-edge": "#706f7d",
|
||||||
|
"scruff": "#8f8e9a",
|
||||||
|
"foot": "#535259"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"blueJay": {
|
||||||
|
"name": "Blue Jay",
|
||||||
|
"description": "This loud and rambunctious bird is native to North America and is known for challenging anything in its path.",
|
||||||
|
"latinName": "Cyanocitta cristata",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Blue_jay",
|
||||||
|
"colors": {
|
||||||
|
"foot": "#5a626b",
|
||||||
|
"face": "#ebf2ff",
|
||||||
|
"belly": "#e5ecfa",
|
||||||
|
"underbelly": "#c4cbd6",
|
||||||
|
"wing": "#5890ff",
|
||||||
|
"wing-edge": "#3a77e8",
|
||||||
|
"hood": "#6391e8",
|
||||||
|
"nose": "#6391e8",
|
||||||
|
"collar": "#2e3136",
|
||||||
|
"scruff": "#6391e8"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"tuft"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"darkEyedJunco": {
|
||||||
|
"name": "Dark-eyed Junco",
|
||||||
|
"description": "Native across North America, these social birds will often be seen hopping along the ground in winter.",
|
||||||
|
"latinName": "Junco hyemalis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Dark-eyed_junco",
|
||||||
|
"colors": {
|
||||||
|
"face": "#55565e",
|
||||||
|
"wing": "#5c5f69",
|
||||||
|
"wing-edge": "#444547",
|
||||||
|
"belly": "#6c7180",
|
||||||
|
"underbelly": "#b8bbcc",
|
||||||
|
"foot": "#87776d",
|
||||||
|
"beak": "#ab8a98"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"houseFinch": {
|
||||||
|
"name": "House Finch",
|
||||||
|
"description": "Native to North America, these highly social birds sing cheerful songs and are often seen at bird feeders.",
|
||||||
|
"latinName": "Haemorhous mexicanus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/House_finch",
|
||||||
|
"colors": {
|
||||||
|
"face": "#cc3a3f",
|
||||||
|
"wing": "#ae8e78",
|
||||||
|
"wing-edge": "#8f6c54",
|
||||||
|
"belly": "#d97c77",
|
||||||
|
"underbelly": "#c5a489",
|
||||||
|
"foot": "#705b4c",
|
||||||
|
"beak": "#cf8479",
|
||||||
|
"hood": "#b02f35",
|
||||||
|
"nose": "#ab2b31",
|
||||||
|
"theme-highlight": "#ef444d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pigeon": {
|
||||||
|
"name": "Rock Pigeon",
|
||||||
|
"description": "Descended from the Rock Dove, these once domesticated birds are often found in cities worldwide. Quite friendly and intelligent, they were favored companions of Nikola Tesla.",
|
||||||
|
"latinName": "Columba livia",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Rock_dove",
|
||||||
|
"colors": {
|
||||||
|
"foot": "#ef6e5b",
|
||||||
|
"face": "#5a6c91",
|
||||||
|
"wing-edge": "#65686e",
|
||||||
|
"nose": "#ebebeb",
|
||||||
|
"belly": "#977699",
|
||||||
|
"underbelly": "#b0b3ba",
|
||||||
|
"wing": "#c7cbd4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redAvadavat": {
|
||||||
|
"name": "Red Avadavat",
|
||||||
|
"description": "Native to India and southeast Asia, these birds are also known as Strawberry Finches due to their speckled plumage.",
|
||||||
|
"latinName": "Amandava amandava",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_avadavat",
|
||||||
|
"colors": {
|
||||||
|
"beak": "#f71919",
|
||||||
|
"foot": "#af7575",
|
||||||
|
"face": "#cb092b",
|
||||||
|
"belly": "#ae1724",
|
||||||
|
"underbelly": "#831b24",
|
||||||
|
"wing": "#7e3030",
|
||||||
|
"wing-edge": "#490f0f",
|
||||||
|
"wing-spots": "#e8e4e4",
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"pinkRobin": {
|
||||||
|
"name": "Pink Robin",
|
||||||
|
"description": "Native to Australia, these bubblegum-pink puffballs are quieter than most, instead relying on their vibrant colours to attract partners.",
|
||||||
|
"latinName": "Petroica rodinogaster",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Pink_robin",
|
||||||
|
"colors": {
|
||||||
|
"face": "#403a46",
|
||||||
|
"wing": "#38333d",
|
||||||
|
"wing-edge": "#252325",
|
||||||
|
"underbelly": "#ff7eb8",
|
||||||
|
"belly": "#ff6eaf",
|
||||||
|
"foot": "#3c393c",
|
||||||
|
"theme-highlight": "#ff82ba"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"spangledCotinga": {
|
||||||
|
"name": "Spangled Cotinga",
|
||||||
|
"description": "This South American bird can be found in the Amazon rainforest, flashing its iridescent turquoise feathers high above in the canopy.",
|
||||||
|
"latinName": "Cotinga cayana",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Spangled_cotinga",
|
||||||
|
"colors": {
|
||||||
|
"face": "#62eafe",
|
||||||
|
"chin": "#a12457",
|
||||||
|
"collar": "#a12457",
|
||||||
|
"belly": "#62eafe",
|
||||||
|
"underbelly": "#5cd8ea",
|
||||||
|
"wing": "#227c89",
|
||||||
|
"wing-edge": "#13353a",
|
||||||
|
"foot": "#68696b",
|
||||||
|
"collar-scruff": "#62eafe"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"elegantEuphonia": {
|
||||||
|
"name": "Elegant Euphonia",
|
||||||
|
"description": "This vividly coloured finch is found throughout Central America and is known for the distinctive blue hood that crowns its head.",
|
||||||
|
"latinName": "Chlorophonia elegantissima",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Elegant_euphonia",
|
||||||
|
"colors": {
|
||||||
|
"wing": "#2d31a1",
|
||||||
|
"wing-edge": "#191c6d",
|
||||||
|
"face": "#1f2392",
|
||||||
|
"hood": "#6bc6ed",
|
||||||
|
"nose-tip": "#fd7e1d",
|
||||||
|
"foot": "#555650",
|
||||||
|
"belly": "#ff952b",
|
||||||
|
"underbelly": "#fd7e1d",
|
||||||
|
"temple": "#57c8fa",
|
||||||
|
"upper-corner-eye": "#57c8fa",
|
||||||
|
"upper-eyelid": "#57c8fa",
|
||||||
|
"collar-scruff": "#57c8fa",
|
||||||
|
"scruff": "#57c8fa",
|
||||||
|
"beak": "#252c31",
|
||||||
|
"collar": "#191c6d"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"paintedBunting": {
|
||||||
|
"name": "Painted Bunting",
|
||||||
|
"description": "A remarkably colourful bird, this North American species is quite difficult to observe despite its vivid palette due to its shy nature and vulnerable habitat.",
|
||||||
|
"latinName": "Passerina ciris",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Painted_bunting",
|
||||||
|
"colors": {
|
||||||
|
"face": "#5567f0",
|
||||||
|
"underbelly": "#f16534",
|
||||||
|
"belly": "#ef3b3b",
|
||||||
|
"wing": "#a3e65a",
|
||||||
|
"wing-edge": "#91cc50",
|
||||||
|
"shoulder": "#f6fe40",
|
||||||
|
"foot": "#767980"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"redWarbler": {
|
||||||
|
"name": "Red Warbler",
|
||||||
|
"description": "Endemic to the highlands of Mexico, this bird has the rare distinction of being one of the very few toxic birds in the world.",
|
||||||
|
"latinName": "Cardellina rubra",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_warbler",
|
||||||
|
"colors": {
|
||||||
|
"face": "#e80a28",
|
||||||
|
"belly": "#d90921",
|
||||||
|
"underbelly": "#c70c18",
|
||||||
|
"wing": "#ba121d",
|
||||||
|
"wing-edge": "#5b3535",
|
||||||
|
"foot": "#5e4645",
|
||||||
|
"behind-eye": "#deedff",
|
||||||
|
"temple": "#e8f0fa",
|
||||||
|
"corner-eye": "#d5e4f5",
|
||||||
|
"lower-eyelid": "#e34a61",
|
||||||
|
"beak": "#873535",
|
||||||
|
"cheek": "#db1734"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"cubanTody": {
|
||||||
|
"name": "Cuban Tody",
|
||||||
|
"description": "As the name suggests, this little green bird is only found on the island of Cuba and is known for being particularly round.",
|
||||||
|
"latinName": "Todus multicolor",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Cuban_tody",
|
||||||
|
"colors": {
|
||||||
|
"beak": "#f16f54",
|
||||||
|
"face": "#5fdf44",
|
||||||
|
"chin": "#f12d3e",
|
||||||
|
"collar": "#f12d3e",
|
||||||
|
"belly": "#f6f5e4",
|
||||||
|
"collar-scruff": "#a3ebff",
|
||||||
|
"underbelly": "#eae9d2",
|
||||||
|
"wing": "#11c751",
|
||||||
|
"wing-edge": "#156631",
|
||||||
|
"foot": "#ac7055",
|
||||||
|
"scruff": "#11c751",
|
||||||
|
"theme-highlight": "#4adc67"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"violetBackedStarling": {
|
||||||
|
"name": "Violet-backed Starling",
|
||||||
|
"description": "Native to Sub-Saharan Africa, these small starlings are known for being the most vividly purple birds in the world.",
|
||||||
|
"latinName": "Cinnyricinclus leucogaster",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Violet-backed_starling",
|
||||||
|
"colors": {
|
||||||
|
"face": "#9c3af2",
|
||||||
|
"wing": "#8f37ed",
|
||||||
|
"wing-edge": "#7029b8",
|
||||||
|
"belly": "#ffffff",
|
||||||
|
"underbelly": "#f2f2f2",
|
||||||
|
"foot": "#736a66",
|
||||||
|
"collar": "#aa60e6"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const PALETTE = Object.freeze(/** @type {const} */ ({
|
||||||
* Palette color names
|
|
||||||
* @type {Record<string, string>}
|
|
||||||
*/
|
|
||||||
const PALETTE = {
|
|
||||||
THEME_HIGHLIGHT: "theme-highlight",
|
THEME_HIGHLIGHT: "theme-highlight",
|
||||||
TRANSPARENT: "transparent",
|
TRANSPARENT: "transparent",
|
||||||
OUTLINE: "outline",
|
OUTLINE: "outline",
|
||||||
@@ -420,23 +665,36 @@
|
|||||||
FACE: "face",
|
FACE: "face",
|
||||||
HOOD: "hood",
|
HOOD: "hood",
|
||||||
EYEBROW: "eyebrow",
|
EYEBROW: "eyebrow",
|
||||||
|
UPPER_EYELID: "upper-eyelid",
|
||||||
|
UPPER_CORNER_EYE: "upper-corner-eye",
|
||||||
|
BEHIND_EYE: "behind-eye",
|
||||||
|
CORNER_EYE: "corner-eye",
|
||||||
|
TEMPLE: "temple",
|
||||||
|
LOWER_EYELID: "lower-eyelid",
|
||||||
NOSE: "nose",
|
NOSE: "nose",
|
||||||
|
NOSE_TIP: "nose-tip",
|
||||||
CHEEK: "cheek",
|
CHEEK: "cheek",
|
||||||
SCRUFF: "scruff",
|
SCRUFF: "scruff",
|
||||||
|
CHIN: "chin",
|
||||||
COLLAR: "collar",
|
COLLAR: "collar",
|
||||||
|
COLLAR_SCRUFF: "collar-scruff",
|
||||||
BELLY: "belly",
|
BELLY: "belly",
|
||||||
UNDERBELLY: "underbelly",
|
UNDERBELLY: "underbelly",
|
||||||
WING: "wing",
|
WING: "wing",
|
||||||
|
SHOULDER: "shoulder",
|
||||||
|
WING_SPOTS: "wing-spots",
|
||||||
WING_EDGE: "wing-edge",
|
WING_EDGE: "wing-edge",
|
||||||
HEART: "heart",
|
HEART: "heart",
|
||||||
HEART_BORDER: "heart-border",
|
HEART_BORDER: "heart-border",
|
||||||
HEART_SHINE: "heart-shine",
|
HEART_SHINE: "heart-shine",
|
||||||
FEATHER_SPINE: "feather-spine",
|
FEATHER_SPINE: "feather-spine",
|
||||||
};
|
}));
|
||||||
|
|
||||||
|
/** @typedef {typeof PALETTE[keyof typeof PALETTE]} PaletteColor */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping of sprite sheet colors to palette colors
|
* Mapping of sprite sheet colors to palette colors
|
||||||
* @type {Record<string, string>}
|
* @type {Record<string, PaletteColor>}
|
||||||
*/
|
*/
|
||||||
const SPRITE_SHEET_COLOR_MAP = {
|
const SPRITE_SHEET_COLOR_MAP = {
|
||||||
"transparent": PALETTE.TRANSPARENT,
|
"transparent": PALETTE.TRANSPARENT,
|
||||||
@@ -449,13 +707,24 @@
|
|||||||
"#639bff": PALETTE.FACE,
|
"#639bff": PALETTE.FACE,
|
||||||
"#99e550": PALETTE.HOOD,
|
"#99e550": PALETTE.HOOD,
|
||||||
"#ff5573": PALETTE.EYEBROW,
|
"#ff5573": PALETTE.EYEBROW,
|
||||||
|
"#ff768e": PALETTE.UPPER_EYELID,
|
||||||
|
"#ff90a4": PALETTE.UPPER_CORNER_EYE,
|
||||||
|
"#ff2c88": PALETTE.BEHIND_EYE,
|
||||||
|
"#e34f9c": PALETTE.CORNER_EYE,
|
||||||
|
"#b53477": PALETTE.TEMPLE,
|
||||||
|
"#ae65f1": PALETTE.LOWER_EYELID,
|
||||||
"#d95763": PALETTE.NOSE,
|
"#d95763": PALETTE.NOSE,
|
||||||
|
"#b93844": PALETTE.NOSE_TIP,
|
||||||
"#ff67a9": PALETTE.CHEEK,
|
"#ff67a9": PALETTE.CHEEK,
|
||||||
"#c5e550": PALETTE.SCRUFF,
|
"#c5e550": PALETTE.SCRUFF,
|
||||||
|
"#b87af1": PALETTE.CHIN,
|
||||||
"#ffe955": PALETTE.COLLAR,
|
"#ffe955": PALETTE.COLLAR,
|
||||||
|
"#f8ff55": PALETTE.COLLAR_SCRUFF,
|
||||||
"#f8b143": PALETTE.BELLY,
|
"#f8b143": PALETTE.BELLY,
|
||||||
"#ec8637": PALETTE.UNDERBELLY,
|
"#ec8637": PALETTE.UNDERBELLY,
|
||||||
"#578ae6": PALETTE.WING,
|
"#578ae6": PALETTE.WING,
|
||||||
|
"#55d1f3": PALETTE.SHOULDER,
|
||||||
|
"#90b0e8": PALETTE.WING_SPOTS,
|
||||||
"#326ed9": PALETTE.WING_EDGE,
|
"#326ed9": PALETTE.WING_EDGE,
|
||||||
"#c82e2e": PALETTE.HEART,
|
"#c82e2e": PALETTE.HEART,
|
||||||
"#501a1a": PALETTE.HEART_BORDER,
|
"#501a1a": PALETTE.HEART_BORDER,
|
||||||
@@ -463,16 +732,52 @@
|
|||||||
"#373737": PALETTE.FEATHER_SPINE,
|
"#373737": PALETTE.FEATHER_SPINE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Partial<Record<PaletteColor, PaletteColor>>}
|
||||||
|
*/
|
||||||
|
({
|
||||||
|
[PALETTE.HOOD]: PALETTE.FACE,
|
||||||
|
[PALETTE.EYEBROW]: PALETTE.FACE,
|
||||||
|
[PALETTE.UPPER_EYELID]: PALETTE.EYEBROW,
|
||||||
|
[PALETTE.UPPER_CORNER_EYE]: PALETTE.EYEBROW,
|
||||||
|
[PALETTE.BEHIND_EYE]: PALETTE.FACE,
|
||||||
|
[PALETTE.CORNER_EYE]: PALETTE.FACE,
|
||||||
|
[PALETTE.TEMPLE]: PALETTE.FACE,
|
||||||
|
[PALETTE.LOWER_EYELID]: PALETTE.FACE,
|
||||||
|
[PALETTE.NOSE]: PALETTE.FACE,
|
||||||
|
[PALETTE.NOSE_TIP]: PALETTE.NOSE,
|
||||||
|
[PALETTE.CHEEK]: PALETTE.FACE,
|
||||||
|
[PALETTE.SCRUFF]: PALETTE.FACE,
|
||||||
|
[PALETTE.CHIN]: PALETTE.FACE,
|
||||||
|
[PALETTE.COLLAR]: PALETTE.FACE,
|
||||||
|
[PALETTE.COLLAR_SCRUFF]: PALETTE.COLLAR,
|
||||||
|
[PALETTE.WING_SPOTS]: PALETTE.WING,
|
||||||
|
[PALETTE.SHOULDER]: PALETTE.WING,
|
||||||
|
});
|
||||||
|
|
||||||
|
const RARITY = Object.freeze(/** @type {const} */ ({
|
||||||
|
COMMON: "common",
|
||||||
|
UNCOMMON: "uncommon"
|
||||||
|
}));
|
||||||
|
|
||||||
|
/** @typedef {typeof RARITY[keyof typeof RARITY]} Rarity */
|
||||||
|
|
||||||
class BirdType {
|
class BirdType {
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {string} description
|
* @param {string} description
|
||||||
|
* @param {string} latinName
|
||||||
|
* @param {string} url
|
||||||
* @param {Record<string, string>} colors
|
* @param {Record<string, string>} colors
|
||||||
* @param {string[]} [tags]
|
* @param {string[]} [tags]
|
||||||
|
* @param {Rarity} [rarity]
|
||||||
*/
|
*/
|
||||||
constructor(name, description, colors, tags = []) {
|
constructor(name, description, latinName, url, colors, tags = [], rarity = RARITY.COMMON) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
this.latinName = latinName;
|
||||||
|
this.url = url;
|
||||||
const defaultColors = {
|
const defaultColors = {
|
||||||
[PALETTE.TRANSPARENT]: "transparent",
|
[PALETTE.TRANSPARENT]: "transparent",
|
||||||
[PALETTE.OUTLINE]: "#000000",
|
[PALETTE.OUTLINE]: "#000000",
|
||||||
@@ -484,15 +789,27 @@
|
|||||||
[PALETTE.HEART_SHINE]: "#ff6b6b",
|
[PALETTE.HEART_SHINE]: "#ff6b6b",
|
||||||
[PALETTE.FEATHER_SPINE]: "#373737",
|
[PALETTE.FEATHER_SPINE]: "#373737",
|
||||||
[PALETTE.HOOD]: colors.face,
|
[PALETTE.HOOD]: colors.face,
|
||||||
[PALETTE.EYEBROW]: colors.face,
|
[PALETTE.EYEBROW]: colors.face,
|
||||||
|
[PALETTE.UPPER_EYELID]: colors.eyebrow || colors.face,
|
||||||
|
[PALETTE.UPPER_CORNER_EYE]: colors.eyebrow || colors.face,
|
||||||
|
[PALETTE.BEHIND_EYE]: colors.face,
|
||||||
|
[PALETTE.CORNER_EYE]: colors.face,
|
||||||
|
[PALETTE.TEMPLE]: colors.face,
|
||||||
|
[PALETTE.LOWER_EYELID]: colors.face,
|
||||||
[PALETTE.NOSE]: colors.face,
|
[PALETTE.NOSE]: colors.face,
|
||||||
|
[PALETTE.NOSE_TIP]: colors.nose || colors.face,
|
||||||
[PALETTE.CHEEK]: colors.face,
|
[PALETTE.CHEEK]: colors.face,
|
||||||
[PALETTE.SCRUFF]: colors.face,
|
[PALETTE.SCRUFF]: colors.face,
|
||||||
|
[PALETTE.CHIN]: colors.face,
|
||||||
[PALETTE.COLLAR]: colors.face,
|
[PALETTE.COLLAR]: colors.face,
|
||||||
|
[PALETTE.COLLAR_SCRUFF]: colors.collar || colors.face,
|
||||||
|
[PALETTE.SHOULDER]: colors.wing,
|
||||||
};
|
};
|
||||||
/** @type {Record<string, string>} */
|
/** @type {Record<string, string>} */
|
||||||
this.colors = { ...defaultColors, ...colors, [PALETTE.THEME_HIGHLIGHT]: colors[PALETTE.THEME_HIGHLIGHT] ?? colors.hood ?? colors.face };
|
this.colors = { ...defaultColors, ...colors, [PALETTE.THEME_HIGHLIGHT]: colors[PALETTE.THEME_HIGHLIGHT] ?? colors.hood ?? colors.face };
|
||||||
this.tags = tags;
|
this.tags = tags;
|
||||||
|
/** @type {Rarity} */
|
||||||
|
this.rarity = rarity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,7 +873,7 @@
|
|||||||
const SPECIES = Object.fromEntries(
|
const SPECIES = Object.fromEntries(
|
||||||
Object.entries(species).map(([id, data]) => [
|
Object.entries(species).map(([id, data]) => [
|
||||||
id,
|
id,
|
||||||
new BirdType(data.name, data.description, data.colors, data.tags ?? []),
|
new BirdType(data.name, data.description, data.latinName, data.url, data.colors, data.tags, data.rarity)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1253,37 +1570,42 @@
|
|||||||
audioContext;
|
audioContext;
|
||||||
|
|
||||||
chirp() {
|
chirp() {
|
||||||
if (!this.audioContext) {
|
const count = Math.floor(1 + Math.random() * 1.5);
|
||||||
this.audioContext = new AudioContext();
|
for (let i = 0; i < count; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.audioContext) {
|
||||||
|
this.audioContext = new AudioContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TIMES = [0, 0.06, 0.10, 0.15];
|
||||||
|
const FREQUENCIES = [2200,
|
||||||
|
3500 + Math.random() * 600 * count,
|
||||||
|
2100 + Math.random() * 200 * count,
|
||||||
|
1600 + Math.random() * 400 * count];
|
||||||
|
const VOLUMES = [0.00005, 0.165, 0.165, 0.0001];
|
||||||
|
|
||||||
|
const oscillator = this.audioContext.createOscillator();
|
||||||
|
oscillator.type = "sine";
|
||||||
|
const gain = this.audioContext.createGain();
|
||||||
|
oscillator.connect(gain);
|
||||||
|
gain.connect(this.audioContext.destination);
|
||||||
|
|
||||||
|
const now = this.audioContext.currentTime;
|
||||||
|
for (let i = 0; i < TIMES.length; i++) {
|
||||||
|
const time = TIMES[i] + now;
|
||||||
|
if (i === 0) {
|
||||||
|
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
|
||||||
|
gain.gain.setValueAtTime(VOLUMES[i], time);
|
||||||
|
} else {
|
||||||
|
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oscillator.start(now);
|
||||||
|
oscillator.stop(now + TIMES[TIMES.length - 1]);
|
||||||
|
}, i * 120);
|
||||||
}
|
}
|
||||||
|
|
||||||
const TIMES = [0, 0.06, 0.10, 0.15];
|
|
||||||
const FREQUENCIES = [2200,
|
|
||||||
3500 + Math.random() * 600,
|
|
||||||
2100 + Math.random() * 200,
|
|
||||||
1600 + Math.random() * 400];
|
|
||||||
const VOLUMES = [0.0001, 0.2, 0.2, 0.0001];
|
|
||||||
|
|
||||||
const oscillator = this.audioContext.createOscillator();
|
|
||||||
oscillator.type = "sine";
|
|
||||||
const gain = this.audioContext.createGain();
|
|
||||||
oscillator.connect(gain);
|
|
||||||
gain.connect(this.audioContext.destination);
|
|
||||||
|
|
||||||
const now = this.audioContext.currentTime;
|
|
||||||
for (let i = 0; i < TIMES.length; i++) {
|
|
||||||
const time = TIMES[i] + now;
|
|
||||||
if (i === 0) {
|
|
||||||
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
|
|
||||||
gain.gain.setValueAtTime(VOLUMES[i], time);
|
|
||||||
} else {
|
|
||||||
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
|
|
||||||
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
oscillator.start(now);
|
|
||||||
oscillator.stop(now + TIMES[TIMES.length - 1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2022,7 +2344,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#birb-field-guide .birb-grid-content {
|
#birb-field-guide .birb-grid-content {
|
||||||
grid-template-rows: repeat(3, auto);
|
grid-template-columns: repeat(4, auto);
|
||||||
}
|
}
|
||||||
|
|
||||||
#birb-wardrobe .birb-grid-content {
|
#birb-wardrobe .birb-grid-content {
|
||||||
@@ -2032,7 +2354,7 @@
|
|||||||
|
|
||||||
.birb-grid-content {
|
.birb-grid-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: row;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
@@ -2064,7 +2386,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.birb-grid-item, .birb-field-guide-description, .birb-message-content {
|
.birb-grid-item, .birb-field-guide-description, .birb-message-content {
|
||||||
border: var(--birb-border-size) solid rgb(255, 207, 144);
|
border: var(--birb-border-size) solid #ffcf90;
|
||||||
box-shadow: 0 0 0 var(--birb-border-size) white;
|
box-shadow: 0 0 0 var(--birb-border-size) white;
|
||||||
background: rgba(255, 221, 177, 0.5);
|
background: rgba(255, 221, 177, 0.5);
|
||||||
}
|
}
|
||||||
@@ -2083,6 +2405,15 @@
|
|||||||
background: var(--birb-mix-color);
|
background: var(--birb-mix-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.birb-field-guide-section-label {
|
||||||
|
padding-top: 4px;
|
||||||
|
/* padding-left: calc(10px + var(--birb-border-size) / 2); */
|
||||||
|
color: #876c4e;
|
||||||
|
text-align: center;
|
||||||
|
/* Italics */
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.birb-field-guide-description {
|
.birb-field-guide-description {
|
||||||
max-width: calc(100% - 20px);
|
max-width: calc(100% - 20px);
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
@@ -2094,7 +2425,14 @@
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: rgb(124, 108, 75);
|
color: #7c6c4b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.birb-field-guide-latin-name {
|
||||||
|
text-decoration: underline;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: bold;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
#birb-feather {
|
#birb-feather {
|
||||||
@@ -2107,7 +2445,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: rgb(124, 108, 75);
|
color: #7c6c4b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.birb-sticky-note {
|
.birb-sticky-note {
|
||||||
@@ -2156,7 +2494,7 @@
|
|||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}`;
|
}`;
|
||||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABFFJREFUeJztnb9rE2EYx79vK1gUQSRLL64apxZcdC84dNBk0LiIoIJCwUGwlP4BUjqIFpSKLioOdYlV6ObUpV26ORRX0yoEHezQgvZxSN7rm+v9SGzu3vea7wdC31wued7k3ueT5727XAFCCCGEENJfKNsdIO4jIhL1mFKKY4jkFg7eHGBTQDp2pVgEANTq9bZ2Fn0ghPQp0qLseVL2vH3tODn2Kn7Z82RlZERkYmJfO+34hKTJgO0OkGQqxSKmCgXUKpV97SxZXV7GVKHgtwnJOxRgjrAloFq9jplGo23ZTKPhT4EJISQVzCnwysiIf8tqChzWhyxjE0IcQELIOrZNAZl9oPwI6TNEpLnz3/ibdXzbArIhf0LS5IjtDuSN1dFRK3GVUkpExOapJzzdhZA+RVc+uvpjFURI/uE3eheY0mM1lD3BLx1uA3JQOIBILtDy084TkcwFSAH3KWFHQDkFJBaQF/WyALB2AKgV2+nxzzztnMQToc1vXvOGhN+o9hIKmADAi3pZN60cAFJK+X1wrfoL5oWNPM0jsUeBzQ/TGHzQy0TEXCeVARGc+hjLISLi2kAk6XG3+MHqtrYp4E4wU6E6u+Evo/+iiRSgKZ716m3gAXB29+TeCvVy2/6YNGTkgoAJ0dgWcAyilPKld9gZHlSy+Vd6si1iK8CB4x7UsWGc+7QEANi98QTeq+sYn98FloDq7CaQkoxcEDAhOUCixPf12+/MO5M2w4NKxud3sXRvoCcSjHwBU0C3X+4voW+NVzD37rl/f2HS08+LDtaFoEREBo57bcvaBAxg69emH9uMSxGSPiFWfmtzJbg6XXeF2A+nVVVBflzFnY/vAQCvLl8DAFx//TT0OVqEIa/VDNihnGwLmBCXqc5uxO7YS1uAUfFbeZibPOtIgNuLYzh68RQAYGflJwDg5vrbjgLoMnxtrtT1uVs2BUyIy0QJyMy3lEUUWX3mSYId/xZYi0+zMOnh/P11nDl9InT9wIY40JGonZWfvviC/QgS3Ci6HzwaRg4Tr0s32oqQYL5lIaCv335H5n9e+O+LIWwvjmHoSgm4vx76eJj4DlJ92RQwIS7y7E8FE0dqQMbiS6I6u4GFSU9c6EsSiVNgtKqn7cWxtse2vmwBAArTq1HP3Qvyn+Izp+BhDF35jPMZCZgQ12g8uuAP7kvf3/jL1+ZKWY1ziSpCzAOTLuddYsfCJKTlF0ZherVnb9y2gAlxHVOCaOZD1mNddBESFKGWoMv515EA0ZJQ49GF2HV7KT8zvi0BE0I6QgAgKMI8CDBxH6C+EGfrjURKME3x6NhofuNFrkf5EWIFhebUu5l8Ebulco15NYza44f+VTF0O80LFAR+6B0aP+0+EEKSCV4xx/V87OrfYiqlUHv80L9vtpFi1aVfNy4+Kz9C3KD101QgB/nY1Wkw5lQ02M6CpPiuf9iEHHbyloNddTaunM3ijduOTwg5XPwDDgmrxnQErq8AAAAASUVORK5CYII=";
|
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABORJREFUeJztnU9IHFccx79vE6g0BEK7BJzNwR6apReFQElKjpZaeol7EHuRQhFaECIEIkF6LGkQGqyQ0EBa+u8iHjZCCfXQUw7qRVDwYKQEirtt0yVtMRaFdn857L7x7Tizf3Rn3oz7/cDi8+3s/t44733m9+afACGEEEII6SyU7QaQ+CMiEvSeUop9iCQWdt4EYFNAOnYukwEA5AuFmnIUbSCEdChSZdBxZNBxDpTrybFd8QcdR5Z6e0XGxg6Uw45PSJikbDeANCaXyeBGOo18LnegHCXLjx7hRjrtlglJOhRggrAloHyhgFulUk3drVLJnQITQkgomFPgpd5e9xXVFNivDVHGJoTEAPEh6tg2BWS2gfIjpMMQkcrBf+Nn1PFtC8iG/AkJk5O2G5A0lvv6rMRVSikREZuXnvByF0I6FJ356OyPWRAhyYd79BYwpcdsKHq8Ox1uA3JU2IFIItDy084TkcgFSAF3KH5nQDkFJBaQe4VBAWDtBFA1dqz7P8dp8zS8ENrc85ovNLhHtZ1QwAQA7hUGddHKCSCllNuGuGV/3nFhY5wmkbpngc0/ptH5oOtEBCIiYXYG79THqA89NokXH2UeWN3WNgXcDOZQGJ4qunX0XzCBAjTFs3DpbfRcexXny2eA1zKAcxYo1EoQIewV4yBgQjS2BVwHUUq50jvudJ9Q8tv/0pZtUTcDTJ1yoF7uxrtr68DaOsoj03A+fx/FNz7DuR+yGFrdrjko3U4RxkHAhCQACRLf5tZ25I0Jm+4TSt77soyHH6faIsHALzAF9NPkP279v38Cbz6dw693f8R06Xu3fq7vtP5ccLAWBCUikjrl1NSVR6bh3K8I+GHXX/jmq09841KEpEOoK7+VmSziOl2PC4EZoL7zAAAGxkex8MV9AEDu01HsnQWure3L7/KTHaSMYw5+HGa6Wt4p1gh4AcCdD//G1tM5nLm7BJSAodXtmrhmRui3Ts3GJiTODE8VrR7YG1rdduOnFvYzzdkJB0mSbt2GVoWF3fl+vHTpFQDA3tIzAMBIzwNcfrKDxcflA59763zKrddp+MpMtuVrt3R8+WPIFfDAeEXAI2tfu8uZ7ahuAL/vqqwwJUiOAUECNMdbyCIKzD6TJMGm7wXW4tPM9Z3GL1c38Pq5yhTUlN7i47J3QxzpTNTe0jMMjI/WtqNnX3yLxrLDU0VfAfNsGDlOfJsdwQcb+7Mw73iLQkCbW9vu+E8qh34Ywu58P7quZIGrGwCAza3a9/3Ed5Tsy6aACYkjd/7LYexkHohYfI0YnipidsKROLSlEQ2nwKhmT7vz/TXvPV9/DgBITy4HfXY/yCHFZ07B/ei68jMuVAXspd0CJiRulG5edDv3O79/59avzGSj6udywUhCTGYnnEQcdmrYMD8Jafn5kZ5cbtuK2xYwIXHHlCAq4yHqvi46CfGKUEswzuOvKQGiKqHSzYt1l22n/Mz4tgRMCGkKAQCvCJMgwIbHAPXlMNUVCZRgmOLRsVHZ4wUuR/kRYgWFytS7MvgCDkslGvNpGPnb192nYuhymA8o8Nzo7Rs/7DYQQhrjfWJO3MdjS/8WUymF/O3r7u9mGSFmXfp768Vn5kdIPKjejAAkYDy2dBmMORX1lqOgUfy4/7EJOe4kbQy21Nh66WwUK247PiHkePECZQPi+PbreqwAAAAASUVORK5CYII=";
|
||||||
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
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==";
|
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==";
|
||||||
|
|
||||||
@@ -2186,6 +2524,7 @@
|
|||||||
const HOP_CHANCE = 1 / (60 * 2.5); // Every 2.5 seconds
|
const HOP_CHANCE = 1 / (60 * 2.5); // Every 2.5 seconds
|
||||||
const FOCUS_SWITCH_CHANCE = 1 / (60 * 20); // Every 20 seconds
|
const FOCUS_SWITCH_CHANCE = 1 / (60 * 20); // Every 20 seconds
|
||||||
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // Every 2 hours
|
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // Every 2 hours
|
||||||
|
const UNCOMMON_FEATHER_CHANCE = 0.15; // 15% of feathers are uncommon
|
||||||
const HAT_CHANCE = 1 / (60 * 60 * 25); // Every 25 minutes
|
const HAT_CHANCE = 1 / (60 * 60 * 25); // Every 25 minutes
|
||||||
|
|
||||||
// Feathers
|
// Feathers
|
||||||
@@ -2292,7 +2631,7 @@
|
|||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
||||||
new MenuItem("2026.3.11", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.11"); }, false),
|
new MenuItem("2026.3.29", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.29"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @type {Birb} */
|
/** @type {Birb} */
|
||||||
@@ -2482,7 +2821,9 @@
|
|||||||
|
|
||||||
setInterval(update, UPDATE_INTERVAL);
|
setInterval(update, UPDATE_INTERVAL);
|
||||||
|
|
||||||
focusOnElement(true);
|
flyToElement(true);
|
||||||
|
// TODO: Remove
|
||||||
|
insertFieldGuide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
@@ -2501,11 +2842,11 @@
|
|||||||
// Idle for a while, do something
|
// Idle for a while, do something
|
||||||
if (focusedElement === null) {
|
if (focusedElement === null) {
|
||||||
// Fly to an element
|
// Fly to an element
|
||||||
focusOnElement();
|
flyToElement();
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
} else if (Math.random() < FOCUS_SWITCH_CHANCE) {
|
} else if (Math.random() < FOCUS_SWITCH_CHANCE) {
|
||||||
// Fly to another element if idle for a longer while
|
// Fly to another element if idle for a longer while
|
||||||
focusOnElement();
|
flyToElement();
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2541,7 +2882,7 @@
|
|||||||
// Update the bird's position
|
// Update the bird's position
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE) {
|
||||||
if (focusedElement && !isWithinHorizontalBounds()) {
|
if (focusedElement && !isWithinHorizontalBounds()) {
|
||||||
flySomewhere();
|
flyToElement();
|
||||||
}
|
}
|
||||||
birdY = getFocusedY();
|
birdY = getFocusedY();
|
||||||
} else if (currentState === States.FLYING) {
|
} else if (currentState === States.FLYING) {
|
||||||
@@ -2557,7 +2898,7 @@
|
|||||||
startY += targetY - oldTargetY;
|
startY += targetY - oldTargetY;
|
||||||
if (targetY < 0 || targetY > getWindowHeight()) {
|
if (targetY < 0 || targetY > getWindowHeight()) {
|
||||||
// Fly to another element or the ground if the focused element moves out of bounds
|
// Fly to another element or the ground if the focused element moves out of bounds
|
||||||
flySomewhere();
|
flyToElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (birb.draw(SPECIES[currentSpecies], currentHat)) {
|
if (birb.draw(SPECIES[currentSpecies], currentHat)) {
|
||||||
@@ -2635,7 +2976,8 @@
|
|||||||
if (document.querySelector("#" + FEATHER_ID)) {
|
if (document.querySelector("#" + FEATHER_ID)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const speciesToUnlock = Object.keys(SPECIES).filter((species) => !unlockedSpecies.includes(species));
|
const rarity = Math.random() < UNCOMMON_FEATHER_CHANCE ? RARITY.UNCOMMON : RARITY.COMMON;
|
||||||
|
const speciesToUnlock = Object.keys(SPECIES).filter((species) => !unlockedSpecies.includes(species) && SPECIES[species].rarity === rarity);
|
||||||
if (speciesToUnlock.length === 0) {
|
if (speciesToUnlock.length === 0) {
|
||||||
// No more species to unlock
|
// No more species to unlock
|
||||||
return;
|
return;
|
||||||
@@ -2830,9 +3172,23 @@
|
|||||||
removeWardrobe();
|
removeWardrobe();
|
||||||
|
|
||||||
const contentContainer = document.createElement("div");
|
const contentContainer = document.createElement("div");
|
||||||
const content = makeElement("birb-grid-content");
|
const familiarBirds = makeElement("birb-grid-content");
|
||||||
|
const uncommonBirds = makeElement("birb-grid-content");
|
||||||
|
|
||||||
|
const familiarLabel = document.createElement("div");
|
||||||
|
familiarLabel.className = "birb-field-guide-section-label";
|
||||||
|
familiarLabel.textContent = `----- Familiar ${birdBirb()}s -----`;
|
||||||
|
|
||||||
|
const uncommonLabel = document.createElement("div");
|
||||||
|
uncommonLabel.className = "birb-field-guide-section-label";
|
||||||
|
uncommonLabel.textContent = `----- Uncommon ${birdBirb()}s -----`;
|
||||||
|
uncommonLabel.title = "Arbitrarily classified birds that are a little harder to find, but worth the wait!";
|
||||||
|
|
||||||
const description = makeElement("birb-field-guide-description");
|
const description = makeElement("birb-field-guide-description");
|
||||||
contentContainer.appendChild(content);
|
contentContainer.appendChild(familiarLabel);
|
||||||
|
contentContainer.appendChild(familiarBirds);
|
||||||
|
contentContainer.appendChild(uncommonLabel);
|
||||||
|
contentContainer.appendChild(uncommonBirds);
|
||||||
contentContainer.appendChild(description);
|
contentContainer.appendChild(description);
|
||||||
|
|
||||||
const fieldGuide = createWindow(
|
const fieldGuide = createWindow(
|
||||||
@@ -2848,14 +3204,26 @@
|
|||||||
const boldName = document.createElement("b");
|
const boldName = document.createElement("b");
|
||||||
boldName.textContent = type.name;
|
boldName.textContent = type.name;
|
||||||
|
|
||||||
const spacer = document.createElement("div");
|
|
||||||
spacer.style.height = "0.3em";
|
const spacerOne = document.createElement("div");
|
||||||
|
spacerOne.style.height = "0.3em";
|
||||||
|
|
||||||
|
const latinName = document.createElement("a");
|
||||||
|
latinName.className = "birb-field-guide-latin-name";
|
||||||
|
latinName.textContent = type.latinName;
|
||||||
|
latinName.href = type.url;
|
||||||
|
latinName.target = "_blank";
|
||||||
|
|
||||||
|
const spacerTwo = document.createElement("div");
|
||||||
|
spacerTwo.style.height = "0.4em";
|
||||||
|
|
||||||
const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description);
|
const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description);
|
||||||
|
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
fragment.appendChild(boldName);
|
fragment.appendChild(boldName);
|
||||||
fragment.appendChild(spacer);
|
fragment.appendChild(spacerOne);
|
||||||
|
fragment.appendChild(latinName);
|
||||||
|
fragment.appendChild(spacerTwo);
|
||||||
fragment.appendChild(descText);
|
fragment.appendChild(descText);
|
||||||
|
|
||||||
return fragment;
|
return fragment;
|
||||||
@@ -2877,7 +3245,11 @@
|
|||||||
}
|
}
|
||||||
birb.getFrames().base.draw(speciesCtx, Directions.RIGHT, CANVAS_PIXEL_SIZE, type.colors, type.tags);
|
birb.getFrames().base.draw(speciesCtx, Directions.RIGHT, CANVAS_PIXEL_SIZE, type.colors, type.tags);
|
||||||
speciesElement.appendChild(speciesCanvas);
|
speciesElement.appendChild(speciesCanvas);
|
||||||
content.appendChild(speciesElement);
|
let section = familiarBirds;
|
||||||
|
if (type.rarity === RARITY.UNCOMMON) {
|
||||||
|
section = uncommonBirds;
|
||||||
|
}
|
||||||
|
section.appendChild(speciesElement);
|
||||||
if (unlocked) {
|
if (unlocked) {
|
||||||
onClick(speciesElement, () => {
|
onClick(speciesElement, () => {
|
||||||
switchSpecies(id);
|
switchSpecies(id);
|
||||||
@@ -3059,26 +3431,6 @@
|
|||||||
return getWindowHeight() - focusedBounds.top;
|
return getWindowHeight() - focusedBounds.top;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fly to either an element or the ground
|
|
||||||
*/
|
|
||||||
function flySomewhere() {
|
|
||||||
// On mobile, always prefer to focus on an element
|
|
||||||
// If not mobile, 50% chance to focus on ground
|
|
||||||
// if ((!isMobile() && coinFlip()) || !focusOnElement()) {
|
|
||||||
// focusOnGround();
|
|
||||||
// }
|
|
||||||
if (!focusOnElement()) {
|
|
||||||
focusOnGround();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusOnGround() {
|
|
||||||
focusedElement = null;
|
|
||||||
updateFocusedElementBounds();
|
|
||||||
flyTo(Math.random() * window.innerWidth, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {HTMLElement|null} The random element, or null if no valid element was found
|
* @returns {HTMLElement|null} The random element, or null if no valid element was found
|
||||||
*/
|
*/
|
||||||
@@ -3110,20 +3462,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Focus on an element within the viewport
|
* Fly to an element within the viewport
|
||||||
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
||||||
* @returns Whether an element to focus on was found
|
* @returns Whether an element to fly to was found (null if flying to the ground)
|
||||||
*/
|
*/
|
||||||
function focusOnElement(teleport = false) {
|
function flyToElement(teleport = false) {
|
||||||
if (frozen) {
|
if (frozen) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const previousElement = focusedElement;
|
||||||
focusedElement = getRandomValidElement();
|
focusedElement = getRandomValidElement();
|
||||||
log("Focusing on element: ", focusedElement);
|
|
||||||
updateFocusedElementBounds();
|
updateFocusedElementBounds();
|
||||||
if (teleport) {
|
if (teleport) {
|
||||||
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
} else {
|
} else if (focusedElement !== previousElement) {
|
||||||
flyTo(getFocusedElementRandomX(), getFocusedY());
|
flyTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
}
|
}
|
||||||
return focusedElement !== null;
|
return focusedElement !== null;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
import { SPRITE_SHEET_COLOR_MAP, PALETTE, loadSpriteSheetPixels } from '../src/animation/sprites.js';
|
import { SPRITE_SHEET_COLOR_MAP, PALETTE, DEFAULT_COLOR_OVERRIDES, loadSpriteSheetPixels } from '../src/animation/sprites.js';
|
||||||
import Layer, { TAG } from '../src/animation/layer.js';
|
import Layer, { TAG } from '../src/animation/layer.js';
|
||||||
import Frame from '../src/animation/frame.js';
|
import Frame from '../src/animation/frame.js';
|
||||||
import { Directions, getLayerPixels } from '../src/shared.js';
|
import { Directions, getLayerPixels } from '../src/shared.js';
|
||||||
@@ -10,15 +10,6 @@ import species from '../src/species.js';
|
|||||||
const COLOR_MAP = SPRITE_SHEET_COLOR_MAP;
|
const COLOR_MAP = SPRITE_SHEET_COLOR_MAP;
|
||||||
const SPRITE_PATH = "../sprites/birb.png";
|
const SPRITE_PATH = "../sprites/birb.png";
|
||||||
const SPRITE_SIZE = 32;
|
const SPRITE_SIZE = 32;
|
||||||
/** @type {Record<string, string>} */
|
|
||||||
const DEFAULT_OVERRIDES = {
|
|
||||||
"hood": "face",
|
|
||||||
"eyebrow": "face",
|
|
||||||
"nose": "face",
|
|
||||||
"cheek": "face",
|
|
||||||
"scruff": "face",
|
|
||||||
"collar": "face",
|
|
||||||
};
|
|
||||||
const IGNORED_PARTS = new Set(
|
const IGNORED_PARTS = new Set(
|
||||||
["transparent", "border", "heart", "heart-border", "heart-shine", "feather-spine"]
|
["transparent", "border", "heart", "heart-border", "heart-shine", "feather-spine"]
|
||||||
);
|
);
|
||||||
@@ -161,8 +152,9 @@ function getColor(part) {
|
|||||||
if (currentSpecies.colors[part]) {
|
if (currentSpecies.colors[part]) {
|
||||||
return currentSpecies.colors[part];
|
return currentSpecies.colors[part];
|
||||||
}
|
}
|
||||||
if (DEFAULT_OVERRIDES[part]) {
|
const override = DEFAULT_COLOR_OVERRIDES[/** @type {keyof typeof DEFAULT_COLOR_OVERRIDES} */ (part)];
|
||||||
return getColor(DEFAULT_OVERRIDES[part]);
|
if (override) {
|
||||||
|
return getColor(override);
|
||||||
}
|
}
|
||||||
for (const [color, partName] of Object.entries(COLOR_MAP)) {
|
for (const [color, partName] of Object.entries(COLOR_MAP)) {
|
||||||
if (partName === part) {
|
if (partName === part) {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 635 B After Width: | Height: | Size: 662 B |
|
Before Width: | Height: | Size: 829 B After Width: | Height: | Size: 848 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 848 B After Width: | Height: | Size: 881 B |
|
Before Width: | Height: | Size: 944 B After Width: | Height: | Size: 996 B |
|
Before Width: | Height: | Size: 914 B After Width: | Height: | Size: 933 B |
|
Before Width: | Height: | Size: 1018 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 881 B After Width: | Height: | Size: 933 B |
|
Before Width: | Height: | Size: 953 B After Width: | Height: | Size: 1008 B |
|
Before Width: | Height: | Size: 829 B After Width: | Height: | Size: 848 B |
|
Before Width: | Height: | Size: 936 B After Width: | Height: | Size: 957 B |
|
Before Width: | Height: | Size: 856 B After Width: | Height: | Size: 866 B |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 1014 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1018 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
BIN
sprites/birb.png
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,10 +1,6 @@
|
|||||||
import species from "../species.js"
|
import species from "../species.js"
|
||||||
|
|
||||||
/**
|
export const PALETTE = Object.freeze(/** @type {const} */ ({
|
||||||
* Palette color names
|
|
||||||
* @type {Record<string, string>}
|
|
||||||
*/
|
|
||||||
export const PALETTE = {
|
|
||||||
THEME_HIGHLIGHT: "theme-highlight",
|
THEME_HIGHLIGHT: "theme-highlight",
|
||||||
TRANSPARENT: "transparent",
|
TRANSPARENT: "transparent",
|
||||||
OUTLINE: "outline",
|
OUTLINE: "outline",
|
||||||
@@ -15,23 +11,36 @@ export const PALETTE = {
|
|||||||
FACE: "face",
|
FACE: "face",
|
||||||
HOOD: "hood",
|
HOOD: "hood",
|
||||||
EYEBROW: "eyebrow",
|
EYEBROW: "eyebrow",
|
||||||
|
UPPER_EYELID: "upper-eyelid",
|
||||||
|
UPPER_CORNER_EYE: "upper-corner-eye",
|
||||||
|
BEHIND_EYE: "behind-eye",
|
||||||
|
CORNER_EYE: "corner-eye",
|
||||||
|
TEMPLE: "temple",
|
||||||
|
LOWER_EYELID: "lower-eyelid",
|
||||||
NOSE: "nose",
|
NOSE: "nose",
|
||||||
|
NOSE_TIP: "nose-tip",
|
||||||
CHEEK: "cheek",
|
CHEEK: "cheek",
|
||||||
SCRUFF: "scruff",
|
SCRUFF: "scruff",
|
||||||
|
CHIN: "chin",
|
||||||
COLLAR: "collar",
|
COLLAR: "collar",
|
||||||
|
COLLAR_SCRUFF: "collar-scruff",
|
||||||
BELLY: "belly",
|
BELLY: "belly",
|
||||||
UNDERBELLY: "underbelly",
|
UNDERBELLY: "underbelly",
|
||||||
WING: "wing",
|
WING: "wing",
|
||||||
|
SHOULDER: "shoulder",
|
||||||
|
WING_SPOTS: "wing-spots",
|
||||||
WING_EDGE: "wing-edge",
|
WING_EDGE: "wing-edge",
|
||||||
HEART: "heart",
|
HEART: "heart",
|
||||||
HEART_BORDER: "heart-border",
|
HEART_BORDER: "heart-border",
|
||||||
HEART_SHINE: "heart-shine",
|
HEART_SHINE: "heart-shine",
|
||||||
FEATHER_SPINE: "feather-spine",
|
FEATHER_SPINE: "feather-spine",
|
||||||
};
|
}));
|
||||||
|
|
||||||
|
/** @typedef {typeof PALETTE[keyof typeof PALETTE]} PaletteColor */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping of sprite sheet colors to palette colors
|
* Mapping of sprite sheet colors to palette colors
|
||||||
* @type {Record<string, string>}
|
* @type {Record<string, PaletteColor>}
|
||||||
*/
|
*/
|
||||||
export const SPRITE_SHEET_COLOR_MAP = {
|
export const SPRITE_SHEET_COLOR_MAP = {
|
||||||
"transparent": PALETTE.TRANSPARENT,
|
"transparent": PALETTE.TRANSPARENT,
|
||||||
@@ -44,13 +53,24 @@ export const SPRITE_SHEET_COLOR_MAP = {
|
|||||||
"#639bff": PALETTE.FACE,
|
"#639bff": PALETTE.FACE,
|
||||||
"#99e550": PALETTE.HOOD,
|
"#99e550": PALETTE.HOOD,
|
||||||
"#ff5573": PALETTE.EYEBROW,
|
"#ff5573": PALETTE.EYEBROW,
|
||||||
|
"#ff768e": PALETTE.UPPER_EYELID,
|
||||||
|
"#ff90a4": PALETTE.UPPER_CORNER_EYE,
|
||||||
|
"#ff2c88": PALETTE.BEHIND_EYE,
|
||||||
|
"#e34f9c": PALETTE.CORNER_EYE,
|
||||||
|
"#b53477": PALETTE.TEMPLE,
|
||||||
|
"#ae65f1": PALETTE.LOWER_EYELID,
|
||||||
"#d95763": PALETTE.NOSE,
|
"#d95763": PALETTE.NOSE,
|
||||||
|
"#b93844": PALETTE.NOSE_TIP,
|
||||||
"#ff67a9": PALETTE.CHEEK,
|
"#ff67a9": PALETTE.CHEEK,
|
||||||
"#c5e550": PALETTE.SCRUFF,
|
"#c5e550": PALETTE.SCRUFF,
|
||||||
|
"#b87af1": PALETTE.CHIN,
|
||||||
"#ffe955": PALETTE.COLLAR,
|
"#ffe955": PALETTE.COLLAR,
|
||||||
|
"#f8ff55": PALETTE.COLLAR_SCRUFF,
|
||||||
"#f8b143": PALETTE.BELLY,
|
"#f8b143": PALETTE.BELLY,
|
||||||
"#ec8637": PALETTE.UNDERBELLY,
|
"#ec8637": PALETTE.UNDERBELLY,
|
||||||
"#578ae6": PALETTE.WING,
|
"#578ae6": PALETTE.WING,
|
||||||
|
"#55d1f3": PALETTE.SHOULDER,
|
||||||
|
"#90b0e8": PALETTE.WING_SPOTS,
|
||||||
"#326ed9": PALETTE.WING_EDGE,
|
"#326ed9": PALETTE.WING_EDGE,
|
||||||
"#c82e2e": PALETTE.HEART,
|
"#c82e2e": PALETTE.HEART,
|
||||||
"#501a1a": PALETTE.HEART_BORDER,
|
"#501a1a": PALETTE.HEART_BORDER,
|
||||||
@@ -58,16 +78,52 @@ export const SPRITE_SHEET_COLOR_MAP = {
|
|||||||
"#373737": PALETTE.FEATHER_SPINE,
|
"#373737": PALETTE.FEATHER_SPINE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Partial<Record<PaletteColor, PaletteColor>>}
|
||||||
|
*/
|
||||||
|
export const DEFAULT_COLOR_OVERRIDES = {
|
||||||
|
[PALETTE.HOOD]: PALETTE.FACE,
|
||||||
|
[PALETTE.EYEBROW]: PALETTE.FACE,
|
||||||
|
[PALETTE.UPPER_EYELID]: PALETTE.EYEBROW,
|
||||||
|
[PALETTE.UPPER_CORNER_EYE]: PALETTE.EYEBROW,
|
||||||
|
[PALETTE.BEHIND_EYE]: PALETTE.FACE,
|
||||||
|
[PALETTE.CORNER_EYE]: PALETTE.FACE,
|
||||||
|
[PALETTE.TEMPLE]: PALETTE.FACE,
|
||||||
|
[PALETTE.LOWER_EYELID]: PALETTE.FACE,
|
||||||
|
[PALETTE.NOSE]: PALETTE.FACE,
|
||||||
|
[PALETTE.NOSE_TIP]: PALETTE.NOSE,
|
||||||
|
[PALETTE.CHEEK]: PALETTE.FACE,
|
||||||
|
[PALETTE.SCRUFF]: PALETTE.FACE,
|
||||||
|
[PALETTE.CHIN]: PALETTE.FACE,
|
||||||
|
[PALETTE.COLLAR]: PALETTE.FACE,
|
||||||
|
[PALETTE.COLLAR_SCRUFF]: PALETTE.COLLAR,
|
||||||
|
[PALETTE.WING_SPOTS]: PALETTE.WING,
|
||||||
|
[PALETTE.SHOULDER]: PALETTE.WING,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RARITY = Object.freeze(/** @type {const} */ ({
|
||||||
|
COMMON: "common",
|
||||||
|
UNCOMMON: "uncommon"
|
||||||
|
}));
|
||||||
|
|
||||||
|
/** @typedef {typeof RARITY[keyof typeof RARITY]} Rarity */
|
||||||
|
|
||||||
export class BirdType {
|
export class BirdType {
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {string} description
|
* @param {string} description
|
||||||
|
* @param {string} latinName
|
||||||
|
* @param {string} url
|
||||||
* @param {Record<string, string>} colors
|
* @param {Record<string, string>} colors
|
||||||
* @param {string[]} [tags]
|
* @param {string[]} [tags]
|
||||||
|
* @param {Rarity} [rarity]
|
||||||
*/
|
*/
|
||||||
constructor(name, description, colors, tags = []) {
|
constructor(name, description, latinName, url, colors, tags = [], rarity = RARITY.COMMON) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
this.latinName = latinName;
|
||||||
|
this.url = url;
|
||||||
const defaultColors = {
|
const defaultColors = {
|
||||||
[PALETTE.TRANSPARENT]: "transparent",
|
[PALETTE.TRANSPARENT]: "transparent",
|
||||||
[PALETTE.OUTLINE]: "#000000",
|
[PALETTE.OUTLINE]: "#000000",
|
||||||
@@ -79,15 +135,27 @@ export class BirdType {
|
|||||||
[PALETTE.HEART_SHINE]: "#ff6b6b",
|
[PALETTE.HEART_SHINE]: "#ff6b6b",
|
||||||
[PALETTE.FEATHER_SPINE]: "#373737",
|
[PALETTE.FEATHER_SPINE]: "#373737",
|
||||||
[PALETTE.HOOD]: colors.face,
|
[PALETTE.HOOD]: colors.face,
|
||||||
[PALETTE.EYEBROW]: colors.face,
|
[PALETTE.EYEBROW]: colors.face,
|
||||||
|
[PALETTE.UPPER_EYELID]: colors.eyebrow || colors.face,
|
||||||
|
[PALETTE.UPPER_CORNER_EYE]: colors.eyebrow || colors.face,
|
||||||
|
[PALETTE.BEHIND_EYE]: colors.face,
|
||||||
|
[PALETTE.CORNER_EYE]: colors.face,
|
||||||
|
[PALETTE.TEMPLE]: colors.face,
|
||||||
|
[PALETTE.LOWER_EYELID]: colors.face,
|
||||||
[PALETTE.NOSE]: colors.face,
|
[PALETTE.NOSE]: colors.face,
|
||||||
|
[PALETTE.NOSE_TIP]: colors.nose || colors.face,
|
||||||
[PALETTE.CHEEK]: colors.face,
|
[PALETTE.CHEEK]: colors.face,
|
||||||
[PALETTE.SCRUFF]: colors.face,
|
[PALETTE.SCRUFF]: colors.face,
|
||||||
|
[PALETTE.CHIN]: colors.face,
|
||||||
[PALETTE.COLLAR]: colors.face,
|
[PALETTE.COLLAR]: colors.face,
|
||||||
|
[PALETTE.COLLAR_SCRUFF]: colors.collar || colors.face,
|
||||||
|
[PALETTE.SHOULDER]: colors.wing,
|
||||||
};
|
};
|
||||||
/** @type {Record<string, string>} */
|
/** @type {Record<string, string>} */
|
||||||
this.colors = { ...defaultColors, ...colors, [PALETTE.THEME_HIGHLIGHT]: colors[PALETTE.THEME_HIGHLIGHT] ?? colors.hood ?? colors.face };
|
this.colors = { ...defaultColors, ...colors, [PALETTE.THEME_HIGHLIGHT]: colors[PALETTE.THEME_HIGHLIGHT] ?? colors.hood ?? colors.face };
|
||||||
this.tags = tags;
|
this.tags = tags;
|
||||||
|
/** @type {Rarity} */
|
||||||
|
this.rarity = rarity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +219,6 @@ export function loadSpriteSheetPixels(src, templateColors = true) {
|
|||||||
export const SPECIES = Object.fromEntries(
|
export const SPECIES = Object.fromEntries(
|
||||||
Object.entries(species).map(([id, data]) => [
|
Object.entries(species).map(([id, data]) => [
|
||||||
id,
|
id,
|
||||||
new BirdType(data.name, data.description, data.colors, data.tags ?? []),
|
new BirdType(data.name, data.description, data.latinName, data.url, data.colors, data.tags, data.rarity)
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
@@ -24,8 +24,8 @@ import {
|
|||||||
} from './shared.js';
|
} from './shared.js';
|
||||||
import {
|
import {
|
||||||
PALETTE,
|
PALETTE,
|
||||||
SPRITE_SHEET_COLOR_MAP,
|
|
||||||
SPECIES,
|
SPECIES,
|
||||||
|
RARITY,
|
||||||
loadSpriteSheetPixels,
|
loadSpriteSheetPixels,
|
||||||
} from './animation/sprites.js';
|
} from './animation/sprites.js';
|
||||||
import {
|
import {
|
||||||
@@ -110,6 +110,7 @@ const HOP_DELAY = 500;
|
|||||||
const HOP_CHANCE = 1 / (60 * 2.5); // Every 2.5 seconds
|
const HOP_CHANCE = 1 / (60 * 2.5); // Every 2.5 seconds
|
||||||
const FOCUS_SWITCH_CHANCE = 1 / (60 * 20); // Every 20 seconds
|
const FOCUS_SWITCH_CHANCE = 1 / (60 * 20); // Every 20 seconds
|
||||||
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // Every 2 hours
|
const FEATHER_CHANCE = 1 / (60 * 60 * 60 * 2); // Every 2 hours
|
||||||
|
const UNCOMMON_FEATHER_CHANCE = 0.15; // 15% of feathers are uncommon
|
||||||
const HAT_CHANCE = 1 / (60 * 60 * 25); // Every 25 minutes
|
const HAT_CHANCE = 1 / (60 * 60 * 25); // Every 25 minutes
|
||||||
|
|
||||||
// Feathers
|
// Feathers
|
||||||
@@ -406,7 +407,9 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
|||||||
|
|
||||||
setInterval(update, UPDATE_INTERVAL);
|
setInterval(update, UPDATE_INTERVAL);
|
||||||
|
|
||||||
focusOnElement(true);
|
flyToElement(true);
|
||||||
|
// TODO: Remove
|
||||||
|
insertFieldGuide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
@@ -425,11 +428,11 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
|||||||
// Idle for a while, do something
|
// Idle for a while, do something
|
||||||
if (focusedElement === null) {
|
if (focusedElement === null) {
|
||||||
// Fly to an element
|
// Fly to an element
|
||||||
focusOnElement();
|
flyToElement();
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
} else if (Math.random() < FOCUS_SWITCH_CHANCE) {
|
} else if (Math.random() < FOCUS_SWITCH_CHANCE) {
|
||||||
// Fly to another element if idle for a longer while
|
// Fly to another element if idle for a longer while
|
||||||
focusOnElement();
|
flyToElement();
|
||||||
lastActionTimestamp = Date.now();
|
lastActionTimestamp = Date.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,7 +468,7 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
|||||||
// Update the bird's position
|
// Update the bird's position
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE) {
|
||||||
if (focusedElement && !isWithinHorizontalBounds()) {
|
if (focusedElement && !isWithinHorizontalBounds()) {
|
||||||
flySomewhere();
|
flyToElement();
|
||||||
}
|
}
|
||||||
birdY = getFocusedY();
|
birdY = getFocusedY();
|
||||||
} else if (currentState === States.FLYING) {
|
} else if (currentState === States.FLYING) {
|
||||||
@@ -481,7 +484,7 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
|||||||
startY += targetY - oldTargetY;
|
startY += targetY - oldTargetY;
|
||||||
if (targetY < 0 || targetY > getWindowHeight()) {
|
if (targetY < 0 || targetY > getWindowHeight()) {
|
||||||
// Fly to another element or the ground if the focused element moves out of bounds
|
// Fly to another element or the ground if the focused element moves out of bounds
|
||||||
flySomewhere();
|
flyToElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (birb.draw(SPECIES[currentSpecies], currentHat)) {
|
if (birb.draw(SPECIES[currentSpecies], currentHat)) {
|
||||||
@@ -562,7 +565,8 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
|||||||
if (document.querySelector("#" + FEATHER_ID)) {
|
if (document.querySelector("#" + FEATHER_ID)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const speciesToUnlock = Object.keys(SPECIES).filter((species) => !unlockedSpecies.includes(species));
|
const rarity = Math.random() < UNCOMMON_FEATHER_CHANCE ? RARITY.UNCOMMON : RARITY.COMMON;
|
||||||
|
const speciesToUnlock = Object.keys(SPECIES).filter((species) => !unlockedSpecies.includes(species) && SPECIES[species].rarity === rarity);
|
||||||
if (speciesToUnlock.length === 0) {
|
if (speciesToUnlock.length === 0) {
|
||||||
// No more species to unlock
|
// No more species to unlock
|
||||||
return;
|
return;
|
||||||
@@ -758,9 +762,23 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
|||||||
removeWardrobe();
|
removeWardrobe();
|
||||||
|
|
||||||
const contentContainer = document.createElement("div");
|
const contentContainer = document.createElement("div");
|
||||||
const content = makeElement("birb-grid-content");
|
const familiarBirds = makeElement("birb-grid-content");
|
||||||
|
const uncommonBirds = makeElement("birb-grid-content");
|
||||||
|
|
||||||
|
const familiarLabel = document.createElement("div");
|
||||||
|
familiarLabel.className = "birb-field-guide-section-label";
|
||||||
|
familiarLabel.textContent = `----- Familiar ${birdBirb()}s -----`;
|
||||||
|
|
||||||
|
const uncommonLabel = document.createElement("div");
|
||||||
|
uncommonLabel.className = "birb-field-guide-section-label";
|
||||||
|
uncommonLabel.textContent = `----- Uncommon ${birdBirb()}s -----`;
|
||||||
|
uncommonLabel.title = "Arbitrarily classified birds that are a little harder to find, but worth the wait!";
|
||||||
|
|
||||||
const description = makeElement("birb-field-guide-description");
|
const description = makeElement("birb-field-guide-description");
|
||||||
contentContainer.appendChild(content);
|
contentContainer.appendChild(familiarLabel);
|
||||||
|
contentContainer.appendChild(familiarBirds);
|
||||||
|
contentContainer.appendChild(uncommonLabel);
|
||||||
|
contentContainer.appendChild(uncommonBirds);
|
||||||
contentContainer.appendChild(description);
|
contentContainer.appendChild(description);
|
||||||
|
|
||||||
const fieldGuide = createWindow(
|
const fieldGuide = createWindow(
|
||||||
@@ -776,14 +794,26 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
|||||||
const boldName = document.createElement("b");
|
const boldName = document.createElement("b");
|
||||||
boldName.textContent = type.name;
|
boldName.textContent = type.name;
|
||||||
|
|
||||||
const spacer = document.createElement("div");
|
|
||||||
spacer.style.height = "0.3em";
|
const spacerOne = document.createElement("div");
|
||||||
|
spacerOne.style.height = "0.3em";
|
||||||
|
|
||||||
|
const latinName = document.createElement("a");
|
||||||
|
latinName.className = "birb-field-guide-latin-name";
|
||||||
|
latinName.textContent = type.latinName;
|
||||||
|
latinName.href = type.url;
|
||||||
|
latinName.target = "_blank";
|
||||||
|
|
||||||
|
const spacerTwo = document.createElement("div");
|
||||||
|
spacerTwo.style.height = "0.4em";
|
||||||
|
|
||||||
const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description);
|
const descText = document.createTextNode(!unlocked ? "Not yet unlocked" : type.description);
|
||||||
|
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
fragment.appendChild(boldName);
|
fragment.appendChild(boldName);
|
||||||
fragment.appendChild(spacer);
|
fragment.appendChild(spacerOne);
|
||||||
|
fragment.appendChild(latinName);
|
||||||
|
fragment.appendChild(spacerTwo);
|
||||||
fragment.appendChild(descText);
|
fragment.appendChild(descText);
|
||||||
|
|
||||||
return fragment;
|
return fragment;
|
||||||
@@ -805,7 +835,11 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
|||||||
}
|
}
|
||||||
birb.getFrames().base.draw(speciesCtx, Directions.RIGHT, CANVAS_PIXEL_SIZE, type.colors, type.tags);
|
birb.getFrames().base.draw(speciesCtx, Directions.RIGHT, CANVAS_PIXEL_SIZE, type.colors, type.tags);
|
||||||
speciesElement.appendChild(speciesCanvas);
|
speciesElement.appendChild(speciesCanvas);
|
||||||
content.appendChild(speciesElement);
|
let section = familiarBirds;
|
||||||
|
if (type.rarity === RARITY.UNCOMMON) {
|
||||||
|
section = uncommonBirds;
|
||||||
|
}
|
||||||
|
section.appendChild(speciesElement);
|
||||||
if (unlocked) {
|
if (unlocked) {
|
||||||
onClick(speciesElement, () => {
|
onClick(speciesElement, () => {
|
||||||
switchSpecies(id);
|
switchSpecies(id);
|
||||||
@@ -987,26 +1021,6 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
|||||||
return getWindowHeight() - focusedBounds.top;
|
return getWindowHeight() - focusedBounds.top;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fly to either an element or the ground
|
|
||||||
*/
|
|
||||||
function flySomewhere() {
|
|
||||||
// On mobile, always prefer to focus on an element
|
|
||||||
// If not mobile, 50% chance to focus on ground
|
|
||||||
// if ((!isMobile() && coinFlip()) || !focusOnElement()) {
|
|
||||||
// focusOnGround();
|
|
||||||
// }
|
|
||||||
if (!focusOnElement()) {
|
|
||||||
focusOnGround();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusOnGround() {
|
|
||||||
focusedElement = null;
|
|
||||||
updateFocusedElementBounds();
|
|
||||||
flyTo(Math.random() * window.innerWidth, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {HTMLElement|null} The random element, or null if no valid element was found
|
* @returns {HTMLElement|null} The random element, or null if no valid element was found
|
||||||
*/
|
*/
|
||||||
@@ -1044,20 +1058,20 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Focus on an element within the viewport
|
* Fly to an element within the viewport
|
||||||
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
* @param {boolean} [teleport] Whether to teleport to the element instead of flying
|
||||||
* @returns Whether an element to focus on was found
|
* @returns Whether an element to fly to was found (null if flying to the ground)
|
||||||
*/
|
*/
|
||||||
function focusOnElement(teleport = false) {
|
function flyToElement(teleport = false) {
|
||||||
if (frozen) {
|
if (frozen) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const previousElement = focusedElement;
|
||||||
focusedElement = getRandomValidElement();
|
focusedElement = getRandomValidElement();
|
||||||
log("Focusing on element: ", focusedElement);
|
|
||||||
updateFocusedElementBounds();
|
updateFocusedElementBounds();
|
||||||
if (teleport) {
|
if (teleport) {
|
||||||
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
teleportTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
} else {
|
} else if (focusedElement !== previousElement) {
|
||||||
flyTo(getFocusedElementRandomX(), getFocusedY());
|
flyTo(getFocusedElementRandomX(), getFocusedY());
|
||||||
}
|
}
|
||||||
return focusedElement !== null;
|
return focusedElement !== null;
|
||||||
|
|||||||
65
src/sound.js
@@ -8,36 +8,41 @@ export class Birdsong {
|
|||||||
audioContext;
|
audioContext;
|
||||||
|
|
||||||
chirp() {
|
chirp() {
|
||||||
if (!this.audioContext) {
|
const count = Math.floor(1 + Math.random() * 1.5);
|
||||||
this.audioContext = new AudioContext();
|
for (let i = 0; i < count; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.audioContext) {
|
||||||
|
this.audioContext = new AudioContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TIMES = [0, 0.06, 0.10, 0.15];
|
||||||
|
const FREQUENCIES = [2200,
|
||||||
|
3500 + Math.random() * 600 * count,
|
||||||
|
2100 + Math.random() * 200 * count,
|
||||||
|
1600 + Math.random() * 400 * count];
|
||||||
|
const VOLUMES = [0.00005, 0.165, 0.165, 0.0001];
|
||||||
|
|
||||||
|
const oscillator = this.audioContext.createOscillator();
|
||||||
|
oscillator.type = "sine";
|
||||||
|
const gain = this.audioContext.createGain();
|
||||||
|
oscillator.connect(gain);
|
||||||
|
gain.connect(this.audioContext.destination);
|
||||||
|
|
||||||
|
const now = this.audioContext.currentTime;
|
||||||
|
for (let i = 0; i < TIMES.length; i++) {
|
||||||
|
const time = TIMES[i] + now;
|
||||||
|
if (i === 0) {
|
||||||
|
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
|
||||||
|
gain.gain.setValueAtTime(VOLUMES[i], time);
|
||||||
|
} else {
|
||||||
|
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
|
||||||
|
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oscillator.start(now);
|
||||||
|
oscillator.stop(now + TIMES[TIMES.length - 1]);
|
||||||
|
}, i * 120);
|
||||||
}
|
}
|
||||||
|
|
||||||
const TIMES = [0, 0.06, 0.10, 0.15];
|
|
||||||
const FREQUENCIES = [2200,
|
|
||||||
3500 + Math.random() * 600,
|
|
||||||
2100 + Math.random() * 200,
|
|
||||||
1600 + Math.random() * 400];
|
|
||||||
const VOLUMES = [0.0001, 0.2, 0.2, 0.0001];
|
|
||||||
|
|
||||||
const oscillator = this.audioContext.createOscillator();
|
|
||||||
oscillator.type = "sine";
|
|
||||||
const gain = this.audioContext.createGain();
|
|
||||||
oscillator.connect(gain);
|
|
||||||
gain.connect(this.audioContext.destination);
|
|
||||||
|
|
||||||
const now = this.audioContext.currentTime;
|
|
||||||
for (let i = 0; i < TIMES.length; i++) {
|
|
||||||
const time = TIMES[i] + now;
|
|
||||||
if (i === 0) {
|
|
||||||
oscillator.frequency.setValueAtTime(FREQUENCIES[i], time);
|
|
||||||
gain.gain.setValueAtTime(VOLUMES[i], time);
|
|
||||||
} else {
|
|
||||||
oscillator.frequency.exponentialRampToValueAtTime(FREQUENCIES[i], time);
|
|
||||||
gain.gain.exponentialRampToValueAtTime(VOLUMES[i], time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
oscillator.start(now);
|
|
||||||
oscillator.stop(now + TIMES[TIMES.length - 1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
275
src/species.js
@@ -9,6 +9,8 @@ export default {
|
|||||||
"bluebird": {
|
"bluebird": {
|
||||||
"name": "Eastern Bluebird",
|
"name": "Eastern Bluebird",
|
||||||
"description": "Native to North American and very social, though can be timid around people.",
|
"description": "Native to North American and very social, though can be timid around people.",
|
||||||
|
"latinName": "Sialia sialis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Eastern_bluebird",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#639bff",
|
"face": "#639bff",
|
||||||
@@ -21,6 +23,8 @@ export default {
|
|||||||
"shimaEnaga": {
|
"shimaEnaga": {
|
||||||
"name": "Shima Enaga",
|
"name": "Shima Enaga",
|
||||||
"description": "Small, fluffy birds found in the snowy regions of Japan, these birds are highly sought after by ornithologists and nature photographers.",
|
"description": "Small, fluffy birds found in the snowy regions of Japan, these birds are highly sought after by ornithologists and nature photographers.",
|
||||||
|
"latinName": "Aegithalos caudatus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Long-tailed_tit",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#ffffff",
|
"face": "#ffffff",
|
||||||
@@ -34,6 +38,8 @@ export default {
|
|||||||
"tuftedTitmouse": {
|
"tuftedTitmouse": {
|
||||||
"name": "Tufted Titmouse",
|
"name": "Tufted Titmouse",
|
||||||
"description": "Native to the eastern United States, full of personality, and notably my wife's favorite bird.",
|
"description": "Native to the eastern United States, full of personality, and notably my wife's favorite bird.",
|
||||||
|
"latinName": "Baeolophus bicolor",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Tufted_titmouse",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#c7cad7",
|
"face": "#c7cad7",
|
||||||
@@ -50,6 +56,8 @@ export default {
|
|||||||
"europeanRobin": {
|
"europeanRobin": {
|
||||||
"name": "European Robin",
|
"name": "European Robin",
|
||||||
"description": "Native to western Europe, this is the quintessential robin. Quite friendly, you'll often find them searching for worms.",
|
"description": "Native to western Europe, this is the quintessential robin. Quite friendly, you'll often find them searching for worms.",
|
||||||
|
"latinName": "Erithacus rubecula",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/European_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#ffaf34",
|
"face": "#ffaf34",
|
||||||
@@ -64,6 +72,8 @@ export default {
|
|||||||
"redCardinal": {
|
"redCardinal": {
|
||||||
"name": "Red Cardinal",
|
"name": "Red Cardinal",
|
||||||
"description": "Native to the eastern United States, this strikingly red bird is hard to miss.",
|
"description": "Native to the eastern United States, this strikingly red bird is hard to miss.",
|
||||||
|
"latinName": "Cardinalis cardinalis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_cardinal",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#d93619",
|
"beak": "#d93619",
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
@@ -83,6 +93,8 @@ export default {
|
|||||||
"americanGoldfinch": {
|
"americanGoldfinch": {
|
||||||
"name": "American Goldfinch",
|
"name": "American Goldfinch",
|
||||||
"description": "Coloured a brilliant yellow, this bird feeds almost entirely on the seeds of plants such as thistle, sunflowers, and coneflowers.",
|
"description": "Coloured a brilliant yellow, this bird feeds almost entirely on the seeds of plants such as thistle, sunflowers, and coneflowers.",
|
||||||
|
"latinName": "Spinus tristis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/American_goldfinch",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#ffaf34",
|
"beak": "#ffaf34",
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
@@ -99,6 +111,8 @@ export default {
|
|||||||
"barnSwallow": {
|
"barnSwallow": {
|
||||||
"name": "Barn Swallow",
|
"name": "Barn Swallow",
|
||||||
"description": "Agile birds that often roost in man-made structures, these birds are known to build nests near Ospreys for protection.",
|
"description": "Agile birds that often roost in man-made structures, these birds are known to build nests near Ospreys for protection.",
|
||||||
|
"latinName": "Hirundo rustica",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Barn_swallow",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#db7c4d",
|
"face": "#db7c4d",
|
||||||
@@ -112,6 +126,8 @@ export default {
|
|||||||
"mistletoebird": {
|
"mistletoebird": {
|
||||||
"name": "Mistletoebird",
|
"name": "Mistletoebird",
|
||||||
"description": "Native to Australia, these birds eat mainly mistletoe and in turn spread the seeds far and wide.",
|
"description": "Native to Australia, these birds eat mainly mistletoe and in turn spread the seeds far and wide.",
|
||||||
|
"latinName": "Dicaeum hirundinaceum",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Mistletoebird",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#6c6a7c",
|
"foot": "#6c6a7c",
|
||||||
"face": "#352e6d",
|
"face": "#352e6d",
|
||||||
@@ -121,22 +137,11 @@ export default {
|
|||||||
"wing-edge": "#282065"
|
"wing-edge": "#282065"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redAvadavat": {
|
|
||||||
"name": "Red Avadavat",
|
|
||||||
"description": "Native to India and southeast Asia, these birds are also known as Strawberry Finches due to their speckled plumage.",
|
|
||||||
"colors": {
|
|
||||||
"beak": "#f71919",
|
|
||||||
"foot": "#af7575",
|
|
||||||
"face": "#cb092b",
|
|
||||||
"belly": "#ae1724",
|
|
||||||
"underbelly": "#831b24",
|
|
||||||
"wing": "#7e3030",
|
|
||||||
"wing-edge": "#490f0f"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scarletRobin": {
|
"scarletRobin": {
|
||||||
"name": "Scarlet Robin",
|
"name": "Scarlet Robin",
|
||||||
"description": "Native to Australia, this striking robin can be found in Eucalyptus forests.",
|
"description": "Native to Australia, this striking robin can be found in Eucalyptus forests.",
|
||||||
|
"latinName": "Petroica boodang",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Scarlet_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#494949",
|
"foot": "#494949",
|
||||||
"face": "#3d3d3d",
|
"face": "#3d3d3d",
|
||||||
@@ -144,12 +149,15 @@ export default {
|
|||||||
"underbelly": "#dcdcdc",
|
"underbelly": "#dcdcdc",
|
||||||
"wing": "#2b2b2b",
|
"wing": "#2b2b2b",
|
||||||
"wing-edge": "#ebebeb",
|
"wing-edge": "#ebebeb",
|
||||||
|
"nose": "#ebebeb",
|
||||||
"theme-highlight": "#fc5633"
|
"theme-highlight": "#fc5633"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"americanRobin": {
|
"americanRobin": {
|
||||||
"name": "American Robin",
|
"name": "American Robin",
|
||||||
"description": "While not a true robin, this social North American bird is so named due to its orange coloring. It seems unbothered by nearby humans.",
|
"description": "While not a true robin, this social North American bird is so named due to its orange coloring. It seems unbothered by nearby humans.",
|
||||||
|
"latinName": "Turdus migratorius",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/American_robin",
|
||||||
"colors": {
|
"colors": {
|
||||||
"beak": "#e89f30",
|
"beak": "#e89f30",
|
||||||
"foot": "#9f8075",
|
"foot": "#9f8075",
|
||||||
@@ -164,6 +172,8 @@ export default {
|
|||||||
"carolinaWren": {
|
"carolinaWren": {
|
||||||
"name": "Carolina Wren",
|
"name": "Carolina Wren",
|
||||||
"description": "Native to the eastern United States, these little birds are known for their curious and energetic nature.",
|
"description": "Native to the eastern United States, these little birds are known for their curious and energetic nature.",
|
||||||
|
"latinName": "Thryothorus ludovicianus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Carolina_wren",
|
||||||
"colors": {
|
"colors": {
|
||||||
"foot": "#af8e75",
|
"foot": "#af8e75",
|
||||||
"face": "#edc7a9",
|
"face": "#edc7a9",
|
||||||
@@ -174,5 +184,244 @@ export default {
|
|||||||
"wing": "#c58a5b",
|
"wing": "#c58a5b",
|
||||||
"wing-edge": "#866348"
|
"wing-edge": "#866348"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"blackCappedChickadee": {
|
||||||
|
"name": "Black-capped Chickadee",
|
||||||
|
"description": "Native to North America, these small and curious birds are known for their distinctive call from which they get their name.",
|
||||||
|
"latinName": "Poecile atricapillus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Black-capped_chickadee",
|
||||||
|
"colors": {
|
||||||
|
"hood": "#363636",
|
||||||
|
"cheek": "#363636",
|
||||||
|
"eyebrow": "#363636",
|
||||||
|
"nose": "#363636",
|
||||||
|
"collar": "#363636",
|
||||||
|
"belly": "#d6d4cf",
|
||||||
|
"underbelly": "#cfc5b4",
|
||||||
|
"face": "#eaeaea",
|
||||||
|
"wing": "#8f8e9a",
|
||||||
|
"wing-edge": "#706f7d",
|
||||||
|
"scruff": "#8f8e9a",
|
||||||
|
"foot": "#535259"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"blueJay": {
|
||||||
|
"name": "Blue Jay",
|
||||||
|
"description": "This loud and rambunctious bird is native to North America and is known for challenging anything in its path.",
|
||||||
|
"latinName": "Cyanocitta cristata",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Blue_jay",
|
||||||
|
"colors": {
|
||||||
|
"foot": "#5a626b",
|
||||||
|
"face": "#ebf2ff",
|
||||||
|
"belly": "#e5ecfa",
|
||||||
|
"underbelly": "#c4cbd6",
|
||||||
|
"wing": "#5890ff",
|
||||||
|
"wing-edge": "#3a77e8",
|
||||||
|
"hood": "#6391e8",
|
||||||
|
"nose": "#6391e8",
|
||||||
|
"collar": "#2e3136",
|
||||||
|
"scruff": "#6391e8"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"tuft"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"darkEyedJunco": {
|
||||||
|
"name": "Dark-eyed Junco",
|
||||||
|
"description": "Native across North America, these social birds will often be seen hopping along the ground in winter.",
|
||||||
|
"latinName": "Junco hyemalis",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Dark-eyed_junco",
|
||||||
|
"colors": {
|
||||||
|
"face": "#55565e",
|
||||||
|
"wing": "#5c5f69",
|
||||||
|
"wing-edge": "#444547",
|
||||||
|
"belly": "#6c7180",
|
||||||
|
"underbelly": "#b8bbcc",
|
||||||
|
"foot": "#87776d",
|
||||||
|
"beak": "#ab8a98"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"houseFinch": {
|
||||||
|
"name": "House Finch",
|
||||||
|
"description": "Native to North America, these highly social birds sing cheerful songs and are often seen at bird feeders.",
|
||||||
|
"latinName": "Haemorhous mexicanus",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/House_finch",
|
||||||
|
"colors": {
|
||||||
|
"face": "#cc3a3f",
|
||||||
|
"wing": "#ae8e78",
|
||||||
|
"wing-edge": "#8f6c54",
|
||||||
|
"belly": "#d97c77",
|
||||||
|
"underbelly": "#c5a489",
|
||||||
|
"foot": "#705b4c",
|
||||||
|
"beak": "#cf8479",
|
||||||
|
"hood": "#b02f35",
|
||||||
|
"nose": "#ab2b31",
|
||||||
|
"theme-highlight": "#ef444d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pigeon": {
|
||||||
|
"name": "Rock Pigeon",
|
||||||
|
"description": "Descended from the Rock Dove, these once domesticated birds are often found in cities worldwide. Quite friendly and intelligent, they were favored companions of Nikola Tesla.",
|
||||||
|
"latinName": "Columba livia",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Rock_dove",
|
||||||
|
"colors": {
|
||||||
|
"foot": "#ef6e5b",
|
||||||
|
"face": "#5a6c91",
|
||||||
|
"wing-edge": "#65686e",
|
||||||
|
"nose": "#ebebeb",
|
||||||
|
"belly": "#977699",
|
||||||
|
"underbelly": "#b0b3ba",
|
||||||
|
"wing": "#c7cbd4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redAvadavat": {
|
||||||
|
"name": "Red Avadavat",
|
||||||
|
"description": "Native to India and southeast Asia, these birds are also known as Strawberry Finches due to their speckled plumage.",
|
||||||
|
"latinName": "Amandava amandava",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_avadavat",
|
||||||
|
"colors": {
|
||||||
|
"beak": "#f71919",
|
||||||
|
"foot": "#af7575",
|
||||||
|
"face": "#cb092b",
|
||||||
|
"belly": "#ae1724",
|
||||||
|
"underbelly": "#831b24",
|
||||||
|
"wing": "#7e3030",
|
||||||
|
"wing-edge": "#490f0f",
|
||||||
|
"wing-spots": "#e8e4e4",
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"pinkRobin": {
|
||||||
|
"name": "Pink Robin",
|
||||||
|
"description": "Native to Australia, these bubblegum-pink puffballs are quieter than most, instead relying on their vibrant colours to attract partners.",
|
||||||
|
"latinName": "Petroica rodinogaster",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Pink_robin",
|
||||||
|
"colors": {
|
||||||
|
"face": "#403a46",
|
||||||
|
"wing": "#38333d",
|
||||||
|
"wing-edge": "#252325",
|
||||||
|
"underbelly": "#ff7eb8",
|
||||||
|
"belly": "#ff6eaf",
|
||||||
|
"foot": "#3c393c",
|
||||||
|
"theme-highlight": "#ff82ba"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"spangledCotinga": {
|
||||||
|
"name": "Spangled Cotinga",
|
||||||
|
"description": "This South American bird can be found in the Amazon rainforest, flashing its iridescent turquoise feathers high above in the canopy.",
|
||||||
|
"latinName": "Cotinga cayana",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Spangled_cotinga",
|
||||||
|
"colors": {
|
||||||
|
"face": "#62eafe",
|
||||||
|
"chin": "#a12457",
|
||||||
|
"collar": "#a12457",
|
||||||
|
"belly": "#62eafe",
|
||||||
|
"underbelly": "#5cd8ea",
|
||||||
|
"wing": "#227c89",
|
||||||
|
"wing-edge": "#13353a",
|
||||||
|
"foot": "#68696b",
|
||||||
|
"collar-scruff": "#62eafe"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"elegantEuphonia": {
|
||||||
|
"name": "Elegant Euphonia",
|
||||||
|
"description": "This vividly coloured finch is found throughout Central America and is known for the distinctive blue hood that crowns its head.",
|
||||||
|
"latinName": "Chlorophonia elegantissima",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Elegant_euphonia",
|
||||||
|
"colors": {
|
||||||
|
"wing": "#2d31a1",
|
||||||
|
"wing-edge": "#191c6d",
|
||||||
|
"face": "#1f2392",
|
||||||
|
"hood": "#6bc6ed",
|
||||||
|
"nose-tip": "#fd7e1d",
|
||||||
|
"foot": "#555650",
|
||||||
|
"belly": "#ff952b",
|
||||||
|
"underbelly": "#fd7e1d",
|
||||||
|
"temple": "#57c8fa",
|
||||||
|
"upper-corner-eye": "#57c8fa",
|
||||||
|
"upper-eyelid": "#57c8fa",
|
||||||
|
"collar-scruff": "#57c8fa",
|
||||||
|
"scruff": "#57c8fa",
|
||||||
|
"beak": "#252c31",
|
||||||
|
"collar": "#191c6d"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"paintedBunting": {
|
||||||
|
"name": "Painted Bunting",
|
||||||
|
"description": "A remarkably colourful bird, this North American species is quite difficult to observe despite its vivid palette due to its shy nature and vulnerable habitat.",
|
||||||
|
"latinName": "Passerina ciris",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Painted_bunting",
|
||||||
|
"colors": {
|
||||||
|
"face": "#5567f0",
|
||||||
|
"underbelly": "#f16534",
|
||||||
|
"belly": "#ef3b3b",
|
||||||
|
"wing": "#a3e65a",
|
||||||
|
"wing-edge": "#91cc50",
|
||||||
|
"shoulder": "#f6fe40",
|
||||||
|
"foot": "#767980"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"redWarbler": {
|
||||||
|
"name": "Red Warbler",
|
||||||
|
"description": "Endemic to the highlands of Mexico, this bird has the rare distinction of being one of the very few toxic birds in the world.",
|
||||||
|
"latinName": "Cardellina rubra",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Red_warbler",
|
||||||
|
"colors": {
|
||||||
|
"face": "#e80a28",
|
||||||
|
"belly": "#d90921",
|
||||||
|
"underbelly": "#c70c18",
|
||||||
|
"wing": "#ba121d",
|
||||||
|
"wing-edge": "#5b3535",
|
||||||
|
"foot": "#5e4645",
|
||||||
|
"behind-eye": "#deedff",
|
||||||
|
"temple": "#e8f0fa",
|
||||||
|
"corner-eye": "#d5e4f5",
|
||||||
|
"lower-eyelid": "#e34a61",
|
||||||
|
"beak": "#873535",
|
||||||
|
"cheek": "#db1734"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"cubanTody": {
|
||||||
|
"name": "Cuban Tody",
|
||||||
|
"description": "As the name suggests, this little green bird is only found on the island of Cuba and is known for being particularly round.",
|
||||||
|
"latinName": "Todus multicolor",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Cuban_tody",
|
||||||
|
"colors": {
|
||||||
|
"beak": "#f16f54",
|
||||||
|
"face": "#5fdf44",
|
||||||
|
"chin": "#f12d3e",
|
||||||
|
"collar": "#f12d3e",
|
||||||
|
"belly": "#f6f5e4",
|
||||||
|
"collar-scruff": "#a3ebff",
|
||||||
|
"underbelly": "#eae9d2",
|
||||||
|
"wing": "#11c751",
|
||||||
|
"wing-edge": "#156631",
|
||||||
|
"foot": "#ac7055",
|
||||||
|
"scruff": "#11c751",
|
||||||
|
"theme-highlight": "#4adc67"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
|
},
|
||||||
|
"violetBackedStarling": {
|
||||||
|
"name": "Violet-backed Starling",
|
||||||
|
"description": "Native to Sub-Saharan Africa, these small starlings are known for being the most vividly purple birds in the world.",
|
||||||
|
"latinName": "Cinnyricinclus leucogaster",
|
||||||
|
"url": "https://en.wikipedia.org/wiki/Violet-backed_starling",
|
||||||
|
"colors": {
|
||||||
|
"face": "#9c3af2",
|
||||||
|
"wing": "#8f37ed",
|
||||||
|
"wing-edge": "#7029b8",
|
||||||
|
"belly": "#ffffff",
|
||||||
|
"underbelly": "#f2f2f2",
|
||||||
|
"foot": "#736a66",
|
||||||
|
"collar": "#aa60e6"
|
||||||
|
},
|
||||||
|
"rarity": "uncommon"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,7 +252,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#birb-field-guide .birb-grid-content {
|
#birb-field-guide .birb-grid-content {
|
||||||
grid-template-rows: repeat(3, auto);
|
grid-template-columns: repeat(4, auto);
|
||||||
}
|
}
|
||||||
|
|
||||||
#birb-wardrobe .birb-grid-content {
|
#birb-wardrobe .birb-grid-content {
|
||||||
@@ -262,7 +262,7 @@
|
|||||||
|
|
||||||
.birb-grid-content {
|
.birb-grid-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: row;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
@@ -294,7 +294,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.birb-grid-item, .birb-field-guide-description, .birb-message-content {
|
.birb-grid-item, .birb-field-guide-description, .birb-message-content {
|
||||||
border: var(--birb-border-size) solid rgb(255, 207, 144);
|
border: var(--birb-border-size) solid #ffcf90;
|
||||||
box-shadow: 0 0 0 var(--birb-border-size) white;
|
box-shadow: 0 0 0 var(--birb-border-size) white;
|
||||||
background: rgba(255, 221, 177, 0.5);
|
background: rgba(255, 221, 177, 0.5);
|
||||||
}
|
}
|
||||||
@@ -313,6 +313,15 @@
|
|||||||
background: var(--birb-mix-color);
|
background: var(--birb-mix-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.birb-field-guide-section-label {
|
||||||
|
padding-top: 4px;
|
||||||
|
/* padding-left: calc(10px + var(--birb-border-size) / 2); */
|
||||||
|
color: #876c4e;
|
||||||
|
text-align: center;
|
||||||
|
/* Italics */
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.birb-field-guide-description {
|
.birb-field-guide-description {
|
||||||
max-width: calc(100% - 20px);
|
max-width: calc(100% - 20px);
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
@@ -324,7 +333,14 @@
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: rgb(124, 108, 75);
|
color: #7c6c4b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.birb-field-guide-latin-name {
|
||||||
|
text-decoration: underline;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: bold;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
#birb-feather {
|
#birb-feather {
|
||||||
@@ -337,7 +353,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: rgb(124, 108, 75);
|
color: #7c6c4b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.birb-sticky-note {
|
.birb-sticky-note {
|
||||||
|
|||||||