Files
echoes-of-the-ash/web-map/editor.html
2025-11-27 16:27:01 +01:00

1332 lines
45 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Map Editor - Echoes of the Ashes</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #e0e0e0;
}
/* Login Screen */
.login-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
.login-box {
background: #2a2a4a;
padding: 40px;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
max-width: 400px;
width: 100%;
}
.login-box h1 {
color: #ffa726;
margin-bottom: 10px;
font-size: 2em;
}
.login-box p {
opacity: 0.7;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
.form-group input {
width: 100%;
padding: 12px;
border: 2px solid #3a3a6a;
border-radius: 6px;
background: #1a1a3e;
color: #e0e0e0;
font-size: 1em;
}
.form-group input:focus {
outline: none;
border-color: #ffa726;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 1em;
font-weight: 500;
transition: all 0.3s ease;
}
.btn-primary {
background: linear-gradient(135deg, #ffa726 0%, #ff8f00 100%);
color: #1a1a2e;
width: 100%;
}
.btn-primary:hover {
background: linear-gradient(135deg, #ffb74d 0%, #ffa726 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 167, 38, 0.4);
}
.error-message {
background: #d32f2f;
color: white;
padding: 12px;
border-radius: 6px;
margin-bottom: 20px;
display: none;
}
/* Editor Interface */
.editor-container {
display: none;
flex-direction: column;
height: 100vh;
}
.sidebar {
background: #1a1a3e;
border-right: 2px solid #2a2a4a;
overflow-y: auto;
height: 100%;
min-height: 0;
}
.sidebar-header {
padding: 20px;
background: #2a2a4a;
border-bottom: 2px solid #3a3a6a;
}
.sidebar-header h2 {
color: #ffa726;
font-size: 1.3em;
margin-bottom: 5px;
}
.location-list {
padding: 10px;
}
.location-item {
padding: 12px;
margin-bottom: 8px;
background: #2a2a4a;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
border: 2px solid transparent;
}
.location-item:hover {
background: #3a3a6a;
border-color: #ffa726;
}
.location-item.active {
background: #3a3a6a;
border-color: #ffa726;
}
.location-item-name {
font-weight: 500;
margin-bottom: 4px;
}
.location-item-coords {
font-size: 0.85em;
opacity: 0.7;
}
.map-canvas-section {
background: #0f0f1e;
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
min-height: 0;
}
.canvas-header {
padding: 15px 20px;
background: #1a1a3e;
border-bottom: 2px solid #2a2a4a;
display: flex;
justify-content: space-between;
align-items: center;
min-height: 60px;
flex-shrink: 0;
}
.canvas-header h1 {
color: #ffa726;
font-size: 1.5em;
margin: 0;
}
/* Tabs */
.editor-tabs {
display: flex;
gap: 5px;
padding: 10px 20px 0;
background: #1a1a3e;
border-bottom: 2px solid #2a2a4a;
min-height: 52px;
flex-shrink: 0;
}
.tab-button {
padding: 10px 20px;
background: transparent;
border: none;
border-bottom: 3px solid transparent;
color: #e0e0e0;
cursor: pointer;
font-size: 1em;
transition: all 0.3s ease;
}
.tab-button:hover {
background: rgba(255, 167, 38, 0.1);
color: #ffa726;
}
.tab-button.active {
color: #ffa726;
border-bottom-color: #ffa726;
}
.tab-content {
display: none !important;
flex: 1;
overflow: hidden;
min-height: 0;
}
.tab-content.active {
display: flex !important;
}
/* Locations tab specific layout */
#tab-locations {
display: grid;
grid-template-columns: 300px 1fr 500px;
grid-template-rows: 1fr;
height: 100%;
min-height: 0;
gap: 0;
}
#tab-locations .sidebar {
display: grid !important;
}
/* Management pages */
.management-container {
display: flex;
flex: 1;
min-height: 0;
overflow: hidden;
}
.management-list {
width: 350px;
flex-shrink: 0;
background: #1a1a3e;
border-right: 2px solid #2a2a4a;
overflow-y: auto;
padding: 20px;
min-height: 0;
}
.management-list h3 {
color: #ffa726;
margin-bottom: 15px;
}
.management-item {
background: #2a2a4a;
padding: 12px;
margin-bottom: 10px;
border-radius: 6px;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.2s ease;
}
.management-item:hover {
background: #3a3a6a;
border-color: #ffa726;
}
.management-item.active {
background: #3a3a6a;
border-color: #ffa726;
}
.management-editor {
flex: 1;
min-width: 0;
min-height: 0;
background: #0f0f1e;
padding: 30px;
overflow-y: auto;
}
.management-editor h2 {
color: #ffa726;
margin-bottom: 30px;
}
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.array-item {
background: #2a2a4a;
padding: 15px;
border-radius: 6px;
margin-bottom: 10px;
position: relative;
}
.array-item-remove {
position: absolute;
top: 10px;
right: 10px;
background: #d32f2f;
color: white;
border: none;
width: 30px;
height: 30px;
border-radius: 50%;
cursor: pointer;
font-size: 1.2em;
line-height: 1;
}
.array-item-remove:hover {
background: #f44336;
}
.btn-logout {
background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
color: white;
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-logout:hover {
background: linear-gradient(135deg, #e57373 0%, #f44336 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(244, 67, 54, 0.4);
}
#editorCanvas {
width: 100%;
height: 100%;
cursor: crosshair;
}
.zoom-controls {
position: absolute;
bottom: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 10px;
z-index: 100;
}
.zoom-btn {
width: 45px;
height: 45px;
background: rgba(42, 42, 74, 0.95);
border: 2px solid #ffa726;
border-radius: 8px;
color: #ffa726;
font-size: 1.5em;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
font-weight: bold;
}
.zoom-btn:hover {
background: #ffa726;
color: #1a1a2e;
transform: scale(1.1);
}
.properties-panel {
background: #1a1a3e;
border-left: 2px solid #2a2a4a;
overflow-y: auto;
padding: 20px;
height: 100%;
min-height: 0;
}
.properties-panel h3 {
color: #ffa726;
margin-bottom: 20px;
font-size: 1.3em;
}
.property-group {
margin-bottom: 25px;
}
.property-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
font-size: 0.9em;
opacity: 0.9;
}
.property-group input,
.property-group textarea,
.property-group select {
width: 100%;
padding: 10px;
border: 2px solid #2a2a4a;
border-radius: 6px;
background: #0f0f1e;
color: #e0e0e0;
font-size: 0.95em;
font-family: inherit;
}
.property-group textarea {
resize: vertical;
min-height: 80px;
}
.property-group input:focus,
.property-group textarea:focus,
.property-group select:focus {
outline: none;
border-color: #ffa726;
}
.property-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.spawn-list {
margin-top: 10px;
}
.spawn-item {
background: #2a2a4a;
padding: 10px;
border-radius: 6px;
margin-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.spawn-item-info {
flex: 1;
}
.spawn-item-name {
font-weight: 500;
}
.spawn-item-weight {
font-size: 0.85em;
opacity: 0.7;
}
.btn-remove {
background: #d32f2f;
color: white;
padding: 6px 12px;
font-size: 0.85em;
}
.btn-remove:hover {
background: #f44336;
}
.btn-add {
background: #4caf50;
color: white;
width: 100%;
margin-top: 10px;
}
.btn-add:hover {
background: #66bb6a;
}
.btn-save {
background: linear-gradient(135deg, #4caf50 0%, #388e3c 100%);
color: white;
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-save:hover {
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4);
}
.btn-restart {
background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
color: white;
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-restart:hover {
background: linear-gradient(135deg, #42a5f5 0%, #2196f3 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(33, 150, 243, 0.4);
}
.btn-delete {
background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
color: white;
width: 100%;
margin-top: 10px;
padding: 14px;
}
.btn-delete:hover {
background: linear-gradient(135deg, #e57373 0%, #f44336 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(244, 67, 54, 0.4);
}
.image-preview {
width: 100%;
height: 150px;
background: #0f0f1e;
border: 2px solid #2a2a4a;
border-radius: 6px;
margin-top: 10px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.image-preview img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.file-input-wrapper {
position: relative;
display: inline-block;
width: 100%;
margin-top: 10px;
}
.file-input-wrapper input[type="file"] {
position: absolute;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.file-input-label {
display: block;
padding: 10px;
background: #2a2a4a;
border: 2px solid #3a3a6a;
border-radius: 6px;
text-align: center;
cursor: pointer;
transition: all 0.2s ease;
}
.file-input-label:hover {
background: #3a3a6a;
border-color: #ffa726;
}
.success-message {
background: #4caf50;
color: white;
padding: 12px;
border-radius: 6px;
margin-bottom: 20px;
display: none;
}
.add-spawn-modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal-content {
background: #2a2a4a;
padding: 30px;
border-radius: 12px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
margin-bottom: 20px;
}
.modal-header h3 {
color: #ffa726;
}
.npc-select-list {
max-height: 400px;
overflow-y: auto;
}
.npc-select-item {
background: #1a1a3e;
padding: 12px;
margin-bottom: 8px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
border: 2px solid transparent;
}
.npc-select-item:hover {
background: #2a2a4a;
border-color: #ffa726;
}
.connection-target-item {
background: #1a1a3e;
padding: 12px;
margin-bottom: 8px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
border: 2px solid transparent;
}
.connection-target-item:hover {
background: #2a2a4a;
border-color: #ffa726;
}
.btn-secondary {
background: #616161;
color: white;
margin-top: 10px;
}
.btn-secondary:hover {
background: #757575;
}
.hidden {
display: none !important;
}
.connection-section {
border-top: 2px solid #2a2a4a;
padding-top: 20px;
margin-top: 20px;
}
.connection-list {
max-height: 200px;
overflow-y: auto;
margin-bottom: 10px;
}
.connection-direction {
font-weight: 500;
color: #ffa726;
}
.connection-item {
background: #0f0f1e;
padding: 10px;
margin-bottom: 8px;
border-radius: 6px;
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #2a2a4a;
}
.connection-info {
flex: 1;
}
.connection-name {
font-weight: 500;
color: #ffa726;
}
.connection-cost {
font-size: 0.85em;
opacity: 0.7;
}
.logs-container {
display: flex;
flex-direction: column;
height: 100%;
padding: 20px;
background: #0a0a1e;
}
.logs-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid #2a2a4a;
}
.logs-controls {
display: flex;
gap: 10px;
align-items: center;
}
.logs-viewer {
flex: 1;
background: #0f0f1e;
border: 1px solid #2a2a4a;
border-radius: 8px;
padding: 15px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.5;
color: #e0e0e0;
}
.log-line {
margin: 2px 0;
white-space: pre-wrap;
word-break: break-all;
}
.log-error {
color: #ff5252;
}
.log-warning {
color: #ffa726;
}
.log-info {
color: #4fc3f7;
}
.log-success {
color: #66bb6a;
}
.btn-refresh-logs {
background: linear-gradient(135deg, #9c27b0 0%, #7b1fa2 100%);
color: white;
padding: 8px 16px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-refresh-logs:hover {
background: linear-gradient(135deg, #ba68c8 0%, #9c27b0 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(156, 39, 176, 0.4);
}
.btn-clear-logs {
background: linear-gradient(135deg, #757575 0%, #616161 100%);
color: white;
padding: 8px 16px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-clear-logs:hover {
background: linear-gradient(135deg, #9e9e9e 0%, #757575 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(117, 117, 117, 0.4);
}
.logs-lines-input {
width: 80px;
padding: 8px;
border: 1px solid #3a3a6a;
border-radius: 4px;
background: #1a1a3e;
color: #e0e0e0;
font-weight: 500;
}
</style>
</head>
<body>
<!-- Login Screen -->
<div class="login-container" id="loginContainer">
<div class="login-box">
<h1>🗺️ Map Editor</h1>
<p>Echoes of the Ashes</p>
<div class="error-message" id="loginError"></div>
<form id="loginForm">
<div class="form-group">
<label for="password">Admin Password</label>
<input type="password" id="password" name="password" required autocomplete="current-password">
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
</div>
</div>
<!-- Editor Interface -->
<div class="editor-container" id="editorContainer">
<!-- Header with tabs -->
<div class="canvas-header">
<h1>🗺️ Game Editor</h1>
<div style="display: flex; gap: 10px;">
<button class="btn btn-save" onclick="saveCurrentItem()">💾 Save</button>
<button class="btn btn-restart" onclick="restartBot()">🔄 Restart Bot</button>
<button class="btn btn-logout" onclick="logout()">Logout</button>
</div>
</div>
<div class="editor-tabs">
<button class="tab-button active" onclick="switchTab('locations')">📍 Locations</button>
<button class="tab-button" onclick="switchTab('npcs')">👹 NPCs</button>
<button class="tab-button" onclick="switchTab('items')">🎒 Items</button>
<button class="tab-button" onclick="switchTab('interactables')">🎯 Interactables</button>
<button class="tab-button" onclick="switchTab('players')">👥 Players</button>
<button class="tab-button" onclick="switchTab('accounts')">👤 Accounts</button>
<button class="tab-button" onclick="switchTab('logs')">📋 Bot Logs</button>
</div>
<!-- Tab 1: Locations (Map Editor) -->
<div id="tab-locations" class="tab-content active">
<!-- Left Sidebar: Location List -->
<div class="sidebar">
<div class="sidebar-header">
<h2>📍 Locations</h2>
<button class="btn btn-add" onclick="createNewLocation()">+ New Location</button>
</div>
<div style="padding: 10px;">
<input type="text" id="locationSearchInput" placeholder="🔍 Search locations..."
style="width: 100%; padding: 8px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;"
oninput="filterLocationList()">
</div>
<div class="location-list" id="locationList">
<!-- Populated dynamically -->
</div>
</div>
<!-- Center: Map Canvas -->
<div class="map-canvas-section">
<canvas id="editorCanvas"></canvas>
<!-- Zoom Controls -->
<div class="zoom-controls">
<button class="zoom-btn" onclick="zoomIn()" title="Zoom In">+</button>
<button class="zoom-btn" onclick="resetView()" title="Reset View"></button>
<button class="zoom-btn" onclick="zoomOut()" title="Zoom Out"></button>
</div>
</div>
<!-- Right Panel: Properties -->
<div class="properties-panel">
<div id="noSelectionMessage">
<h3>No Location Selected</h3>
<p>Select a location from the list or click on the map to create a new one.</p>
</div>
<div id="propertiesForm" class="hidden">
<h3>📝 Edit Location</h3>
<div class="success-message" id="saveSuccess"></div>
<div class="property-group">
<label for="locationId">Location ID</label>
<input type="text" id="locationId" readonly>
</div>
<div class="property-group">
<label for="locationName">Name</label>
<input type="text" id="locationName" placeholder="Location Name">
</div>
<div class="property-group">
<label for="locationDescription">Description</label>
<textarea id="locationDescription" placeholder="Enter location description..."></textarea>
</div>
<div class="property-group">
<label>Coordinates</label>
<div class="property-row">
<input type="number" id="locationX" step="0.1" placeholder="X">
<input type="number" id="locationY" step="0.1" placeholder="Y">
</div>
</div>
<div class="property-group">
<label for="dangerLevel">Danger Level (0-4)</label>
<select id="dangerLevel">
<option value="0">0 - Safe Zone</option>
<option value="1">1 - Low Danger</option>
<option value="2">2 - Medium Danger</option>
<option value="3">3 - High Danger</option>
<option value="4">4 - Extreme Danger</option>
</select>
</div>
<div class="property-group">
<label for="encounterRate">Encounter Rate (0.0-1.0)</label>
<input type="number" id="encounterRate" min="0" max="1" step="0.05" placeholder="0.15">
</div>
<div class="property-group">
<label for="wanderingChance">Wandering Enemy Chance (0.0-1.0)</label>
<input type="number" id="wanderingChance" min="0" max="1" step="0.05" placeholder="0.20">
</div>
<div class="property-group">
<label>Location Image</label>
<input type="text" id="imagePath" placeholder="images/locations/example.webp">
<div class="file-input-wrapper">
<input type="file" id="imageUpload" accept="image/*" onchange="uploadImage()">
<label for="imageUpload" class="file-input-label">📤 Upload New Image</label>
</div>
<div class="image-preview" id="imagePreview">
<span>No image</span>
</div>
</div>
<div class="property-group">
<label>Spawn NPCs</label>
<div class="spawn-list" id="spawnList">
<!-- Populated dynamically -->
</div>
<button class="btn btn-add" onclick="showAddSpawnModal()">+ Add NPC Spawn</button>
</div>
<div class="property-group">
<label>Interactables</label>
<div class="spawn-list" id="interactablesList">
<!-- Populated dynamically -->
</div>
<button class="btn btn-add" onclick="showAddInteractableModal()">+ Add Interactable</button>
</div>
<!-- Connection Management -->
<div class="connection-section">
<div class="property-group">
<label>🔗 Connections</label>
<div class="connection-list" id="connectionList">
<!-- Populated dynamically -->
</div>
<button class="btn btn-add" onclick="showAddConnectionModal()">+ Add Connection</button>
</div>
</div>
<div style="margin-top: 20px; padding-top: 20px; border-top: 2px solid #3a3a6a;">
<button class="btn btn-danger" style="width: 100%;" onclick="deleteCurrentLocation()">🗑️ Delete
Location</button>
</div>
</div>
</div>
</div>
<!-- Tab 2: NPCs Management -->
<div id="tab-npcs" class="tab-content">
<div class="management-container">
<div class="management-list">
<div
style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<h3>👹 NPCs</h3>
<button class="btn btn-add" onclick="createNewNPC()">+ New</button>
</div>
<input type="text" id="npcManagementSearch" placeholder="🔍 Search NPCs..."
style="width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;"
oninput="filterNPCManagementList()">
<div id="npcManagementList">
<!-- Populated dynamically -->
</div>
</div>
<div class="management-editor" id="npcEditor">
<div style="text-align: center; opacity: 0.5; padding: 50px;">
<h3>Select an NPC or create a new one</h3>
</div>
</div>
</div>
</div>
<!-- Tab 3: Items Management -->
<div id="tab-items" class="tab-content">
<div class="management-container">
<div class="management-list">
<div
style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<h3>🎒 Items</h3>
<button class="btn btn-add" onclick="createNewItem()">+ New</button>
</div>
<select id="itemCategoryFilter"
style="width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;"
onchange="filterItemManagementList()">
<option value="all">All Categories</option>
<option value="weapon">⚔️ Weapons</option>
<option value="armor">🛡️ Armor</option>
<option value="consumable">💊 Consumables</option>
<option value="resource">📦 Resources</option>
<option value="container">🎒 Containers</option>
<option value="tool">🔧 Tools</option>
<option value="quest">📜 Quest Items</option>
</select>
<input type="text" id="itemManagementSearch" placeholder="🔍 Search items..."
style="width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;"
oninput="filterItemManagementList()">
<div id="itemManagementList">
<!-- Populated dynamically -->
</div>
</div>
<div class="management-editor" id="itemEditor">
<div style="text-align: center; opacity: 0.5; padding: 50px;">
<h3>Select an item or create a new one</h3>
</div>
</div>
</div>
</div>
<!-- Tab 4: Interactables Management -->
<div id="tab-interactables" class="tab-content">
<div class="management-container">
<!-- Left: List of Interactables -->
<div class="management-list">
<h3>🎯 Interactable Templates</h3>
<button class="btn btn-add" onclick="createNewInteractable()"
style="width: 100%; margin-bottom: 15px;">+ New Interactable</button>
<input type="text" id="interactableManagementSearch" placeholder="🔍 Search interactables..."
oninput="filterInteractableManagementList()"
style="width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;">
<div id="interactableManagementList"></div>
</div>
<!-- Right: Interactable Editor -->
<div class="management-editor">
<div id="interactableEditor" style="padding: 20px;">
<h3>Select an interactable to edit</h3>
<p style="opacity: 0.7;">Choose from the list or create a new one.</p>
</div>
</div>
</div>
</div>
<!-- Tab 5: Players Management -->
<div id="tab-players" class="tab-content">
<div class="management-container">
<!-- Left: Player List -->
<div class="management-list">
<div
style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<h3>👥 Players</h3>
</div>
<input type="text" id="playerSearch" placeholder="🔍 Search players..."
style="width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;"
oninput="filterPlayerList()">
<select id="playerStatusFilter"
style="width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;"
onchange="filterPlayerList()">
<option value="all">All Players</option>
<option value="active">Active</option>
<option value="banned">Banned</option>
<option value="premium">Premium</option>
</select>
<div id="playerList">
<!-- Populated dynamically -->
</div>
</div>
<!-- Right: Player Editor -->
<div class="management-editor" id="playerEditor">
<div style="text-align: center; opacity: 0.5; padding: 50px;">
<h3>Select a player to edit</h3>
</div>
</div>
</div>
</div>
<!-- Tab 6: Accounts Management -->
<div id="tab-accounts" class="tab-content">
<div class="management-container">
<!-- Left: Account List -->
<div class="management-list">
<div
style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<h3>👤 Accounts</h3>
</div>
<input type="text" id="accountSearch" placeholder="🔍 Search accounts..."
style="width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid #3a3a6a; border-radius: 4px; background: #1a1a3e; color: #e0e0e0;"
oninput="filterAccountList()">
<div id="accountList">
<!-- Populated dynamically -->
</div>
</div>
<!-- Right: Account Editor -->
<div class="management-editor" id="accountEditor">
<div style="text-align: center; opacity: 0.5; padding: 50px;">
<h3>Select an account to edit</h3>
</div>
</div>
</div>
</div>
<!-- Tab 7: Bot Logs -->
<div id="tab-logs" class="tab-content">
<div class="logs-container">
<div class="logs-header">
<h2>📋 Bot Logs (echoes_of_the_ashes_api)</h2>
<div class="logs-controls">
<label style="color: #e0e0e0; font-weight: 500;">Lines:</label>
<input type="number" id="logsLineCount" class="logs-lines-input" value="100" min="10" max="1000"
step="10">
<button class="btn btn-refresh-logs" onclick="refreshBotLogs()">🔄 Refresh</button>
<button class="btn btn-clear-logs" onclick="clearLogsDisplay()">🗑️ Clear</button>
</div>
</div>
<div class="logs-viewer" id="logsViewer">
<div style="opacity: 0.5; text-align: center; padding: 20px;">
Click "Refresh" to load bot logs...
</div>
</div>
</div>
</div>
</div> <!-- Close editor-container -->
<!-- Add Spawn Modal -->
<div class="add-spawn-modal" id="addSpawnModal">
<div class="modal-content">
<div class="modal-header">
<h3>Add NPC Spawn</h3>
</div>
<div class="property-group">
<input type="text" id="npcSearch" placeholder="🔍 Search NPCs..." oninput="filterNPCList()"
style="margin-bottom: 10px;">
</div>
<div class="npc-select-list" id="npcSelectList">
<!-- Populated dynamically -->
</div>
<button class="btn btn-secondary" onclick="closeAddSpawnModal()">Cancel</button>
</div>
</div>
<!-- Add Connection Modal -->
<div class="add-spawn-modal" id="addConnectionModal">
<div class="modal-content">
<div class="modal-header">
<h3>Add Connection</h3>
<p style="opacity: 0.7; margin-top: 10px;">Select a destination location</p>
</div>
<div class="property-group">
<input type="text" id="connectionSearch" placeholder="🔍 Search locations..."
oninput="filterConnectionList()" style="margin-bottom: 10px;">
</div>
<div class="npc-select-list" id="connectionTargetList">
<!-- Populated dynamically -->
</div>
<button class="btn btn-secondary" onclick="closeAddConnectionModal()">Cancel</button>
</div>
</div>
<!-- Select Direction Modal -->
<div class="add-spawn-modal" id="selectDirectionModal">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h3>Select Direction</h3>
<p style="opacity: 0.7; margin-top: 10px;" id="directionModalSubtitle">
From <strong id="fromLocationName"></strong> to <strong id="toLocationName"></strong>
</p>
</div>
<div class="property-group">
<label for="directionSelect">Direction from source:</label>
<select id="directionSelect" style="width: 100%; padding: 10px; border-radius: 6px;
background: #1a1a3e; color: #e0e0e0; border: 2px solid #3a3a6a; font-size: 1em;">
<option value="">-- Select Direction --</option>
<option value="north">⬆️ North</option>
<option value="south">⬇️ South</option>
<option value="east">➡️ East</option>
<option value="west">⬅️ West</option>
<option value="northeast">↗️ Northeast</option>
<option value="northwest">↖️ Northwest</option>
<option value="southeast">↘️ Southeast</option>
<option value="southwest">↙️ Southwest</option>
<option value="up">⬆️ Up</option>
<option value="down">⬇️ Down</option>
<option value="inside">🚪 Inside</option>
<option value="outside">🚪 Outside</option>
</select>
</div>
<div class="property-group"
style="background: #2a2a4a; padding: 15px; border-radius: 8px; margin-top: 15px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="autoReverseConnection" checked
style="margin-right: 10px; width: 20px; height: 20px;">
<span>
<strong>Automatically add reverse connection</strong><br>
<span style="opacity: 0.7; font-size: 0.9em;" id="reverseConnectionText">
Will also create a connection back from destination to source
</span>
</span>
</label>
</div>
<div style="display: flex; gap: 10px; margin-top: 20px;">
<button class="btn btn-primary" onclick="confirmAddConnection()">Add Connection</button>
<button class="btn btn-secondary" onclick="closeSelectDirectionModal()">Cancel</button>
</div>
</div>
</div>
<!-- Delete Connection Modal -->
<div class="add-spawn-modal" id="deleteConnectionModal">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h3 style="color: #f44336;">⚠️ Delete Connection</h3>
<p style="opacity: 0.7; margin-top: 10px;">
<strong id="deleteFromLocationName"></strong><span id="deleteDirectionText"></span><strong
id="deleteToLocationName"></strong>
</p>
</div>
<div class="property-group"
style="background: #3a2a2a; padding: 15px; border-radius: 8px; border: 2px solid #f44336;">
<p style="margin-bottom: 10px;">⚠️ This will permanently delete this connection.</p>
</div>
<div class="property-group" id="deleteReverseOption"
style="background: #2a2a4a; padding: 15px; border-radius: 8px; margin-top: 15px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="deleteBothConnections" checked
style="margin-right: 10px; width: 20px; height: 20px;">
<span>
<strong>Also delete reverse connection</strong><br>
<span style="opacity: 0.7; font-size: 0.9em;" id="deleteReverseText">
Will also delete the connection back
</span>
</span>
</label>
</div>
<div style="display: flex; gap: 10px; margin-top: 20px;">
<button class="btn btn-primary" style="background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);"
onclick="confirmDeleteConnection()">Delete Connection</button>
<button class="btn btn-secondary" onclick="closeDeleteConnectionModal()">Cancel</button>
</div>
</div>
</div>
<!-- Add Interactable Modal -->
<div class="add-spawn-modal" id="addInteractableModal">
<div class="modal-content">
<div class="modal-header">
<h3>Add Interactable</h3>
<p style="opacity: 0.7; margin-top: 10px;">Select an interactable template</p>
</div>
<div class="property-group">
<input type="text" id="interactableSearch" placeholder="🔍 Search interactables..."
oninput="filterInteractableSelectList()" style="margin-bottom: 10px;">
</div>
<div class="npc-select-list" id="interactableSelectList">
<!-- Populated dynamically -->
</div>
<button class="btn btn-secondary" onclick="closeAddInteractableModal()">Cancel</button>
</div>
</div>
<!-- Edit Interactable Instance Modal -->
<div class="add-spawn-modal" id="editInteractableInstanceModal">
<div class="modal-content" style="max-width: 700px; max-height: 80vh; overflow-y: auto;">
<div class="modal-header">
<h3>Configure Interactable</h3>
</div>
<div id="interactableInstanceEditor">
<!-- Populated dynamically -->
</div>
<div style="display: flex; gap: 10px; margin-top: 20px;">
<button class="btn btn-primary" onclick="saveInteractableInstance()">Save</button>
<button class="btn btn-secondary" onclick="closeEditInteractableInstanceModal()">Cancel</button>
</div>
</div>
</div>
<script src="editor_enhanced.js"></script>
</body>
</html>