|
|
|
@@ -1,5 +1,5 @@
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, ref } from 'vue'
|
|
|
|
|
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
|
|
|
|
import { Bot, Sparkles } from '@lucide/vue'
|
|
|
|
|
import AgentNode from './AgentNode.vue'
|
|
|
|
|
import AgentModal from './AgentModal.vue'
|
|
|
|
@@ -23,41 +23,217 @@ function closeModal(): void {
|
|
|
|
|
selectedAgent.value = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const agentColorMap: Record<string, string> = {
|
|
|
|
|
developer: '#3b82f6',
|
|
|
|
|
devops: '#eab308',
|
|
|
|
|
researcher: '#22c55e',
|
|
|
|
|
reviewer: '#a855f7',
|
|
|
|
|
// ── Layout measurement ──
|
|
|
|
|
const networkRef = ref<HTMLDivElement | null>(null)
|
|
|
|
|
|
|
|
|
|
interface CardBox {
|
|
|
|
|
left: number
|
|
|
|
|
right: number
|
|
|
|
|
top: number
|
|
|
|
|
bottom: number
|
|
|
|
|
cx: number
|
|
|
|
|
cy: number
|
|
|
|
|
width: number
|
|
|
|
|
height: number
|
|
|
|
|
}
|
|
|
|
|
const cardPositions = ref<Record<string, CardBox>>({})
|
|
|
|
|
const svgWidth = ref(0)
|
|
|
|
|
const svgHeight = ref(0)
|
|
|
|
|
|
|
|
|
|
function updatePositions() {
|
|
|
|
|
if (!networkRef.value) return
|
|
|
|
|
const rect = networkRef.value.getBoundingClientRect()
|
|
|
|
|
svgWidth.value = rect.width
|
|
|
|
|
svgHeight.value = rect.height
|
|
|
|
|
|
|
|
|
|
const positions: Record<string, CardBox> = {}
|
|
|
|
|
|
|
|
|
|
const irisEl = networkRef.value.querySelector('[data-agent-id="iris"]')
|
|
|
|
|
if (irisEl) {
|
|
|
|
|
const r = irisEl.getBoundingClientRect()
|
|
|
|
|
positions['iris'] = {
|
|
|
|
|
left: r.left - rect.left,
|
|
|
|
|
right: r.left + r.width - rect.left,
|
|
|
|
|
top: r.top - rect.top,
|
|
|
|
|
bottom: r.top + r.height - rect.top,
|
|
|
|
|
cx: r.left + r.width / 2 - rect.left,
|
|
|
|
|
cy: r.top + r.height / 2 - rect.top,
|
|
|
|
|
width: r.width,
|
|
|
|
|
height: r.height,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cards = networkRef.value.querySelectorAll('[data-agent-id]')
|
|
|
|
|
cards.forEach(el => {
|
|
|
|
|
const id = el.getAttribute('data-agent-id')
|
|
|
|
|
if (!id || id === 'iris') return
|
|
|
|
|
const r = el.getBoundingClientRect()
|
|
|
|
|
positions[id] = {
|
|
|
|
|
left: r.left - rect.left,
|
|
|
|
|
right: r.left + r.width - rect.left,
|
|
|
|
|
top: r.top - rect.top,
|
|
|
|
|
bottom: r.top + r.height - rect.top,
|
|
|
|
|
cx: r.left + r.width / 2 - rect.left,
|
|
|
|
|
cy: r.top + r.height / 2 - rect.top,
|
|
|
|
|
width: r.width,
|
|
|
|
|
height: r.height,
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
cardPositions.value = positions
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const agentLineActive: Record<string, boolean> = {
|
|
|
|
|
developer: true,
|
|
|
|
|
devops: false,
|
|
|
|
|
researcher: true,
|
|
|
|
|
reviewer: false,
|
|
|
|
|
// ── SVG connection paths ──
|
|
|
|
|
interface ConnectionPath {
|
|
|
|
|
d: string
|
|
|
|
|
length: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const NETWORK_W = 440
|
|
|
|
|
const IRIS_CX = NETWORK_W / 2
|
|
|
|
|
const IRIS_CY = 80
|
|
|
|
|
const AGENT_START_Y = 170
|
|
|
|
|
const connectionPaths = computed<Record<string, ConnectionPath | null>>(() => {
|
|
|
|
|
const result: Record<string, ConnectionPath | null> = {}
|
|
|
|
|
const pos = cardPositions.value
|
|
|
|
|
const iris = pos['iris']
|
|
|
|
|
if (!iris) return result
|
|
|
|
|
|
|
|
|
|
const agentPositions = computed(() => [
|
|
|
|
|
{ id: 'developer', x: 60, y: AGENT_START_Y },
|
|
|
|
|
{ id: 'researcher', x: NETWORK_W - 60, y: AGENT_START_Y },
|
|
|
|
|
{ id: 'devops', x: 60, y: AGENT_START_Y + 110 },
|
|
|
|
|
{ id: 'reviewer', x: NETWORK_W - 60, y: AGENT_START_Y + 110 },
|
|
|
|
|
])
|
|
|
|
|
for (const agent of props.agents) {
|
|
|
|
|
const agentPos = pos[agent.id]
|
|
|
|
|
if (!agentPos) {
|
|
|
|
|
result[agent.id] = null
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const activeLines = computed(() =>
|
|
|
|
|
agentPositions.value.filter(p => agentLineActive[p.id])
|
|
|
|
|
)
|
|
|
|
|
const startX = iris.cx
|
|
|
|
|
const startY = iris.bottom - 1
|
|
|
|
|
const endX = agentPos.cx
|
|
|
|
|
const endY = agentPos.top + 4
|
|
|
|
|
|
|
|
|
|
// Bézier control points
|
|
|
|
|
const dy = endY - startY
|
|
|
|
|
const cy1 = startY + dy * 0.4
|
|
|
|
|
const cy2 = startY + dy * 0.7
|
|
|
|
|
|
|
|
|
|
const d = `M ${startX} ${startY} C ${startX} ${cy1}, ${endX} ${cy2}, ${endX} ${endY}`
|
|
|
|
|
result[agent.id] = { d, length: 0 }
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// ── Pulse animation (JS-driven via requestAnimationFrame) ──
|
|
|
|
|
let animFrameId: number | null = null
|
|
|
|
|
let lastAnimTime = 0
|
|
|
|
|
|
|
|
|
|
const pathElements = ref<Record<string, SVGPathElement | null>>({})
|
|
|
|
|
const pulseElements = ref<Record<string, SVGPathElement | null>>({})
|
|
|
|
|
const pulseOffsets = ref<Record<string, number>>({})
|
|
|
|
|
|
|
|
|
|
function storePathRef(id: string) {
|
|
|
|
|
return (el: SVGPathElement | null) => {
|
|
|
|
|
pathElements.value[id] = el
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function storePulseRef(id: string) {
|
|
|
|
|
return (el: SVGPathElement | null) => {
|
|
|
|
|
pulseElements.value[id] = el
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function refreshPathLengths() {
|
|
|
|
|
for (const agent of props.agents) {
|
|
|
|
|
const pathEl = pathElements.value[agent.id]
|
|
|
|
|
const pulseEl = pulseElements.value[agent.id]
|
|
|
|
|
const p = connectionPaths.value[agent.id]
|
|
|
|
|
if (pathEl && p) {
|
|
|
|
|
p.length = pathEl.getTotalLength()
|
|
|
|
|
}
|
|
|
|
|
if (pulseEl && p && p.length > 0) {
|
|
|
|
|
if (pulseOffsets.value[agent.id] === undefined) {
|
|
|
|
|
pulseOffsets.value[agent.id] = 0
|
|
|
|
|
}
|
|
|
|
|
pulseEl.setAttribute('stroke-dasharray', `12 ${p.length}`)
|
|
|
|
|
pulseEl.setAttribute('stroke-dashoffset', String(-pulseOffsets.value[agent.id]))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function startPulseAnimation() {
|
|
|
|
|
refreshPathLengths()
|
|
|
|
|
|
|
|
|
|
const speeds: Record<string, number> = {}
|
|
|
|
|
for (const agent of props.agents) {
|
|
|
|
|
const p = connectionPaths.value[agent.id]
|
|
|
|
|
if (p && p.length > 0) {
|
|
|
|
|
speeds[agent.id] = p.length / 3000 // full traversal in ~3s
|
|
|
|
|
if (pulseOffsets.value[agent.id] === undefined) {
|
|
|
|
|
pulseOffsets.value[agent.id] = 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lastAnimTime = performance.now()
|
|
|
|
|
|
|
|
|
|
function tick(now: number) {
|
|
|
|
|
const dt = now - lastAnimTime
|
|
|
|
|
lastAnimTime = now
|
|
|
|
|
|
|
|
|
|
for (const agent of props.agents) {
|
|
|
|
|
const pulseEl = pulseElements.value[agent.id]
|
|
|
|
|
const p = connectionPaths.value[agent.id]
|
|
|
|
|
if (!pulseEl || !p || p.length <= 0) continue
|
|
|
|
|
|
|
|
|
|
const currentOffset = pulseOffsets.value[agent.id] ?? 0
|
|
|
|
|
const speed = speeds[agent.id] ?? p.length / 3000
|
|
|
|
|
const newOffset = currentOffset + speed * dt
|
|
|
|
|
const cycleLen = p.length + 12
|
|
|
|
|
pulseOffsets.value[agent.id] = newOffset > cycleLen ? newOffset % cycleLen : newOffset
|
|
|
|
|
pulseEl.setAttribute('stroke-dashoffset', String(-pulseOffsets.value[agent.id]))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
animFrameId = requestAnimationFrame(tick)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
animFrameId = requestAnimationFrame(tick)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function stopPulseAnimation() {
|
|
|
|
|
if (animFrameId !== null) {
|
|
|
|
|
cancelAnimationFrame(animFrameId)
|
|
|
|
|
animFrameId = null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Lifecycle ──
|
|
|
|
|
let resizeObserver: ResizeObserver | null = null
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
await nextTick()
|
|
|
|
|
updatePositions()
|
|
|
|
|
await nextTick()
|
|
|
|
|
updatePositions()
|
|
|
|
|
refreshPathLengths()
|
|
|
|
|
startPulseAnimation()
|
|
|
|
|
|
|
|
|
|
resizeObserver = new ResizeObserver(() => {
|
|
|
|
|
updatePositions()
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
refreshPathLengths()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
if (networkRef.value) {
|
|
|
|
|
resizeObserver.observe(networkRef.value)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
stopPulseAnimation()
|
|
|
|
|
resizeObserver?.disconnect()
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div class="team-network">
|
|
|
|
|
<div ref="networkRef" class="team-network">
|
|
|
|
|
<!-- Iris Node -->
|
|
|
|
|
<div class="iris-node">
|
|
|
|
|
<div class="iris-node" data-agent-id="iris">
|
|
|
|
|
<div class="iris-avatar-ring">
|
|
|
|
|
<svg class="ring-svg" viewBox="0 0 60 60" width="60" height="60">
|
|
|
|
|
<circle cx="30" cy="30" r="27" fill="none" stroke="rgba(167,139,250,0.12)" stroke-width="2" />
|
|
|
|
@@ -88,59 +264,86 @@ const activeLines = computed(() =>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- SVG Connections -->
|
|
|
|
|
<!-- SVG Connection Layer -->
|
|
|
|
|
<svg
|
|
|
|
|
v-if="svgWidth > 0 && svgHeight > 0"
|
|
|
|
|
class="network-svg"
|
|
|
|
|
:viewBox="`0 0 ${NETWORK_W} 400`"
|
|
|
|
|
preserveAspectRatio="xMidYMid meet"
|
|
|
|
|
:width="svgWidth"
|
|
|
|
|
:height="svgHeight"
|
|
|
|
|
:viewBox="`0 0 ${svgWidth} ${svgHeight}`"
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
|
|
|
>
|
|
|
|
|
<defs>
|
|
|
|
|
<filter id="lineglow">
|
|
|
|
|
<feGaussianBlur stdDeviation="2" result="blur" />
|
|
|
|
|
<filter
|
|
|
|
|
v-for="agent in agents"
|
|
|
|
|
:key="`glow-${agent.id}`"
|
|
|
|
|
:id="`glow-${agent.id}`"
|
|
|
|
|
x="-30%" y="-30%" width="160%" height="160%"
|
|
|
|
|
>
|
|
|
|
|
<feGaussianBlur stdDeviation="4" result="blur" />
|
|
|
|
|
<feMerge>
|
|
|
|
|
<feMergeNode in="blur" />
|
|
|
|
|
<feMergeNode in="blur" />
|
|
|
|
|
<feMergeNode in="SourceGraphic" />
|
|
|
|
|
</feMerge>
|
|
|
|
|
</filter>
|
|
|
|
|
</defs>
|
|
|
|
|
|
|
|
|
|
<line
|
|
|
|
|
v-for="pos in agentPositions"
|
|
|
|
|
:key="'conn-' + pos.id"
|
|
|
|
|
:x1="IRIS_CX" :y1="IRIS_CY + 32"
|
|
|
|
|
:x2="pos.x + 22" :y2="pos.y"
|
|
|
|
|
:stroke="agentColorMap[pos.id]"
|
|
|
|
|
:stroke-width="agentLineActive[pos.id] ? 1.5 : 1"
|
|
|
|
|
:opacity="agentLineActive[pos.id] ? 0.4 : 0.1"
|
|
|
|
|
class="conn-line"
|
|
|
|
|
:class="{ 'line-pulse': agentLineActive[pos.id] }"
|
|
|
|
|
filter="url(#lineglow)"
|
|
|
|
|
<template v-for="agent in agents" :key="agent.id">
|
|
|
|
|
<!-- Base connection line -->
|
|
|
|
|
<path
|
|
|
|
|
v-if="connectionPaths[agent.id]"
|
|
|
|
|
:ref="storePathRef(agent.id)"
|
|
|
|
|
:d="connectionPaths[agent.id]!.d"
|
|
|
|
|
:stroke="agent.color"
|
|
|
|
|
:stroke-width="agent.active ? 2.5 : 1.5"
|
|
|
|
|
fill="none"
|
|
|
|
|
:opacity="agent.active ? 0.7 : 0.25"
|
|
|
|
|
stroke-linecap="round"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<g v-for="pos in activeLines" :key="'fx-' + pos.id">
|
|
|
|
|
<circle
|
|
|
|
|
:cx="pos.x + 22" :cy="pos.y"
|
|
|
|
|
r="2.5" fill="#ffffff"
|
|
|
|
|
class="pulse-end" :style="{ '--pulse-color': agentColorMap[pos.id] }"
|
|
|
|
|
<!-- Glow for active agents -->
|
|
|
|
|
<path
|
|
|
|
|
v-if="agent.active && connectionPaths[agent.id]"
|
|
|
|
|
:d="connectionPaths[agent.id]!.d"
|
|
|
|
|
:stroke="agent.color"
|
|
|
|
|
stroke-width="4"
|
|
|
|
|
fill="none"
|
|
|
|
|
stroke-linecap="round"
|
|
|
|
|
:filter="`url(#glow-${agent.id})`"
|
|
|
|
|
opacity="0.5"
|
|
|
|
|
/>
|
|
|
|
|
<circle
|
|
|
|
|
:cx="IRIS_CX" :cy="IRIS_CY + 32"
|
|
|
|
|
r="2.5" fill="#ffffff"
|
|
|
|
|
class="pulse-origin"
|
|
|
|
|
|
|
|
|
|
<!-- Moving pulse dot -->
|
|
|
|
|
<path
|
|
|
|
|
v-if="connectionPaths[agent.id]"
|
|
|
|
|
:ref="storePulseRef(agent.id)"
|
|
|
|
|
:d="connectionPaths[agent.id]!.d"
|
|
|
|
|
stroke="white"
|
|
|
|
|
stroke-width="3"
|
|
|
|
|
fill="none"
|
|
|
|
|
stroke-linecap="round"
|
|
|
|
|
stroke-linejoin="round"
|
|
|
|
|
:opacity="agent.active ? 1 : 0.4"
|
|
|
|
|
/>
|
|
|
|
|
</g>
|
|
|
|
|
</template>
|
|
|
|
|
</svg>
|
|
|
|
|
|
|
|
|
|
<!-- Agent Cards -->
|
|
|
|
|
<!-- Agent Grid (dynamic: 2 columns, extends downward) -->
|
|
|
|
|
<div class="agents-grid">
|
|
|
|
|
<AgentNode
|
|
|
|
|
<div
|
|
|
|
|
v-for="agent in agents"
|
|
|
|
|
:key="agent.id"
|
|
|
|
|
:data-agent-id="agent.id"
|
|
|
|
|
class="agent-slot"
|
|
|
|
|
>
|
|
|
|
|
<AgentNode
|
|
|
|
|
:agent="agent"
|
|
|
|
|
:runtime="getAgentRuntime(agent.id)"
|
|
|
|
|
@select="onAgentSelect"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Focus Banner -->
|
|
|
|
|
<div v-if="irisFocus" class="focus-banner">
|
|
|
|
@@ -160,24 +363,21 @@ const activeLines = computed(() =>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.team-network {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
padding: 24px 20px 20px;
|
|
|
|
|
background: rgba(22, 27, 34, 0.75);
|
|
|
|
|
border: 1px solid rgba(139, 124, 246, 0.12);
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
backdrop-filter: blur(12px);
|
|
|
|
|
-webkit-backdrop-filter: blur(12px);
|
|
|
|
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
|
|
|
|
|
transition: border-color 0.2s ease;
|
|
|
|
|
position: relative;
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 24px 20px 20px;
|
|
|
|
|
background: rgba(22, 27, 34, 0.5);
|
|
|
|
|
border: 1px solid rgba(139, 124, 246, 0.08);
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
backdrop-filter: blur(8px);
|
|
|
|
|
-webkit-backdrop-filter: blur(8px);
|
|
|
|
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
|
|
|
|
|
min-height: 480px;
|
|
|
|
|
transition: border-color 0.2s ease;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
min-height: 520px;
|
|
|
|
|
}
|
|
|
|
|
.team-network:hover {
|
|
|
|
|
border-color: rgba(139, 124, 246, 0.18);
|
|
|
|
|
border-color: rgba(139, 124, 246, 0.14);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Iris Node */
|
|
|
|
@@ -187,6 +387,7 @@ const activeLines = computed(() =>
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
padding: 20px 28px;
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
background: rgba(167, 139, 250, 0.06);
|
|
|
|
|
border: 1px solid rgba(167, 139, 250, 0.15);
|
|
|
|
|
border-radius: 14px;
|
|
|
|
@@ -194,6 +395,8 @@ const activeLines = computed(() =>
|
|
|
|
|
z-index: 2;
|
|
|
|
|
width: 100%;
|
|
|
|
|
max-width: 320px;
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
margin-right: auto;
|
|
|
|
|
}
|
|
|
|
|
.iris-avatar-ring {
|
|
|
|
|
position: relative;
|
|
|
|
@@ -267,50 +470,28 @@ const activeLines = computed(() =>
|
|
|
|
|
color: #a78bfa;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* SVG Lines */
|
|
|
|
|
/* SVG Connection Lines */
|
|
|
|
|
.network-svg {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
}
|
|
|
|
|
.conn-line {
|
|
|
|
|
transition: opacity 0.4s ease, stroke-width 0.4s ease;
|
|
|
|
|
}
|
|
|
|
|
.line-pulse {
|
|
|
|
|
animation: line-glow 2s ease-in-out infinite;
|
|
|
|
|
}
|
|
|
|
|
@keyframes line-glow {
|
|
|
|
|
0%, 100% { opacity: 0.4; }
|
|
|
|
|
50% { opacity: 0.7; }
|
|
|
|
|
}
|
|
|
|
|
.pulse-origin {
|
|
|
|
|
animation: pulse-origin 2s ease-in-out infinite;
|
|
|
|
|
}
|
|
|
|
|
.pulse-end {
|
|
|
|
|
animation: pulse-end 2s ease-in-out infinite;
|
|
|
|
|
}
|
|
|
|
|
@keyframes pulse-origin {
|
|
|
|
|
0%, 100% { opacity: 0; r: 1.5; }
|
|
|
|
|
50% { opacity: 0.8; r: 3.5; }
|
|
|
|
|
}
|
|
|
|
|
@keyframes pulse-end {
|
|
|
|
|
0%, 100% { opacity: 0.2; r: 1.5; }
|
|
|
|
|
50% { opacity: 0.9; r: 3; }
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
overflow: visible;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Agent Cards */
|
|
|
|
|
/* Agent Grid — dynamic, 2 columns, extends downward as agents grow */
|
|
|
|
|
.agents-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 2;
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
}
|
|
|
|
|
.agent-slot {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Focus Banner */
|
|
|
|
@@ -320,10 +501,10 @@ const activeLines = computed(() =>
|
|
|
|
|
gap: 8px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 10px 16px;
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
background: rgba(234, 179, 8, 0.05);
|
|
|
|
|
border: 1px solid rgba(234, 179, 8, 0.1);
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 2;
|
|
|
|
|
}
|
|
|
|
|