2026-03-21 10:13:35 +08:00
|
|
|
<template>
|
|
|
|
|
<div class="agent-view scanlines">
|
|
|
|
|
<div class="bg-grid"></div>
|
|
|
|
|
<div class="bg-glow"></div>
|
2026-03-24 21:42:01 +08:00
|
|
|
<div class="bg-circuit"></div>
|
2026-03-21 10:13:35 +08:00
|
|
|
<div class="bg-particles">
|
|
|
|
|
<span v-for="p in bgParticles" :key="p.id" class="bg-particle" :style="p.style"></span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="view-header">
|
|
|
|
|
<div class="header-title">
|
|
|
|
|
<span class="title-bracket">[</span>
|
2026-03-24 21:42:01 +08:00
|
|
|
<span class="title-text">ULTRON COMMAND CENTER</span>
|
2026-03-21 10:13:35 +08:00
|
|
|
<span class="title-bracket">]</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="header-actions">
|
|
|
|
|
<button class="btn-icon" @click="refreshStats" :class="{ spinning: loading }" title="刷新状态">
|
|
|
|
|
<RefreshCw :size="14" />
|
|
|
|
|
</button>
|
|
|
|
|
<div class="status-bar">
|
|
|
|
|
<span class="status-dot" :class="connectionStatus"></span>
|
|
|
|
|
<span class="status-label">{{ connectionLabel }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
<div class="command-stage">
|
|
|
|
|
<div class="board-shell holo-card">
|
|
|
|
|
<div class="board-trace trace-1"></div>
|
|
|
|
|
<div class="board-trace trace-2"></div>
|
|
|
|
|
<div class="board-trace trace-3"></div>
|
|
|
|
|
|
|
|
|
|
<section class="topology-stage">
|
|
|
|
|
<div class="topology-core-lane">
|
|
|
|
|
<div class="stage-caption">
|
|
|
|
|
<span class="stage-caption-kicker">COMMAND CORE</span>
|
|
|
|
|
<span class="stage-caption-title">战术蓝图主控矩阵</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
class="master-chip chip-card topology-master"
|
|
|
|
|
data-testid="agent-chip-master"
|
|
|
|
|
:class="[
|
|
|
|
|
getStatusClass('master'),
|
|
|
|
|
{ selected: selectedAgentId === 'master', energized: isAgentEnergized('master') },
|
|
|
|
|
]"
|
|
|
|
|
@click="selectAgent('master')"
|
|
|
|
|
>
|
|
|
|
|
<div class="chip-shell master-chip-shell">
|
|
|
|
|
<span class="chip-pin pin-top"></span>
|
|
|
|
|
<span class="chip-pin pin-right"></span>
|
|
|
|
|
<span class="chip-pin pin-bottom"></span>
|
|
|
|
|
<span class="chip-pin pin-left"></span>
|
|
|
|
|
<div class="chip-overlay"></div>
|
|
|
|
|
<div class="chip-status-strip">
|
|
|
|
|
<span class="chip-state-dot" :class="getStatusClass('master')"></span>
|
|
|
|
|
<span class="chip-state-label">MASTER CORE</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="chip-title-row">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="chip-code">JRV-PRIME</div>
|
|
|
|
|
<div class="chip-name">{{ getAgentName('master') }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="chip-runtime">
|
|
|
|
|
<span class="runtime-tag">{{ activeCommanderSkillId === 'skill_orchestration' ? 'ROUTING' : 'STANDBY' }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="chip-role">{{ getAgentRole('master') }}</div>
|
|
|
|
|
<div class="chip-desc">{{ getAgentDesc('master') }}</div>
|
|
|
|
|
<div class="chip-footer">
|
|
|
|
|
<span class="node-stat">
|
|
|
|
|
<span class="stat-label">调用</span>
|
|
|
|
|
<span class="stat-val">{{ agentData.master?.callCount || 0 }}</span>
|
|
|
|
|
</span>
|
|
|
|
|
<span v-if="agentData.master?.currentTask" class="node-task-tag">{{ agentData.master.currentTask }}</span>
|
|
|
|
|
<span v-else class="node-idle">总控待命</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="route-telemetry" data-testid="route-telemetry">
|
|
|
|
|
<div class="route-telemetry-header">
|
|
|
|
|
<span class="route-telemetry-label">ACTIVE ROUTE</span>
|
|
|
|
|
<span class="route-telemetry-state">{{ activeRouteTelemetry.state }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="route-telemetry-path">
|
|
|
|
|
<span
|
|
|
|
|
v-for="segment in activeRouteTelemetry.segments"
|
|
|
|
|
:key="segment"
|
|
|
|
|
class="route-segment"
|
|
|
|
|
>
|
|
|
|
|
{{ segment }}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="commander-skills embedded-commander-skills" data-testid="commander-skills" @click.stop>
|
|
|
|
|
<div class="skills-header embedded-skills-header">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="skills-eyebrow">COMMANDER MATRIX</div>
|
|
|
|
|
<div class="skills-title">指挥官技能</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="skills-badge">{{ activeCommanderSkillId ? 'ENGAGED' : 'STANDBY' }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="skills-grid embedded-skills-grid">
|
|
|
|
|
<article
|
|
|
|
|
v-for="skill in commanderSkillsForDisplay"
|
|
|
|
|
:key="skill.id"
|
|
|
|
|
:data-testid="`commander-skill-${skill.id}`"
|
|
|
|
|
class="skill-chip embedded-skill-chip"
|
|
|
|
|
:class="{ active: skill.active, standby: !skill.active }"
|
|
|
|
|
>
|
|
|
|
|
<div class="skill-chip-top">
|
|
|
|
|
<span class="skill-label">{{ skill.label }}</span>
|
|
|
|
|
<span class="skill-state">{{ skill.active ? skill.stateLabel : 'STANDBY' }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="skill-title">{{ skill.title }}</div>
|
|
|
|
|
<div class="skill-description">{{ skill.description }}</div>
|
|
|
|
|
</article>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-21 10:13:35 +08:00
|
|
|
</div>
|
2026-03-24 21:42:01 +08:00
|
|
|
|
|
|
|
|
<div class="topology-trunk" :class="{ energized: !!activeMainAgentId || activeCommanderSkillId === 'skill_orchestration' }">
|
|
|
|
|
<span class="topology-trunk-joint"></span>
|
|
|
|
|
<span class="bus-spine-label">PRIMARY TRUNK</span>
|
2026-03-21 10:13:35 +08:00
|
|
|
</div>
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
<div class="main-grid-shell">
|
|
|
|
|
<div class="stage-caption bus-caption">
|
|
|
|
|
<span class="stage-caption-kicker">BUS ARRAY</span>
|
|
|
|
|
<span class="stage-caption-title">主 Agent 分层总线</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="topology-main-grid">
|
|
|
|
|
<article
|
|
|
|
|
v-for="agentId in MAIN_AGENT_IDS"
|
|
|
|
|
:key="agentId"
|
|
|
|
|
class="topology-column"
|
|
|
|
|
:class="{ expanded: expandedAgentId === agentId }"
|
|
|
|
|
>
|
|
|
|
|
<div class="topology-drop" :class="{ energized: isBranchEnergized(agentId) }">
|
|
|
|
|
<span class="link-joint"></span>
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
class="chip-card agent-chip topology-main-chip"
|
|
|
|
|
:data-testid="`agent-chip-${agentId}`"
|
|
|
|
|
:class="[
|
|
|
|
|
getStatusClass(agentId),
|
|
|
|
|
{ selected: selectedAgentId === agentId, disabled: !localAgents[agentId]?.enabled, energized: isAgentEnergized(agentId) },
|
|
|
|
|
]"
|
|
|
|
|
@click="selectAgent(agentId)"
|
|
|
|
|
>
|
|
|
|
|
<div class="chip-shell compact">
|
|
|
|
|
<span class="chip-pin pin-top"></span>
|
|
|
|
|
<span class="chip-pin pin-right"></span>
|
|
|
|
|
<span class="chip-pin pin-bottom"></span>
|
|
|
|
|
<span class="chip-pin pin-left"></span>
|
|
|
|
|
<div class="chip-overlay"></div>
|
|
|
|
|
<div class="chip-status-strip">
|
|
|
|
|
<span class="chip-state-dot" :class="getStatusClass(agentId)"></span>
|
|
|
|
|
<span class="chip-state-label">{{ getAgentName(agentId) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="chip-code">{{ getAgentRole(agentId) }}</div>
|
|
|
|
|
<div class="chip-desc">{{ getAgentDesc(agentId) }}</div>
|
|
|
|
|
<div class="chip-footer compact-footer">
|
|
|
|
|
<span class="node-stat">
|
|
|
|
|
<span class="stat-label">调用</span>
|
|
|
|
|
<span class="stat-val">{{ agentData[agentId]?.callCount || 0 }}</span>
|
|
|
|
|
</span>
|
|
|
|
|
<span v-if="agentData[agentId]?.currentTask" class="node-task-tag">{{ agentData[agentId]?.currentTask }}</span>
|
|
|
|
|
<span v-else class="node-idle">待机中</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
class="main-link topology-link-label"
|
|
|
|
|
:data-testid="`bus-link-${agentId}`"
|
|
|
|
|
:class="{ energized: isBranchEnergized(agentId) }"
|
|
|
|
|
>
|
|
|
|
|
<span class="main-link-label">{{ RELATION_LABELS[`master-${agentId}`] }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<button class="cluster-toggle" @click="toggleCluster(agentId)">
|
|
|
|
|
<span>{{ expandedAgentId === agentId ? '收起子指挥官' : '展开子指挥官' }}</span>
|
|
|
|
|
<span class="toggle-count">{{ getSubCommanders(agentId).length }}</span>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<Transition name="cluster-fade">
|
|
|
|
|
<div v-if="expandedAgentId === agentId" class="sub-cluster topology-sub-cluster">
|
|
|
|
|
<div
|
|
|
|
|
v-for="sub in getSubCommanders(agentId)"
|
|
|
|
|
:key="sub.id"
|
|
|
|
|
class="sub-node topology-sub-node"
|
|
|
|
|
:class="getSubCommanderStatus(sub.id)"
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
class="sub-link topology-sub-link"
|
|
|
|
|
:data-testid="`sub-link-${sub.id}`"
|
|
|
|
|
:class="{ energized: isBranchEnergized(agentId, sub.id) }"
|
|
|
|
|
>
|
|
|
|
|
<span class="link-joint"></span>
|
|
|
|
|
<span class="sub-link-line"></span>
|
|
|
|
|
<span class="sub-link-label">{{ sub.relationLabel }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="sub-node-card holo-card" :class="{ energized: isBranchEnergized(agentId, sub.id) }">
|
|
|
|
|
<div class="sub-node-topline">
|
|
|
|
|
<span class="sub-name">{{ sub.name }}</span>
|
|
|
|
|
<span class="sub-badge">{{ sub.toolScopeLabel }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="sub-role">{{ sub.role }}</div>
|
|
|
|
|
<div class="sub-desc">{{ sub.description }}</div>
|
|
|
|
|
<div class="sub-footer">
|
|
|
|
|
<span class="sub-status-dot"></span>
|
|
|
|
|
<span class="sub-status-text">{{ getSubCommanderTask(sub.id) || getSubCommanderStatusText(sub.id) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Transition>
|
|
|
|
|
</article>
|
2026-03-21 10:13:35 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-24 21:42:01 +08:00
|
|
|
</section>
|
2026-03-21 10:13:35 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Transition :css="false" @enter="animateIn" @leave="animateOut">
|
|
|
|
|
<div v-if="drawerOpen" class="config-drawer">
|
|
|
|
|
<div class="drawer-header">
|
|
|
|
|
<span class="drawer-title">// AGENT CONFIGURATION</span>
|
|
|
|
|
<button class="btn-close" @click="drawerOpen = false"><X :size="16" /></button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="drawer-body" v-if="editAgent">
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">// AGENT NAME</label>
|
|
|
|
|
<input v-model="editAgent.name" type="text" class="form-input" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">// ROLE</label>
|
|
|
|
|
<input v-model="editAgent.role" type="text" class="form-input" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">// DESCRIPTION</label>
|
|
|
|
|
<textarea v-model="editAgent.description" class="form-textarea" rows="2"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group flex-1">
|
|
|
|
|
<label class="form-label">// SYSTEM PROMPT</label>
|
|
|
|
|
<textarea v-model="editAgent.systemPrompt" class="form-textarea code-textarea" rows="10"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label class="form-label">// STATUS</label>
|
|
|
|
|
<div class="toggle-row">
|
|
|
|
|
<span class="toggle-label" :class="{ dim: editAgent.enabled }">DISABLED</span>
|
|
|
|
|
<button class="toggle-btn" :class="{ active: editAgent.enabled }" @click="editAgent.enabled = !editAgent.enabled">
|
|
|
|
|
<span class="toggle-knob"></span>
|
|
|
|
|
</button>
|
|
|
|
|
<span class="toggle-label" :class="{ dim: !editAgent.enabled }">ENABLED</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="drawer-actions">
|
|
|
|
|
<button class="btn-secondary" @click="resetConfig">重置</button>
|
|
|
|
|
<button class="btn-primary" @click="saveConfig" :disabled="saving">
|
|
|
|
|
<span v-if="saving" class="btn-loader"></span>
|
|
|
|
|
{{ saving ? '保存中...' : '保存配置' }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Transition>
|
|
|
|
|
|
|
|
|
|
<Transition :css="false" @enter="fadeIn" @leave="fadeOut">
|
|
|
|
|
<div v-if="drawerOpen" class="drawer-backdrop" @click="drawerOpen = false"></div>
|
|
|
|
|
</Transition>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2026-03-24 21:42:01 +08:00
|
|
|
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
|
|
|
|
|
import { RefreshCw, X } from 'lucide-vue-next'
|
|
|
|
|
import {
|
|
|
|
|
COMMANDER_SKILLS,
|
|
|
|
|
DEFAULT_AGENTS,
|
|
|
|
|
MAIN_AGENT_ORDER,
|
|
|
|
|
RELATION_LABELS,
|
|
|
|
|
SUB_COMMANDERS_BY_PARENT,
|
|
|
|
|
type Agent,
|
|
|
|
|
type MainAgentId,
|
|
|
|
|
type SubCommander,
|
|
|
|
|
} from '@/data/agents'
|
|
|
|
|
import { agentApi, type AgentHierarchyStats } from '@/api/agent'
|
|
|
|
|
|
|
|
|
|
const MAIN_AGENT_IDS = MAIN_AGENT_ORDER as Exclude<MainAgentId, 'master'>[]
|
|
|
|
|
|
2026-03-21 10:13:35 +08:00
|
|
|
const bgParticles = Array.from({ length: 60 }, (_, i) => {
|
|
|
|
|
const d = 3 + Math.random() * 5
|
|
|
|
|
const delay = Math.random() * 4
|
|
|
|
|
const o = 0.25 + Math.random() * 0.5
|
|
|
|
|
const size = 1 + Math.random() * 2.5
|
|
|
|
|
return {
|
|
|
|
|
id: i,
|
|
|
|
|
style: {
|
|
|
|
|
left: `${Math.random() * 98}%`,
|
|
|
|
|
top: `${Math.random() * 95}%`,
|
|
|
|
|
width: `${size}px`,
|
|
|
|
|
height: `${size}px`,
|
|
|
|
|
'--d': `${d}s`,
|
|
|
|
|
'--delay': `${delay}s`,
|
|
|
|
|
'--o': String(o),
|
|
|
|
|
opacity: o,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
type AgentRuntimeState = { callCount: number; currentTask: string | null; status: string }
|
|
|
|
|
type SubCommanderRuntimeState = AgentRuntimeState & { parentId: string }
|
|
|
|
|
const cleanupFns: Array<() => void> = []
|
2026-03-21 10:13:35 +08:00
|
|
|
const selectedAgentId = ref<string | null>(null)
|
2026-03-24 21:42:01 +08:00
|
|
|
const expandedAgentId = ref<Exclude<MainAgentId, 'master'> | null>('planner')
|
2026-03-21 10:13:35 +08:00
|
|
|
const drawerOpen = ref(false)
|
|
|
|
|
const editAgent = ref<{ name: string; role: string; description: string; systemPrompt: string; enabled: boolean } | null>(null)
|
|
|
|
|
const saving = ref(false)
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const connectionStatus = ref<'connected' | 'disconnected'>('disconnected')
|
|
|
|
|
const connectionLabel = computed(() => connectionStatus.value === 'connected' ? '实时同步' : '离线模式')
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
const agentData = reactive<Record<string, AgentRuntimeState>>({})
|
|
|
|
|
const subCommanderData = reactive<Record<string, SubCommanderRuntimeState>>({})
|
|
|
|
|
const localAgents = reactive<Record<string, Agent>>(Object.fromEntries(DEFAULT_AGENTS.map((a) => [a.id, { ...a }])))
|
|
|
|
|
const demoMainAgentId = ref<Exclude<MainAgentId, 'master'> | null>(null)
|
|
|
|
|
const demoSubCommanderId = ref<string | null>(null)
|
2026-03-21 10:13:35 +08:00
|
|
|
|
|
|
|
|
let pollInterval: ReturnType<typeof setInterval> | null = null
|
2026-03-24 21:42:01 +08:00
|
|
|
let demoInterval: ReturnType<typeof setInterval> | null = null
|
2026-03-21 10:13:35 +08:00
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
function getSubCommanders(agentId: Exclude<MainAgentId, 'master'>): SubCommander[] {
|
|
|
|
|
return SUB_COMMANDERS_BY_PARENT[agentId]
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
function getStatusClass(agentId: string) {
|
|
|
|
|
const data = agentData[agentId]
|
|
|
|
|
const agent = localAgents[agentId]
|
|
|
|
|
if (!agent?.enabled) return 'disabled'
|
|
|
|
|
if (!data) return 'idle'
|
|
|
|
|
return data.status === 'active' ? 'active' : 'idle'
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
function getAgentName(id: string) {
|
|
|
|
|
return localAgents[id]?.name || DEFAULT_AGENTS.find((a) => a.id === id)?.name || id.toUpperCase()
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
function getAgentRole(id: string) {
|
|
|
|
|
return localAgents[id]?.role || DEFAULT_AGENTS.find((a) => a.id === id)?.role || ''
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
function getAgentDesc(id: string) {
|
|
|
|
|
return localAgents[id]?.description || DEFAULT_AGENTS.find((a) => a.id === id)?.description || ''
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
function getSubCommanderStatus(subId: string) {
|
|
|
|
|
const data = subCommanderData[subId]
|
2026-03-21 10:13:35 +08:00
|
|
|
if (!data) return 'idle'
|
2026-03-24 21:42:01 +08:00
|
|
|
return data.status === 'active' ? 'active' : data.status === 'disabled' ? 'disabled' : 'idle'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getSubCommanderTask(subId: string) {
|
|
|
|
|
return subCommanderData[subId]?.currentTask || null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getSubCommanderStatusText(subId: string) {
|
|
|
|
|
const status = getSubCommanderStatus(subId)
|
|
|
|
|
if (status === 'active') return '子指挥官处理中'
|
|
|
|
|
if (status === 'disabled') return '当前停用'
|
|
|
|
|
return '待命中'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toggleCluster(agentId: Exclude<MainAgentId, 'master'>) {
|
|
|
|
|
expandedAgentId.value = expandedAgentId.value === agentId ? null : agentId
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function selectAgent(id: string) {
|
|
|
|
|
const agent = localAgents[id]
|
|
|
|
|
if (!agent) return
|
|
|
|
|
selectedAgentId.value = id
|
2026-03-24 21:42:01 +08:00
|
|
|
editAgent.value = {
|
|
|
|
|
name: agent.name,
|
|
|
|
|
role: agent.role,
|
|
|
|
|
description: agent.description,
|
|
|
|
|
systemPrompt: agent.systemPrompt,
|
|
|
|
|
enabled: agent.enabled,
|
|
|
|
|
}
|
2026-03-21 10:13:35 +08:00
|
|
|
drawerOpen.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetConfig() {
|
2026-03-24 21:42:01 +08:00
|
|
|
const original = DEFAULT_AGENTS.find((a) => a.id === selectedAgentId.value)
|
|
|
|
|
if (original && editAgent.value) {
|
|
|
|
|
Object.assign(editAgent.value, {
|
|
|
|
|
name: original.name,
|
|
|
|
|
role: original.role,
|
|
|
|
|
description: original.description,
|
|
|
|
|
systemPrompt: original.systemPrompt,
|
|
|
|
|
enabled: original.enabled,
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function saveConfig() {
|
|
|
|
|
if (!editAgent.value || !selectedAgentId.value) return
|
|
|
|
|
saving.value = true
|
|
|
|
|
try {
|
|
|
|
|
if (localAgents[selectedAgentId.value]) Object.assign(localAgents[selectedAgentId.value], editAgent.value)
|
|
|
|
|
try {
|
2026-03-24 21:42:01 +08:00
|
|
|
await agentApi.updateConfig(selectedAgentId.value, {
|
|
|
|
|
name: editAgent.value.name,
|
|
|
|
|
description: editAgent.value.description,
|
|
|
|
|
system_prompt: editAgent.value.systemPrompt,
|
|
|
|
|
enabled: editAgent.value.enabled,
|
|
|
|
|
})
|
|
|
|
|
} catch {
|
|
|
|
|
// fallback to local only
|
|
|
|
|
}
|
2026-03-21 10:13:35 +08:00
|
|
|
drawerOpen.value = false
|
2026-03-24 21:42:01 +08:00
|
|
|
} finally {
|
|
|
|
|
saving.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyFlatStats() {
|
|
|
|
|
agentData.master = { callCount: 47, currentTask: '协调主角色与子指挥官', status: 'active' }
|
|
|
|
|
agentData.planner = { callCount: 12, currentTask: null, status: 'idle' }
|
|
|
|
|
agentData.executor = { callCount: 8, currentTask: '推进执行链路', status: 'active' }
|
|
|
|
|
agentData.librarian = { callCount: 5, currentTask: null, status: 'idle' }
|
|
|
|
|
agentData.analyst = { callCount: 3, currentTask: null, status: 'idle' }
|
|
|
|
|
|
|
|
|
|
subCommanderData.planner_scope = { parentId: 'planner', callCount: 6, currentTask: null, status: 'idle' }
|
|
|
|
|
subCommanderData.planner_steps = { parentId: 'planner', callCount: 9, currentTask: '拆解执行步骤', status: 'active' }
|
|
|
|
|
subCommanderData.executor_tasks = { parentId: 'executor', callCount: 8, currentTask: '处理任务动作', status: 'active' }
|
|
|
|
|
subCommanderData.executor_forum = { parentId: 'executor', callCount: 4, currentTask: null, status: 'idle' }
|
|
|
|
|
subCommanderData.librarian_retrieval = { parentId: 'librarian', callCount: 5, currentTask: null, status: 'idle' }
|
|
|
|
|
subCommanderData.librarian_graph = { parentId: 'librarian', callCount: 2, currentTask: null, status: 'idle' }
|
|
|
|
|
subCommanderData.analyst_progress = { parentId: 'analyst', callCount: 2, currentTask: null, status: 'idle' }
|
|
|
|
|
subCommanderData.analyst_insights = { parentId: 'analyst', callCount: 3, currentTask: '生成趋势判断', status: 'active' }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyHierarchyStats(stats: AgentHierarchyStats) {
|
|
|
|
|
agentData.master = { callCount: 47, currentTask: '协调主角色与子指挥官', status: 'active' }
|
|
|
|
|
for (const main of stats.main_agents) {
|
|
|
|
|
agentData[main.agent_id] = {
|
|
|
|
|
callCount: main.call_count,
|
|
|
|
|
currentTask: main.current_task,
|
|
|
|
|
status: main.status,
|
|
|
|
|
}
|
|
|
|
|
for (const child of main.sub_commanders) {
|
|
|
|
|
subCommanderData[child.agent_id] = {
|
|
|
|
|
parentId: main.agent_id,
|
|
|
|
|
callCount: child.call_count,
|
|
|
|
|
currentTask: child.current_task,
|
|
|
|
|
status: child.status,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getDemoSequence() {
|
|
|
|
|
return MAIN_AGENT_IDS.flatMap((agentId) => {
|
|
|
|
|
const subs = getSubCommanders(agentId)
|
|
|
|
|
return subs.length > 0
|
|
|
|
|
? subs.map((sub) => ({ mainId: agentId, subId: sub.id }))
|
|
|
|
|
: [{ mainId: agentId, subId: null }]
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setDemoPath(mainId: Exclude<MainAgentId, 'master'> | null, subId: string | null) {
|
|
|
|
|
demoMainAgentId.value = mainId
|
|
|
|
|
demoSubCommanderId.value = subId
|
|
|
|
|
if (mainId) {
|
|
|
|
|
expandedAgentId.value = mainId
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function startDemoCircuit() {
|
|
|
|
|
if (demoInterval) return
|
|
|
|
|
const sequence = getDemoSequence()
|
|
|
|
|
if (sequence.length === 0) return
|
|
|
|
|
let index = 0
|
|
|
|
|
const applyStep = () => {
|
|
|
|
|
const current = sequence[index % sequence.length]
|
|
|
|
|
setDemoPath(current.mainId, current.subId)
|
|
|
|
|
index += 1
|
|
|
|
|
}
|
|
|
|
|
applyStep()
|
|
|
|
|
demoInterval = setInterval(applyStep, 1600)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function stopDemoCircuit() {
|
|
|
|
|
if (demoInterval) {
|
|
|
|
|
clearInterval(demoInterval)
|
|
|
|
|
demoInterval = null
|
|
|
|
|
}
|
|
|
|
|
setDemoPath(null, null)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const activeSubCommanderId = computed(() => {
|
|
|
|
|
const realActive = Object.entries(subCommanderData).find(([, data]) => data.status === 'active')?.[0] ?? null
|
|
|
|
|
return connectionStatus.value === 'connected' ? realActive : (demoSubCommanderId.value ?? realActive)
|
|
|
|
|
})
|
|
|
|
|
const activeMainAgentId = computed<Exclude<MainAgentId, 'master'> | null>(() => {
|
|
|
|
|
if (connectionStatus.value !== 'connected' && demoMainAgentId.value) {
|
|
|
|
|
return demoMainAgentId.value
|
|
|
|
|
}
|
|
|
|
|
const activeMain = MAIN_AGENT_IDS.find((agentId) => agentData[agentId]?.status === 'active')
|
|
|
|
|
if (activeMain) return activeMain
|
|
|
|
|
if (activeSubCommanderId.value) {
|
|
|
|
|
const parentId = subCommanderData[activeSubCommanderId.value]?.parentId
|
|
|
|
|
if (parentId && MAIN_AGENT_IDS.includes(parentId as Exclude<MainAgentId, 'master'>)) {
|
|
|
|
|
return parentId as Exclude<MainAgentId, 'master'>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return agentData.master?.status === 'active' ? 'planner' : null
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const activeCommanderSkillId = computed(() => {
|
|
|
|
|
if (activeSubCommanderId.value) {
|
|
|
|
|
return COMMANDER_SKILLS.find((skill) => skill.relatedAgentIds.includes(activeSubCommanderId.value as string))?.id ?? null
|
|
|
|
|
}
|
|
|
|
|
if (activeMainAgentId.value) {
|
|
|
|
|
return COMMANDER_SKILLS.find((skill) => skill.relatedAgentIds.includes(activeMainAgentId.value as string))?.id ?? null
|
|
|
|
|
}
|
|
|
|
|
return agentData.master?.status === 'active' ? 'skill_orchestration' : null
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const commanderSkillsForDisplay = computed(() => COMMANDER_SKILLS.map((skill) => ({
|
|
|
|
|
...skill,
|
|
|
|
|
active: skill.id === activeCommanderSkillId.value,
|
|
|
|
|
})))
|
|
|
|
|
|
|
|
|
|
const activeRouteTelemetry = computed(() => {
|
|
|
|
|
const segments = ['JARVIS']
|
|
|
|
|
|
|
|
|
|
if (activeMainAgentId.value) {
|
|
|
|
|
segments.push(getAgentName(activeMainAgentId.value))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (activeSubCommanderId.value) {
|
|
|
|
|
const activeSub = Object.values(SUB_COMMANDERS_BY_PARENT)
|
|
|
|
|
.flat()
|
|
|
|
|
.find((item) => item.id === activeSubCommanderId.value)
|
|
|
|
|
if (activeSub) {
|
|
|
|
|
segments.push(activeSub.name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
segments,
|
|
|
|
|
state: activeCommanderSkillId.value ? 'ENGAGED' : 'STANDBY',
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function isAgentEnergized(agentId: string) {
|
|
|
|
|
if (agentId === 'master') return agentData.master?.status === 'active' || !!activeMainAgentId.value || !!activeSubCommanderId.value
|
|
|
|
|
if (activeSubCommanderId.value) {
|
|
|
|
|
return agentId === activeMainAgentId.value || agentId === activeSubCommanderId.value
|
|
|
|
|
}
|
|
|
|
|
return agentId === activeMainAgentId.value
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
function isBranchEnergized(parentId: string, subId?: string) {
|
|
|
|
|
if (subId) return activeSubCommanderId.value === subId
|
|
|
|
|
return activeMainAgentId.value === parentId || (agentData.master?.status === 'active' && activeCommanderSkillId.value === 'skill_orchestration' && parentId === 'planner')
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function refreshStats() {
|
|
|
|
|
loading.value = true
|
|
|
|
|
try {
|
2026-03-24 21:42:01 +08:00
|
|
|
const stats = await agentApi.getHierarchyStats()
|
|
|
|
|
applyHierarchyStats(stats)
|
2026-03-21 10:13:35 +08:00
|
|
|
connectionStatus.value = 'connected'
|
2026-03-24 21:42:01 +08:00
|
|
|
stopDemoCircuit()
|
2026-03-21 10:13:35 +08:00
|
|
|
} catch {
|
2026-03-24 21:42:01 +08:00
|
|
|
applyFlatStats()
|
2026-03-21 10:13:35 +08:00
|
|
|
connectionStatus.value = 'disconnected'
|
2026-03-24 21:42:01 +08:00
|
|
|
startDemoCircuit()
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
2026-03-21 22:13:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function runTransition(
|
|
|
|
|
el: Element,
|
|
|
|
|
keyframes: Keyframe[],
|
|
|
|
|
options: KeyframeAnimationOptions,
|
|
|
|
|
done?: () => void,
|
|
|
|
|
) {
|
2026-03-24 21:42:01 +08:00
|
|
|
const animation = (el as HTMLElement).animate(keyframes, { fill: 'forwards', ...options })
|
2026-03-21 22:13:12 +08:00
|
|
|
const finish = () => done?.()
|
|
|
|
|
animation.addEventListener('finish', finish, { once: true })
|
|
|
|
|
cleanupFns.push(() => animation.cancel())
|
|
|
|
|
return animation
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function animateIn(el: Element, done: () => void) {
|
|
|
|
|
runTransition(
|
|
|
|
|
el,
|
2026-03-24 21:42:01 +08:00
|
|
|
[{ opacity: 0, transform: 'translateX(80px)' }, { opacity: 1, transform: 'translateX(0)' }],
|
2026-03-21 22:13:12 +08:00
|
|
|
{ duration: 350, easing: 'cubic-bezier(0.4, 0, 0.2, 1)' },
|
|
|
|
|
done,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function animateOut(el: Element, done: () => void) {
|
|
|
|
|
runTransition(
|
|
|
|
|
el,
|
2026-03-24 21:42:01 +08:00
|
|
|
[{ opacity: 1, transform: 'translateX(0)' }, { opacity: 0, transform: 'translateX(80px)' }],
|
2026-03-21 22:13:12 +08:00
|
|
|
{ duration: 250, easing: 'cubic-bezier(0.4, 0, 1, 1)' },
|
|
|
|
|
done,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function fadeIn(el: Element, done: () => void) {
|
|
|
|
|
runTransition(el, [{ opacity: 0 }, { opacity: 1 }], { duration: 250 }, done)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function fadeOut(el: Element, done: () => void) {
|
|
|
|
|
runTransition(el, [{ opacity: 1 }, { opacity: 0 }], { duration: 200 }, done)
|
|
|
|
|
}
|
2026-03-21 10:13:35 +08:00
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
await refreshStats()
|
|
|
|
|
pollInterval = setInterval(refreshStats, 5000)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
if (pollInterval) clearInterval(pollInterval)
|
2026-03-24 21:42:01 +08:00
|
|
|
stopDemoCircuit()
|
|
|
|
|
cleanupFns.forEach((cleanup) => cleanup())
|
2026-03-21 10:13:35 +08:00
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.agent-view {
|
|
|
|
|
height: 100%;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
2026-03-24 21:42:01 +08:00
|
|
|
overflow: auto;
|
2026-03-21 10:13:35 +08:00
|
|
|
position: relative;
|
2026-03-24 21:42:01 +08:00
|
|
|
background:
|
|
|
|
|
radial-gradient(circle at top, rgba(0, 245, 212, 0.07), transparent 34%),
|
|
|
|
|
linear-gradient(180deg, #03050a 0%, #07101a 35%, #03050a 100%);
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
.bg-grid,
|
|
|
|
|
.bg-glow,
|
|
|
|
|
.bg-circuit,
|
|
|
|
|
.bg-particles {
|
2026-03-21 10:13:35 +08:00
|
|
|
position: absolute;
|
|
|
|
|
inset: 0;
|
2026-03-24 21:42:01 +08:00
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bg-grid {
|
2026-03-21 10:13:35 +08:00
|
|
|
background-image:
|
2026-03-24 21:42:01 +08:00
|
|
|
linear-gradient(rgba(0, 245, 212, 0.04) 1px, transparent 1px),
|
|
|
|
|
linear-gradient(90deg, rgba(0, 245, 212, 0.04) 1px, transparent 1px);
|
2026-03-21 10:13:35 +08:00
|
|
|
background-size: 40px 40px;
|
|
|
|
|
}
|
2026-03-24 21:42:01 +08:00
|
|
|
|
2026-03-21 10:13:35 +08:00
|
|
|
.bg-glow {
|
2026-03-24 21:42:01 +08:00
|
|
|
background:
|
|
|
|
|
radial-gradient(circle at 20% 20%, rgba(0, 245, 212, 0.08), transparent 26%),
|
|
|
|
|
radial-gradient(circle at 80% 14%, rgba(123, 44, 191, 0.08), transparent 22%),
|
|
|
|
|
radial-gradient(circle at 50% 70%, rgba(0, 245, 212, 0.05), transparent 30%);
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
2026-03-24 21:42:01 +08:00
|
|
|
|
|
|
|
|
.bg-circuit {
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
background-image:
|
|
|
|
|
linear-gradient(90deg, transparent 12%, rgba(0, 245, 212, 0.08) 12%, rgba(0, 245, 212, 0.08) 12.4%, transparent 12.4%),
|
|
|
|
|
linear-gradient(transparent 20%, rgba(0, 245, 212, 0.06) 20%, rgba(0, 245, 212, 0.06) 20.5%, transparent 20.5%),
|
|
|
|
|
radial-gradient(circle at 12% 20%, rgba(0, 245, 212, 0.25) 0 2px, transparent 3px),
|
|
|
|
|
radial-gradient(circle at 88% 36%, rgba(0, 245, 212, 0.22) 0 2px, transparent 3px),
|
|
|
|
|
radial-gradient(circle at 58% 68%, rgba(0, 245, 212, 0.18) 0 2px, transparent 3px);
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
2026-03-24 21:42:01 +08:00
|
|
|
|
2026-03-21 10:13:35 +08:00
|
|
|
.bg-particle {
|
|
|
|
|
position: absolute;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: var(--accent-cyan);
|
|
|
|
|
box-shadow: 0 0 4px rgba(0,245,212,0.6), 0 0 8px rgba(0,245,212,0.2);
|
|
|
|
|
animation: star-twinkle var(--d, 4s) ease-in-out infinite var(--delay, 0s);
|
|
|
|
|
}
|
2026-03-24 21:42:01 +08:00
|
|
|
|
2026-03-21 10:13:35 +08:00
|
|
|
@keyframes star-twinkle {
|
|
|
|
|
0%, 100% { opacity: var(--o, 0.4); transform: scale(1); }
|
2026-03-24 21:42:01 +08:00
|
|
|
50% { opacity: calc(var(--o, 0.4) * 0.3); transform: scale(0.45); }
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.view-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 14px 24px;
|
|
|
|
|
border-bottom: 1px solid var(--border-dim);
|
2026-03-24 21:42:01 +08:00
|
|
|
position: sticky;
|
|
|
|
|
top: 0;
|
2026-03-21 10:13:35 +08:00
|
|
|
z-index: 10;
|
2026-03-24 21:42:01 +08:00
|
|
|
background: rgba(5,8,16,0.72);
|
2026-03-21 10:13:35 +08:00
|
|
|
backdrop-filter: blur(8px);
|
|
|
|
|
}
|
2026-03-24 21:42:01 +08:00
|
|
|
|
|
|
|
|
.header-title {
|
|
|
|
|
font-family: var(--font-display);
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
letter-spacing: 0.2em;
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.title-bracket {
|
|
|
|
|
color: var(--accent-cyan);
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
2026-03-21 10:13:35 +08:00
|
|
|
|
|
|
|
|
.btn-icon {
|
2026-03-24 21:42:01 +08:00
|
|
|
width: 32px;
|
|
|
|
|
height: 32px;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
border: 1px solid var(--border-dim);
|
|
|
|
|
background: rgba(255,255,255,0.02);
|
|
|
|
|
color: var(--accent-cyan);
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
.btn-icon.spinning svg {
|
|
|
|
|
animation: spin 1s linear infinite;
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
@keyframes spin {
|
|
|
|
|
to { transform: rotate(360deg); }
|
|
|
|
|
}
|
2026-03-21 10:13:35 +08:00
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
.status-bar {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
border: 1px solid var(--border-dim);
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: rgba(255,255,255,0.02);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.status-dot {
|
|
|
|
|
width: 8px;
|
|
|
|
|
height: 8px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
box-shadow: 0 0 8px currentColor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.status-dot.connected {
|
|
|
|
|
background: var(--accent-green);
|
|
|
|
|
color: var(--accent-green);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.status-dot.disconnected {
|
|
|
|
|
background: var(--accent-amber);
|
|
|
|
|
color: var(--accent-amber);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.status-label {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
letter-spacing: 0.08em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.command-stage {
|
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
padding: 18px 20px 22px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.board-shell {
|
2026-03-21 10:13:35 +08:00
|
|
|
position: relative;
|
|
|
|
|
overflow: hidden;
|
2026-03-24 21:42:01 +08:00
|
|
|
padding: 18px 18px 20px;
|
|
|
|
|
border-radius: 24px;
|
|
|
|
|
border: 1px solid rgba(0, 245, 212, 0.12);
|
|
|
|
|
background:
|
|
|
|
|
linear-gradient(180deg, rgba(8, 14, 24, 0.95), rgba(3, 8, 16, 0.96)),
|
|
|
|
|
linear-gradient(135deg, rgba(0, 245, 212, 0.03), transparent 35%);
|
|
|
|
|
box-shadow: inset 0 1px 0 rgba(255,255,255,0.04), 0 24px 60px rgba(0,0,0,0.32), 0 0 24px rgba(0,245,212,0.05);
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
2026-03-24 21:42:01 +08:00
|
|
|
|
|
|
|
|
.board-shell::before,
|
|
|
|
|
.board-shell::after {
|
|
|
|
|
content: '';
|
2026-03-21 10:13:35 +08:00
|
|
|
position: absolute;
|
2026-03-24 21:42:01 +08:00
|
|
|
inset: 14px;
|
|
|
|
|
border: 1px solid rgba(0, 245, 212, 0.07);
|
|
|
|
|
border-radius: 22px;
|
2026-03-21 10:13:35 +08:00
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
2026-03-24 21:42:01 +08:00
|
|
|
|
|
|
|
|
.board-shell::after {
|
|
|
|
|
inset: auto 28px 28px auto;
|
|
|
|
|
width: 88px;
|
|
|
|
|
height: 88px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
filter: blur(12px);
|
|
|
|
|
background: radial-gradient(circle, rgba(0, 245, 212, 0.16), transparent 70%);
|
|
|
|
|
border: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.board-trace {
|
2026-03-21 10:13:35 +08:00
|
|
|
position: absolute;
|
2026-03-24 21:42:01 +08:00
|
|
|
background: linear-gradient(90deg, transparent, rgba(0,245,212,0.12), transparent);
|
|
|
|
|
opacity: 0.6;
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
.trace-1 { top: 96px; left: 10%; width: 22%; height: 1px; }
|
|
|
|
|
.trace-2 { top: 240px; right: 8%; width: 18%; height: 1px; }
|
|
|
|
|
.trace-3 { bottom: 120px; left: 6%; width: 16%; height: 1px; }
|
|
|
|
|
|
|
|
|
|
.topology-stage {
|
2026-03-21 10:13:35 +08:00
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
2026-03-24 21:42:01 +08:00
|
|
|
gap: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stage-caption {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
margin: 0 auto 12px;
|
|
|
|
|
padding: 8px 14px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
border: 1px solid rgba(0,245,212,0.12);
|
|
|
|
|
background: linear-gradient(90deg, rgba(3,8,16,0.78), rgba(7,14,24,0.9));
|
|
|
|
|
box-shadow: 0 0 18px rgba(0,245,212,0.06);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stage-caption-kicker {
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
letter-spacing: 0.22em;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
color: var(--accent-cyan);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stage-caption-title {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
letter-spacing: 0.12em;
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bus-caption {
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-core-lane {
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.master-chip-shell {
|
|
|
|
|
min-height: 0;
|
|
|
|
|
background:
|
|
|
|
|
linear-gradient(180deg, rgba(9,15,28,0.2), transparent 28%),
|
|
|
|
|
radial-gradient(circle at top center, rgba(0,245,212,0.08), transparent 40%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-master {
|
|
|
|
|
justify-self: center;
|
|
|
|
|
width: min(720px, 100%);
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-master::before,
|
|
|
|
|
.topology-master::after {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-master::before {
|
|
|
|
|
inset: -10px 6%;
|
|
|
|
|
border-radius: 32px;
|
|
|
|
|
border: 1px solid rgba(0,245,212,0.08);
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-master::after {
|
|
|
|
|
inset: auto 12% -14px 12%;
|
|
|
|
|
height: 24px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: radial-gradient(circle, rgba(0,245,212,0.24), transparent 72%);
|
|
|
|
|
filter: blur(10px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.embedded-commander-skills {
|
2026-03-21 10:13:35 +08:00
|
|
|
position: relative;
|
2026-03-24 21:42:01 +08:00
|
|
|
margin-top: 8px;
|
|
|
|
|
padding: 12px;
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
border: 1px solid rgba(0,245,212,0.12);
|
|
|
|
|
background:
|
|
|
|
|
linear-gradient(180deg, rgba(8,14,24,0.68), rgba(4,8,16,0.88)),
|
|
|
|
|
repeating-linear-gradient(90deg, rgba(0,245,212,0.03) 0 1px, transparent 1px 18px);
|
|
|
|
|
box-shadow: inset 0 1px 0 rgba(255,255,255,0.03), 0 0 18px rgba(0,245,212,0.05);
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
2026-03-24 21:42:01 +08:00
|
|
|
|
|
|
|
|
.embedded-commander-skills::before {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 16px;
|
|
|
|
|
right: 16px;
|
|
|
|
|
top: 0;
|
|
|
|
|
height: 1px;
|
|
|
|
|
background: linear-gradient(90deg, transparent, rgba(0,245,212,0.55), transparent);
|
2026-03-21 10:13:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-24 21:42:01 +08:00
|
|
|
.embedded-skills-header {
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.embedded-skills-grid {
|
|
|
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
|
|
gap: 8px;
|
|
|
|
|
margin-top: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.embedded-skill-chip {
|
|
|
|
|
min-height: 84px;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
background: linear-gradient(180deg, rgba(10,16,28,0.92), rgba(6,10,18,0.98));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.embedded-skill-chip .skill-title {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.embedded-skill-chip .skill-description {
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
line-height: 1.45;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-trunk {
|
|
|
|
|
position: relative;
|
|
|
|
|
width: 4px;
|
|
|
|
|
height: 70px;
|
|
|
|
|
margin: -4px auto 0;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: linear-gradient(180deg, rgba(0,245,212,0.15), rgba(0,245,212,0.7), rgba(0,245,212,0.12));
|
|
|
|
|
box-shadow: 0 0 16px rgba(0,245,212,0.12);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-grid-shell {
|
|
|
|
|
position: relative;
|
|
|
|
|
padding: 20px 18px 10px;
|
|
|
|
|
border-radius: 24px;
|
|
|
|
|
border: 1px solid rgba(0,245,212,0.08);
|
|
|
|
|
background:
|
|
|
|
|
linear-gradient(180deg, rgba(4,9,18,0.84), rgba(3,7,14,0.95)),
|
|
|
|
|
radial-gradient(circle at top center, rgba(0,245,212,0.08), transparent 42%);
|
|
|
|
|
box-shadow: inset 0 1px 0 rgba(255,255,255,0.03), 0 20px 44px rgba(0,0,0,0.22);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-grid-shell::before,
|
|
|
|
|
.main-grid-shell::after {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-grid-shell::before {
|
|
|
|
|
inset: 12px;
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
border: 1px solid rgba(0,245,212,0.06);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-grid-shell::after {
|
|
|
|
|
left: 24px;
|
|
|
|
|
right: 24px;
|
|
|
|
|
top: 62px;
|
|
|
|
|
height: 1px;
|
|
|
|
|
background: linear-gradient(90deg, transparent, rgba(0,245,212,0.3), transparent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-trunk::before,
|
|
|
|
|
.topology-trunk::after,
|
|
|
|
|
.topology-drop::before,
|
|
|
|
|
.topology-drop::after,
|
|
|
|
|
.topology-sub-link .sub-link-line::before,
|
|
|
|
|
.topology-sub-link .sub-link-line::after {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
inset: 0;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-trunk-joint {
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 50%;
|
|
|
|
|
bottom: -8px;
|
|
|
|
|
width: 14px;
|
|
|
|
|
height: 14px;
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: rgba(0,245,212,0.78);
|
|
|
|
|
box-shadow: 0 0 18px rgba(0,245,212,0.55), 0 0 34px rgba(0,245,212,0.22);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-trunk.energized::before,
|
|
|
|
|
.topology-drop.energized::before,
|
|
|
|
|
.topology-sub-link.energized .sub-link-line::before {
|
|
|
|
|
border-radius: inherit;
|
|
|
|
|
background: linear-gradient(180deg, transparent 0%, rgba(255,255,255,0.2) 20%, rgba(130,255,245,0.98) 42%, rgba(0,245,212,1) 50%, rgba(130,255,245,0.98) 58%, rgba(255,255,255,0.14) 78%, transparent 100%);
|
|
|
|
|
animation: current-vertical 1.15s linear infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-trunk.energized::after,
|
|
|
|
|
.topology-drop.energized::after,
|
|
|
|
|
.topology-sub-link.energized .sub-link-line::after {
|
|
|
|
|
border-radius: inherit;
|
|
|
|
|
background: linear-gradient(180deg, transparent 0%, rgba(0,245,212,0.08) 20%, rgba(0,245,212,0.5) 50%, rgba(0,245,212,0.08) 80%, transparent 100%);
|
|
|
|
|
filter: blur(6px);
|
|
|
|
|
animation: current-trail-vertical 1.15s linear infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-main-grid {
|
|
|
|
|
position: relative;
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(4, minmax(190px, 1fr));
|
|
|
|
|
gap: 14px;
|
|
|
|
|
align-items: start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-main-grid::after {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 8%;
|
|
|
|
|
right: 8%;
|
|
|
|
|
top: 10px;
|
|
|
|
|
height: 44px;
|
|
|
|
|
border: 1px solid rgba(0,245,212,0.06);
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
border-radius: 20px 20px 0 0;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-main-grid::before {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 12.5%;
|
|
|
|
|
right: 12.5%;
|
|
|
|
|
top: 10px;
|
|
|
|
|
height: 3px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: linear-gradient(90deg, rgba(0,245,212,0.18), rgba(0,245,212,0.68), rgba(0,245,212,0.18));
|
|
|
|
|
box-shadow: 0 0 18px rgba(0,245,212,0.12);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-column {
|
|
|
|
|
position: relative;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-drop {
|
|
|
|
|
position: relative;
|
|
|
|
|
width: 3px;
|
|
|
|
|
height: 46px;
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: linear-gradient(180deg, rgba(0,245,212,0.6), rgba(0,245,212,0.08));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-drop .link-joint {
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 50%;
|
|
|
|
|
top: -6px;
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-main-chip {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-link-label {
|
|
|
|
|
justify-content: center;
|
|
|
|
|
min-height: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-link-label.energized .main-link-label {
|
|
|
|
|
color: var(--accent-cyan);
|
|
|
|
|
text-shadow: 0 0 10px rgba(0,245,212,0.35);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-sub-cluster {
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-sub-node {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-sub-link {
|
|
|
|
|
position: relative;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-sub-link .link-joint {
|
|
|
|
|
width: 9px;
|
|
|
|
|
height: 9px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-sub-link .sub-link-line {
|
|
|
|
|
width: 3px;
|
|
|
|
|
min-width: 3px;
|
|
|
|
|
min-height: 42px;
|
|
|
|
|
height: 42px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: linear-gradient(180deg, rgba(0,245,212,0.6), rgba(0,245,212,0.08));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-sub-link .sub-link-label {
|
|
|
|
|
text-align: center;
|
|
|
|
|
white-space: normal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-skills-grid {
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-card {
|
|
|
|
|
position: relative;
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
border: 1px solid rgba(0, 245, 212, 0.16);
|
|
|
|
|
background:
|
|
|
|
|
linear-gradient(180deg, rgba(9, 14, 26, 0.95), rgba(5, 9, 16, 0.98)),
|
|
|
|
|
linear-gradient(135deg, rgba(255,255,255,0.02), transparent 55%);
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
transition: transform var(--transition-mid), border-color var(--transition-mid), box-shadow var(--transition-mid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-card:hover {
|
|
|
|
|
transform: translateY(-2px);
|
|
|
|
|
border-color: rgba(0, 245, 212, 0.28);
|
|
|
|
|
box-shadow: 0 14px 34px rgba(0,0,0,0.24), 0 0 18px rgba(0,245,212,0.08);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-card.selected,
|
|
|
|
|
.chip-card.energized {
|
|
|
|
|
border-color: rgba(0, 245, 212, 0.46);
|
|
|
|
|
box-shadow: 0 0 0 1px rgba(0,245,212,0.18) inset, 0 0 24px rgba(0,245,212,0.15);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-card.energized {
|
|
|
|
|
box-shadow:
|
|
|
|
|
0 0 0 1px rgba(0,245,212,0.22) inset,
|
|
|
|
|
0 0 18px rgba(0,245,212,0.18),
|
|
|
|
|
0 0 42px rgba(0,245,212,0.16),
|
|
|
|
|
0 0 70px rgba(0,245,212,0.07);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-card.disabled {
|
|
|
|
|
opacity: 0.48;
|
|
|
|
|
filter: saturate(0.5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-shell {
|
|
|
|
|
position: relative;
|
|
|
|
|
min-height: 212px;
|
|
|
|
|
padding: 18px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-shell.compact {
|
|
|
|
|
min-height: 168px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-main-chip .chip-shell.compact {
|
|
|
|
|
min-height: 182px;
|
|
|
|
|
padding-top: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-pin {
|
|
|
|
|
position: absolute;
|
|
|
|
|
opacity: 0.45;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pin-top,
|
|
|
|
|
.pin-bottom {
|
|
|
|
|
left: 24px;
|
|
|
|
|
right: 24px;
|
|
|
|
|
height: 8px;
|
|
|
|
|
background: repeating-linear-gradient(90deg, rgba(0,245,212,0.2) 0 10px, transparent 10px 18px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pin-top { top: 0; }
|
|
|
|
|
.pin-bottom { bottom: 0; }
|
|
|
|
|
|
|
|
|
|
.pin-left,
|
|
|
|
|
.pin-right {
|
|
|
|
|
top: 24px;
|
|
|
|
|
bottom: 24px;
|
|
|
|
|
width: 8px;
|
|
|
|
|
background: repeating-linear-gradient(180deg, rgba(0,245,212,0.2) 0 10px, transparent 10px 18px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pin-left { left: 0; }
|
|
|
|
|
.pin-right { right: 0; }
|
|
|
|
|
|
|
|
|
|
.chip-overlay {
|
|
|
|
|
position: absolute;
|
|
|
|
|
inset: 12px;
|
|
|
|
|
border-radius: 18px;
|
|
|
|
|
border: 1px solid rgba(0, 245, 212, 0.08);
|
|
|
|
|
background:
|
|
|
|
|
linear-gradient(135deg, rgba(255,255,255,0.04), transparent 34%),
|
|
|
|
|
linear-gradient(180deg, transparent, rgba(0,245,212,0.04));
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-overlay::after {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
inset: 0;
|
|
|
|
|
background: linear-gradient(120deg, transparent 10%, rgba(0,245,212,0.15) 45%, transparent 70%);
|
|
|
|
|
transform: translateX(-120%);
|
|
|
|
|
animation: chip-scan 6s linear infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes chip-scan {
|
|
|
|
|
to { transform: translateX(120%); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes node-flicker {
|
|
|
|
|
0%, 100% {
|
|
|
|
|
transform: scale(1);
|
|
|
|
|
opacity: 0.92;
|
|
|
|
|
}
|
|
|
|
|
50% {
|
|
|
|
|
transform: scale(1.22);
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-status-strip,
|
|
|
|
|
.chip-title-row,
|
|
|
|
|
.chip-footer,
|
|
|
|
|
.skills-header,
|
|
|
|
|
.skill-chip-top,
|
|
|
|
|
.sub-node-topline,
|
|
|
|
|
.sub-footer,
|
|
|
|
|
.drawer-header,
|
|
|
|
|
.toggle-row,
|
|
|
|
|
.drawer-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-state-dot,
|
|
|
|
|
.sub-status-dot {
|
|
|
|
|
width: 8px;
|
|
|
|
|
height: 8px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: currentColor;
|
|
|
|
|
box-shadow: 0 0 10px currentColor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-state-dot.active,
|
|
|
|
|
.sub-node.active .sub-status-dot { color: var(--accent-green); }
|
|
|
|
|
.chip-state-dot.idle,
|
|
|
|
|
.sub-node.idle .sub-status-dot { color: var(--accent-cyan); }
|
|
|
|
|
.chip-state-dot.disabled,
|
|
|
|
|
.sub-node.disabled .sub-status-dot { color: var(--text-dim); }
|
|
|
|
|
|
|
|
|
|
.chip-state-label,
|
|
|
|
|
.skills-eyebrow,
|
|
|
|
|
.skill-label,
|
|
|
|
|
.sub-name,
|
|
|
|
|
.drawer-title {
|
|
|
|
|
font-family: var(--font-display);
|
|
|
|
|
letter-spacing: 0.16em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-state-label,
|
|
|
|
|
.skills-eyebrow,
|
|
|
|
|
.skill-label {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
color: var(--accent-cyan);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-code,
|
|
|
|
|
.runtime-tag,
|
|
|
|
|
.skills-badge,
|
|
|
|
|
.skill-state,
|
|
|
|
|
.main-link-label,
|
|
|
|
|
.sub-link-label,
|
|
|
|
|
.sub-badge,
|
|
|
|
|
.node-task-tag,
|
|
|
|
|
.node-idle,
|
|
|
|
|
.toggle-count {
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
letter-spacing: 0.12em;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-code {
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-name,
|
|
|
|
|
.skills-title,
|
|
|
|
|
.skill-title {
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-name {
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
letter-spacing: 0.08em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.skills-title {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-role,
|
|
|
|
|
.skill-state,
|
|
|
|
|
.sub-role {
|
|
|
|
|
color: var(--accent-purple);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chip-desc,
|
|
|
|
|
.skill-description,
|
|
|
|
|
.sub-desc {
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
line-height: 1.65;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.runtime-tag,
|
|
|
|
|
.skills-badge,
|
|
|
|
|
.skill-state,
|
|
|
|
|
.sub-badge,
|
|
|
|
|
.node-task-tag,
|
|
|
|
|
.node-idle,
|
|
|
|
|
.toggle-count {
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.runtime-tag,
|
|
|
|
|
.skills-badge,
|
|
|
|
|
.node-task-tag {
|
|
|
|
|
background: rgba(0,245,212,0.1);
|
|
|
|
|
color: var(--accent-cyan);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.node-idle {
|
|
|
|
|
background: rgba(255,255,255,0.04);
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.node-stat {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: rgba(255,255,255,0.04);
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-label { color: var(--text-dim); }
|
|
|
|
|
.stat-val { color: var(--accent-cyan); }
|
|
|
|
|
|
|
|
|
|
.route-telemetry {
|
|
|
|
|
position: relative;
|
|
|
|
|
padding: 12px 14px;
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
border: 1px solid rgba(0,245,212,0.14);
|
|
|
|
|
background:
|
|
|
|
|
linear-gradient(180deg, rgba(7,12,22,0.92), rgba(4,8,16,0.98)),
|
|
|
|
|
radial-gradient(circle at left center, rgba(0,245,212,0.08), transparent 45%);
|
|
|
|
|
box-shadow: inset 0 1px 0 rgba(255,255,255,0.03), 0 0 18px rgba(0,245,212,0.06);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.route-telemetry::before {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
inset: 0;
|
|
|
|
|
border-radius: inherit;
|
|
|
|
|
background: linear-gradient(90deg, transparent 0%, rgba(0,245,212,0.08) 30%, transparent 70%);
|
|
|
|
|
transform: translateX(-100%);
|
|
|
|
|
animation: telemetry-scan 4s linear infinite;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.route-telemetry-header,
|
|
|
|
|
.route-telemetry-path {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.route-telemetry-header {
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.route-telemetry-label,
|
|
|
|
|
.route-telemetry-state {
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
letter-spacing: 0.16em;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.route-telemetry-label {
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.route-telemetry-state {
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
color: var(--accent-cyan);
|
|
|
|
|
background: rgba(0,245,212,0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.route-segment {
|
|
|
|
|
position: relative;
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
padding: 4px 10px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
border: 1px solid rgba(0,245,212,0.12);
|
|
|
|
|
background: rgba(255,255,255,0.03);
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
letter-spacing: 0.12em;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.route-segment:not(:last-child)::after {
|
|
|
|
|
content: '→';
|
|
|
|
|
position: relative;
|
|
|
|
|
right: -4px;
|
|
|
|
|
color: rgba(0,245,212,0.72);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes telemetry-scan {
|
|
|
|
|
to { transform: translateX(100%); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.commander-skills {
|
|
|
|
|
padding: 18px;
|
|
|
|
|
border-radius: 24px;
|
|
|
|
|
border: 1px solid rgba(0, 245, 212, 0.12);
|
|
|
|
|
background: linear-gradient(180deg, rgba(8, 14, 24, 0.9), rgba(4, 8, 16, 0.96));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.skills-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
gap: 12px;
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.skill-chip {
|
|
|
|
|
position: relative;
|
|
|
|
|
min-height: 118px;
|
|
|
|
|
border-radius: 18px;
|
|
|
|
|
border: 1px solid rgba(0,245,212,0.1);
|
|
|
|
|
background: linear-gradient(180deg, rgba(9,16,28,0.9), rgba(6,10,18,0.96));
|
|
|
|
|
padding: 14px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.skill-chip::before,
|
|
|
|
|
.main-link-line::before,
|
|
|
|
|
.sub-link-line::before,
|
|
|
|
|
.bus-spine::before {
|
|
|
|
|
content: '';
|
|
|
|
|
position: absolute;
|
|
|
|
|
inset: 0;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.skill-chip::before {
|
|
|
|
|
background: linear-gradient(120deg, transparent 0%, rgba(0,245,212,0.08) 45%, transparent 65%);
|
|
|
|
|
transform: translateX(-140%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.skill-chip.active {
|
|
|
|
|
border-color: rgba(0,245,212,0.4);
|
|
|
|
|
box-shadow: 0 0 22px rgba(0,245,212,0.12), inset 0 0 0 1px rgba(0,245,212,0.12);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.skill-chip.active::before {
|
|
|
|
|
animation: skill-sweep 2.8s linear infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes skill-sweep {
|
|
|
|
|
to { transform: translateX(140%); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.link-joint {
|
|
|
|
|
width: 8px;
|
|
|
|
|
height: 8px;
|
|
|
|
|
min-width: 8px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: rgba(0,245,212,0.65);
|
|
|
|
|
box-shadow: 0 0 10px rgba(0,245,212,0.35);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pcb-link.energized .link-joint {
|
|
|
|
|
background: #9ffef4;
|
|
|
|
|
box-shadow: 0 0 12px rgba(159,254,244,0.9), 0 0 24px rgba(0,245,212,0.55), 0 0 40px rgba(0,245,212,0.28);
|
|
|
|
|
animation: node-flicker 0.9s ease-in-out infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-link-line,
|
|
|
|
|
.sub-link-line {
|
|
|
|
|
position: relative;
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-link-line {
|
|
|
|
|
height: 3px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: linear-gradient(90deg, rgba(0,245,212,0.6), rgba(0,245,212,0.08));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sub-link-line {
|
|
|
|
|
width: 3px;
|
|
|
|
|
min-width: 3px;
|
|
|
|
|
min-height: 34px;
|
|
|
|
|
background: linear-gradient(180deg, rgba(0,245,212,0.6), rgba(0,245,212,0.08));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-link-label,
|
|
|
|
|
.sub-link-label {
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.agent-chip .chip-name {
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.compact-footer {
|
|
|
|
|
margin-top: auto;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.cluster-toggle {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
border: 1px solid rgba(0,245,212,0.12);
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
background: rgba(5,10,18,0.72);
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
letter-spacing: 0.08em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toggle-count {
|
|
|
|
|
background: rgba(0,245,212,0.08);
|
|
|
|
|
color: var(--accent-cyan);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sub-cluster {
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sub-node {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sub-node-card {
|
|
|
|
|
flex: 1;
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
border: 1px solid rgba(0,245,212,0.1);
|
|
|
|
|
background: rgba(10,14,24,0.9);
|
|
|
|
|
padding: 12px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sub-node-card.energized,
|
|
|
|
|
.sub-node.active .sub-node-card {
|
|
|
|
|
border-color: rgba(0,245,212,0.3);
|
|
|
|
|
box-shadow: 0 0 18px rgba(0,245,212,0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sub-node.disabled .sub-node-card {
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sub-name { color: var(--accent-cyan); font-size: 11px; }
|
|
|
|
|
.sub-badge { background: rgba(123, 44, 191, 0.14); color: var(--accent-purple); }
|
|
|
|
|
.sub-role { font-size: 12px; font-weight: 600; }
|
|
|
|
|
.sub-footer { font-size: 11px; color: var(--text-dim); margin-top: auto; justify-content: flex-start; }
|
|
|
|
|
|
|
|
|
|
.cluster-fade-enter-active,
|
|
|
|
|
.cluster-fade-leave-active {
|
|
|
|
|
transition: opacity 0.22s ease, transform 0.22s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.cluster-fade-enter-from,
|
|
|
|
|
.cluster-fade-leave-to {
|
|
|
|
|
opacity: 0;
|
|
|
|
|
transform: translateY(-8px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.config-drawer {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0;
|
|
|
|
|
right: 0;
|
|
|
|
|
width: min(420px, 100%);
|
|
|
|
|
height: 100%;
|
|
|
|
|
background: linear-gradient(180deg, rgba(9, 13, 23, 0.98), rgba(5, 8, 16, 0.98));
|
|
|
|
|
border-left: 1px solid rgba(0,245,212,0.14);
|
|
|
|
|
z-index: 30;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-header {
|
|
|
|
|
padding: 18px 18px 14px;
|
|
|
|
|
border-bottom: 1px solid var(--border-dim);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-title {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: var(--accent-cyan);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-close {
|
|
|
|
|
width: 32px;
|
|
|
|
|
height: 32px;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
border: 1px solid var(--border-dim);
|
|
|
|
|
background: transparent;
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-body {
|
|
|
|
|
padding: 18px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 14px;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.form-group {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.flex-1 {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.form-label {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
letter-spacing: 0.12em;
|
|
|
|
|
color: var(--text-dim);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.form-input,
|
|
|
|
|
.form-textarea {
|
|
|
|
|
width: 100%;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
border: 1px solid var(--border-dim);
|
|
|
|
|
background: rgba(255,255,255,0.03);
|
|
|
|
|
color: var(--text-primary);
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.code-textarea {
|
|
|
|
|
font-family: var(--font-mono);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toggle-label {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toggle-label.dim {
|
|
|
|
|
opacity: 0.45;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toggle-btn {
|
|
|
|
|
width: 50px;
|
|
|
|
|
height: 28px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
border: 1px solid var(--border-dim);
|
|
|
|
|
background: rgba(255,255,255,0.04);
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toggle-btn.active {
|
|
|
|
|
background: rgba(0,245,212,0.12);
|
|
|
|
|
border-color: rgba(0,245,212,0.2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toggle-knob {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 3px;
|
|
|
|
|
left: 3px;
|
|
|
|
|
width: 20px;
|
|
|
|
|
height: 20px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: var(--text-primary);
|
|
|
|
|
transition: transform 0.2s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toggle-btn.active .toggle-knob {
|
|
|
|
|
transform: translateX(22px);
|
|
|
|
|
background: var(--accent-cyan);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-secondary,
|
|
|
|
|
.btn-primary {
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
padding: 10px 14px;
|
|
|
|
|
border: 1px solid var(--border-dim);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-secondary {
|
|
|
|
|
background: rgba(255,255,255,0.03);
|
|
|
|
|
color: var(--text-secondary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-primary {
|
|
|
|
|
background: rgba(0,245,212,0.12);
|
|
|
|
|
border-color: rgba(0,245,212,0.24);
|
|
|
|
|
color: var(--accent-cyan);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-loader {
|
|
|
|
|
display: inline-block;
|
|
|
|
|
width: 10px;
|
|
|
|
|
height: 10px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
border: 2px solid currentColor;
|
|
|
|
|
border-right-color: transparent;
|
|
|
|
|
animation: spin 0.9s linear infinite;
|
|
|
|
|
margin-right: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.drawer-backdrop {
|
|
|
|
|
position: fixed;
|
|
|
|
|
inset: 0;
|
|
|
|
|
background: rgba(0,0,0,0.45);
|
|
|
|
|
z-index: 20;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 1400px) {
|
|
|
|
|
.command-stage { padding: 20px 18px 28px; }
|
|
|
|
|
.topology-main-grid {
|
|
|
|
|
grid-template-columns: repeat(2, minmax(240px, 1fr));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 1100px) {
|
|
|
|
|
.embedded-skills-grid {
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 860px) {
|
|
|
|
|
.view-header {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header-actions {
|
|
|
|
|
width: 100%;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.command-stage {
|
|
|
|
|
padding: 16px 14px 26px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.board-shell {
|
|
|
|
|
padding: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-main-grid,
|
|
|
|
|
.embedded-skills-grid {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topology-main-grid::before {
|
|
|
|
|
left: 50%;
|
|
|
|
|
right: auto;
|
|
|
|
|
top: 0;
|
|
|
|
|
width: 3px;
|
|
|
|
|
height: calc(100% - 12px);
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
background: linear-gradient(180deg, rgba(0,245,212,0.18), rgba(0,245,212,0.68), rgba(0,245,212,0.18));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
|
|
|
.bg-particle,
|
|
|
|
|
.chip-overlay::after,
|
|
|
|
|
.skill-chip.active::before,
|
|
|
|
|
.bus-spine.energized::before,
|
|
|
|
|
.main-link.energized .main-link-line::before,
|
|
|
|
|
.sub-link.energized .sub-link-line::before {
|
|
|
|
|
animation: none !important;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-21 10:13:35 +08:00
|
|
|
</style>
|