// ═══ renderer-shop-overlay.js ═══ // ============================================================ // RENDERER SHOP — canvas armory overlay // ============================================================ // ── SHOP OVERLAY ────────────────────────────────────────────── const _SH_HDR_H = 56; const _SH_TAB_H = 38; const _SH_BODY_Y = _SH_HDR_H + _SH_TAB_H; // 94 const _SH_PAD = 24; const _SH_UPG_W = 130; const _SH_UPG_H = 78; const _SH_ARR_W = 26; let _shopScrollY = 0; let _shopScrollMax = 0; const _shopRightClick = []; function _shopWrapText(text, maxW, maxLines) { if (!text) return []; const words = text.split(' '); const lines = []; let cur = ''; for (const w of words) { const test = cur ? cur + ' ' + w : w; if (ctx.measureText(test).width <= maxW) { cur = test; } else { if (cur) lines.push(cur); cur = w; if (lines.length >= maxLines) break; } } if (cur && lines.length < maxLines) lines.push(cur); return lines; } function _shopUpgNode(sx, screenY, upg, bought, locked, cantAfford, onBuy, onRefund) { const hov = !bought && !locked && !cantAfford && isHovered(sx, screenY, _SH_UPG_W, _SH_UPG_H); const border = bought ? '#1a5030' : locked ? '#0e1e28' : cantAfford ? '#1a2030' : hov ? '#ffd700' : '#1a3048'; ctx.save(); ctx.globalAlpha = bought ? 0.75 : locked ? 0.32 : cantAfford ? 0.55 : 1; ctx.fillStyle = bought ? '#071410' : '#060e18'; ctx.strokeStyle = border; ctx.lineWidth = 1; ctx.fillRect(sx, screenY, _SH_UPG_W, _SH_UPG_H); ctx.strokeRect(sx, screenY, _SH_UPG_W, _SH_UPG_H); ctx.save(); ctx.beginPath(); ctx.rect(sx + 5, screenY + 5, _SH_UPG_W - 10, 16); ctx.clip(); ctx.font = '11px Orbitron, "Share Tech Mono", monospace'; ctx.letterSpacing = '0.5px'; ctx.textAlign = 'left'; ctx.textBaseline = 'top'; ctx.fillStyle = bought ? '#00ff88' : '#b8d8e8'; ctx.fillText(upg.label, sx + 5, screenY + 5); ctx.restore(); ctx.save(); ctx.beginPath(); ctx.rect(sx + 5, screenY + 22, _SH_UPG_W - 10, 30); ctx.clip(); ctx.font = '10px "Share Tech Mono", monospace'; ctx.letterSpacing = '0px'; ctx.textAlign = 'left'; ctx.textBaseline = 'top'; ctx.fillStyle = '#3a6080'; const lines = _shopWrapText(upg.desc, _SH_UPG_W - 12, 2); for (let li = 0; li < lines.length; li++) ctx.fillText(lines[li], sx + 5, screenY + 22 + li * 14); ctx.restore(); ctx.font = '10px "Share Tech Mono", monospace'; ctx.letterSpacing = '0px'; ctx.textAlign = 'left'; ctx.textBaseline = 'top'; if (bought) { ctx.fillStyle = '#1a4030'; ctx.fillText('✓ right-click refund', sx + 5, screenY + 58); } else if (locked) { ctx.fillStyle = '#1a2838'; ctx.fillText('🔒 locked', sx + 5, screenY + 58); } else { ctx.fillStyle = cantAfford ? '#ff3355' : '#ffd700'; ctx.fillText(upg.cost + '¢', sx + 5, screenY + 58); } ctx.restore(); if (onBuy) addHitRegion(sx, screenY, _SH_UPG_W, _SH_UPG_H, onBuy); if (onRefund) _shopRightClick.push({ x: sx, y: screenY, w: _SH_UPG_W, h: _SH_UPG_H, action: onRefund }); } function drawShopOverlay() { if (!G.shopOpen) return; _shopRightClick.length = 0; const W = canvas.width, H = canvas.height; const BODY_H = H - _SH_BODY_Y; ctx.fillStyle = 'rgba(2,8,14,0.97)'; ctx.fillRect(0, 0, W, H); ctx.save(); ctx.fillStyle = '#040c14'; ctx.fillRect(0, 0, W, _SH_HDR_H); ctx.strokeStyle = '#1a3048'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(0, _SH_HDR_H); ctx.lineTo(W, _SH_HDR_H); ctx.stroke(); ctx.font = '900 15px Orbitron, "Share Tech Mono", monospace'; ctx.letterSpacing = '6px'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillStyle = '#00d4ff'; ctx.shadowColor = '#00d4ff44'; ctx.shadowBlur = 14; ctx.fillText('ARMORY', _SH_PAD, _SH_HDR_H / 2); ctx.shadowBlur = 0; ctx.letterSpacing = '0px'; ctx.font = '18px Orbitron, "Share Tech Mono", monospace'; ctx.textAlign = 'center'; ctx.fillStyle = '#ffd700'; ctx.fillText('💰 ' + G.credits + '¢', W / 2, _SH_HDR_H / 2); const CBW = 160, CBH = 32; const CBX = W - _SH_PAD - CBW, CBY = (_SH_HDR_H - CBH) / 2; const cbHov = isHovered(CBX, CBY, CBW, CBH); ctx.fillStyle = cbHov ? '#3d0808' : 'transparent'; ctx.strokeStyle = '#ff3355'; ctx.lineWidth = 1; ctx.fillRect(CBX, CBY, CBW, CBH); ctx.strokeRect(CBX, CBY, CBW, CBH); ctx.font = '11px Orbitron, monospace'; ctx.letterSpacing = '2px'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillStyle = '#ff3355'; ctx.fillText('✕ CLOSE [Esc]', CBX + CBW / 2, CBY + CBH / 2); ctx.letterSpacing = '0px'; addHitRegion(CBX, CBY, CBW, CBH, closeShop); ctx.fillStyle = '#030a12'; ctx.fillRect(0, _SH_HDR_H, W, _SH_TAB_H); ctx.strokeStyle = '#1a3048'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(0, _SH_BODY_Y); ctx.lineTo(W, _SH_BODY_Y); ctx.stroke(); const equippedWeapons = getEquippedWeapons(); const tabDefs = [ { id: 'tower', label: '🏰 TOWER' }, { id: 'weapons', label: '🔧 BUY WEAPON' }, ...equippedWeapons.map(w => { const def = getWeaponDef(w); return { id: w.instanceId, label: (def?.icon || '?') + ' ' + (def?.name || '?') }; }), ]; ctx.font = '11px Orbitron, "Share Tech Mono", monospace'; ctx.letterSpacing = '1.5px'; let tx = _SH_PAD; const TB_Y = _SH_HDR_H + 4, TB_H = _SH_TAB_H - 4; for (const tab of tabDefs) { const tw = Math.ceil(ctx.measureText(tab.label).width) + 32; const active = G.shopTab === tab.id; const tabHov = isHovered(tx, TB_Y, tw, TB_H); if (active) { ctx.fillStyle = '#060e18'; ctx.strokeStyle = '#1a3048'; ctx.lineWidth = 1; ctx.fillRect(tx, TB_Y, tw, TB_H + 2); ctx.strokeRect(tx, TB_Y, tw, TB_H); } ctx.fillStyle = active ? '#00d4ff' : (tabHov ? '#b8d8e8' : '#3a6080'); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(tab.label, tx + tw / 2, TB_Y + TB_H / 2); addHitRegion(tx, TB_Y, tw, TB_H, ((tid) => () => setShopTab(tid))(tab.id)); tx += tw + 4; } ctx.letterSpacing = '0px'; ctx.save(); ctx.beginPath(); ctx.rect(0, _SH_BODY_Y, W, BODY_H); ctx.clip(); const bodyCX = _SH_PAD, bodyCW = W - _SH_PAD * 2; let yOff = _SH_PAD; if (G.shopTab === 'tower') { yOff = _shopDrawTowerContent(yOff, bodyCX, bodyCW, H); } else if (G.shopTab === 'weapons') { yOff = _shopDrawBuyContent(yOff, bodyCX, bodyCW, H); } else { const w = equippedWeapons.find(w => w.instanceId === G.shopTab); if (w) yOff = _shopDrawWeaponContent(yOff, bodyCX, bodyCW, H, w); } _shopScrollMax = Math.max(0, yOff - BODY_H + _SH_PAD); ctx.restore(); ctx.restore(); }