diff --git a/client/src/ui/InteractionEmoji.ts b/client/src/ui/InteractionEmoji.ts index 1d9e52c..cd773b6 100644 --- a/client/src/ui/InteractionEmoji.ts +++ b/client/src/ui/InteractionEmoji.ts @@ -2,6 +2,7 @@ import type { EntityState } from '@dflike/shared'; const EMOJI_POSITIVE = '\u{1F60A}'; const EMOJI_NEGATIVE = '\u{1F620}'; +const EMOJI_PROPOSAL = '\u{1F48D}'; export class InteractionEmojiManager { private activeEmojis: Map = new Map(); @@ -18,10 +19,10 @@ export class InteractionEmojiManager { const emoting = new Set(); for (const entity of entities) { - if (entity.socialState?.phase === 'emoting' && entity.socialState.outcome) { + if ((entity.socialState?.phase === 'emoting' || entity.socialState?.phase === 'proposing') && entity.socialState.outcome) { emoting.add(entity.id); if (!this.activeEmojis.has(entity.id)) { - this.createEmoji(entity.id, entity.socialState.outcome); + this.createEmoji(entity.id, entity.socialState.outcome, entity.socialState.phase); } } } @@ -37,7 +38,7 @@ export class InteractionEmojiManager { } } - private createEmoji(entityId: number, outcome: string): void { + private createEmoji(entityId: number, outcome: string, phase?: string): void { const div = document.createElement('div'); div.style.cssText = ` position: fixed; @@ -47,7 +48,13 @@ export class InteractionEmojiManager { transform: translate(-50%, -100%); `; div.style.animation = 'emoji-float 2s ease-out forwards'; - div.textContent = outcome === 'positive' ? EMOJI_POSITIVE : EMOJI_NEGATIVE; + let emoji: string; + if (phase === 'proposing') { + emoji = EMOJI_PROPOSAL; + } else { + emoji = outcome === 'positive' ? EMOJI_POSITIVE : EMOJI_NEGATIVE; + } + div.textContent = emoji; // Self-cleanup when animation completes div.addEventListener('animationend', () => { diff --git a/client/src/ui/NpcInfoPanel.ts b/client/src/ui/NpcInfoPanel.ts index 1d98cbf..7bd0817 100644 --- a/client/src/ui/NpcInfoPanel.ts +++ b/client/src/ui/NpcInfoPanel.ts @@ -483,7 +483,7 @@ export class NpcInfoPanel { return; } - const tierOrder = ['Partner', 'Close Friend', 'Friend', 'Acquaintance', 'Stranger', 'Wary', 'Rival', 'Enemy', 'Nemesis']; + const tierOrder = ['Partner', 'Devoted', 'Close Friend', 'Friend', 'Acquaintance', 'Stranger', 'Wary', 'Rival', 'Enemy', 'Nemesis']; const groups = new Map(); const memories: typeof relationships = []; @@ -500,7 +500,7 @@ export class NpcInfoPanel { this.relationshipsContent.innerHTML = ''; const tierIcons: Record = { - 'Partner': '\u2665', 'Close Friend': '\u2605', 'Friend': '\u25C6', + 'Partner': '\u2665', 'Devoted': '\u2764', 'Close Friend': '\u2605', 'Friend': '\u25C6', 'Acquaintance': '\u25CB', 'Stranger': '\u00B7', 'Wary': '\u25B2', 'Rival': '\u2716', 'Enemy': '\u2620', 'Nemesis': '\u2620', }; @@ -525,7 +525,7 @@ export class NpcInfoPanel { tierEl.appendChild(tierLabel); for (const rel of shown) { - tierEl.appendChild(this.createRelSlider(rel.name, rel.value, tier)); + tierEl.appendChild(this.createRelSlider(rel.name, rel.value, tier, rel.bond)); } if (hidden.length > 0) { @@ -533,7 +533,7 @@ export class NpcInfoPanel { const overflow = document.createElement('div'); overflow.style.cssText = isExpanded ? '' : 'display: none;'; for (const rel of hidden) { - overflow.appendChild(this.createRelSlider(rel.name, rel.value, tier)); + overflow.appendChild(this.createRelSlider(rel.name, rel.value, tier, rel.bond)); } tierEl.appendChild(overflow); @@ -579,7 +579,7 @@ export class NpcInfoPanel { const memHidden = memories.slice(2); for (const rel of memShown) { - memEl.appendChild(this.createRelSlider(rel.name + ' \u2020', rel.value, 'memory')); + memEl.appendChild(this.createRelSlider(rel.name + ' \u2020', rel.value, 'memory', rel.bond)); } if (memHidden.length > 0) { @@ -588,7 +588,7 @@ export class NpcInfoPanel { const overflow = document.createElement('div'); overflow.style.cssText = isExpanded ? '' : 'display: none;'; for (const rel of memHidden) { - overflow.appendChild(this.createRelSlider(rel.name + ' \u2020', rel.value, 'memory')); + overflow.appendChild(this.createRelSlider(rel.name + ' \u2020', rel.value, 'memory', rel.bond)); } memEl.appendChild(overflow); @@ -623,7 +623,7 @@ export class NpcInfoPanel { } } - private createRelSlider(name: string, value: number, tier: string): HTMLDivElement { + private createRelSlider(name: string, value: number, tier: string, bond?: string | null): HTMLDivElement { const row = document.createElement('div'); row.style.cssText = ` display: flex; @@ -635,7 +635,8 @@ export class NpcInfoPanel { const nameEl = document.createElement('span'); nameEl.style.cssText = `color: ${EB.textPrimary}; min-width: 70px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;`; - nameEl.textContent = name; + const bondIcon = bond === 'partner' ? ' \u{1F48D}' : bond === 'former_partner' ? ' \u{1F48D}\u{FE0E}' : ''; + nameEl.textContent = name + bondIcon; const slider = document.createElement('div'); slider.style.cssText = ` @@ -682,6 +683,7 @@ export class NpcInfoPanel { private getRelColor(tier: string): string { switch (tier) { case 'Partner': return '#ff69b4'; + case 'Devoted': return '#ff69b4'; case 'Close Friend': return '#58d858'; case 'Friend': return '#40b840'; case 'Acquaintance': return '#6868a8';