class BuildingToolbox extends HTMLElement { static get observedAttributes() { return ['player-money', 'player-population']; } constructor() { super(); this.app = null; this.buildingItems = []; // A cache for the building DOM elements this.buildings = [ { type: 'small_house', name: 'Small House', cost: 5000, income: -50, pop: 10 }, { type: 'medium_house', name: 'Medium House', cost: 12000, income: -120, pop: 25 }, { type: 'large_house', name: 'Large House', cost: 25000, income: -250, pop: 50 }, { type: 'small_shop', name: 'Small Shop', cost: 8000, income: 100, pop: -5, req: 20 }, { type: 'supermarket', name: 'Supermarket', cost: 25000, income: 300, pop: -15, req: 50 }, { type: 'mall', name: 'Shopping Mall', cost: 80000, income: 800, pop: -40, req: 100 }, { type: 'small_factory', name: 'Small Factory', cost: 15000, income: 200, pop: -20 }, { type: 'large_factory', name: 'Large Factory', cost: 50000, income: 500, pop: -50 }, { type: 'road', name: 'Road', cost: 500, income: 0, pop: 0 }, { type: 'park', name: 'Park', cost: 3000, income: -20, pop: 5 }, { type: 'plaza', name: 'Plaza', cost: 8000, income: -40, pop: 10 }, { type: 'town_hall', name: 'Town Hall', cost: 50000, income: -100, pop: 100 }, { type: 'power_plant', name: 'Power Plant', cost: 100000, income: -500, pop: -30 } ]; } connectedCallback() { // 1. Initial full render happens only once this.innerHTML = this.renderHTML(); // 2. Cache the DOM elements for efficient future updates this.buildingItems = this.querySelectorAll('.building-item'); // 3. Add click handlers only once this.addClickHandlers(); } attributeChangedCallback() { // When player stats change, run the lightweight update function // instead of a full re-render. if (this.buildingItems.length > 0) { this.updateItemStates(); } } renderHTML() { // This function generates the initial HTML string. const money = parseInt(this.getAttribute('player-money') || '0'); const population = parseInt(this.getAttribute('player-population') || '0'); return `
Buildings
${this.buildings.map(building => { const canAfford = money >= building.cost; const meetsReq = !building.req || population >= building.req; const enabled = canAfford && meetsReq; return `
${building.name}
Cost: $${building.cost.toLocaleString()}
${building.income !== 0 ? `Income: $${building.income}/tick` : ''} ${building.pop !== 0 ? `Pop: ${building.pop > 0 ? '+' : ''}${building.pop}` : ''} ${building.req ? `(Req: ${building.req} pop)` : ''}
`; }).join('')}
`; } addClickHandlers() { this.buildingItems.forEach(item => { item.addEventListener('click', () => { const type = item.dataset.type; const building = this.buildings.find(b => b.type === type); const money = parseInt(this.getAttribute('player-money') || '0'); const population = parseInt(this.getAttribute('player-population') || '0'); if (money >= building.cost && (!building.req || population >= building.req)) { if (this.app) { this.app.selectBuilding(type); } } }); }); } updateItemStates() { // This lightweight function only updates styles, preserving the DOM and scroll position. const money = parseInt(this.getAttribute('player-money') || '0'); const population = parseInt(this.getAttribute('player-population') || '0'); this.buildingItems.forEach(item => { const type = item.dataset.type; const building = this.buildings.find(b => b.type === type); if (!building) return; const canAfford = money >= building.cost; const meetsReq = !building.req || population >= building.req; const enabled = canAfford && meetsReq; // Directly update only the styles that change item.style.opacity = enabled ? '1' : '0.5'; item.style.cursor = enabled ? 'pointer' : 'not-allowed'; }); } } customElements.define('building-toolbox', BuildingToolbox);