diff --git a/game.js b/game.js index b135e35..be3fa96 100644 --- a/game.js +++ b/game.js @@ -74,6 +74,56 @@ const IdealRecipe = { } } +/** + * Tiered pricing structure for supplies. + * Price per unit decreases with larger quantities. + * @type {Object.>} + */ +const SupplyPricing = { + lemons: [ + { min: 1, max: 50, price: 0.02 }, + { min: 51, max: 100, price: 0.018 }, + { min: 101, max: Infinity, price: 0.015 } + ], + sugar: [ + { min: 1, max: 50, price: 0.01 }, + { min: 51, max: 100, price: 0.009 }, + { min: 101, max: Infinity, price: 0.008 } + ], + ice: [ + { min: 1, max: 100, price: 0.01 }, + { min: 101, max: 300, price: 0.009 }, + { min: 301, max: Infinity, price: 0.008 } + ], + cups: [ + { min: 1, max: 100, price: 0.01 }, + { min: 101, max: Infinity, price: 0.009 } + ] +}; + +/** + * Calculate the cost of purchasing a supply item based on tiered pricing. + * @param {string} item - The supply type (lemons, sugar, ice, cups) + * @param {number} quantity - The quantity to purchase + * @returns {number} The total cost + */ +export function calculate_supply_cost(item, quantity) { + if (quantity <= 0) return 0; + const tiers = SupplyPricing[item]; + if (!tiers) return 0; + const tier = tiers.find(t => quantity >= t.min && quantity <= t.max); + return tier ? Math.round(quantity * tier.price * 100) / 100 : 0; +} + +/** + * Get the pricing tiers for a supply item. + * @param {string} item - The supply type + * @returns {Array.<{min: number, max: number, price: number}>} The pricing tiers + */ +export function get_supply_pricing(item) { + return SupplyPricing[item] || []; +} + /** * Probability weights for each weather type. * Used to randomly determine the day's weather. diff --git a/index.html b/index.html index 7f9ea29..12bfaad 100644 --- a/index.html +++ b/index.html @@ -60,24 +60,55 @@
- Lemons - $4.80 / 12 - +
+ Lemons +
+ 1-50: $0.02 + 51-100: $0.018 + 101+: $0.015 +
+
+ + $0.20 +
- Sugar - $4.80 / 12 - +
+ Sugar +
+ 1-50: $0.01 + 51-100: $0.009 + 101+: $0.008 +
+
+ + $0.10 +
- Ice - $1.00 / 50 - +
+ Ice +
+ 1-100: $0.01 + 101-300: $0.009 + 301+: $0.008 +
+
+ + $0.50 +
- Cups - $1.00 / 75 - +
+ Cups +
+ 1-100: $0.01 + 101+: $0.009 +
+
+ + $0.25 +
diff --git a/index.js b/index.js index 7409f9b..c14e446 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ * Initializes game state, wires up UI events, and coordinates modules. */ -import { init_game, set_price_per_cup } from './game.js'; +import { init_game, set_price_per_cup, calculate_supply_cost } from './game.js'; import { sprites, cups, render, whenSpritesReady } from './canvasController.js'; import { createReactiveState, updateBindings } from './binding.js'; @@ -35,10 +35,53 @@ const priceInput = document.querySelector('.price_input'); const priceSaveBtn = document.querySelector('.price_change_save_btn'); +// Shopping modal - quantity inputs and dynamic pricing +const shopQtyInputs = document.querySelectorAll('.shop_qty_input'); +const shopBuyBtns = document.querySelectorAll('.shop_item_btn'); + +function updateShopPrice(item) { + const input = document.querySelector(`.shop_qty_input[data-item="${item}"]`); + const priceDisplay = document.querySelector(`.shop_item_price[data-price="${item}"]`); + const qty = parseInt(input.value) || 0; + const cost = calculate_supply_cost(item, qty); + priceDisplay.textContent = '$' + cost.toFixed(2); +} + +// Update prices when quantity changes +shopQtyInputs.forEach(input => { + input.addEventListener('input', () => { + updateShopPrice(input.dataset.item); + }); +}); + +// Buy button handlers +shopBuyBtns.forEach(btn => { + btn.addEventListener('click', () => { + const item = btn.dataset.item; + const input = document.querySelector(`.shop_qty_input[data-item="${item}"]`); + const qty = parseInt(input.value) || 0; + const cost = calculate_supply_cost(item, qty); + + if (cost > gameState.player_money) { + alert("Not enough money!"); + return; + } + + setState({ + player_money: gameState.player_money - cost, + supplies: { + ...gameState.supplies, + [item]: gameState.supplies[item] + qty + } + }); + }); +}); + // Event handlers if (goShoppingBtn) { goShoppingBtn.addEventListener('click', () => { - console.log('hey'); + // Update all prices when modal opens + ['lemons', 'sugar', 'ice', 'cups'].forEach(updateShopPrice); shoppingModal.classList.add('open'); }); diff --git a/style.css b/style.css index 3ae4c68..67060b2 100644 --- a/style.css +++ b/style.css @@ -115,7 +115,7 @@ canvas { border: 4px solid #8fd16a; border-radius: 20px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); - width: 400px; + width: 580px; max-width: 90%; } @@ -165,17 +165,81 @@ canvas { border-bottom: none; } +.shop_item_info { + flex: 1; + min-width: 0; +} + .shop_item_name { font-family: 'Fredoka', sans-serif; font-size: 18px; color: #5C4632; - flex: 1; + display: block; + margin-bottom: 4px; } -.shop_item_price { +.shop_tiers { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.shop_tier { font-family: 'Inter', sans-serif; - font-size: 14px; + font-size: 11px; color: #7A6146; + background: #f5f0e6; + padding: 2px 6px; + border-radius: 4px; +} + +.shop_tier.best { + background: #d4f0c4; + color: #3f7a33; + font-weight: 500; +} + +.shop_qty_input { + font-family: 'Fredoka', sans-serif; + font-size: 18px; + font-weight: 500; + color: #5C4632; + background: linear-gradient(180deg, #fff 0%, #f9f9f5 100%); + border: 2px solid #c5e8a8; + border-radius: 10px; + padding: 8px 12px; + width: 80px; + text-align: center; + margin-right: 12px; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04); + transition: all 0.15s ease; + -moz-appearance: textfield; +} + +.shop_qty_input::-webkit-outer-spin-button, +.shop_qty_input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.shop_qty_input:hover { + border-color: #8fd16a; + background: linear-gradient(180deg, #fff 0%, #f5f5f0 100%); +} + +.shop_qty_input:focus { + outline: none; + border-color: #3f7a33; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.06), 0 0 0 3px rgba(143, 209, 106, 0.25); +} + +.shop_item_price { + font-family: 'Fredoka', sans-serif; + font-size: 16px; + font-weight: 600; + color: #5C4632; + min-width: 60px; + text-align: right; margin-right: 16px; }