// Shop UI overlay for dougk // DOM-based shop interface that appears when Donny or Ollie approach import gameState from '../gameState.js' import inventory from './inventory.js' import { OUTFITS, BUILDINGS, CHARACTERS, getOutfitsForCharacter, getAllBuildings, getItem } from './items.js' import { playPurchase, playShopOpen, playShopClose } from '../sounds.js' let shopOverlay = null let currentTab = 'outfits' let currentCharacter = CHARACTERS.DOUG let currentShopkeeper = null let onCloseCallback = null let onPurchaseCallback = null const STYLES = ` .shop-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.7); display: flex; justify-content: center; align-items: center; z-index: 2000; font-family: 'Courier New', monospace; } .shop-container { background: linear-gradient(135deg, #2a1f1a 0%, #3d2e26 100%); border: 4px solid #8b6914; border-radius: 16px; padding: 20px; max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); } .shop-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 2px solid #8b6914; } .shop-title { color: #ffd700; font-size: 24px; font-weight: bold; margin: 0; } .shop-koi-count { color: #ffd700; font-size: 18px; display: flex; align-items: center; gap: 6px; } .shop-close { background: #8b0000; color: white; border: none; border-radius: 50%; width: 32px; height: 32px; font-size: 18px; cursor: pointer; display: flex; align-items: center; justify-content: center; } .shop-close:hover { background: #a00000; } .shop-tabs { display: flex; gap: 8px; margin-bottom: 16px; } .shop-tab { flex: 1; padding: 10px 16px; background: #4a3828; border: 2px solid #6b4423; border-radius: 8px; color: #c9a96e; font-size: 14px; font-weight: bold; cursor: pointer; transition: all 0.2s; } .shop-tab:hover { background: #5a4838; } .shop-tab.active { background: #8b6914; border-color: #ffd700; color: #fff; } .character-selector { display: flex; gap: 8px; margin-bottom: 16px; } .character-btn { flex: 1; padding: 8px; background: #3a2a1a; border: 2px solid #5a4a3a; border-radius: 6px; color: #a89070; font-size: 12px; cursor: pointer; transition: all 0.2s; } .character-btn:hover { background: #4a3a2a; } .character-btn.active { background: #6b5030; border-color: #c9a96e; color: #ffd700; } .item-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; } .item-card { background: #3a2a1a; border: 2px solid #5a4a3a; border-radius: 8px; padding: 12px; text-align: center; transition: all 0.2s; } .item-card:hover { border-color: #8b6914; } .item-card.owned { background: #2a3a2a; border-color: #4a8b4a; } .item-card.equipped { border-color: #ffd700; box-shadow: 0 0 8px rgba(255, 215, 0, 0.3); } .item-card.cant-afford { opacity: 0.5; } .item-name { color: #e8d8c8; font-size: 14px; font-weight: bold; margin-bottom: 8px; } .item-price { color: #ffd700; font-size: 16px; margin-bottom: 8px; display: flex; align-items: center; justify-content: center; gap: 4px; } .item-btn { width: 100%; padding: 8px 12px; border: none; border-radius: 6px; font-size: 12px; font-weight: bold; cursor: pointer; transition: all 0.2s; } .item-btn.buy { background: #8b6914; color: white; } .item-btn.buy:hover { background: #a07a1a; } .item-btn.buy:disabled { background: #4a4a4a; cursor: not-allowed; } .item-btn.equip { background: #4a8b4a; color: white; } .item-btn.equip:hover { background: #5a9b5a; } .item-btn.unequip { background: #8b4a4a; color: white; } .item-btn.unequip:hover { background: #9b5a5a; } .empty-message { color: #888; text-align: center; padding: 40px; font-style: italic; } ` function injectStyles() { if (document.getElementById('shop-styles')) return const style = document.createElement('style') style.id = 'shop-styles' style.textContent = STYLES document.head.appendChild(style) } function createShopOverlay() { injectStyles() const overlay = document.createElement('div') overlay.className = 'shop-overlay' overlay.innerHTML = `

${getShopTitle()}

🐟 ${gameState.getKoi()}
` // Event listeners overlay.querySelector('.shop-close').addEventListener('click', closeShop) overlay.addEventListener('click', (e) => { if (e.target === overlay) closeShop() }) return overlay } function getShopTitle() { if (currentShopkeeper === 'donny') return "Donny's Fine Structures" if (currentShopkeeper === 'ollie') return "Ollie's Outfit Oddities" return 'Shop' } function updateTabStyles() { shopOverlay.querySelectorAll('.shop-tab').forEach(tab => { tab.classList.toggle('active', tab.dataset.tab === currentTab) }) } function updateShopContent() { const content = shopOverlay.querySelector('#shop-content') // Donny sells buildings, Ollie sells outfits if (currentShopkeeper === 'donny') { content.innerHTML = renderBuildingsTab() attachBuildingListeners(content) } else if (currentShopkeeper === 'ollie') { content.innerHTML = renderOutfitsTab() attachOutfitListeners(content) } // Update koi count const koiCount = shopOverlay.querySelector('#shop-koi-count') if (koiCount) koiCount.textContent = gameState.getKoi() } function renderOutfitsTab() { const characterBtns = Object.values(CHARACTERS).map(char => ` `).join('') const outfits = getOutfitsForCharacter(currentCharacter) if (outfits.length === 0) { return `
${characterBtns}
No outfits available for ${currentCharacter}
` } const itemCards = outfits.map(outfit => { const owned = inventory.owns(outfit.id) const equipped = inventory.isEquipped(outfit.character, outfit.id) const canAfford = gameState.getKoi() >= outfit.price let btnClass, btnText if (!owned) { btnClass = 'buy' btnText = canAfford ? 'Buy' : 'Need more 🐟' } else if (equipped) { btnClass = 'unequip' btnText = 'Unequip' } else { btnClass = 'equip' btnText = 'Equip' } const cardClasses = ['item-card'] if (owned) cardClasses.push('owned') if (equipped) cardClasses.push('equipped') if (!owned && !canAfford) cardClasses.push('cant-afford') return `
${outfit.name}
${!owned ? `
🐟 ${outfit.price}
` : ''}
` }).join('') return `
${characterBtns}
${itemCards}
` } function renderBuildingsTab() { const buildings = getAllBuildings() const itemCards = buildings.map(building => { const owned = inventory.owns(building.id) const canAfford = gameState.getKoi() >= building.price let btnClass, btnText if (owned) { btnClass = 'equip' btnText = 'Place' } else { btnClass = 'buy' btnText = canAfford ? 'Buy' : 'Need more 🐟' } const cardClasses = ['item-card'] if (owned) cardClasses.push('owned') if (!owned && !canAfford) cardClasses.push('cant-afford') return `
${building.name}
${!owned ? `
🐟 ${building.price}
` : ''}
` }).join('') return `
${itemCards}
` } function attachOutfitListeners(content) { // Character selector content.querySelectorAll('.character-btn').forEach(btn => { btn.addEventListener('click', () => { currentCharacter = btn.dataset.character updateShopContent() }) }) // Item buttons content.querySelectorAll('.item-btn').forEach(btn => { btn.addEventListener('click', () => { const itemId = btn.dataset.itemId const action = btn.dataset.action const item = getItem(itemId) if (action === 'buy') { if (gameState.spendKoi(item.price)) { inventory.purchase(itemId) // Recalculate affordable items after purchase gameState.onPurchase() // Auto-equip after buying (this also unequips same-type items) const unequippedIds = inventory.equip(item.character, itemId) // Notify about unequipped items first if (onPurchaseCallback && unequippedIds) { for (const oldId of unequippedIds) { const oldItem = getItem(oldId) if (oldItem) onPurchaseCallback(oldItem, 'unequip') } } if (onPurchaseCallback) onPurchaseCallback(item, 'equip') playPurchase() updateShopContent() } } else if (action === 'equip') { // Equip returns any auto-unequipped items of the same type const unequippedIds = inventory.equip(item.character, itemId) // Notify about unequipped items first if (onPurchaseCallback && unequippedIds) { for (const oldId of unequippedIds) { const oldItem = getItem(oldId) if (oldItem) onPurchaseCallback(oldItem, 'unequip') } } if (onPurchaseCallback) onPurchaseCallback(item, 'equip') updateShopContent() } else if (action === 'unequip') { inventory.unequip(item.character, itemId) if (onPurchaseCallback) onPurchaseCallback(item, 'unequip') updateShopContent() } }) }) } function attachBuildingListeners(content) { content.querySelectorAll('.item-btn').forEach(btn => { btn.addEventListener('click', () => { const itemId = btn.dataset.itemId const action = btn.dataset.action const item = getItem(itemId) if (action === 'buy') { if (gameState.spendKoi(item.price)) { inventory.purchase(itemId) // Recalculate affordable items after purchase gameState.onPurchase() if (onPurchaseCallback) onPurchaseCallback(item) playPurchase() updateShopContent() } } else if (action === 'equip') { // Building placement - close shop and enter placement mode // Save callback before closeShop clears it const callback = onPurchaseCallback closeShop() if (callback) callback(item, 'place') } }) }) } export function openShop(shopkeeper, container, callbacks = {}) { if (shopOverlay) return // Already open currentShopkeeper = shopkeeper onCloseCallback = callbacks.onClose onPurchaseCallback = callbacks.onPurchase // Set default tab based on shopkeeper specialty // Donny sells buildings, Ollie sells outfits currentTab = shopkeeper === 'donny' ? 'buildings' : 'outfits' shopOverlay = createShopOverlay() container.appendChild(shopOverlay) updateShopContent() // Listen for koi count changes gameState.addListener(updateKoiDisplay) playShopOpen() } function updateKoiDisplay(count) { if (shopOverlay) { const koiCount = shopOverlay.querySelector('#shop-koi-count') if (koiCount) koiCount.textContent = count } } export function closeShop() { if (!shopOverlay) return playShopClose() gameState.removeListener(updateKoiDisplay) shopOverlay.remove() shopOverlay = null if (onCloseCallback) { onCloseCallback() onCloseCallback = null } currentShopkeeper = null onPurchaseCallback = null } export function isShopOpen() { return shopOverlay !== null }