diff --git a/Pages/Garden.php b/Pages/Garden.php
index 800b4e2..aaf2359 100644
--- a/Pages/Garden.php
+++ b/Pages/Garden.php
@@ -164,6 +164,50 @@ $PageSection = "Garden";
+
+
{
+ QuickBuyAmountMode =
+ Mode;
+
+ RenderQuickBuy();
+ }
+ );
+ }
+}
+
+
+function GetQuickBuyAmount() {
+ if (
+ QuickBuyAmountMode ===
+ "Two"
+ ) {
+ return 2;
+ }
+
+ if (
+ QuickBuyAmountMode ===
+ "Garden"
+ ) {
+ return Math.max(
+ 1,
+ GameSave.Garden.Width *
+ GameSave.Garden.Height
+ );
+ }
+
+ return 1;
+}
+
+
+function RenderQuickBuy() {
+ const PlantList =
+ document.getElementById(
+ "QuickBuyPlantList"
+ );
+
+ if (PlantList === null) {
+ return;
+ }
+
+
+ const Amount =
+ GetQuickBuyAmount();
+
+ const OneButton =
+ document.getElementById(
+ "QuickBuyOneButton"
+ );
+
+ const TwoButton =
+ document.getElementById(
+ "QuickBuyTwoButton"
+ );
+
+ const GardenButton =
+ document.getElementById(
+ "QuickBuyGardenButton"
+ );
+
+
+ const AmountButtons = [
+ [OneButton, "One", 1],
+ [TwoButton, "Two", 2],
+ [
+ GardenButton,
+ "Garden",
+ Math.max(
+ 1,
+ GameSave.Garden.Width *
+ GameSave.Garden.Height
+ )
+ ]
+ ];
+
+
+ for (
+ const [Button, Mode, ButtonAmount]
+ of AmountButtons
+ ) {
+ if (Button === null) {
+ continue;
+ }
+
+ Button.textContent =
+ "×" +
+ ButtonAmount.toLocaleString();
+
+ Button.disabled =
+ QuickBuyPurchasePending;
+
+ if (
+ QuickBuyAmountMode ===
+ Mode
+ ) {
+ Button.setAttribute(
+ "aria-current",
+ "true"
+ );
+ } else {
+ Button.removeAttribute(
+ "aria-current"
+ );
+ }
+ }
+
+
+ PlantList.replaceChildren();
+
+
+ const AvailablePlants =
+ Object.values(Plants)
+ .filter(
+ Plant => {
+ if (
+ !IsPlantAvailableInShop(
+ GameSave,
+ Plant
+ )
+ ) {
+ return false;
+ }
+
+ const Cost =
+ GetPlantShopCost(
+ GameSave,
+ Plant.Id
+ );
+
+ return (
+ Cost !== null &&
+ GameSave.Currency.Dew >=
+ Cost * Amount
+ );
+ }
+ )
+ .sort(
+ (A, B) =>
+ A.Id - B.Id
+ );
+
+
+ if (
+ AvailablePlants.length === 0
+ ) {
+ const EmptyMessage =
+ document.createElement(
+ "p"
+ );
+
+ EmptyMessage.className =
+ "QuickBuyEmpty";
+
+ EmptyMessage.textContent =
+ Amount === 1
+ ? "You can't afford any available seeds."
+ : "You can't afford this many of any available seed.";
+
+ PlantList.appendChild(
+ EmptyMessage
+ );
+
+ return;
+ }
+
+
+ for (
+ const Plant
+ of AvailablePlants
+ ) {
+ PlantList.appendChild(
+ CreateQuickBuyPlantButton(
+ Plant,
+ Amount
+ )
+ );
+ }
+}
+
+
+function CreateQuickBuyPlantButton(
+ Plant,
+ Amount
+) {
+ const UnitCost =
+ GetPlantShopCost(
+ GameSave,
+ Plant.Id
+ );
+
+ const TotalCost =
+ UnitCost * Amount;
+
+
+ const Button =
+ document.createElement(
+ "button"
+ );
+
+ Button.className =
+ "PlantTile QuickBuyPlantButton";
+
+ Button.type = "button";
+
+ Button.disabled =
+ QuickBuyPurchasePending;
+
+ Button.title =
+ "Buy " +
+ Amount.toLocaleString() +
+ (Amount === 1
+ ? " seed of "
+ : " seeds of ") +
+ Plant.Name +
+ " for " +
+ TotalCost.toLocaleString() +
+ " Dew";
+
+
+ const Name =
+ document.createElement(
+ "span"
+ );
+
+ Name.className =
+ "QuickBuyPlantName";
+
+ Name.textContent =
+ Plant.Name;
+
+
+ const Visual =
+ document.createElement(
+ "span"
+ );
+
+ Visual.className =
+ "QuickBuyPlantVisual";
+
+
+ const MatureImage =
+ GetPlantMatureImageSource(
+ Plant
+ );
+
+
+ if (MatureImage === null) {
+ const Missing =
+ document.createElement(
+ "span"
+ );
+
+ Missing.className =
+ "QuickBuyPlantMissing";
+
+ Missing.textContent =
+ "No image";
+
+ Visual.appendChild(
+ Missing
+ );
+ } else {
+ const Image =
+ document.createElement(
+ "img"
+ );
+
+ Image.className =
+ "PlantSprite";
+
+ Image.src =
+ MatureImage;
+
+ Image.alt =
+ Plant.Name;
+
+ Visual.appendChild(
+ Image
+ );
+ }
+
+
+ Button.append(
+ Name,
+ Visual
+ );
+
+
+ Button.addEventListener(
+ "click",
+ () => {
+ BuyQuickBuyPlantSeed(
+ Plant.Id
+ );
+ }
+ );
+
+
+ return Button;
+}
+
+
+async function BuyQuickBuyPlantSeed(
+ PlantId
+) {
+ if (QuickBuyPurchasePending) {
+ return;
+ }
+
+
+ const Plant =
+ GetPlantById(
+ PlantId
+ );
+
+ if (
+ Plant === null ||
+ !IsPlantAvailableInShop(
+ GameSave,
+ Plant
+ )
+ ) {
+ return;
+ }
+
+
+ const Amount =
+ GetQuickBuyAmount();
+
+ const UnitCost =
+ GetPlantShopCost(
+ GameSave,
+ PlantId
+ );
+
+ if (UnitCost === null) {
+ SetGardenMessage(
+ "That seed doesn't have a purchasable recipe yet."
+ );
+
+ return;
+ }
+
+
+ const TotalCost =
+ UnitCost * Amount;
+
+ if (
+ GameSave.Currency.Dew <
+ TotalCost
+ ) {
+ SetGardenMessage(
+ "You don't have enough Dew for " +
+ Amount.toLocaleString() +
+ (Amount === 1
+ ? " seed of "
+ : " seeds of ") +
+ Plant.Name +
+ "."
+ );
+
+ RenderQuickBuy();
+
+ return;
+ }
+
+
+ QuickBuyPurchasePending = true;
+
+ try {
+ GameSave.Currency.Dew -=
+ TotalCost;
+
+ GameSave.Statistics.CurrencySpent.Dew +=
+ TotalCost;
+
+ GameSave.Statistics.SeedsPurchased +=
+ Amount;
+
+ AddSeed(
+ GameSave,
+ PlantId,
+ Amount
+ );
+
+ DiscoverPlant(
+ GameSave,
+ PlantId
+ );
+
+
+ SetGardenMessage(
+ "Bought " +
+ Amount.toLocaleString() +
+ " " +
+ Plant.Name +
+ (Amount === 1
+ ? " seed for "
+ : " seeds for ") +
+ TotalCost.toLocaleString() +
+ " Dew."
+ );
+
+ RenderGame();
+
+ await SaveGame(
+ GameSave
+ );
+ } finally {
+ QuickBuyPurchasePending =
+ false;
+
+ RenderGame();
+ }
+}
+
+
function RenderCurrency() {
const DewAmount =
document.getElementById(
diff --git a/Scripts/Garden/Shop.js b/Scripts/Garden/Shop.js
index 1598e26..40c2fe1 100644
--- a/Scripts/Garden/Shop.js
+++ b/Scripts/Garden/Shop.js
@@ -1001,43 +1001,95 @@ function CreateShopSeedCard(
);
- const BuyButton =
+ const BuyOptions =
document.createElement(
- "button"
+ "div"
);
- BuyButton.className =
- "ActionButton ShopBuyButton";
+ BuyOptions.className =
+ "ShopSeedBuyOptions";
- BuyButton.type = "button";
- BuyButton.textContent =
- Cost === null
- ? "Unavailable"
- : "Buy seed - " +
- Cost.toLocaleString() +
- " Dew";
+ const GardenSize =
+ Math.max(
+ 1,
+ ShopSave.Garden.Width *
+ ShopSave.Garden.Height
+ );
- BuyButton.disabled =
- Cost === null ||
- ShopSave.Currency.Dew <
- Cost ||
- ShopPurchasePending;
-
- BuyButton.addEventListener(
- "click",
- () => {
- BuyPlantSeed(
- Plant.Id
- );
+ const PurchaseOptions = [
+ {
+ Amount: 1,
+ Label: "Buy seed"
+ },
+ {
+ Amount: 10,
+ Label: "Buy 10 seeds"
+ },
+ {
+ Amount: GardenSize,
+ Label:
+ "Buy " +
+ GardenSize.toLocaleString() +
+ " seeds"
}
- );
+ ];
+
+
+ for (
+ const Option
+ of PurchaseOptions
+ ) {
+ const BuyButton =
+ document.createElement(
+ "button"
+ );
+
+ BuyButton.className =
+ "ActionButton ShopBuyButton";
+
+ BuyButton.type = "button";
+
+
+ const TotalCost =
+ Cost === null
+ ? null
+ : Cost * Option.Amount;
+
+ BuyButton.textContent =
+ TotalCost === null
+ ? "Unavailable"
+ : Option.Label +
+ " - " +
+ TotalCost.toLocaleString() +
+ " Dew";
+
+ BuyButton.disabled =
+ TotalCost === null ||
+ ShopSave.Currency.Dew <
+ TotalCost ||
+ ShopPurchasePending;
+
+ BuyButton.addEventListener(
+ "click",
+ () => {
+ BuyPlantSeed(
+ Plant.Id,
+ Option.Amount
+ );
+ }
+ );
+
+ BuyOptions.appendChild(
+ BuyButton
+ );
+ }
Card.append(
Header,
Body,
- BuyButton
+ BuyOptions
);
@@ -1225,13 +1277,22 @@ function CreateShopStat(
async function BuyPlantSeed(
- PlantId
+ PlantId,
+ Amount = 1
) {
if (ShopPurchasePending) {
return;
}
+ Amount = Math.max(
+ 1,
+ Math.floor(
+ Number(Amount) || 1
+ )
+ );
+
+
const Plant =
GetPlantById(
PlantId
@@ -1248,13 +1309,13 @@ async function BuyPlantSeed(
}
- const Cost =
+ const UnitCost =
GetPlantShopCost(
ShopSave,
PlantId
);
- if (Cost === null) {
+ if (UnitCost === null) {
SetShopMessage(
"That seed doesn't have a purchasable recipe yet."
);
@@ -1263,12 +1324,19 @@ async function BuyPlantSeed(
}
+ const TotalCost =
+ UnitCost * Amount;
+
if (
ShopSave.Currency.Dew <
- Cost
+ TotalCost
) {
SetShopMessage(
"You don't have enough Dew for " +
+ Amount.toLocaleString() +
+ (Amount === 1
+ ? " seed of "
+ : " seeds of ") +
Plant.Name +
"."
);
@@ -1279,45 +1347,51 @@ async function BuyPlantSeed(
ShopPurchasePending = true;
- ShopSave.Currency.Dew -=
- Cost;
+ try {
+ ShopSave.Currency.Dew -=
+ TotalCost;
- ShopSave.Statistics.CurrencySpent.Dew +=
- Cost;
+ ShopSave.Statistics.CurrencySpent.Dew +=
+ TotalCost;
- ShopSave.Statistics.SeedsPurchased++;
+ ShopSave.Statistics.SeedsPurchased +=
+ Amount;
- AddSeed(
- ShopSave,
- PlantId
- );
+ AddSeed(
+ ShopSave,
+ PlantId,
+ Amount
+ );
- DiscoverPlant(
- ShopSave,
- PlantId
- );
-
- SetShopMessage(
- "Bought a " +
- Plant.Name +
- " seed for " +
- Cost +
- " Dew."
- );
-
- RenderShop();
+ DiscoverPlant(
+ ShopSave,
+ PlantId
+ );
- await SaveGame(
- ShopSave
- );
+ SetShopMessage(
+ "Bought " +
+ Amount.toLocaleString() +
+ " " +
+ Plant.Name +
+ (Amount === 1
+ ? " seed for "
+ : " seeds for ") +
+ TotalCost.toLocaleString() +
+ " Dew."
+ );
+ RenderShop();
- ShopPurchasePending = false;
- RenderShop();
+ await SaveGame(
+ ShopSave
+ );
+ } finally {
+ ShopPurchasePending = false;
+ RenderShop();
+ }
}
-
async function BuyGardenExpansion(
Direction
) {
diff --git a/Styles/Style.css b/Styles/Style.css
index ece30de..9c7a2ad 100644
--- a/Styles/Style.css
+++ b/Styles/Style.css
@@ -415,6 +415,140 @@ input, button, textarea, select {
}
}
+.GardenQuickBuyPanel {
+ margin-bottom: 1rem;
+ padding: 0.75rem;
+}
+
+.GardenQuickBuyHeader {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 0.75rem;
+}
+
+.GardenQuickBuyHeader h2 {
+ margin: 0;
+}
+
+.QuickBuyAmountList {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+}
+
+.QuickBuyAmountButton {
+ min-width: 3.25rem;
+ padding: 0.4rem 0.55rem;
+ cursor: pointer;
+ color: var(--Text);
+ background-color: var(--Surface0);
+ border: 3px solid var(--Surface2);
+ border-radius: 0;
+ box-shadow: 2px 2px 0 var(--Crust);
+ appearance: none;
+}
+
+.QuickBuyAmountButton:hover:not(:disabled) {
+ color: var(--Green);
+ border-color: var(--Green);
+}
+
+.QuickBuyAmountButton[aria-current="true"] {
+ color: var(--Green);
+ background-color: var(--Surface1);
+ border-color: var(--Green);
+}
+
+.QuickBuyAmountButton:disabled {
+ cursor: not-allowed;
+}
+
+.QuickBuyAmountButton:focus-visible,
+.QuickBuyPlantButton:focus-visible {
+ outline: 3px solid var(--Yellow);
+ outline-offset: 3px;
+}
+
+.QuickBuyPlantList {
+ display: flex;
+ gap: 0.65rem;
+ padding-bottom: 0.45rem;
+ overflow-x: auto;
+ overflow-y: hidden;
+}
+
+.QuickBuyPlantButton {
+ display: flex;
+ flex: 0 0 7rem;
+ width: 7rem;
+ padding: 0;
+ flex-direction: column;
+ cursor: pointer;
+ color: var(--Text);
+ border-color: var(--Surface2);
+ border-radius: 0;
+ box-shadow: 3px 3px 0 var(--Crust);
+ appearance: none;
+}
+
+.QuickBuyPlantButton:hover:not(:disabled) {
+ border-color: var(--Green);
+ transform: translate(1px, 1px);
+ box-shadow: 2px 2px 0 var(--Crust);
+}
+
+.QuickBuyPlantButton:disabled {
+ cursor: not-allowed;
+}
+
+.QuickBuyPlantName {
+ display: flex;
+ width: 100%;
+ min-width: 0;
+ min-height: 1.7rem;
+ padding: 0.25rem 0.35rem;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ color: var(--Subtext1);
+ background-color: var(--Mantle);
+ border-bottom: 2px solid var(--Surface2);
+ font-size: 0.75rem;
+ line-height: 1;
+ text-align: center;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.QuickBuyPlantVisual {
+ display: flex;
+ width: 100%;
+ aspect-ratio: 1;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ background-color: var(--Surface0);
+}
+
+.QuickBuyPlantMissing {
+ padding: 0.35rem;
+ color: var(--Overlay1);
+ font-size: 0.75rem;
+ text-align: center;
+}
+
+.QuickBuyEmpty {
+ flex: 0 0 100%;
+ margin: 0;
+ padding: 0.75rem;
+ color: var(--Subtext1);
+ background-color: var(--Mantle);
+ border: 3px solid var(--Surface2);
+}
+
.GardenLayout {
display: grid;
grid-template-columns: minmax(0, 1fr) 12rem;
@@ -1077,6 +1211,13 @@ input, button, textarea, select {
padding: 0.65rem 0.8rem;
}
+.ShopSeedBuyOptions {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ margin-top: auto;
+}
+
.ShopBuyButton:disabled {
cursor: not-allowed;
color: var(--Overlay1);