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 <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -4,6 +4,10 @@ const ACTIVITY_LABELS: Record<string, string> = {
|
||||
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<string, HTMLDivElement> = 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<string, number>): 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}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user