diff --git a/Assets/Img/Guide.png b/Assets/Img/Guide.png
index d2f5431..94478d7 100644
Binary files a/Assets/Img/Guide.png and b/Assets/Img/Guide.png differ
diff --git a/Assets/Img/Plants.png b/Assets/Img/Plants.png
new file mode 100644
index 0000000..f8fa048
Binary files /dev/null and b/Assets/Img/Plants.png differ
diff --git a/Pages/Bits/Navbar.php b/Pages/Bits/Navbar.php
index 2f665cf..2ff4219 100644
--- a/Pages/Bits/Navbar.php
+++ b/Pages/Bits/Navbar.php
@@ -5,6 +5,7 @@ $NavbarPages = [
"Sarahtonin" => "/Pages/Sarahtonin.html",
"Garden" => "/Pages/Garden.html",
"Shop" => "/Pages/Shop.html",
+ "Plants" => "/Pages/Plants.html",
"Guide" => "/Pages/Guide.html",
"Profile" => "/Pages/Profile.html"
];
diff --git a/Pages/Guide.php b/Pages/Guide.php
index f574616..7b653bc 100644
--- a/Pages/Guide.php
+++ b/Pages/Guide.php
@@ -71,7 +71,7 @@ $PageSection = "Guide";
id="Harvesting"
class="GuideSection"
>
-
Harvesting
+ Harvesting
Click a mature plant to harvest it. Harvesting gives
@@ -178,7 +178,7 @@ $PageSection = "Guide";
id="Discoveries"
class="GuideSection"
>
-
Discoveries
+ Discoveries
Discovering a new plant or mutation is saved with
diff --git a/Pages/Plants.php b/Pages/Plants.php
new file mode 100644
index 0000000..52a97c1
--- /dev/null
+++ b/Pages/Plants.php
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+ Plant encyclopedia
+
+
+ Every plant you discover is recorded here.
+ Undiscovered plants stay hidden until you find
+ them in your Garden.
+
+
+
+
+
+ Loading discovered plants...
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Scripts/Garden/PlantEncyclopedia.js b/Scripts/Garden/PlantEncyclopedia.js
new file mode 100644
index 0000000..763c281
--- /dev/null
+++ b/Scripts/Garden/PlantEncyclopedia.js
@@ -0,0 +1,673 @@
+let PlantEncyclopediaSave;
+
+
+async function StartPlantEncyclopedia() {
+ try {
+ await LoadGameContent();
+ PlantEncyclopediaSave =
+ await LoadGame();
+
+ RenderPlantEncyclopedia();
+ } catch (Error) {
+ console.error(
+ "Couldn't load Plant encyclopedia:",
+ Error
+ );
+
+ SetPlantEncyclopediaMessage(
+ "Couldn't load your discovered plants."
+ );
+ }
+}
+
+
+function RenderPlantEncyclopedia() {
+ const List =
+ document.getElementById(
+ "PlantEncyclopediaList"
+ );
+
+ List.replaceChildren();
+
+
+ const DiscoveredPlants =
+ Object.values(Plants)
+ .filter(
+ Plant =>
+ HasDiscoveredPlant(
+ PlantEncyclopediaSave,
+ Plant.Id
+ )
+ )
+ .sort(
+ (A, B) =>
+ A.Id - B.Id
+ );
+
+
+ if (
+ DiscoveredPlants.length === 0
+ ) {
+ SetPlantEncyclopediaMessage(
+ "No plants discovered yet."
+ );
+
+ return;
+ }
+
+
+ for (
+ const Plant
+ of DiscoveredPlants
+ ) {
+ List.appendChild(
+ CreatePlantEncyclopediaCard(
+ Plant
+ )
+ );
+ }
+
+
+ SetPlantEncyclopediaMessage(
+ DiscoveredPlants.length === 1
+ ? "1 plant discovered."
+ : DiscoveredPlants.length
+ .toLocaleString() +
+ " plants discovered."
+ );
+}
+
+
+function CreatePlantEncyclopediaCard(
+ Plant
+) {
+ const Card =
+ document.createElement(
+ "article"
+ );
+
+ Card.className =
+ "Panel PlantEncyclopediaCard";
+
+ Card.id =
+ "Plant-" + Plant.Id;
+
+
+ const Header =
+ document.createElement(
+ "header"
+ );
+
+ Header.className =
+ "PlantEncyclopediaHeader";
+
+
+ const Number =
+ document.createElement(
+ "span"
+ );
+
+ Number.className =
+ "PlantEncyclopediaNumber";
+
+ Number.textContent =
+ String(
+ Plant.Id
+ ).padStart(
+ 3,
+ "0"
+ );
+
+
+ const Name =
+ document.createElement(
+ "h2"
+ );
+
+ Name.textContent =
+ Plant.Name;
+
+
+ Header.append(
+ Number,
+ Name
+ );
+
+
+ const Body =
+ document.createElement(
+ "div"
+ );
+
+ Body.className =
+ "PlantEncyclopediaBody";
+
+
+ Body.append(
+ CreatePlantEncyclopediaVisual(
+ Plant
+ ),
+ CreatePlantEncyclopediaDetails(
+ Plant
+ ),
+ CreatePlantEncyclopediaRelations(
+ Plant
+ )
+ );
+
+
+ Card.append(
+ Header,
+ Body
+ );
+
+
+ return Card;
+}
+
+
+function CreatePlantEncyclopediaVisual(
+ Plant
+) {
+ const Visual =
+ document.createElement(
+ "div"
+ );
+
+ Visual.className =
+ "PlantTile PlantEncyclopediaVisual";
+
+
+ const PlantKey =
+ GetPlantKeyById(
+ Plant.Id
+ );
+
+ const Images =
+ PlantKey === null
+ ? []
+ : PlantImages[
+ PlantKey
+ ] ?? [];
+
+
+ if (Images.length === 0) {
+ const Missing =
+ document.createElement(
+ "span"
+ );
+
+ Missing.className =
+ "PlantEncyclopediaMissing";
+
+ Missing.textContent =
+ "No image";
+
+ Visual.appendChild(
+ Missing
+ );
+
+ return Visual;
+ }
+
+
+ const Image =
+ document.createElement(
+ "img"
+ );
+
+ Image.className =
+ "PlantSprite";
+
+ Image.src =
+ Images[
+ Images.length - 1
+ ];
+
+ Image.alt =
+ Plant.Name;
+
+
+ Visual.appendChild(
+ Image
+ );
+
+
+ return Visual;
+}
+
+
+function CreatePlantEncyclopediaDetails(
+ Plant
+) {
+ const Details =
+ document.createElement(
+ "div"
+ );
+
+ Details.className =
+ "PlantEncyclopediaDetails";
+
+
+ const Description =
+ document.createElement(
+ "p"
+ );
+
+ Description.className =
+ "PlantEncyclopediaDescription";
+
+ Description.textContent =
+ Plant.Description ||
+ "No description.";
+
+
+ Details.appendChild(
+ Description
+ );
+
+
+ const Tags =
+ Array.isArray(
+ Plant.Tags
+ )
+ ? Plant.Tags
+ : [];
+
+ Details.appendChild(
+ CreatePlantEncyclopediaStat(
+ "Tags",
+ Tags.length === 0
+ ? "None"
+ : Tags.join(", ")
+ )
+ );
+
+
+ Details.appendChild(
+ CreatePlantEncyclopediaStat(
+ "Growth",
+ FormatPlantEncyclopediaTime(
+ Plant.GrowthTime
+ )
+ )
+ );
+
+
+ const Reward =
+ GetPlantHarvestReward(
+ PlantEncyclopediaSave,
+ Plant.Id
+ );
+
+ Details.appendChild(
+ CreatePlantEncyclopediaStat(
+ "Reward",
+ Reward === null
+ ? "Unavailable"
+ : Reward.toLocaleString() +
+ " Dew"
+ )
+ );
+
+
+ const RecipeProgress =
+ GetPlantRecipeProgress(
+ Plant.Id
+ );
+
+ Details.appendChild(
+ CreatePlantEncyclopediaStat(
+ "Found recipes",
+ RecipeProgress.Found
+ .toLocaleString() +
+ "/" +
+ RecipeProgress.Total
+ .toLocaleString()
+ )
+ );
+
+
+ return Details;
+}
+
+
+function CreatePlantEncyclopediaStat(
+ Name,
+ Value
+) {
+ const Row =
+ document.createElement(
+ "div"
+ );
+
+ Row.className =
+ "PlantEncyclopediaStat";
+
+
+ const NameElement =
+ document.createElement(
+ "span"
+ );
+
+ NameElement.className =
+ "PlantEncyclopediaStatName";
+
+ NameElement.textContent =
+ Name;
+
+
+ const ValueElement =
+ document.createElement(
+ "span"
+ );
+
+ ValueElement.textContent =
+ Value;
+
+
+ Row.append(
+ NameElement,
+ ValueElement
+ );
+
+
+ return Row;
+}
+
+
+function CreatePlantEncyclopediaRelations(
+ Plant
+) {
+ const Relations =
+ document.createElement(
+ "div"
+ );
+
+ Relations.className =
+ "PlantEncyclopediaRelations";
+
+
+ Relations.append(
+ CreatePlantMutationRelationGroup(
+ "Created by",
+ GetPlantRelatedMutations(
+ Plant.Id,
+ "PlantsCreated"
+ )
+ ),
+ CreatePlantMutationRelationGroup(
+ "Used in",
+ GetPlantRelatedMutations(
+ Plant.Id,
+ "PlantsUsed"
+ )
+ )
+ );
+
+
+ return Relations;
+}
+
+
+function CreatePlantMutationRelationGroup(
+ Heading,
+ Mutations
+) {
+ const Group =
+ document.createElement(
+ "section"
+ );
+
+ Group.className =
+ "PlantEncyclopediaRelationGroup";
+
+
+ const Title =
+ document.createElement(
+ "h3"
+ );
+
+ Title.textContent =
+ Heading;
+
+
+ const List =
+ document.createElement(
+ "ul"
+ );
+
+ List.className =
+ "PlantEncyclopediaRelationList";
+
+
+ if (Mutations.length === 0) {
+ const Empty =
+ document.createElement(
+ "li"
+ );
+
+ Empty.className =
+ "PlantEncyclopediaRelationEmpty";
+
+ Empty.textContent =
+ "None";
+
+ List.appendChild(
+ Empty
+ );
+ } else {
+ for (
+ const Mutation
+ of Mutations
+ ) {
+ List.appendChild(
+ CreatePlantMutationRelation(
+ Mutation
+ )
+ );
+ }
+ }
+
+
+ Group.append(
+ Title,
+ List
+ );
+
+
+ return Group;
+}
+
+
+function CreatePlantMutationRelation(
+ Mutation
+) {
+ const Item =
+ document.createElement(
+ "li"
+ );
+
+
+ if (
+ !HasDiscoveredMutation(
+ PlantEncyclopediaSave,
+ Mutation.Id
+ )
+ ) {
+ Item.className =
+ "PlantEncyclopediaRelationUnknown";
+
+ Item.textContent =
+ "????????";
+
+ return Item;
+ }
+
+
+ const Link =
+ document.createElement(
+ "a"
+ );
+
+ Link.href =
+ "/Pages/Mutations.html#Mutation-" +
+ Mutation.Id;
+
+ Link.textContent =
+ Mutation.Name;
+
+
+ Item.appendChild(
+ Link
+ );
+
+
+ return Item;
+}
+
+
+function GetPlantRelatedMutations(
+ PlantId,
+ RelationName
+) {
+ return Object.values(
+ MutationSets
+ )
+ .filter(
+ Mutation =>
+ Mutation.Relations?.[
+ RelationName
+ ]?.includes(
+ Number(PlantId)
+ )
+ )
+ .sort(
+ (A, B) =>
+ A.Id - B.Id
+ );
+}
+
+
+function GetPlantRecipeProgress(
+ PlantId
+) {
+ const RelatedMutationIds =
+ new Set();
+
+
+ for (
+ const Mutation
+ of Object.values(
+ MutationSets
+ )
+ ) {
+ const Relations =
+ Mutation.Relations ?? {};
+
+ const IsRelated =
+ Relations.PlantsCreated
+ ?.includes(
+ Number(PlantId)
+ ) ||
+ Relations.PlantsUsed
+ ?.includes(
+ Number(PlantId)
+ );
+
+ if (IsRelated) {
+ RelatedMutationIds.add(
+ Mutation.Id
+ );
+ }
+ }
+
+
+ let Found = 0;
+
+ for (
+ const MutationId
+ of RelatedMutationIds
+ ) {
+ if (
+ HasDiscoveredMutation(
+ PlantEncyclopediaSave,
+ MutationId
+ )
+ ) {
+ Found++;
+ }
+ }
+
+
+ return {
+ Found: Found,
+ Total: RelatedMutationIds.size
+ };
+}
+
+
+function FormatPlantEncyclopediaTime(
+ Milliseconds
+) {
+ const TotalMinutes =
+ Math.max(
+ 1,
+ Math.ceil(
+ Number(
+ Milliseconds
+ ) /
+ 60000
+ )
+ );
+
+ const Hours =
+ Math.floor(
+ TotalMinutes /
+ 60
+ );
+
+ const Minutes =
+ TotalMinutes %
+ 60;
+
+
+ if (
+ Hours > 0 &&
+ Minutes > 0
+ ) {
+ return (
+ Hours +
+ "h " +
+ Minutes +
+ "m"
+ );
+ }
+
+ if (Hours > 0) {
+ return Hours + "h";
+ }
+
+ return Minutes + "m";
+}
+
+
+function SetPlantEncyclopediaMessage(
+ Message
+) {
+ const Element =
+ document.getElementById(
+ "PlantEncyclopediaMessage"
+ );
+
+ if (Element !== null) {
+ Element.textContent =
+ Message;
+ }
+}
+
+
+document.addEventListener(
+ "DOMContentLoaded",
+ StartPlantEncyclopedia
+);
diff --git a/Styles/Style.css b/Styles/Style.css
index 3fd96f4..0f2de74 100644
--- a/Styles/Style.css
+++ b/Styles/Style.css
@@ -1299,3 +1299,179 @@ select {
height: 3rem;
}
}
+
+/*
+ * Plant encyclopedia
+ */
+
+.PlantEncyclopediaList {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ margin-top: 1.5rem;
+}
+
+.PlantEncyclopediaCard {
+ overflow: hidden;
+
+ box-shadow: 3px 3px 0 var(--Crust);
+}
+
+.PlantEncyclopediaHeader {
+ display: flex;
+ gap: 0.75rem;
+
+ align-items: baseline;
+
+ padding: 0.75rem 1rem;
+
+ background-color: var(--Surface0);
+ border-bottom: 3px solid var(--Surface2);
+}
+
+.PlantEncyclopediaHeader h2 {
+ margin: 0;
+
+ color: var(--Green);
+}
+
+.PlantEncyclopediaNumber {
+ flex: 0 0 auto;
+
+ color: var(--Overlay2);
+
+ font-size: 1.1rem;
+}
+
+.PlantEncyclopediaBody {
+ display: grid;
+ grid-template-columns:
+ 7rem
+ minmax(0, 1fr)
+ minmax(10rem, 0.8fr);
+ gap: 1rem;
+
+ padding: 1rem;
+
+ align-items: start;
+}
+
+.PlantEncyclopediaVisual {
+ display: flex;
+
+ width: 7rem;
+ height: 7rem;
+
+ align-items: center;
+ justify-content: center;
+
+ background-color: var(--Base);
+}
+
+.PlantEncyclopediaMissing {
+ padding: 0.5rem;
+
+ color: var(--Overlay1);
+
+ text-align: center;
+}
+
+.PlantEncyclopediaDescription {
+ margin-bottom: 0.75rem;
+}
+
+.PlantEncyclopediaStat {
+ display: flex;
+ justify-content: space-between;
+ gap: 0.75rem;
+
+ padding: 0.35rem 0;
+
+ border-bottom: 2px solid var(--Surface0);
+}
+
+.PlantEncyclopediaStat:last-child {
+ border-bottom: 0;
+}
+
+.PlantEncyclopediaStatName {
+ flex: 0 0 auto;
+
+ color: var(--Subtext1);
+}
+
+.PlantEncyclopediaStat > :last-child {
+ min-width: 0;
+
+ text-align: right;
+ overflow-wrap: anywhere;
+}
+
+.PlantEncyclopediaRelations {
+ min-width: 0;
+
+ background-color: var(--Base);
+ border: 3px solid var(--Surface2);
+}
+
+.PlantEncyclopediaRelationGroup {
+ padding: 0.65rem 0.75rem;
+}
+
+.PlantEncyclopediaRelationGroup +
+.PlantEncyclopediaRelationGroup {
+ border-top: 3px solid var(--Surface2);
+}
+
+.PlantEncyclopediaRelationGroup h3 {
+ margin-bottom: 0.5rem;
+
+ color: var(--Peach);
+
+ font-size: 1rem;
+}
+
+.PlantEncyclopediaRelationList {
+ max-height: 7rem;
+ margin: 0;
+ padding-left: 1.25rem;
+
+ overflow-y: auto;
+}
+
+.PlantEncyclopediaRelationList li {
+ margin-bottom: 0.35rem;
+}
+
+.PlantEncyclopediaRelationList li:last-child {
+ margin-bottom: 0;
+}
+
+.PlantEncyclopediaRelationUnknown,
+.PlantEncyclopediaRelationEmpty {
+ color: var(--Overlay1);
+}
+
+@media (max-width: 700px) {
+ .PlantEncyclopediaBody {
+ grid-template-columns: 1fr;
+ }
+
+ .PlantEncyclopediaVisual {
+ width: min(8rem, 100%);
+ height: auto;
+ aspect-ratio: 1;
+
+ justify-self: center;
+ }
+
+ .PlantEncyclopediaStat > :last-child {
+ text-align: left;
+ }
+
+ .PlantEncyclopediaStat {
+ flex-direction: column;
+ gap: 0.2rem;
+ }
+}