Files
dflike/client/src/ui/CommandPanel.ts
T
root 3de56316bb Merge branch 'worktree-relationship-system'
# Conflicts:
#	client/src/ui/CommandPanel.ts
2026-03-07 16:47:43 +00:00

173 lines
4.4 KiB
TypeScript

// EarthBound-inspired color palette (matches NpcInfoPanel)
const EB = {
bgDeep: '#0c0824',
bgPanel: '#141038',
borderOuter: '#7878d8',
borderInner: '#5858b8',
borderGap: '#1c1450',
textPrimary: '#f0f0ff',
textSecondary: '#9898d0',
textMuted: '#6868a8',
shine: 'rgba(200,200,255,0.08)',
};
interface Command {
key: string;
label: string;
}
export class CommandPanel {
private outerFrame: HTMLDivElement;
private listContainer: HTMLDivElement;
private visible = false;
constructor(id = 'command-panel', title = 'COMMANDS', commands: Command[] = [{ key: '1', label: 'Spawn NPC' }]) {
// Outer frame (doubled border, same as NpcInfoPanel)
this.outerFrame = document.createElement('div');
this.outerFrame.id = id;
this.outerFrame.style.cssText = `
position: fixed;
bottom: 16px;
right: 16px;
min-width: 160px;
border-radius: 12px;
border: 3px solid ${EB.borderOuter};
background: ${EB.borderGap};
z-index: 1000;
opacity: 0;
transform: translateY(20px);
transition: opacity 0.3s cubic-bezier(0.22, 1, 0.36, 1),
transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
pointer-events: none;
box-shadow:
0 0 16px rgba(80, 60, 160, 0.3),
0 4px 20px rgba(0, 0, 0, 0.5),
inset 0 1px 0 rgba(160, 160, 255, 0.15);
`;
// Inner container
const container = document.createElement('div');
container.style.cssText = `
margin: 3px;
border-radius: 8px;
border: 2px solid ${EB.borderInner};
background: ${EB.bgPanel};
overflow: hidden;
position: relative;
padding: 8px 12px 10px;
font-family: 'Press Start 2P', monospace;
`;
// Shine overlay
const shine = document.createElement('div');
shine.style.cssText = `
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: linear-gradient(
135deg,
${EB.shine} 0%,
transparent 40%,
transparent 60%,
rgba(0,0,0,0.05) 100%
);
pointer-events: none;
z-index: 1;
`;
container.appendChild(shine);
// Title
const titleEl = document.createElement('div');
titleEl.style.cssText = `
font-size: 9px;
color: ${EB.textMuted};
text-align: center;
letter-spacing: 4px;
padding-bottom: 6px;
user-select: none;
position: relative;
z-index: 2;
`;
titleEl.textContent = `\u25C6 ${title} \u25C6`;
container.appendChild(titleEl);
// Commands list
this.listContainer = document.createElement('div');
this.listContainer.style.cssText = `
display: flex;
flex-direction: column;
gap: 4px;
position: relative;
z-index: 2;
`;
container.appendChild(this.listContainer);
this.outerFrame.appendChild(container);
document.body.appendChild(this.outerFrame);
// Add commands
for (const cmd of commands) {
this.addCommand(cmd);
}
}
private addCommand(cmd: Command): void {
const row = document.createElement('div');
row.style.cssText = `
display: flex;
align-items: center;
gap: 8px;
`;
const keyBadge = document.createElement('div');
keyBadge.style.cssText = `
font-size: 10px;
color: ${EB.bgDeep};
background: ${EB.borderOuter};
border-radius: 3px;
padding: 2px 5px;
min-width: 12px;
text-align: center;
text-shadow: none;
font-weight: bold;
box-shadow: 0 1px 0 rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.2);
`;
keyBadge.textContent = cmd.key;
const label = document.createElement('div');
label.style.cssText = `
font-size: 10px;
color: ${EB.textSecondary};
letter-spacing: 0.5px;
`;
label.textContent = cmd.label;
row.appendChild(keyBadge);
row.appendChild(label);
this.listContainer.appendChild(row);
}
show(): void {
if (this.visible) return;
this.visible = true;
this.outerFrame.style.opacity = '1';
this.outerFrame.style.transform = 'translateY(0)';
this.outerFrame.style.pointerEvents = 'auto';
}
hide(): void {
if (!this.visible) return;
this.visible = false;
this.outerFrame.style.opacity = '0';
this.outerFrame.style.transform = 'translateY(20px)';
this.outerFrame.style.pointerEvents = 'none';
}
isVisible(): boolean {
return this.visible;
}
destroy(): void {
this.outerFrame.remove();
}
}