diff --git a/app.js b/app.js
index 8ea6fc2e..62f97ed3 100644
--- a/app.js
+++ b/app.js
@@ -2032,6 +2032,7 @@ function setupControls() {
document.getElementById('atlas-toggle-btn').addEventListener('click', openPortalAtlas);
document.getElementById('atlas-close-btn').addEventListener('click', closePortalAtlas);
+ initAtlasControls();
}
function sendChatMessage(overrideText = null) {
@@ -2815,58 +2816,142 @@ function closeVisionOverlay() {
document.getElementById('vision-overlay').style.display = 'none';
}
-// ═══ PORTAL ATLAS ═══
+// ═══ PORTAL ATLAS / WORLD DIRECTORY ═══
+let atlasActiveFilter = 'all';
+let atlasSearchQuery = '';
+
function openPortalAtlas() {
atlasOverlayActive = true;
document.getElementById('atlas-overlay').style.display = 'flex';
populateAtlas();
+ // Focus search input
+ setTimeout(() => document.getElementById('atlas-search')?.focus(), 100);
}
function closePortalAtlas() {
atlasOverlayActive = false;
document.getElementById('atlas-overlay').style.display = 'none';
+ atlasSearchQuery = '';
+ atlasActiveFilter = 'all';
+}
+
+function initAtlasControls() {
+ const searchInput = document.getElementById('atlas-search');
+ if (searchInput) {
+ searchInput.addEventListener('input', (e) => {
+ atlasSearchQuery = e.target.value.toLowerCase().trim();
+ populateAtlas();
+ });
+ }
+
+ const filterBtns = document.querySelectorAll('.atlas-filter-btn');
+ filterBtns.forEach(btn => {
+ btn.addEventListener('click', () => {
+ filterBtns.forEach(b => b.classList.remove('active'));
+ btn.classList.add('active');
+ atlasActiveFilter = btn.dataset.filter;
+ populateAtlas();
+ });
+ });
+}
+
+function matchesAtlasFilter(config) {
+ if (atlasActiveFilter === 'all') return true;
+ if (atlasActiveFilter === 'harness') return (config.portal_type || 'harness') === 'harness' || !config.portal_type;
+ if (atlasActiveFilter === 'game-world') return config.portal_type === 'game-world';
+ return config.status === atlasActiveFilter;
+}
+
+function matchesAtlasSearch(config) {
+ if (!atlasSearchQuery) return true;
+ const haystack = [config.name, config.description, config.id,
+ config.world_category, config.portal_type, config.destination?.type]
+ .filter(Boolean).join(' ').toLowerCase();
+ return haystack.includes(atlasSearchQuery);
}
function populateAtlas() {
const grid = document.getElementById('atlas-grid');
grid.innerHTML = '';
-
+
let onlineCount = 0;
let standbyCount = 0;
-
+ let downloadedCount = 0;
+ let visibleCount = 0;
+
portals.forEach(portal => {
const config = portal.config;
if (config.status === 'online') onlineCount++;
if (config.status === 'standby') standbyCount++;
-
+ if (config.status === 'downloaded') downloadedCount++;
+
+ if (!matchesAtlasFilter(config) || !matchesAtlasSearch(config)) return;
+ visibleCount++;
+
const card = document.createElement('div');
card.className = 'atlas-card';
card.style.setProperty('--portal-color', config.color);
-
+
const statusClass = `status-${config.status || 'online'}`;
-
+ const statusLabel = (config.status || 'ONLINE').toUpperCase();
+ const portalType = config.portal_type || 'harness';
+ const categoryLabel = config.world_category
+ ? config.world_category.replace(/-/g, ' ').toUpperCase()
+ : portalType.replace(/-/g, ' ').toUpperCase();
+
+ // Readiness bar for game-worlds
+ let readinessHTML = '';
+ if (config.readiness_steps) {
+ const steps = Object.values(config.readiness_steps);
+ readinessHTML = `
`;
+ steps.forEach(step => {
+ readinessHTML += `
`;
+ });
+ readinessHTML += '
';
+ }
+
+ // Action label
+ const actionLabel = config.destination?.action_label
+ || (config.status === 'online' ? 'ENTER' : config.status === 'downloaded' ? 'LAUNCH' : 'VIEW');
+
card.innerHTML = `
${config.description}
+ ${readinessHTML}
`;
-
+
card.addEventListener('click', () => {
focusPortal(portal);
closePortalAtlas();
});
-
+
grid.appendChild(card);
});
-
+
+ // Show empty state
+ if (visibleCount === 0) {
+ const empty = document.createElement('div');
+ empty.className = 'atlas-empty';
+ empty.textContent = atlasSearchQuery
+ ? `No worlds match "${atlasSearchQuery}"`
+ : 'No worlds in this category';
+ grid.appendChild(empty);
+ }
+
document.getElementById('atlas-online-count').textContent = onlineCount;
document.getElementById('atlas-standby-count').textContent = standbyCount;
+ document.getElementById('atlas-downloaded-count').textContent = downloadedCount;
+ document.getElementById('atlas-total-count').textContent = portals.length;
// Update Bannerlord HUD status
const bannerlord = portals.find(p => p.config.id === 'bannerlord');
diff --git a/index.html b/index.html
index 52feb99c..2209276e 100644
--- a/index.html
+++ b/index.html
@@ -113,9 +113,9 @@
-
diff --git a/style.css b/style.css
index 3e2aa626..006f60c8 100644
--- a/style.css
+++ b/style.css
@@ -410,6 +410,123 @@ canvas#nexus-canvas {
font-style: italic;
}
+/* Atlas Controls */
+.atlas-controls {
+ padding: 15px 30px;
+ border-bottom: 1px solid var(--color-border);
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.atlas-search {
+ width: 100%;
+ padding: 10px 15px;
+ background: rgba(20, 30, 60, 0.6);
+ border: 1px solid var(--color-border);
+ color: var(--color-text);
+ font-family: var(--font-body);
+ font-size: 13px;
+ outline: none;
+ transition: border-color 0.2s;
+}
+
+.atlas-search:focus {
+ border-color: var(--color-primary);
+}
+
+.atlas-search::placeholder {
+ color: rgba(160, 184, 208, 0.4);
+}
+
+.atlas-filters {
+ display: flex;
+ gap: 8px;
+ flex-wrap: wrap;
+}
+
+.atlas-filter-btn {
+ background: transparent;
+ border: 1px solid var(--color-border);
+ color: var(--color-text-muted);
+ padding: 4px 12px;
+ font-family: var(--font-display);
+ font-size: 10px;
+ cursor: pointer;
+ transition: all 0.2s;
+ letter-spacing: 1px;
+}
+
+.atlas-filter-btn:hover {
+ border-color: var(--color-primary);
+ color: var(--color-primary);
+}
+
+.atlas-filter-btn.active {
+ background: rgba(74, 240, 192, 0.15);
+ border-color: var(--color-primary);
+ color: var(--color-primary);
+}
+
+/* Enhanced Atlas Cards */
+.status-downloaded { background: rgba(255, 165, 0, 0.2); color: #ffa500; border: 1px solid #ffa500; }
+
+.status-indicator.downloaded { background: #ffa500; box-shadow: 0 0 5px #ffa500; }
+
+.atlas-card-category {
+ font-family: var(--font-display);
+ font-size: 9px;
+ padding: 2px 6px;
+ border-radius: 2px;
+ text-transform: uppercase;
+ background: rgba(255, 255, 255, 0.05);
+ color: var(--color-text-muted);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ margin-left: 6px;
+}
+
+.atlas-card-readiness {
+ display: flex;
+ gap: 4px;
+ margin-top: 10px;
+ margin-bottom: 5px;
+}
+
+.readiness-step {
+ flex: 1;
+ height: 3px;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 1px;
+ position: relative;
+}
+
+.readiness-step.done {
+ background: var(--portal-color, var(--color-primary));
+}
+
+.readiness-step[title] {
+ cursor: help;
+}
+
+.atlas-card-action {
+ font-family: var(--font-display);
+ font-size: 10px;
+ color: var(--portal-color, var(--color-primary));
+ letter-spacing: 1px;
+}
+
+.atlas-total {
+ color: var(--color-text-muted);
+}
+
+.atlas-empty {
+ grid-column: 1 / -1;
+ text-align: center;
+ padding: 40px;
+ color: var(--color-text-muted);
+ font-style: italic;
+}
+
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }