Files
dflike/client/src/ui/SuperlativesPanel.ts
T
root 554b5a6f30 feat: refactor left panel into tabbed container with Stats + Events tabs
Extract SuperlativesPanel into content-only component, create LeftPanel
as the outer shell with two tabs (S for Stats, E for Events), and add
EventsFeed component for displaying narration events with clickable
NPC names and auto-scroll behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:19:20 +00:00

116 lines
3.4 KiB
TypeScript

import type { SuperlativesData } from '@dflike/shared';
type SuperlativeCategory = {
key: keyof SuperlativesData;
label: string;
};
const CATEGORIES: SuperlativeCategory[] = [
{ key: 'mostLoved', label: 'Most Loved' },
{ key: 'mostReviled', label: 'Most Reviled' },
{ key: 'mostPopular', label: 'Most Popular' },
{ key: 'mostAnnoying', label: 'Most Annoying' },
{ key: 'mostOutgoing', label: 'Most Outgoing' },
{ key: 'shyest', label: 'Shyest' },
{ key: 'socialButterfly', label: 'Social Butterfly' },
{ key: 'loneliest', label: 'Loneliest' },
{ key: 'mostDevoted', label: 'Most Devoted' },
{ key: 'mostPolarizing', label: 'Most Polarizing' },
{ key: 'biggestHeartbreaker', label: 'Heartbreaker' },
];
export class SuperlativesPanel {
private contentEl: HTMLDivElement;
private categoryElements: Map<string, { nameEl: HTMLSpanElement; valueEl: HTMLSpanElement }> = new Map();
constructor() {
// Content area (no outer container, tab, or slide logic)
this.contentEl = document.createElement('div');
for (const cat of CATEGORIES) {
const row = document.createElement('div');
row.style.cssText = 'margin-bottom: 10px;';
const label = document.createElement('div');
label.style.cssText = `
color: #8878a8;
font-size: 12px;
margin-bottom: 2px;
text-transform: uppercase;
`;
label.textContent = cat.label;
const entryRow = document.createElement('div');
entryRow.style.cssText = `
display: flex;
justify-content: space-between;
align-items: baseline;
`;
const nameEl = document.createElement('span');
nameEl.style.cssText = `
color: #58d858;
font-size: 14px;
cursor: pointer;
text-decoration: none;
`;
nameEl.textContent = '\u2014';
nameEl.addEventListener('mouseenter', () => {
if (nameEl.dataset.entityId) {
nameEl.style.color = '#88ff88';
nameEl.style.textDecoration = 'underline';
}
});
nameEl.addEventListener('mouseleave', () => {
nameEl.style.color = '#58d858';
nameEl.style.textDecoration = 'none';
});
nameEl.addEventListener('click', () => {
const entityId = nameEl.dataset.entityId;
if (entityId) {
document.dispatchEvent(new CustomEvent('superlative-follow', {
detail: { entityId: parseInt(entityId, 10) },
}));
}
});
const valueEl = document.createElement('span');
valueEl.style.cssText = `
color: #a0a0c0;
font-size: 12px;
margin-left: 4px;
flex-shrink: 0;
`;
entryRow.appendChild(nameEl);
entryRow.appendChild(valueEl);
row.appendChild(label);
row.appendChild(entryRow);
this.contentEl.appendChild(row);
this.categoryElements.set(cat.key, { nameEl, valueEl });
}
}
getElement(): HTMLDivElement {
return this.contentEl;
}
update(data: SuperlativesData): void {
for (const cat of CATEGORIES) {
const els = this.categoryElements.get(cat.key);
if (!els) continue;
const entry = data[cat.key];
if (entry) {
els.nameEl.textContent = entry.name;
els.nameEl.dataset.entityId = String(entry.entityId);
els.valueEl.textContent = `(${entry.value})`;
} else {
els.nameEl.textContent = '\u2014';
delete els.nameEl.dataset.entityId;
els.valueEl.textContent = '';
}
}
}
}