diff --git a/frontend/src/data/agents.ts b/frontend/src/data/agents.ts index 532104d..cd28062 100644 --- a/frontend/src/data/agents.ts +++ b/frontend/src/data/agents.ts @@ -1,14 +1,35 @@ export interface Agent { id: string name: string - role: string // 中文角色名 - roleKey: string // 英文 key: master/planner/executor/librarian/analyst + role: string + roleKey: string description: string systemPrompt: string enabled: boolean isMaster?: boolean } +export interface SubCommander { + id: string + parentId: MainAgentId + name: string + role: string + description: string + relationLabel: string + toolScopeLabel: string +} + +export interface CommanderSkill { + id: string + label: string + title: string + description: string + relatedAgentIds: string[] + stateLabel: string +} + +export type MainAgentId = 'master' | 'planner' | 'executor' | 'librarian' | 'analyst' + export const DEFAULT_AGENTS: Agent[] = [ { id: 'master', @@ -58,12 +79,14 @@ export const DEFAULT_AGENTS: Agent[] = [ }, ] -export const AGENT_RELATIONS: Record = { +export const MAIN_AGENT_ORDER: MainAgentId[] = ['planner', 'executor', 'librarian', 'analyst'] + +export const AGENT_RELATIONS: Record = { master: ['planner', 'executor', 'librarian', 'analyst'], - planner: [], - executor: [], - librarian: [], - analyst: [], + planner: ['planner_scope', 'planner_steps'], + executor: ['executor_tasks', 'executor_forum'], + librarian: ['librarian_retrieval', 'librarian_graph'], + analyst: ['analyst_progress', 'analyst_insights'], } export const RELATION_LABELS: Record = { @@ -71,4 +94,137 @@ export const RELATION_LABELS: Record = { 'master-executor': '执行指令', 'master-librarian': '查询知识', 'master-analyst': '请求分析', + 'planner-planner_scope': '收束目标', + 'planner-planner_steps': '拆解路径', + 'executor-executor_tasks': '调度任务工具', + 'executor-executor_forum': '调度论坛工具', + 'librarian-librarian_retrieval': '检索知识', + 'librarian-librarian_graph': '沉淀图谱', + 'analyst-analyst_progress': '汇总进度', + 'analyst-analyst_insights': '生成洞察', +} + +export const COMMANDER_SKILLS: CommanderSkill[] = [ + { + id: 'skill_orchestration', + label: 'CORE-01', + title: '战略编排', + description: '统一调度主控链路,决定由谁接管当前任务。', + relatedAgentIds: ['master'], + stateLabel: 'ROUTING', + }, + { + id: 'skill_planner', + label: 'CORE-02', + title: '路径拆解', + description: '把复杂目标压缩成清晰路径、优先级与执行步骤。', + relatedAgentIds: ['planner', 'planner_scope', 'planner_steps'], + stateLabel: 'STRUCTURE', + }, + { + id: 'skill_executor', + label: 'CORE-03', + title: '执行调度', + description: '驱动任务工具与交互动作,把计划转成实际推进。', + relatedAgentIds: ['executor', 'executor_tasks', 'executor_forum'], + stateLabel: 'ACTION', + }, + { + id: 'skill_librarian', + label: 'CORE-04', + title: '知识检索', + description: '连接知识与上下文,让信息可以被实时调取和利用。', + relatedAgentIds: ['librarian', 'librarian_retrieval', 'librarian_graph'], + stateLabel: 'RETRIEVE', + }, + { + id: 'skill_analyst', + label: 'CORE-05', + title: '洞察研判', + description: '从状态和数据中提炼风险、趋势与行动建议。', + relatedAgentIds: ['analyst', 'analyst_progress', 'analyst_insights'], + stateLabel: 'INSIGHT', + }, +] + +export const SUB_COMMANDERS: SubCommander[] = [ + { + id: 'planner_scope', + parentId: 'planner', + name: 'SCOPE', + role: '目标收束官', + description: '负责澄清目标、识别边界与约束,让规划前提先变清楚。', + relationLabel: RELATION_LABELS['planner-planner_scope'], + toolScopeLabel: '约束/需求', + }, + { + id: 'planner_steps', + parentId: 'planner', + name: 'STEPS', + role: '步骤拆解官', + description: '负责把目标拆成步骤、优先级与依赖关系,形成执行路径。', + relationLabel: RELATION_LABELS['planner-planner_steps'], + toolScopeLabel: '路径/排序', + }, + { + id: 'executor_tasks', + parentId: 'executor', + name: 'TASK OPS', + role: '任务执行官', + description: '负责调用任务相关工具,推进创建、更新与状态执行。', + relationLabel: RELATION_LABELS['executor-executor_tasks'], + toolScopeLabel: 'Task Tools', + }, + { + id: 'executor_forum', + parentId: 'executor', + name: 'FORUM OPS', + role: '论坛执行官', + description: '负责处理论坛发帖、扫描指令等交互型执行操作。', + relationLabel: RELATION_LABELS['executor-executor_forum'], + toolScopeLabel: 'Forum Tools', + }, + { + id: 'librarian_retrieval', + parentId: 'librarian', + name: 'RETRIEVAL', + role: '检索问答官', + description: '负责检索知识库与上下文,快速给出证据范围内的回答。', + relationLabel: RELATION_LABELS['librarian-librarian_retrieval'], + toolScopeLabel: 'Search / Hybrid', + }, + { + id: 'librarian_graph', + parentId: 'librarian', + name: 'GRAPH LINK', + role: '图谱沉淀官', + description: '负责知识图谱构建、关系串联与结构化沉淀。', + relationLabel: RELATION_LABELS['librarian-librarian_graph'], + toolScopeLabel: 'Graph Context', + }, + { + id: 'analyst_progress', + parentId: 'analyst', + name: 'PROGRESS', + role: '进度研判官', + description: '负责汇总任务与讨论状态,判断阶段进展与卡点。', + relationLabel: RELATION_LABELS['analyst-analyst_progress'], + toolScopeLabel: 'Status / Tasks', + }, + { + id: 'analyst_insights', + parentId: 'analyst', + name: 'INSIGHTS', + role: '洞察建议官', + description: '负责提炼趋势、风险与建议,把数据转成判断。', + relationLabel: RELATION_LABELS['analyst-analyst_insights'], + toolScopeLabel: 'Trends / Advice', + }, +] + +export const SUB_COMMANDERS_BY_PARENT: Record, SubCommander[]> = { + planner: SUB_COMMANDERS.filter((item) => item.parentId === 'planner'), + executor: SUB_COMMANDERS.filter((item) => item.parentId === 'executor'), + librarian: SUB_COMMANDERS.filter((item) => item.parentId === 'librarian'), + analyst: SUB_COMMANDERS.filter((item) => item.parentId === 'analyst'), } diff --git a/frontend/src/pages/agents/agentsPage.test.ts b/frontend/src/pages/agents/agentsPage.test.ts new file mode 100644 index 0000000..9cdda37 --- /dev/null +++ b/frontend/src/pages/agents/agentsPage.test.ts @@ -0,0 +1,121 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { mount } from '@vue/test-utils' + +const mocks = vi.hoisted(() => ({ + getHierarchyStats: vi.fn(), + updateConfig: vi.fn(), +})) + +vi.mock('@/api/agent', () => ({ + agentApi: { + getHierarchyStats: mocks.getHierarchyStats, + updateConfig: mocks.updateConfig, + }, +})) + +import AgentsPage from './index.vue' + +const hierarchyStats = { + main_agents: [ + { + agent_id: 'planner', + call_count: 12, + current_task: null, + status: 'idle', + sub_commanders: [ + { agent_id: 'planner_scope', call_count: 4, current_task: null, status: 'idle' }, + { agent_id: 'planner_steps', call_count: 9, current_task: '拆解执行步骤', status: 'active' }, + ], + }, + { + agent_id: 'executor', + call_count: 8, + current_task: null, + status: 'idle', + sub_commanders: [ + { agent_id: 'executor_tasks', call_count: 8, current_task: null, status: 'idle' }, + { agent_id: 'executor_forum', call_count: 4, current_task: null, status: 'idle' }, + ], + }, + { + agent_id: 'librarian', + call_count: 5, + current_task: null, + status: 'idle', + sub_commanders: [ + { agent_id: 'librarian_retrieval', call_count: 5, current_task: null, status: 'idle' }, + { agent_id: 'librarian_graph', call_count: 2, current_task: null, status: 'idle' }, + ], + }, + { + agent_id: 'analyst', + call_count: 3, + current_task: null, + status: 'idle', + sub_commanders: [ + { agent_id: 'analyst_progress', call_count: 2, current_task: null, status: 'idle' }, + { agent_id: 'analyst_insights', call_count: 3, current_task: null, status: 'idle' }, + ], + }, + ], +} + +describe('agents page pcb command center', () => { + beforeEach(() => { + vi.clearAllMocks() + vi.useFakeTimers() + mocks.getHierarchyStats.mockResolvedValue(hierarchyStats) + mocks.updateConfig.mockResolvedValue({}) + }) + + it('shows commander skills and active route telemetry for an active sub commander path', async () => { + const wrapper = mount(AgentsPage) + await Promise.resolve() + await Promise.resolve() + + const skillsPanel = wrapper.get('[data-testid="commander-skills"]') + expect(skillsPanel.text()).toContain('指挥官技能') + expect(skillsPanel.text()).toContain('路径拆解') + + const activeSkill = wrapper.get('[data-testid="commander-skill-skill_planner"]') + expect(activeSkill.classes()).toContain('active') + + const plannerBus = wrapper.get('[data-testid="bus-link-planner"]') + expect(plannerBus.classes()).toContain('energized') + + const plannerStepsBranch = wrapper.get('[data-testid="sub-link-planner_steps"]') + expect(plannerStepsBranch.classes()).toContain('energized') + + const routeTelemetry = wrapper.get('[data-testid="route-telemetry"]') + expect(routeTelemetry.text()).toContain('ACTIVE ROUTE') + expect(routeTelemetry.text()).toContain('PLANNER') + expect(routeTelemetry.text()).toContain('STEPS') + }) + + it('keeps the configuration drawer available when selecting a main agent chip', async () => { + const wrapper = mount(AgentsPage) + await Promise.resolve() + await Promise.resolve() + + await wrapper.get('[data-testid="agent-chip-planner"]').trigger('click') + + expect(wrapper.text()).toContain('AGENT CONFIGURATION') + expect(wrapper.find('input').exists()).toBe(true) + }) + + it('cycles the energized demo path when hierarchy stats are offline', async () => { + mocks.getHierarchyStats.mockRejectedValue(new Error('offline')) + const wrapper = mount(AgentsPage) + await Promise.resolve() + await Promise.resolve() + + expect(wrapper.get('[data-testid="bus-link-planner"]').classes()).toContain('energized') + expect(wrapper.get('[data-testid="sub-link-planner_scope"]').classes()).toContain('energized') + + await vi.advanceTimersByTimeAsync(1700) + + expect(wrapper.get('[data-testid="sub-link-planner_scope"]').classes()).not.toContain('energized') + expect(wrapper.get('[data-testid="sub-link-planner_steps"]').classes()).toContain('energized') + }) +}) + diff --git a/frontend/src/pages/agents/index.vue b/frontend/src/pages/agents/index.vue index bc8d088..f0a3600 100644 --- a/frontend/src/pages/agents/index.vue +++ b/frontend/src/pages/agents/index.vue @@ -1,27 +1,22 @@ @@ -562,35 +643,46 @@ onUnmounted(() => { height: 100%; display: flex; flex-direction: column; - overflow: hidden; + overflow: auto; position: relative; - background: var(--bg-void); + background: + radial-gradient(circle at top, rgba(0, 245, 212, 0.07), transparent 34%), + linear-gradient(180deg, #03050a 0%, #07101a 35%, #03050a 100%); } -.bg-grid { - position: absolute; - inset: 0; - background-image: - linear-gradient(rgba(0,245,212,0.04) 1px, transparent 1px), - linear-gradient(90deg, rgba(0,245,212,0.04) 1px, transparent 1px); - background-size: 40px 40px; - pointer-events: none; - z-index: 0; -} -.bg-glow { - position: absolute; - inset: 0; - background: radial-gradient(ellipse 80% 60% at 50% 40%, rgba(0,245,212,0.05) 0%, transparent 70%); - pointer-events: none; - z-index: 0; -} +.bg-grid, +.bg-glow, +.bg-circuit, .bg-particles { position: absolute; inset: 0; pointer-events: none; - z-index: 0; - overflow: hidden; } + +.bg-grid { + background-image: + linear-gradient(rgba(0, 245, 212, 0.04) 1px, transparent 1px), + linear-gradient(90deg, rgba(0, 245, 212, 0.04) 1px, transparent 1px); + background-size: 40px 40px; +} + +.bg-glow { + 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%); +} + +.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); +} + .bg-particle { position: absolute; border-radius: 50%; @@ -598,226 +690,1168 @@ onUnmounted(() => { 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); } + @keyframes star-twinkle { 0%, 100% { opacity: var(--o, 0.4); transform: scale(1); } - 50% { opacity: calc(var(--o, 0.4) * 0.3); transform: scale(0.5); } + 50% { opacity: calc(var(--o, 0.4) * 0.3); transform: scale(0.45); } } -/* Header */ .view-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 24px; border-bottom: 1px solid var(--border-dim); - position: relative; + position: sticky; + top: 0; z-index: 10; - background: rgba(5,8,16,0.6); + background: rgba(5,8,16,0.72); backdrop-filter: blur(8px); } -.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; } + +.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; +} .btn-icon { - width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; - background: var(--bg-card); border: 1px solid var(--border-mid); border-radius: var(--radius-sm); - color: var(--text-secondary); cursor: pointer; transition: all var(--transition-fast); + 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; } -.btn-icon:hover { border-color: var(--accent-cyan); color: var(--accent-cyan); box-shadow: var(--glow-cyan); } -.btn-icon.spinning svg { animation: spin 0.8s linear infinite; } -@keyframes spin { to { transform: rotate(360deg); } } -.btn-add { - display: flex; align-items: center; gap: 6px; padding: 6px 14px; - background: rgba(0,245,212,0.08); border: 1px solid rgba(0,245,212,0.3); - border-radius: var(--radius-sm); color: var(--accent-cyan); font-family: var(--font-mono); - font-size: 11px; letter-spacing: 0.1em; cursor: pointer; transition: all var(--transition-fast); +.btn-icon.spinning svg { + animation: spin 1s linear infinite; } -.btn-add:hover { background: rgba(0,245,212,0.15); border-color: var(--accent-cyan); box-shadow: var(--glow-cyan); } -.status-bar { display: flex; align-items: center; gap: 6px; font-size: 10px; color: var(--text-dim); letter-spacing: 0.1em; } -.status-dot { width: 6px; height: 6px; border-radius: 50%; } -.status-dot.connected { background: var(--accent-cyan); box-shadow: 0 0 6px var(--accent-cyan); } -.status-dot.disconnected { background: var(--text-dim); } +@keyframes spin { + to { transform: rotate(360deg); } +} -/* Canvas */ -.nodes-canvas { - flex: 1; +.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 { position: relative; overflow: hidden; + 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); } -.conn-svg { + +.board-shell::before, +.board-shell::after { + content: ''; position: absolute; - inset: 0; - width: 100%; - height: 100%; + inset: 14px; + border: 1px solid rgba(0, 245, 212, 0.07); + border-radius: 22px; pointer-events: none; - z-index: 1; } -.conn-path { - fill: none; - stroke: rgba(0,245,212,0.25); - stroke-width: 1.5; - stroke-dasharray: 5 5; - animation: dash-flow 4s linear infinite; -} -@keyframes dash-flow { to { stroke-dashoffset: -30; } } -.conn-path.active { - stroke: var(--accent-amber); - stroke-opacity: 0.7; - stroke-dasharray: none; - filter: url(#lineGlow); - animation: none; -} -.pulse-dot { fill: var(--accent-amber); filter: drop-shadow(0 0 8px var(--accent-amber)); } -/* Node Cards */ -.node-card { +.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 { position: absolute; - height: 170px; - z-index: 2; - cursor: pointer; + background: linear-gradient(90deg, transparent, rgba(0,245,212,0.12), transparent); + opacity: 0.6; } -.node-sub.disabled { opacity: 0.35; cursor: not-allowed; } -.node-inner { - width: 100%; - height: 100%; - background: rgba(13,21,37,0.92); - border: 1px solid rgba(0,245,212,0.2); - border-radius: var(--radius-md); - padding: 14px 16px; +.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 { display: flex; flex-direction: column; - gap: 3px; + 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 { position: relative; + 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); +} + +.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); +} + +.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; - backdrop-filter: blur(12px); - transition: border-color 0.2s, box-shadow 0.2s; -} -.node-master .node-inner { - background: linear-gradient(135deg, rgba(0,245,212,0.06) 0%, rgba(13,21,37,0.95) 100%); - border-color: rgba(0,245,212,0.35); -} -.node-card:hover .node-inner { - border-color: rgba(0,245,212,0.5); - box-shadow: 0 8px 32px rgba(0,245,212,0.15), 0 0 0 1px rgba(0,245,212,0.1); -} -.node-card.selected .node-inner { - border-color: var(--accent-cyan); - box-shadow: 0 0 0 2px rgba(0,245,212,0.3), var(--glow-cyan); + transition: transform var(--transition-mid), border-color var(--transition-mid), box-shadow var(--transition-mid); } -.node-corner { position: absolute; width: 10px; height: 10px; opacity: 0.6; } -.node-corner.tl { top: 6px; left: 6px; border-top: 1.5px solid var(--accent-cyan); border-left: 1.5px solid var(--accent-cyan); } -.node-corner.tr { top: 6px; right: 6px; border-top: 1.5px solid var(--accent-cyan); border-right: 1.5px solid var(--accent-cyan); } -.node-corner.bl { bottom: 6px; left: 6px; border-bottom: 1.5px solid var(--accent-cyan); border-left: 1.5px solid var(--accent-cyan); } -.node-corner.br { bottom: 6px; right: 6px; border-bottom: 1.5px solid var(--accent-cyan); border-right: 1.5px solid var(--accent-cyan); } - -.node-status { position: absolute; top: 10px; right: 10px; width: 10px; height: 10px; border-radius: 50%; display: flex; align-items: center; justify-content: center; } -.status-ring { width: 8px; height: 8px; border-radius: 50%; } -.node-status.active .status-ring { background: var(--accent-cyan); box-shadow: 0 0 8px var(--accent-cyan); animation: status-pulse 1.5s ease-in-out infinite; } -.node-status.idle .status-ring { background: var(--text-secondary); } -.node-status.disabled .status-ring { background: var(--text-dim); opacity: 0.4; } -@keyframes status-pulse { 0%,100%{opacity:1} 50%{opacity:0.4} } - -.node-label { font-family: var(--font-display); font-size: 8px; letter-spacing: 0.2em; color: var(--text-dim); margin-bottom: 1px; } -.node-master .node-label { color: rgba(0,245,212,0.5); } -.node-name { font-family: var(--font-display); font-size: 15px; font-weight: 700; letter-spacing: 0.08em; color: var(--accent-cyan); line-height: 1.2; } -.node-master .node-name { font-size: 18px; } -.node-role { font-family: var(--font-mono); font-size: 10px; color: var(--accent-amber); letter-spacing: 0.05em; } -.node-desc { - font-family: var(--font-mono); font-size: 10px; color: var(--text-secondary); - line-height: 1.5; flex: 1; overflow: hidden; display: -webkit-box; - -webkit-line-clamp: 3; -webkit-box-orient: vertical; text-overflow: ellipsis; +.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); } -.node-footer { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 2px; } -.node-stat { display: flex; align-items: center; gap: 4px; font-family: var(--font-mono); font-size: 9px; } -.stat-label { color: var(--text-dim); } -.stat-val { color: var(--accent-cyan); font-weight: 600; } + +.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 { - font-family: var(--font-mono); font-size: 9px; color: var(--accent-amber); - background: rgba(249,168,37,0.1); border: 1px solid rgba(249,168,37,0.2); - border-radius: 3px; padding: 1px 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 120px; + 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); } -.node-idle { font-family: var(--font-mono); font-size: 9px; color: var(--text-dim); font-style: italic; } -.rel-label { - position: absolute; font-family: var(--font-mono); font-size: 8px; color: var(--text-dim); - letter-spacing: 0.05em; pointer-events: none; left: 50%; transform: translateX(-50%); - bottom: -20px; white-space: nowrap; -} -.particle { position: absolute; border-radius: 50%; background: var(--accent-cyan); pointer-events: none; } -/* Drawer */ .config-drawer { - position: fixed; top: 0; right: 0; width: 420px; height: 100%; - background: rgba(5,8,16,0.97); border-left: 1px solid var(--border-mid); - backdrop-filter: blur(20px); z-index: 100; display: flex; flex-direction: column; - box-shadow: -10px 0 40px rgba(0,0,0,0.5); + 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-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 99; } -.drawer-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid var(--border-dim); } -.drawer-title { font-family: var(--font-display); font-size: 11px; letter-spacing: 0.15em; color: var(--accent-cyan); } -.btn-close { - width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; - background: transparent; border: 1px solid var(--border-dim); border-radius: var(--radius-sm); - color: var(--text-dim); cursor: pointer; transition: all var(--transition-fast); -} -.btn-close:hover { border-color: var(--accent-red); color: var(--accent-red); } -.drawer-body { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 16px; } -.drawer-body::-webkit-scrollbar { width: 4px; } -.drawer-body::-webkit-scrollbar-thumb { background: var(--border-mid); border-radius: 2px; } -.form-group { display: flex; flex-direction: column; gap: 6px; } -.form-group.flex-1 { flex: 1; display: flex; flex-direction: column; } -.form-label { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.15em; color: var(--text-dim); } -.form-input { - background: var(--bg-card); border: 1px solid var(--border-mid); border-radius: var(--radius-sm); - padding: 10px 12px; color: var(--text-primary); font-family: var(--font-mono); font-size: 12px; outline: none; - transition: border-color var(--transition-fast); -} -.form-input:focus { border-color: var(--accent-cyan); box-shadow: 0 0 0 1px rgba(0,245,212,.1); } -.form-textarea { - background: var(--bg-card); border: 1px solid var(--border-mid); border-radius: var(--radius-sm); - padding: 10px 12px; color: var(--text-primary); font-family: var(--font-mono); font-size: 11px; - outline: none; resize: none; line-height: 1.5; transition: border-color var(--transition-fast); -} -.form-textarea:focus { border-color: var(--accent-cyan); box-shadow: 0 0 0 1px rgba(0,245,212,.1); } -.code-textarea { font-size: 10px; flex: 1; } -.toggle-row { display: flex; align-items: center; gap: 12px; } -.toggle-label { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em; color: var(--accent-cyan); transition: color .2s; } -.toggle-label.dim { color: var(--text-dim); } -.toggle-btn { width: 44px; height: 22px; background: var(--bg-card); border: 1px solid var(--border-mid); border-radius: 11px; padding: 2px; cursor: pointer; transition: all .25s; } -.toggle-btn.active { background: rgba(0,245,212,.15); border-color: var(--accent-cyan); } -.toggle-knob { display: block; width: 16px; height: 16px; border-radius: 50%; background: var(--text-dim); transition: all .25s; } -.toggle-btn.active .toggle-knob { background: var(--accent-cyan); box-shadow: 0 0 8px var(--accent-cyan); transform: translateX(22px); } -.drawer-actions { display: flex; gap: 12px; padding-top: 8px; } -.btn-secondary,.btn-primary { - flex: 1; padding: 10px 16px; border-radius: var(--radius-sm); font-family: var(--font-mono); - font-size: 11px; letter-spacing: 0.1em; cursor: pointer; transition: all var(--transition-fast); - display: flex; align-items: center; justify-content: center; gap: 6px; -} -.btn-secondary { background: transparent; border: 1px solid var(--border-mid); color: var(--text-secondary); } -.btn-secondary:hover { border-color: var(--accent-cyan); color: var(--accent-cyan); } -.btn-primary { background: rgba(0,245,212,.1); border: 1px solid var(--accent-cyan); color: var(--accent-cyan); } -.btn-primary:hover { background: rgba(0,245,212,.2); box-shadow: var(--glow-cyan); } -.btn-primary:disabled { opacity: 0.4; cursor: not-allowed; } -.btn-loader { width: 12px; height: 12px; border: 1.5px solid transparent; border-top-color: var(--accent-cyan); border-radius: 50%; animation: spin .6s linear infinite; } -/* Modal */ -.modal-overlay { - position: fixed; inset: 0; background: rgba(0,0,0,.7); backdrop-filter: blur(4px); - z-index: 200; display: flex; align-items: center; justify-content: center; +.drawer-header { + padding: 18px 18px 14px; + border-bottom: 1px solid var(--border-dim); } -.modal-card { - width: 480px; max-height: 80vh; background: rgba(10,15,26,.98); border: 1px solid var(--border-mid); - border-radius: var(--radius-lg); display: flex; flex-direction: column; - box-shadow: 0 20px 60px rgba(0,0,0,.6), 0 0 0 1px rgba(0,245,212,.05); + +.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; + } } -.modal-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid var(--border-dim); } -.modal-title { font-family: var(--font-display); font-size: 11px; letter-spacing: 0.15em; color: var(--accent-cyan); } -.modal-body { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 14px; } -.modal-body::-webkit-scrollbar { width: 4px; } -.modal-body::-webkit-scrollbar-thumb { background: var(--border-mid); border-radius: 2px; } -.modal-footer { display: flex; gap: 12px; padding: 16px 20px; border-top: 1px solid var(--border-dim); }