From c7bf7c9ef4e529c197bb38c8600b7bb0ed75f574 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 9 Mar 2026 00:13:15 +0000 Subject: [PATCH] feat(client): add Phase D UI integration (D.1-D.3) - D.1: Inventory display in NPC info panel (two-column grid below stats) - D.2: Activity labels for gather/craft/build/dropoff goals - D.3: Gold color for invention events in feed, lightbulb icon in history Co-Authored-By: Claude Opus 4.6 --- client/src/ui/EventsFeed.ts | 2 +- client/src/ui/NpcInfoPanel.ts | 77 +++++++++++++++++++ ...26-03-08-resource-tool-invention-design.md | 4 +- 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/client/src/ui/EventsFeed.ts b/client/src/ui/EventsFeed.ts index fbb6eff..f9a7c6b 100644 --- a/client/src/ui/EventsFeed.ts +++ b/client/src/ui/EventsFeed.ts @@ -87,7 +87,7 @@ export class EventsFeed { const el = document.createElement('div'); el.style.cssText = ` font-size: 11px; - color: ${event.isLlmGenerated ? '#d0d0f0' : '#b0b0d0'}; + color: ${event.type === 'invention' ? '#f0d060' : event.isLlmGenerated ? '#d0d0f0' : '#b0b0d0'}; padding: 4px 0; border-bottom: 1px solid #2a2a4e; line-height: 1.5; diff --git a/client/src/ui/NpcInfoPanel.ts b/client/src/ui/NpcInfoPanel.ts index 566c02f..6fe1c21 100644 --- a/client/src/ui/NpcInfoPanel.ts +++ b/client/src/ui/NpcInfoPanel.ts @@ -4,6 +4,10 @@ const ACTIVITY_LABELS: Record = { wander: 'Wandering', eat: 'Eating', rest: 'Resting', + gather: 'Gathering', + craft: 'Crafting', + build: 'Building', + dropoff: 'Dropping off', }; // EarthBound-inspired color palette @@ -40,6 +44,8 @@ export class NpcInfoPanel { private backstoryEl: HTMLDivElement; private statsContainer: HTMLDivElement; private statElements: Map = new Map(); + private inventorySeparator: HTMLDivElement; + private inventoryContainer: HTMLDivElement; private statusTab!: HTMLDivElement; private relationshipsTab!: HTMLDivElement; private historyTab!: HTMLDivElement; @@ -333,6 +339,30 @@ export class NpcInfoPanel { `; this.statusContent.appendChild(this.statsContainer); + // Inventory separator (hidden when empty) + this.inventorySeparator = document.createElement('div'); + this.inventorySeparator.style.cssText = ` + text-align: center; + font-size: 11px; + color: ${EB.textMuted}; + padding: 4px 0; + letter-spacing: 6px; + user-select: none; + display: none; + `; + this.inventorySeparator.textContent = '\u25C6\u25C6\u25C6'; + this.statusContent.appendChild(this.inventorySeparator); + + // Inventory container - two columns like stats + this.inventoryContainer = document.createElement('div'); + this.inventoryContainer.style.cssText = ` + display: none; + grid-template-columns: 1fr 1fr; + gap: 3px 8px; + font-family: 'Press Start 2P', monospace; + `; + this.statusContent.appendChild(this.inventoryContainer); + contentWrapper.appendChild(this.statusContent); this.relationshipsContent = document.createElement('div'); @@ -402,6 +432,8 @@ export class NpcInfoPanel { if (entity.relationships) { this.updateRelationships(entity.relationships); } + + this.updateInventory(entity.inventory); } updateNeeds(needs: { hunger: number; energy: number }): void { @@ -409,6 +441,50 @@ export class NpcInfoPanel { this.setNeedBar('Energy', needs.energy); } + private updateInventory(inventory?: Record): void { + const items = inventory ? Object.entries(inventory).filter(([, qty]) => qty > 0) : []; + if (items.length === 0) { + this.inventorySeparator.style.display = 'none'; + this.inventoryContainer.style.display = 'none'; + return; + } + + this.inventorySeparator.style.display = ''; + this.inventoryContainer.style.display = 'grid'; + this.inventoryContainer.innerHTML = ''; + + // Section label spanning both columns + const label = document.createElement('div'); + label.style.cssText = ` + grid-column: 1 / -1; + font-size: 14px; + color: ${EB.textSecondary}; + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 2px; + `; + label.textContent = 'Inventory'; + this.inventoryContainer.appendChild(label); + + for (const [itemId, qty] of items) { + const el = document.createElement('div'); + el.style.cssText = ` + display: flex; + justify-content: space-between; + font-size: 14px; + `; + const nameEl = document.createElement('span'); + nameEl.style.cssText = `color: ${EB.textSecondary}; text-transform: capitalize;`; + nameEl.textContent = itemId.replace(/_/g, ' '); + const qtyEl = document.createElement('span'); + qtyEl.style.cssText = `color: ${EB.textPrimary}; text-shadow: 1px 1px 0 rgba(0,0,0,0.5);`; + qtyEl.textContent = String(qty); + el.appendChild(nameEl); + el.appendChild(qtyEl); + this.inventoryContainer.appendChild(el); + } + } + updateRecentEvents(events: NarrationEvent[]): void { if (events.length === 0) { this.recentEventsEl.style.display = 'none'; @@ -878,6 +954,7 @@ export class NpcInfoPanel { case 'bond_formed': return '\u{2764}'; case 'bond_dissolved': return '\u{1F525}'; case 'spawned': return '\u{1F331}'; + case 'invention': return '\u{1F4A1}'; default: return '\u{25CF}'; } } diff --git a/docs/plans/2026-03-08-resource-tool-invention-design.md b/docs/plans/2026-03-08-resource-tool-invention-design.md index 410a240..edbebec 100644 --- a/docs/plans/2026-03-08-resource-tool-invention-design.md +++ b/docs/plans/2026-03-08-resource-tool-invention-design.md @@ -283,7 +283,7 @@ Add `'craft'` and `'build'` to goal type union. **Goal:** NPCs occasionally have "eureka moments" where the LLM invents new items, recipes, or workshops from existing materials. Requires Phase B complete. -**Status:** NOT STARTED +**Status:** COMPLETE ### C.1 Invention System @@ -396,7 +396,7 @@ Once new items exist, they appear in future invention prompts: **Goal:** Surface inventory, industry tasks, and inventions in the client UI. Can start after Phase A; grows with each subsequent phase. -**Status:** NOT STARTED +**Status:** D.1-D.3 COMPLETE, D.4 NOT STARTED ### D.1 NPC Info Panel — Inventory Display