Add falling feather

This commit is contained in:
Idrees Hassan
2024-12-28 16:17:39 -05:00
parent 8d7679f9bd
commit ddbaa4c029

130
birb.js
View File

@@ -39,6 +39,7 @@ const AFK_TIME = 1000 * 30;
const SPRITE_HEIGHT = 32; const SPRITE_HEIGHT = 32;
const START_MENU_ID = "birb-start-menu"; const START_MENU_ID = "birb-start-menu";
const FIELD_GUIDE_ID = "birb-field-guide"; const FIELD_GUIDE_ID = "birb-field-guide";
const FEATHER_ID = "birb-feather";
const styles = ` const styles = `
#birb { #birb {
@@ -47,7 +48,7 @@ const styles = `
bottom: 0; bottom: 0;
transform: scale(${CSS_SCALE}); transform: scale(${CSS_SCALE});
transform-origin: bottom; transform-origin: bottom;
z-index: 999999999; z-index: 999999998;
cursor: pointer; cursor: pointer;
} }
@@ -62,7 +63,7 @@ const styles = `
.birb-window { .birb-window {
font-family: "Monocraft"; font-family: "Monocraft";
z-index: 1000; z-index: 999999999;
position: fixed; position: fixed;
background-color: #ffecda; background-color: #ffecda;
box-shadow: box-shadow:
@@ -189,13 +190,10 @@ const styles = `
} }
.birb-grid-item { .birb-grid-item {
border: var(--border-size) solid rgb(255, 207, 144);
box-shadow: 0 0 0 var(--border-size) white;
width: 64px; width: 64px;
height: 64px; height: 64px;
overflow: hidden; overflow: hidden;
margin: 6px; margin: 6px;
background: rgb(255, 221, 177, 0.5);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@@ -207,6 +205,21 @@ const styles = `
transform: scale(2); transform: scale(2);
padding-bottom: var(--border-size); padding-bottom: var(--border-size);
} }
.birb-grid-item, .birb-field-guide-description {
border: var(--border-size) solid rgb(255, 207, 144);
box-shadow: 0 0 0 var(--border-size) white;
background: rgba(255, 221, 177, 0.5);
}
.birb-field-guide-description {
margin-top: 10px;
padding: 8px;
padding-top: 4px;
padding-bottom: 4px;
font-size: 14px;
color: rgb(124, 108, 75);
}
`; `;
class Layer { class Layer {
@@ -266,7 +279,7 @@ class Frame {
/** /**
* @param {CanvasRenderingContext2D} ctx * @param {CanvasRenderingContext2D} ctx
* @param {number} direction * @param {number} direction
* @param {Theme} [theme] * @param {BirdType} [theme]
*/ */
draw(ctx, direction, theme) { draw(ctx, direction, theme) {
for (let y = 0; y < this.pixels.length; y++) { for (let y = 0; y < this.pixels.length; y++) {
@@ -300,7 +313,7 @@ class Anim {
* @param {CanvasRenderingContext2D} ctx * @param {CanvasRenderingContext2D} ctx
* @param {number} direction * @param {number} direction
* @param {number} timeStart The start time of the animation in milliseconds * @param {number} timeStart The start time of the animation in milliseconds
* @param {Theme} [theme] The theme to use for the animation * @param {BirdType} [theme] The theme to use for the animation
* @returns {boolean} Whether the animation is complete * @returns {boolean} Whether the animation is complete
*/ */
draw(ctx, direction, timeStart, theme) { draw(ctx, direction, timeStart, theme) {
@@ -337,6 +350,7 @@ const WING_EDGE = "wing-edge";
const HEART = "heart"; const HEART = "heart";
const HEART_BORDER = "heart-border"; const HEART_BORDER = "heart-border";
const HEART_SHINE = "heart-shine"; const HEART_SHINE = "heart-shine";
const FEATHER_SPINE = "feather-spine";
const SPRITESHEET_COLOR_MAP = { const SPRITESHEET_COLOR_MAP = {
"transparent": TRANSPARENT, "transparent": TRANSPARENT,
@@ -352,14 +366,19 @@ const SPRITESHEET_COLOR_MAP = {
"#326ed9": WING_EDGE, "#326ed9": WING_EDGE,
"#c82e2e": HEART, "#c82e2e": HEART,
"#501a1a": HEART_BORDER, "#501a1a": HEART_BORDER,
"#ff6b6b": HEART_SHINE "#ff6b6b": HEART_SHINE,
"#373737": FEATHER_SPINE,
}; };
class Theme { class BirdType {
/** /**
* @param {string} name
* @param {string} description
* @param {Record<string, string>} colors * @param {Record<string, string>} colors
*/ */
constructor(colors) { constructor(name, description, colors) {
this.name = name;
this.description = description;
const defaultColors = { const defaultColors = {
[TRANSPARENT]: "transparent", [TRANSPARENT]: "transparent",
[OUTLINE]: "#000000", [OUTLINE]: "#000000",
@@ -367,13 +386,15 @@ class Theme {
[HEART]: "#c82e2e", [HEART]: "#c82e2e",
[HEART_BORDER]: "#501a1a", [HEART_BORDER]: "#501a1a",
[HEART_SHINE]: "#ff6b6b", [HEART_SHINE]: "#ff6b6b",
[FEATHER_SPINE]: "#373737",
}; };
this.colors = { ...defaultColors, ...colors }; this.colors = { ...defaultColors, ...colors };
} }
} }
const themes = { const species = {
bluebird: new Theme({ bluebird: new BirdType("Eastern Bluebird",
"Native to North American and very social, though can be timid around people.", {
[BEAK]: "#000000", [BEAK]: "#000000",
[FOOT]: "#af8e75", [FOOT]: "#af8e75",
[EYE]: "#000000", [EYE]: "#000000",
@@ -383,7 +404,8 @@ const themes = {
[WING]: "#578ae6", [WING]: "#578ae6",
[WING_EDGE]: "#326ed9", [WING_EDGE]: "#326ed9",
}), }),
shimaEnaga: new Theme({ shimaEnaga: new BirdType("Shima Enaga",
"Small, fluffy birds found in the snowy regions of Japan", {
[BEAK]: "#000000", [BEAK]: "#000000",
[FOOT]: "#af8e75", [FOOT]: "#af8e75",
[EYE]: "#000000", [EYE]: "#000000",
@@ -403,8 +425,10 @@ const Directions = {
const SPRITE_WIDTH = 32; const SPRITE_WIDTH = 32;
const DECORATIONS_SPRITE_WIDTH = 48; const DECORATIONS_SPRITE_WIDTH = 48;
const FEATHER_SPRITE_WIDTH = 32;
const SPRITE_SHEET_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASAAAAAgCAYAAACy9KU0AAAAAXNSR0IArs4c6QAABBdJREFUeJztnL9LHEEUx79zWkgk3RFwTRvsDKS5KpWpbDSVVQgkjSCYKkfIHyDBIhAhIAiBkOoqsUmVVDbaBFKkkLTxhHBgEQM28aU4Z53d25lddXfm1vt+4Ni5X/vm9t77znfm9hYghBBCCCGEEOIJFboDhOQhImJ7TinFHK4x/PJILiEFQMd+PD0NANg+PEy0ffSBEBIIOWcximQxigbaLnEqK/5iFMne7KzIyspAu+r4pFrGQ3eAFCOr0HyN/I+np/Gq2UTr4cOBtnYhPtjf3cWrZjNuk/rTCN0Bkk96GqK3vkf/UAKwfXiIN71e4rE3vZ5X8SNkZDGnIebWhwCZU7C92dn45msKltUHn7FJtdABFUQyCNEP7UB8oad5pgsx3YePaWC6D1x8JiOHiPQXP42tz9ihHYDZh1ACHFr8SflwBCmIiMj+/fvx/db3715HYHMdKJQDMAuf7oMQj+iRV7sfOgBCrk+hUcyW8KM2CtIBEFIuuUWkiy5db7oWfRQiBZDUCeZrcZwnIpri83wreUyVUhAR8zWVHFyXAIqI8Eslw0BadGwDNkliFSCX+ADA8y2JD3JVYjAMAkhIUcwUXFrvxu1OOwrRncqYGlNy9E9KqTenA2pMRlC3pvDhRf8APnvXxeflBuY3zwBcHOQqxGAYBJCQgohSKiE6N5WpMSXzm2f4vNwoRYSsO8gTAAA4OT6K21rlXVbzMgIhItKYTI4caQHU8TvtKBGXQkQ8Ii7hMeuCeTmI1QEppZTr515TfICkG8riKi7l7G93QAC1+KRjp91Q1v6YAKRknOJjwNSzUOjf8O/vPMLK7y9xGwCeHn/KfG36C/n56w+Ai2laUYZBAAmxsbTezU3mKtd+bPGNmLXIc2cnzwsWpztzA89NLHzFg9UD3Lt7O/O9Wni+bczoffUDXnIapuMPCOBBtgC6+kEbTMoiT4A67QhL61102lFV+WZ1X+ciVIs8v/L1gE535jCxMAOsHmQ+nxYe4Ho+VAuPptOOriSAhJTBx5kn1kHQg/gA6Oe4Lf/rQq4DAvrTmrQLOvlxAgBovt63vfciyBWFx+XAgAsXlkXZAkhImt5aS1bGtwEkp1s+xAeAuAbgurigQmdCp0VAi08Wzdf7pZ0lHVoACcmjt9aKE80QI1/5ZhUh85fhYc7/S/0Vo7fWcr62TPEx44cSQEJqgOhZQFqItAgNcx3krgHpX6POP4hVhKosfB0bgFMEKT5kBFHfNmb6SW8RohuBcRkI2X77UgDEN32/qstEpC5D4YzPS1WQEUbQd0TyYPWg0pr0SlbxZ92vWoBCxSekLui60LcbURPpYrdtqxSgkPEJqQuSQeg+uSh8HpBeh3FtqyR0fELqQN3WPi91VnLuzir88KHjE0LK5z8tFuzphqiPOAAAAABJRU5ErkJggg=="; const SPRITE_SHEET_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASAAAAAgCAYAAACy9KU0AAAAAXNSR0IArs4c6QAABBdJREFUeJztnL9LHEEUx79zWkgk3RFwTRvsDKS5KpWpbDSVVQgkjSCYKkfIHyDBIhAhIAiBkOoqsUmVVDbaBFKkkLTxhHBgEQM28aU4Z53d25lddXfm1vt+4Ni5X/vm9t77znfm9hYghBBCCCGEEOIJFboDhOQhImJ7TinFHK4x/PJILiEFQMd+PD0NANg+PEy0ffSBEBIIOWcximQxigbaLnEqK/5iFMne7KzIyspAu+r4pFrGQ3eAFCOr0HyN/I+np/Gq2UTr4cOBtnYhPtjf3cWrZjNuk/rTCN0Bkk96GqK3vkf/UAKwfXiIN71e4rE3vZ5X8SNkZDGnIebWhwCZU7C92dn45msKltUHn7FJtdABFUQyCNEP7UB8oad5pgsx3YePaWC6D1x8JiOHiPQXP42tz9ihHYDZh1ACHFr8SflwBCmIiMj+/fvx/db3715HYHMdKJQDMAuf7oMQj+iRV7sfOgBCrk+hUcyW8KM2CtIBEFIuuUWkiy5db7oWfRQiBZDUCeZrcZwnIpri83wreUyVUhAR8zWVHFyXAIqI8Eslw0BadGwDNkliFSCX+ADA8y2JD3JVYjAMAkhIUcwUXFrvxu1OOwrRncqYGlNy9E9KqTenA2pMRlC3pvDhRf8APnvXxeflBuY3zwBcHOQqxGAYBJCQgohSKiE6N5WpMSXzm2f4vNwoRYSsO8gTAAA4OT6K21rlXVbzMgIhItKYTI4caQHU8TvtKBGXQkQ8Ii7hMeuCeTmI1QEppZTr515TfICkG8riKi7l7G93QAC1+KRjp91Q1v6YAKRknOJjwNSzUOjf8O/vPMLK7y9xGwCeHn/KfG36C/n56w+Ai2laUYZBAAmxsbTezU3mKtd+bPGNmLXIc2cnzwsWpztzA89NLHzFg9UD3Lt7O/O9Wni+bczoffUDXnIapuMPCOBBtgC6+kEbTMoiT4A67QhL61102lFV+WZ1X+ciVIs8v/L1gE535jCxMAOsHmQ+nxYe4Ho+VAuPptOOriSAhJTBx5kn1kHQg/gA6Oe4Lf/rQq4DAvrTmrQLOvlxAgBovt63vfciyBWFx+XAgAsXlkXZAkhImt5aS1bGtwEkp1s+xAeAuAbgurigQmdCp0VAi08Wzdf7pZ0lHVoACcmjt9aKE80QI1/5ZhUh85fhYc7/S/0Vo7fWcr62TPEx44cSQEJqgOhZQFqItAgNcx3krgHpX6POP4hVhKosfB0bgFMEKT5kBFHfNmb6SW8RohuBcRkI2X77UgDEN32/qstEpC5D4YzPS1WQEUbQd0TyYPWg0pr0SlbxZ92vWoBCxSekLui60LcbURPpYrdtqxSgkPEJqQuSQeg+uSh8HpBeh3FtqyR0fELqQN3WPi91VnLuzir88KHjE0LK5z8tFuzphqiPOAAAAABJRU5ErkJggg==";
const DECORATIONS_SPRITE_SHEET_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAPNJREFUaIHtmTESgzAMBHWZDC+gp0vP/x9Bn44+L6BRmrhJA4csM05uGzfY1s1JxggzIYQQQgghxEnATnB3zwikAICKiXq4BE/uwaxvn/UPb3BnNwFg27Ky0w6vzRp8S4mkIbQD3wzzFJofdTMkYJgn89czFADGKSSiSgphfFBjTaoIKC4cHWvSxIFMmjiQSYoDLUlxoCVywOwHHWjpROop1IL/vsxty2oYO77M1QggSvcpJAFXE66BPfa+2C4v4j2yi7z7FJKAq6FrwN3TO3MMlAAAKO3F2sVZTiu2N9p9CnUv4FR7PbMG2BQ69SJL/kVA8QauAnHUj36BVwAAAABJRU5ErkJggg=="; const DECORATIONS_SPRITE_SHEET_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAPNJREFUaIHtmTESgzAMBHWZDC+gp0vP/x9Bn44+L6BRmrhJA4csM05uGzfY1s1JxggzIYQQQgghxEnATnB3zwikAICKiXq4BE/uwaxvn/UPb3BnNwFg27Ky0w6vzRp8S4mkIbQD3wzzFJofdTMkYJgn89czFADGKSSiSgphfFBjTaoIKC4cHWvSxIFMmjiQSYoDLUlxoCVywOwHHWjpROop1IL/vsxty2oYO77M1QggSvcpJAFXE66BPfa+2C4v4j2yi7z7FJKAq6FrwN3TO3MMlAAAKO3F2sVZTiu2N9p9CnUv4FR7PbMG2BQ69SJL/kVA8QauAnHUj36BVwAAAABJRU5ErkJggg==";
const FEATHER_SPRITE_SHEET_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
/** /**
* Load the spritesheet and return the pixelmap template * Load the spritesheet and return the pixelmap template
@@ -462,9 +486,10 @@ function loadSpritesheetPixels(dataUri, templateColors = true) {
}); });
} }
Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECORATIONS_SPRITE_SHEET_URI, false)]).then(([birbPixels, decorationPixels]) => { Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECORATIONS_SPRITE_SHEET_URI, false), loadSpritesheetPixels(FEATHER_SPRITE_SHEET_URI)]).then(([birbPixels, decorationPixels, featherPixels ]) => {
const SPRITE_SHEET = birbPixels; const SPRITE_SHEET = birbPixels;
const DECORATIONS_SPRITE_SHEET = decorationPixels; const DECORATIONS_SPRITE_SHEET = decorationPixels;
const FEATHER_SPRITE_SHEET = featherPixels;
const layers = { const layers = {
base: new Layer(getLayer(SPRITE_SHEET, 0)), base: new Layer(getLayer(SPRITE_SHEET, 0)),
@@ -482,6 +507,10 @@ Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECO
mac: new Layer(getLayer(DECORATIONS_SPRITE_SHEET, 0, DECORATIONS_SPRITE_WIDTH)), mac: new Layer(getLayer(DECORATIONS_SPRITE_SHEET, 0, DECORATIONS_SPRITE_WIDTH)),
}; };
const featherLayers = {
feather: new Layer(getLayer(FEATHER_SPRITE_SHEET, 0, FEATHER_SPRITE_WIDTH)),
};
const birbFrames = { const birbFrames = {
base: new Frame([layers.base]), base: new Frame([layers.base]),
headDown: new Frame([layers.down]), headDown: new Frame([layers.down]),
@@ -497,6 +526,10 @@ Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECO
mac: new Frame([decorationLayers.mac]), mac: new Frame([decorationLayers.mac]),
}; };
const featherFrames = {
feather: new Frame([featherLayers.feather]),
};
const Animations = { const Animations = {
STILL: new Anim([birbFrames.base], [1000]), STILL: new Anim([birbFrames.base], [1000]),
BOB: new Anim([ BOB: new Anim([
@@ -546,6 +579,14 @@ Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECO
]), ]),
}; };
const FEATHER_ANIMATIONS = {
feather: new Anim([
featherFrames.feather,
], [
1000,
]),
};
const styleElement = document.createElement("style"); const styleElement = document.createElement("style");
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
@@ -644,6 +685,7 @@ Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECO
setState(States.IDLE); setState(States.IDLE);
} }
} }
updateFeather();
} }
function draw() { function draw() {
@@ -676,7 +718,7 @@ Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECO
} }
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
if (currentAnimation.draw(ctx, direction, animStart, themes[currentTheme])) { if (currentAnimation.draw(ctx, direction, animStart, species[currentTheme])) {
setAnimation(Animations.STILL); setAnimation(Animations.STILL);
} }
@@ -724,8 +766,42 @@ Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECO
makeDraggable(decorationCanvas, false); makeDraggable(decorationCanvas, false);
} }
insertFeather();
function insertFeather() {
let theme = species[currentTheme];
const featherCanvas = document.createElement("canvas");
featherCanvas.id = "birb-feather";
featherCanvas.classList.add("birb-decoration");
featherCanvas.width = FEATHER_SPRITE_WIDTH * CANVAS_PIXEL_SIZE;
featherCanvas.height = FEATHER_SPRITE_WIDTH * CANVAS_PIXEL_SIZE;
const x = featherCanvas.width * 2 + Math.random() * (window.innerWidth - featherCanvas.width * 4);
featherCanvas.style.marginLeft = `${x}px`;
const featherCtx = featherCanvas.getContext("2d");
if (!featherCtx) {
return;
}
FEATHER_ANIMATIONS.feather.draw(featherCtx, Directions.LEFT, Date.now(), theme);
document.body.appendChild(featherCanvas);
}
function updateFeather() {
const feather = document.querySelector("#birb-feather");
const featherGravity = 1;
if (!feather || !(feather instanceof HTMLElement)) {
return;
}
const y = parseInt(feather.style.top || "0") + featherGravity;
feather.style.top = `${Math.min(y, window.innerHeight - feather.offsetHeight)}px`;
if (y < window.innerHeight - feather.offsetHeight) {
feather.style.left = `${Math.sin(3.14 * 2 * (ticks / 120)) * 25}px`;
}
}
// insertDecoration(); // insertDecoration();
// insertFieldGuide(); insertFieldGuide();
function insertFieldGuide() { function insertFieldGuide() {
if (document.querySelector("#" + FIELD_GUIDE_ID)) { if (document.querySelector("#" + FIELD_GUIDE_ID)) {
@@ -748,6 +824,8 @@ Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECO
<div class="birb-grid-item"></div> <div class="birb-grid-item"></div>
<div class="birb-grid-item"></div> <div class="birb-grid-item"></div>
</div> </div>
<div class="birb-field-guide-description">
</div>
</div>` </div>`
const fieldGuide = makeElement("birb-window", undefined, FIELD_GUIDE_ID); const fieldGuide = makeElement("birb-window", undefined, FIELD_GUIDE_ID);
fieldGuide.innerHTML = html; fieldGuide.innerHTML = html;
@@ -765,7 +843,17 @@ Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECO
return; return;
} }
content.innerHTML = ""; content.innerHTML = "";
for (const [name, theme] of Object.entries(themes)) {
const generateDescription = (theme) => {
return "<b>" + theme.name + "</b><div style='height: 0.3em'></div>" + theme.description;
};
const description = fieldGuide.querySelector(".birb-field-guide-description");
if (!description) {
return;
}
description.innerHTML = generateDescription(species[currentTheme]);
for (const [name, theme] of Object.entries(species)) {
const themeElement = makeElement("birb-grid-item"); const themeElement = makeElement("birb-grid-item");
const themeCanvas = document.createElement("canvas"); const themeCanvas = document.createElement("canvas");
themeCanvas.width = SPRITE_WIDTH * CANVAS_PIXEL_SIZE; themeCanvas.width = SPRITE_WIDTH * CANVAS_PIXEL_SIZE;
@@ -774,7 +862,6 @@ Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECO
if (!themeCtx) { if (!themeCtx) {
return; return;
} }
// Draw the bird with the theme
birbFrames.base.draw(themeCtx, Directions.RIGHT, theme); birbFrames.base.draw(themeCtx, Directions.RIGHT, theme);
themeElement.appendChild(themeCanvas); themeElement.appendChild(themeCanvas);
content.appendChild(themeElement); content.appendChild(themeElement);
@@ -782,6 +869,13 @@ Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECO
switchTheme(name); switchTheme(name);
fieldGuide.remove(); fieldGuide.remove();
}); });
themeElement.addEventListener("mouseover", () => {
console.log("mouseover");
description.innerHTML = generateDescription(theme);
});
themeElement.addEventListener("mouseout", () => {
description.innerHTML = generateDescription(species[currentTheme]);
});
} }
} }