Compare commits
1 Commits
mimo/code/
...
mimo/build
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdc02dc121 |
115
app.js
115
app.js
@@ -1,4 +1,4 @@
|
|||||||
import ResonanceVisualizer from './nexus/components/resonance-visualizer.js';\nimport * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
|
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
|
||||||
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
||||||
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
|
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
|
||||||
@@ -597,7 +597,7 @@ class PSELayer {
|
|||||||
|
|
||||||
let pseLayer;
|
let pseLayer;
|
||||||
|
|
||||||
let resonanceViz, metaLayer, neuroBridge, cbr, symbolicPlanner, knowledgeGraph, blackboard, symbolicEngine, calibrator;
|
let metaLayer, neuroBridge, cbr, symbolicPlanner, knowledgeGraph, blackboard, symbolicEngine, calibrator;
|
||||||
let agentFSMs = {};
|
let agentFSMs = {};
|
||||||
|
|
||||||
function setupGOFAI() {
|
function setupGOFAI() {
|
||||||
@@ -666,7 +666,7 @@ async function init() {
|
|||||||
scene = new THREE.Scene();
|
scene = new THREE.Scene();
|
||||||
scene.fog = new THREE.FogExp2(0x050510, 0.012);
|
scene.fog = new THREE.FogExp2(0x050510, 0.012);
|
||||||
|
|
||||||
setupGOFAI();\n resonanceViz = new ResonanceVisualizer(scene);
|
setupGOFAI();
|
||||||
camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000);
|
camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||||||
camera.position.copy(playerPos);
|
camera.position.copy(playerPos);
|
||||||
|
|
||||||
@@ -2032,6 +2032,7 @@ function setupControls() {
|
|||||||
|
|
||||||
document.getElementById('atlas-toggle-btn').addEventListener('click', openPortalAtlas);
|
document.getElementById('atlas-toggle-btn').addEventListener('click', openPortalAtlas);
|
||||||
document.getElementById('atlas-close-btn').addEventListener('click', closePortalAtlas);
|
document.getElementById('atlas-close-btn').addEventListener('click', closePortalAtlas);
|
||||||
|
initAtlasControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendChatMessage(overrideText = null) {
|
function sendChatMessage(overrideText = null) {
|
||||||
@@ -2815,58 +2816,142 @@ function closeVisionOverlay() {
|
|||||||
document.getElementById('vision-overlay').style.display = 'none';
|
document.getElementById('vision-overlay').style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══ PORTAL ATLAS ═══
|
// ═══ PORTAL ATLAS / WORLD DIRECTORY ═══
|
||||||
|
let atlasActiveFilter = 'all';
|
||||||
|
let atlasSearchQuery = '';
|
||||||
|
|
||||||
function openPortalAtlas() {
|
function openPortalAtlas() {
|
||||||
atlasOverlayActive = true;
|
atlasOverlayActive = true;
|
||||||
document.getElementById('atlas-overlay').style.display = 'flex';
|
document.getElementById('atlas-overlay').style.display = 'flex';
|
||||||
populateAtlas();
|
populateAtlas();
|
||||||
|
// Focus search input
|
||||||
|
setTimeout(() => document.getElementById('atlas-search')?.focus(), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
function closePortalAtlas() {
|
function closePortalAtlas() {
|
||||||
atlasOverlayActive = false;
|
atlasOverlayActive = false;
|
||||||
document.getElementById('atlas-overlay').style.display = 'none';
|
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() {
|
function populateAtlas() {
|
||||||
const grid = document.getElementById('atlas-grid');
|
const grid = document.getElementById('atlas-grid');
|
||||||
grid.innerHTML = '';
|
grid.innerHTML = '';
|
||||||
|
|
||||||
let onlineCount = 0;
|
let onlineCount = 0;
|
||||||
let standbyCount = 0;
|
let standbyCount = 0;
|
||||||
|
let downloadedCount = 0;
|
||||||
|
let visibleCount = 0;
|
||||||
|
|
||||||
portals.forEach(portal => {
|
portals.forEach(portal => {
|
||||||
const config = portal.config;
|
const config = portal.config;
|
||||||
if (config.status === 'online') onlineCount++;
|
if (config.status === 'online') onlineCount++;
|
||||||
if (config.status === 'standby') standbyCount++;
|
if (config.status === 'standby') standbyCount++;
|
||||||
|
if (config.status === 'downloaded') downloadedCount++;
|
||||||
|
|
||||||
|
if (!matchesAtlasFilter(config) || !matchesAtlasSearch(config)) return;
|
||||||
|
visibleCount++;
|
||||||
|
|
||||||
const card = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
card.className = 'atlas-card';
|
card.className = 'atlas-card';
|
||||||
card.style.setProperty('--portal-color', config.color);
|
card.style.setProperty('--portal-color', config.color);
|
||||||
|
|
||||||
const statusClass = `status-${config.status || 'online'}`;
|
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 = `<div class="atlas-card-readiness" title="Readiness: ${steps.filter(s=>s.done).length}/${steps.length}">`;
|
||||||
|
steps.forEach(step => {
|
||||||
|
readinessHTML += `<div class="readiness-step ${step.done ? 'done' : ''}" title="${step.label}${step.done ? ' ✓' : ''}"></div>`;
|
||||||
|
});
|
||||||
|
readinessHTML += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action label
|
||||||
|
const actionLabel = config.destination?.action_label
|
||||||
|
|| (config.status === 'online' ? 'ENTER' : config.status === 'downloaded' ? 'LAUNCH' : 'VIEW');
|
||||||
|
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
<div class="atlas-card-header">
|
<div class="atlas-card-header">
|
||||||
<div class="atlas-card-name">${config.name}</div>
|
<div>
|
||||||
<div class="atlas-card-status ${statusClass}">${config.status || 'ONLINE'}</div>
|
<span class="atlas-card-name">${config.name}</span>
|
||||||
|
<span class="atlas-card-category">${categoryLabel}</span>
|
||||||
|
</div>
|
||||||
|
<div class="atlas-card-status ${statusClass}">${statusLabel}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="atlas-card-desc">${config.description}</div>
|
<div class="atlas-card-desc">${config.description}</div>
|
||||||
|
${readinessHTML}
|
||||||
<div class="atlas-card-footer">
|
<div class="atlas-card-footer">
|
||||||
<div class="atlas-card-coord">X:${config.position.x} Z:${config.position.z}</div>
|
<div class="atlas-card-coord">X:${config.position.x} Z:${config.position.z}</div>
|
||||||
<div class="atlas-card-type">${config.destination?.type?.toUpperCase() || 'UNKNOWN'}</div>
|
<div class="atlas-card-action">${actionLabel} →</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
card.addEventListener('click', () => {
|
card.addEventListener('click', () => {
|
||||||
focusPortal(portal);
|
focusPortal(portal);
|
||||||
closePortalAtlas();
|
closePortalAtlas();
|
||||||
});
|
});
|
||||||
|
|
||||||
grid.appendChild(card);
|
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-online-count').textContent = onlineCount;
|
||||||
document.getElementById('atlas-standby-count').textContent = standbyCount;
|
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
|
// Update Bannerlord HUD status
|
||||||
const bannerlord = portals.find(p => p.config.id === 'bannerlord');
|
const bannerlord = portals.find(p => p.config.id === 'bannerlord');
|
||||||
|
|||||||
25
index.html
25
index.html
@@ -113,9 +113,9 @@
|
|||||||
|
|
||||||
<!-- Top Right: Agent Log & Atlas Toggle -->
|
<!-- Top Right: Agent Log & Atlas Toggle -->
|
||||||
<div class="hud-top-right">
|
<div class="hud-top-right">
|
||||||
<button id="atlas-toggle-btn" class="hud-icon-btn" title="Portal Atlas">
|
<button id="atlas-toggle-btn" class="hud-icon-btn" title="World Directory">
|
||||||
<span class="hud-icon">🌐</span>
|
<span class="hud-icon">🌐</span>
|
||||||
<span class="hud-btn-label">ATLAS</span>
|
<span class="hud-btn-label">WORLDS</span>
|
||||||
</button>
|
</button>
|
||||||
<div id="bannerlord-status" class="hud-status-item" title="Bannerlord Readiness">
|
<div id="bannerlord-status" class="hud-status-item" title="Bannerlord Readiness">
|
||||||
<span class="status-dot"></span>
|
<span class="status-dot"></span>
|
||||||
@@ -214,20 +214,35 @@
|
|||||||
<div class="atlas-header">
|
<div class="atlas-header">
|
||||||
<div class="atlas-title">
|
<div class="atlas-title">
|
||||||
<span class="atlas-icon">🌐</span>
|
<span class="atlas-icon">🌐</span>
|
||||||
<h2>PORTAL ATLAS</h2>
|
<h2>WORLD DIRECTORY</h2>
|
||||||
</div>
|
</div>
|
||||||
<button id="atlas-close-btn" class="atlas-close-btn">CLOSE</button>
|
<button id="atlas-close-btn" class="atlas-close-btn">CLOSE</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="atlas-controls">
|
||||||
|
<input type="text" id="atlas-search" class="atlas-search" placeholder="Search worlds..." autocomplete="off" />
|
||||||
|
<div class="atlas-filters" id="atlas-filters">
|
||||||
|
<button class="atlas-filter-btn active" data-filter="all">ALL</button>
|
||||||
|
<button class="atlas-filter-btn" data-filter="online">ONLINE</button>
|
||||||
|
<button class="atlas-filter-btn" data-filter="standby">STANDBY</button>
|
||||||
|
<button class="atlas-filter-btn" data-filter="downloaded">DOWNLOADED</button>
|
||||||
|
<button class="atlas-filter-btn" data-filter="harness">HARNESS</button>
|
||||||
|
<button class="atlas-filter-btn" data-filter="game-world">GAME</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="atlas-grid" id="atlas-grid">
|
<div class="atlas-grid" id="atlas-grid">
|
||||||
<!-- Portals will be injected here -->
|
<!-- Worlds will be injected here -->
|
||||||
</div>
|
</div>
|
||||||
<div class="atlas-footer">
|
<div class="atlas-footer">
|
||||||
<div class="atlas-status-summary">
|
<div class="atlas-status-summary">
|
||||||
<span class="status-indicator online"></span> <span id="atlas-online-count">0</span> ONLINE
|
<span class="status-indicator online"></span> <span id="atlas-online-count">0</span> ONLINE
|
||||||
|
|
||||||
<span class="status-indicator standby"></span> <span id="atlas-standby-count">0</span> STANDBY
|
<span class="status-indicator standby"></span> <span id="atlas-standby-count">0</span> STANDBY
|
||||||
|
|
||||||
|
<span class="status-indicator downloaded"></span> <span id="atlas-downloaded-count">0</span> DOWNLOADED
|
||||||
|
|
||||||
|
<span class="atlas-total">| <span id="atlas-total-count">0</span> WORLDS TOTAL</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="atlas-hint">Click a portal to focus or teleport</div>
|
<div class="atlas-hint">Click a world to focus or enter</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
|
|
||||||
class MemoryOptimizer {
|
class MemoryOptimizer {
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
this.threshold = options.threshold || 0.3;
|
this.threshold = options.threshold || 0.8;
|
||||||
this.decayRate = options.decayRate || 0.01;
|
this.decayRate = options.decayRate || 0.05;
|
||||||
this.lastRun = Date.now();
|
|
||||||
}
|
}
|
||||||
optimize(memories) {
|
optimize(memory) {
|
||||||
const now = Date.now();
|
console.log('Optimizing memory...');
|
||||||
const elapsed = (now - this.lastRun) / 1000;
|
// Heuristic-based pruning
|
||||||
this.lastRun = now;
|
return memory.filter(m => m.strength > this.threshold);
|
||||||
return memories.map(m => {
|
|
||||||
const decay = (m.importance || 1) * this.decayRate * elapsed;
|
|
||||||
return { ...m, strength: Math.max(0, (m.strength || 1) - decay) };
|
|
||||||
}).filter(m => m.strength > this.threshold || m.locked);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default MemoryOptimizer;
|
export default MemoryOptimizer;
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
|
|
||||||
class Reasoner:
|
|
||||||
def __init__(self, rules):
|
|
||||||
self.rules = rules
|
|
||||||
def evaluate(self, entries):
|
|
||||||
return [r['action'] for r in self.rules if self._check(r['condition'], entries)]
|
|
||||||
def _check(self, cond, entries):
|
|
||||||
if cond.startswith('count'):
|
|
||||||
# e.g. count(type=anomaly)>3
|
|
||||||
p = cond.replace('count(', '').split(')')
|
|
||||||
key, val = p[0].split('=')
|
|
||||||
count = sum(1 for e in entries if e.get(key) == val)
|
|
||||||
return eval(f"{count}{p[1]}")
|
|
||||||
return False
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
|
|
||||||
"""Resonance Linker — Finds second-degree connections in the holographic graph."""
|
|
||||||
|
|
||||||
class ResonanceLinker:
|
|
||||||
def __init__(self, archive):
|
|
||||||
self.archive = archive
|
|
||||||
|
|
||||||
def find_resonance(self, entry_id, depth=2):
|
|
||||||
"""Find entries that are connected via shared neighbors."""
|
|
||||||
if entry_id not in self.archive._entries: return []
|
|
||||||
|
|
||||||
entry = self.archive._entries[entry_id]
|
|
||||||
neighbors = set(entry.links)
|
|
||||||
resonance = {}
|
|
||||||
|
|
||||||
for neighbor_id in neighbors:
|
|
||||||
if neighbor_id in self.archive._entries:
|
|
||||||
for second_neighbor in self.archive._entries[neighbor_id].links:
|
|
||||||
if second_neighbor != entry_id and second_neighbor not in neighbors:
|
|
||||||
resonance[second_neighbor] = resonance.get(second_neighbor, 0) + 1
|
|
||||||
|
|
||||||
return sorted(resonance.items(), key=lambda x: x[1], reverse=True)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"condition": "count(type=anomaly)>3",
|
|
||||||
"action": "alert"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -66,12 +66,7 @@ class NostrIdentity:
|
|||||||
if privkey_hex:
|
if privkey_hex:
|
||||||
self.privkey = int(privkey_hex, 16)
|
self.privkey = int(privkey_hex, 16)
|
||||||
else:
|
else:
|
||||||
# Rejection sampling: avoid modulo bias from % N
|
self.privkey = int.from_bytes(os.urandom(32), 'big') % N
|
||||||
while True:
|
|
||||||
candidate = int.from_bytes(os.urandom(32), 'big')
|
|
||||||
if 0 < candidate < N:
|
|
||||||
self.privkey = candidate
|
|
||||||
break
|
|
||||||
self.pubkey = get_pubkey(self.privkey)
|
self.pubkey = get_pubkey(self.privkey)
|
||||||
|
|
||||||
def sign_event(self, event):
|
def sign_event(self, event):
|
||||||
|
|||||||
20
server.py
20
server.py
@@ -50,21 +50,23 @@ async def broadcast_handler(websocket: websockets.WebSocketServerProtocol):
|
|||||||
# Broadcast to all OTHER clients
|
# Broadcast to all OTHER clients
|
||||||
if not clients:
|
if not clients:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
disconnected = set()
|
disconnected = set()
|
||||||
# Track (client, task) pairs so failed clients are identified directly
|
# Create broadcast tasks for efficiency
|
||||||
send_jobs = []
|
tasks = []
|
||||||
for client in clients:
|
for client in clients:
|
||||||
if client != websocket and client.open:
|
if client != websocket and client.open:
|
||||||
send_jobs.append((client, asyncio.create_task(client.send(message))))
|
tasks.append(asyncio.create_task(client.send(message)))
|
||||||
|
|
||||||
if send_jobs:
|
if tasks:
|
||||||
results = await asyncio.gather(*[job[1] for job in send_jobs], return_exceptions=True)
|
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
for (target_client, _), result in zip(send_jobs, results):
|
for i, result in enumerate(results):
|
||||||
if isinstance(result, Exception):
|
if isinstance(result, Exception):
|
||||||
|
# Find the client that failed
|
||||||
|
target_client = [c for c in clients if c != websocket][i]
|
||||||
logger.error(f"Failed to send to a client {target_client.remote_address}: {result}")
|
logger.error(f"Failed to send to a client {target_client.remote_address}: {result}")
|
||||||
disconnected.add(target_client)
|
disconnected.add(target_client)
|
||||||
|
|
||||||
if disconnected:
|
if disconnected:
|
||||||
clients.difference_update(disconnected)
|
clients.difference_update(disconnected)
|
||||||
|
|
||||||
|
|||||||
117
style.css
117
style.css
@@ -410,6 +410,123 @@ canvas#nexus-canvas {
|
|||||||
font-style: italic;
|
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 {
|
@keyframes fadeIn {
|
||||||
from { opacity: 0; }
|
from { opacity: 0; }
|
||||||
to { opacity: 1; }
|
to { opacity: 1; }
|
||||||
|
|||||||
Reference in New Issue
Block a user