feat: display backstory in NPC info panel with fade-in transition
Add backstory element to the Status tab between the activity label and diamond separator. When a backstory arrives from the LLM, it fades in with italic styling. Empty backstories remain hidden. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,7 @@ export class NpcInfoPanel {
|
||||
private separatorEl: HTMLDivElement;
|
||||
private needsBarsContainer: HTMLDivElement;
|
||||
private needsBars: Map<string, { wrapper: HTMLDivElement; fill: HTMLDivElement; valueEl: HTMLDivElement }> = new Map();
|
||||
private backstoryEl: HTMLDivElement;
|
||||
private statsContainer: HTMLDivElement;
|
||||
private statElements: Map<string, HTMLDivElement> = new Map();
|
||||
private statusTab!: HTMLDivElement;
|
||||
@@ -53,12 +54,12 @@ export class NpcInfoPanel {
|
||||
position: fixed;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
width: 280px;
|
||||
width: 400px;
|
||||
border-radius: 16px;
|
||||
border: 3px solid ${EB.borderOuter};
|
||||
background: ${EB.borderGap};
|
||||
z-index: 1000;
|
||||
transform: translateX(350px);
|
||||
transform: translateX(470px);
|
||||
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
box-shadow:
|
||||
0 0 20px rgba(80, 60, 160, 0.4),
|
||||
@@ -154,7 +155,7 @@ export class NpcInfoPanel {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
font-size: 9px;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
color: ${EB.textPrimary};
|
||||
border-bottom: 2px solid ${EB.borderOuter};
|
||||
@@ -167,7 +168,7 @@ export class NpcInfoPanel {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
font-size: 9px;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
color: ${EB.textMuted};
|
||||
user-select: none;
|
||||
@@ -197,7 +198,7 @@ export class NpcInfoPanel {
|
||||
// Name
|
||||
this.nameEl = document.createElement('div');
|
||||
this.nameEl.style.cssText = `
|
||||
font-size: 13px;
|
||||
font-size: 26px;
|
||||
color: ${EB.textPrimary};
|
||||
text-align: center;
|
||||
padding: 2px 0 4px;
|
||||
@@ -209,18 +210,34 @@ export class NpcInfoPanel {
|
||||
// Activity
|
||||
this.activityEl = document.createElement('div');
|
||||
this.activityEl.style.cssText = `
|
||||
font-size: 10px;
|
||||
font-size: 20px;
|
||||
color: ${EB.textSecondary};
|
||||
text-align: center;
|
||||
padding-bottom: 2px;
|
||||
`;
|
||||
this.statusContent.appendChild(this.activityEl);
|
||||
|
||||
// Backstory text
|
||||
this.backstoryEl = document.createElement('div');
|
||||
this.backstoryEl.style.cssText = `
|
||||
font-size: 14px;
|
||||
color: ${EB.textSecondary};
|
||||
text-align: center;
|
||||
padding: 2px 8px 4px;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
line-height: 1.6;
|
||||
font-style: italic;
|
||||
min-height: 0;
|
||||
transition: min-height 0.3s ease, opacity 0.3s ease;
|
||||
opacity: 0;
|
||||
`;
|
||||
this.statusContent.appendChild(this.backstoryEl);
|
||||
|
||||
// Decorative separator (EarthBound diamond pattern)
|
||||
this.separatorEl = document.createElement('div');
|
||||
this.separatorEl.style.cssText = `
|
||||
text-align: center;
|
||||
font-size: 7px;
|
||||
font-size: 14px;
|
||||
color: ${EB.textMuted};
|
||||
padding: 4px 0;
|
||||
letter-spacing: 6px;
|
||||
@@ -242,7 +259,7 @@ export class NpcInfoPanel {
|
||||
const statsSeparator = document.createElement('div');
|
||||
statsSeparator.style.cssText = `
|
||||
text-align: center;
|
||||
font-size: 7px;
|
||||
font-size: 14px;
|
||||
color: ${EB.textMuted};
|
||||
padding: 4px 0;
|
||||
letter-spacing: 6px;
|
||||
@@ -287,7 +304,7 @@ export class NpcInfoPanel {
|
||||
|
||||
hide(): void {
|
||||
this.visible = false;
|
||||
this.outerFrame.style.transform = 'translateX(350px)';
|
||||
this.outerFrame.style.transform = 'translateX(470px)';
|
||||
}
|
||||
|
||||
updateInfo(entity: EntityState): void {
|
||||
@@ -301,6 +318,18 @@ export class NpcInfoPanel {
|
||||
this.activityEl.textContent = goal ? (ACTIVITY_LABELS[goal] ?? goal) : 'Idle';
|
||||
}
|
||||
|
||||
// Update backstory
|
||||
const backstory = entity.backstory;
|
||||
if (backstory && backstory.length > 0) {
|
||||
this.backstoryEl.textContent = `"${backstory}"`;
|
||||
this.backstoryEl.style.opacity = '1';
|
||||
this.backstoryEl.style.minHeight = '40px';
|
||||
} else {
|
||||
this.backstoryEl.textContent = '';
|
||||
this.backstoryEl.style.opacity = '0';
|
||||
this.backstoryEl.style.minHeight = '0';
|
||||
}
|
||||
|
||||
if (entity.needs) {
|
||||
this.updateNeeds(entity.needs);
|
||||
}
|
||||
@@ -346,7 +375,7 @@ export class NpcInfoPanel {
|
||||
|
||||
const labelEl = document.createElement('div');
|
||||
labelEl.style.cssText = `
|
||||
font-size: 9px;
|
||||
font-size: 18px;
|
||||
color: ${EB.textSecondary};
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
@@ -355,7 +384,7 @@ export class NpcInfoPanel {
|
||||
|
||||
const valueEl = document.createElement('div');
|
||||
valueEl.style.cssText = `
|
||||
font-size: 10px;
|
||||
font-size: 20px;
|
||||
color: ${EB.textPrimary};
|
||||
text-shadow: 1px 1px 0 rgba(0,0,0,0.5);
|
||||
`;
|
||||
@@ -437,7 +466,7 @@ export class NpcInfoPanel {
|
||||
el.style.cssText = `
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 9px;
|
||||
font-size: 18px;
|
||||
`;
|
||||
const labelEl = document.createElement('span');
|
||||
labelEl.style.cssText = `color: ${EB.textSecondary}; text-transform: uppercase; letter-spacing: 0.5px;`;
|
||||
@@ -477,7 +506,7 @@ export class NpcInfoPanel {
|
||||
if (relationships.length === 0) {
|
||||
this.relationshipsContent.innerHTML = '';
|
||||
const empty = document.createElement('div');
|
||||
empty.style.cssText = `text-align: center; color: ${EB.textMuted}; font-size: 9px; padding: 16px 0;`;
|
||||
empty.style.cssText = `text-align: center; color: ${EB.textMuted}; font-size: 18px; padding: 16px 0;`;
|
||||
empty.textContent = 'No relationships yet';
|
||||
this.relationshipsContent.appendChild(empty);
|
||||
return;
|
||||
@@ -520,7 +549,7 @@ export class NpcInfoPanel {
|
||||
|
||||
const tierLabel = document.createElement('div');
|
||||
const icon = tierIcons[tier] ?? '';
|
||||
tierLabel.style.cssText = `font-size: 8px; color: ${EB.textSecondary}; margin-bottom: 4px;`;
|
||||
tierLabel.style.cssText = `font-size: 16px; color: ${EB.textSecondary}; margin-bottom: 4px;`;
|
||||
tierLabel.textContent = `${icon} ${tier}`;
|
||||
tierEl.appendChild(tierLabel);
|
||||
|
||||
@@ -539,7 +568,7 @@ export class NpcInfoPanel {
|
||||
|
||||
const toggle = document.createElement('div');
|
||||
toggle.style.cssText = `
|
||||
font-size: 7px;
|
||||
font-size: 14px;
|
||||
color: ${EB.borderOuter};
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
@@ -571,7 +600,7 @@ export class NpcInfoPanel {
|
||||
const memEl = document.createElement('div');
|
||||
memEl.style.cssText = 'margin-bottom: 8px;';
|
||||
const memLabel = document.createElement('div');
|
||||
memLabel.style.cssText = `font-size: 8px; color: ${EB.textMuted}; margin-bottom: 4px;`;
|
||||
memLabel.style.cssText = `font-size: 16px; color: ${EB.textMuted}; margin-bottom: 4px;`;
|
||||
memLabel.textContent = '\u25CB Memories';
|
||||
memEl.appendChild(memLabel);
|
||||
|
||||
@@ -594,7 +623,7 @@ export class NpcInfoPanel {
|
||||
|
||||
const toggle = document.createElement('div');
|
||||
toggle.style.cssText = `
|
||||
font-size: 7px;
|
||||
font-size: 14px;
|
||||
color: ${EB.borderOuter};
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
@@ -630,11 +659,11 @@ export class NpcInfoPanel {
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-bottom: 3px;
|
||||
font-size: 8px;
|
||||
font-size: 16px;
|
||||
`;
|
||||
|
||||
const nameEl = document.createElement('span');
|
||||
nameEl.style.cssText = `color: ${EB.textPrimary}; min-width: 70px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;`;
|
||||
nameEl.style.cssText = `color: ${EB.textPrimary}; min-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;`;
|
||||
const bondIcon = bond === 'partner' ? ' \u{1F48D}' : bond === 'former_partner' ? ' \u{1F48D}\u{FE0E}' : '';
|
||||
nameEl.textContent = name + bondIcon;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user