554b5a6f30
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>
116 lines
3.4 KiB
TypeScript
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 = '';
|
|
}
|
|
}
|
|
}
|
|
}
|