feat: scene traps, material legends, and scene editor window

Add trap overlays with session state, material legend editor/panel, and a dedicated scene editor window wired through IPC and project persistence.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-24 16:29:43 +08:00
parent d3b1c4660d
commit 02d73ddf81
39 changed files with 2563 additions and 36 deletions
+103
View File
@@ -0,0 +1,103 @@
/** Простые SVG-иконки ловушек (MVP). */
import type { SceneTrapStatus, SceneTrapType } from '../../shared/types';
const TYPE_COLOR: Record<SceneTrapType, string> = {
mimic: '#c4783a',
explosion: '#e85d3a',
poison: '#6bcb5a',
pit: '#5a5a6e',
arrow: '#d4a017',
laser: '#3ad0e8',
freeform: '#a78bfa',
};
export function trapAccentColor(type: SceneTrapType, status: SceneTrapStatus = 'inactive'): string {
if (status === 'disarmed') return '#6b7280';
if (status === 'active') return TYPE_COLOR[type];
return TYPE_COLOR[type];
}
export function TrapGlyph({
type,
status = 'inactive',
size = 28,
}: {
type: SceneTrapType;
status?: SceneTrapStatus;
size?: number;
}) {
const stroke = trapAccentColor(type, status);
const opacity = status === 'disarmed' ? 0.55 : 1;
const common = {
width: size,
height: size,
viewBox: '0 0 24 24',
fill: 'none',
stroke,
strokeWidth: 1.8,
strokeLinecap: 'round' as const,
strokeLinejoin: 'round' as const,
opacity,
'aria-hidden': true as const,
};
switch (type) {
case 'mimic':
return (
<svg {...common}>
<rect x="4" y="8" width="16" height="10" rx="1.5" />
<path d="M8 8 V6.5 a4 4 0 0 1 8 0 V8" />
<path d="M9 13h6M10 16h4" />
</svg>
);
case 'explosion':
return (
<svg {...common}>
<circle cx="12" cy="12" r="3.2" fill={stroke} stroke="none" opacity={opacity * 0.9} />
<path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1" />
</svg>
);
case 'poison':
return (
<svg {...common}>
<path d="M12 3c2.5 3.5 5 6.2 5 10a5 5 0 1 1-10 0c0-3.8 2.5-6.5 5-10z" />
<circle cx="10" cy="14" r="0.9" fill={stroke} stroke="none" />
<circle cx="13.5" cy="15.5" r="0.7" fill={stroke} stroke="none" />
</svg>
);
case 'pit':
return (
<svg {...common}>
<path d="M4 8h16M6 8l2 10h8l2-10" />
<path d="M9 14h6" opacity={0.7} />
</svg>
);
case 'arrow':
return (
<svg {...common}>
<path d="M4 12h14" />
<path d="M14 7l5 5-5 5" />
<path d="M4 9v6" />
</svg>
);
case 'laser':
return (
<svg {...common}>
<circle cx="6" cy="12" r="2.2" />
<path d="M9 12h11" strokeWidth="2.4" />
<path d="M17 9l3 3-3 3" />
</svg>
);
case 'freeform':
return (
<svg {...common}>
<path d="M12 3l2.2 6.2H21l-5.2 3.8 2 6.5L12 16.2 6.2 19.5l2-6.5L3 9.2h6.8z" />
</svg>
);
default: {
const _x: never = type;
return <svg {...common}>{String(_x)}</svg>;
}
}
}