feat: create EarthBound-styled CommandPanel UI component
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
// 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() {
|
||||
// Outer frame (doubled border, same as NpcInfoPanel)
|
||||
this.outerFrame = document.createElement('div');
|
||||
this.outerFrame.id = 'command-panel';
|
||||
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 title = document.createElement('div');
|
||||
title.style.cssText = `
|
||||
font-size: 6px;
|
||||
color: ${EB.textMuted};
|
||||
text-align: center;
|
||||
letter-spacing: 4px;
|
||||
padding-bottom: 6px;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
`;
|
||||
title.textContent = '\u25C6 COMMANDS \u25C6';
|
||||
container.appendChild(title);
|
||||
|
||||
// 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 default commands
|
||||
this.addCommand({ key: '1', label: 'Spawn NPC' });
|
||||
}
|
||||
|
||||
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: 7px;
|
||||
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: 7px;
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user