6cedd8410f
- Remove duplicate @keyframes pulse-* from 3 component files (already in nexus-tokens.css) - Rename AgentDetail → AgentDetailData in dashboard types to avoid collision with types/agent.ts - Extract shared formatNumber/initials/formatTime to utils/format.ts - Simplify FlowBoard: use agentStore modal/selection getters instead of duplicating local state - Add error banner + empty state to IrisChat; add loading skeleton + error/empty states to TaskStrip - Remove 105-line unused mockAgents array from useFlowLayout - Reduce operations store fallbacks from hardcoded preview data to minimal safe defaults - Update operations store tests to match lean fallback structure - Net: -73 lines, cleaner imports, fewer magic strings
764 lines
18 KiB
Vue
764 lines
18 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* AgentDetailModal — Detailansicht für einen Agenten im V2 Dashboard
|
||
*
|
||
* Props:
|
||
* agent – AgentDetail (s. Interface unten)
|
||
*
|
||
* Emits:
|
||
* close – Modal schließen
|
||
* select(id) – Zum nächsten/vorherigen Agenten springen
|
||
* changeModel(id, modelId) – Modell wechseln
|
||
*/
|
||
|
||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||
import type { ThinkingItem, AgentDetailData } from './types'
|
||
import { formatNumber } from '../../../utils/format'
|
||
|
||
/* ── Props ──────────────────────────────────────────── */
|
||
|
||
const props = defineProps<{
|
||
agent: AgentDetailData
|
||
// Agent-Liste für Pfeilnavigation (IDs in Anzeigereihenfolge)
|
||
agentOrder: string[]
|
||
}>()
|
||
|
||
const emit = defineEmits<{
|
||
close: []
|
||
select: [id: string]
|
||
changeModel: [agentId: string, modelId: string]
|
||
}>()
|
||
|
||
/* ── Model Dropdown ────────────────────────────────── */
|
||
|
||
const modelDropdownOpen = ref(false)
|
||
const selectedModel = ref(props.agent.model)
|
||
|
||
watch(
|
||
() => props.agent.id,
|
||
() => {
|
||
selectedModel.value = props.agent.model
|
||
modelDropdownOpen.value = false
|
||
}
|
||
)
|
||
|
||
function toggleModelDropdown() {
|
||
modelDropdownOpen.value = !modelDropdownOpen.value
|
||
}
|
||
|
||
function selectModel(modelId: string) {
|
||
selectedModel.value = modelId
|
||
modelDropdownOpen.value = false
|
||
emit('changeModel', props.agent.id, modelId)
|
||
}
|
||
|
||
/* ── Keyboard Navigation ──────────────────────────── */
|
||
|
||
function handleKeydown(e: KeyboardEvent) {
|
||
if (e.key === 'Escape') {
|
||
e.preventDefault()
|
||
emit('close')
|
||
return
|
||
}
|
||
|
||
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
|
||
e.preventDefault()
|
||
const idx = props.agentOrder.indexOf(props.agent.id)
|
||
if (idx === -1) return
|
||
|
||
const nextIdx = e.key === 'ArrowRight'
|
||
? (idx + 1) % props.agentOrder.length
|
||
: (idx - 1 + props.agentOrder.length) % props.agentOrder.length
|
||
|
||
emit('select', props.agentOrder[nextIdx])
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
document.addEventListener('keydown', handleKeydown)
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
document.removeEventListener('keydown', handleKeydown)
|
||
})
|
||
|
||
/* ── Backdrop Click ───────────────────────────────── */
|
||
|
||
function onBackdropClick(e: MouseEvent) {
|
||
if ((e.target as HTMLElement).classList.contains('modal-backdrop')) {
|
||
emit('close')
|
||
}
|
||
}
|
||
|
||
/* ── Metrics Config ───────────────────────────────── */
|
||
|
||
interface MetricDef {
|
||
label: string
|
||
value: string
|
||
sub?: string
|
||
}
|
||
|
||
function getMetrics(a: AgentDetailData): MetricDef[] {
|
||
return [
|
||
{
|
||
label: 'Tasks aktiv',
|
||
value: String(a.activeTaskCount),
|
||
sub: 'aktuelle Aufgaben',
|
||
},
|
||
{
|
||
label: 'Tokens heute',
|
||
value: formatNumber(a.tokensToday),
|
||
sub: 'verbraucht',
|
||
},
|
||
{
|
||
label: 'Kosten',
|
||
value: '$' + a.costToday.toFixed(2),
|
||
sub: 'heute gesamt',
|
||
},
|
||
{
|
||
label: 'Workload',
|
||
value: a.workload + '%',
|
||
sub: a.workload > 70 ? 'Ausgelastet' : a.workload > 30 ? 'Moderat' : 'Gering',
|
||
},
|
||
{
|
||
label: 'Uptime',
|
||
value: a.uptime,
|
||
sub: 'aktuelle Session',
|
||
},
|
||
{
|
||
label: 'Letzte Aktivität',
|
||
value: a.lastActive,
|
||
sub: '',
|
||
},
|
||
]
|
||
}
|
||
|
||
/* ── Pretty Model Name ────────────────────────────── */
|
||
|
||
function modelLabel(alias: string): string {
|
||
return alias
|
||
}
|
||
|
||
/* ── Thinking type helpers ─────────────────────────── */
|
||
|
||
const typeConfig: Record<ThinkingItem['type'], { dotClass: string; label: string }> = {
|
||
thought: { dotClass: 'dot-thought', label: 'Thought' },
|
||
action: { dotClass: 'dot-action', label: 'Action' },
|
||
result: { dotClass: 'dot-result', label: 'Result' },
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="modal-backdrop" @click="onBackdropClick">
|
||
<div class="modal-panel" role="dialog" aria-modal="true">
|
||
<!-- Close Button -->
|
||
<button class="m-close" @click="emit('close')" aria-label="Schließen">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="close-icon">
|
||
<path d="M18 6 6 18M6 6l12 12" />
|
||
</svg>
|
||
</button>
|
||
|
||
<!-- Header -->
|
||
<div class="m-head">
|
||
<!-- Avatar -->
|
||
<div :class="['m-av', { 'm-av-iris': agent.id === 'iris' }]">
|
||
<span>{{ agent.id === 'iris' ? 'IR' : agent.name.slice(0, 2).toUpperCase() }}</span>
|
||
</div>
|
||
|
||
<div class="m-head-info">
|
||
<div class="m-name">{{ agent.name }}</div>
|
||
<div class="m-sub">
|
||
<span class="m-role">{{ agent.role }}</span>
|
||
<span class="m-status-pill">
|
||
<span :class="['dot', `dot-${agent.status}`]"></span>
|
||
{{ agent.status === 'work' ? 'Arbeitet' : agent.status === 'think' ? 'Denkt' : 'Bereit' }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Model Info + Dropdown -->
|
||
<div class="m-model-area">
|
||
<div class="m-model-current">
|
||
<span class="m-model-label">Model</span>
|
||
<button class="m-model-btn" @click="toggleModelDropdown">
|
||
<span class="m-model-name">{{ selectedModel }}</span>
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="chevron-down">
|
||
<path d="m6 9 6 6 6-6" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Dropdown Menu -->
|
||
<div v-if="modelDropdownOpen" class="m-model-dropdown">
|
||
<button
|
||
v-for="m in agent.availableModels"
|
||
:key="m.id"
|
||
:class="['m-model-option', { active: m.alias === selectedModel }]"
|
||
@click="selectModel(m.alias)"
|
||
>
|
||
<span class="option-check" v-if="m.alias === selectedModel">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="check-icon">
|
||
<path d="M20 6 9 17l-5-5" />
|
||
</svg>
|
||
</span>
|
||
{{ modelLabel(m.alias) }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Metrics Grid (3 columns, 6 items) -->
|
||
<div class="m-metrics">
|
||
<div v-for="(metric, idx) in getMetrics(agent)" :key="idx" class="m-metric">
|
||
<div class="m-metric-label">{{ metric.label }}</div>
|
||
<div class="m-metric-value">{{ metric.value }}</div>
|
||
<div v-if="metric.sub" class="m-metric-sub">{{ metric.sub }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Thinking Feed -->
|
||
<div v-if="agent.thinking.length > 0" class="m-feed">
|
||
<div class="m-feed-title">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="feed-icon">
|
||
<circle cx="12" cy="12" r="9" />
|
||
<path d="M12 8v4l3 3" />
|
||
</svg>
|
||
Live Thinking
|
||
</div>
|
||
|
||
<div class="m-feed-items">
|
||
<div
|
||
v-for="(item, idx) in agent.thinking"
|
||
:key="idx"
|
||
class="m-feed-item"
|
||
>
|
||
<div class="feed-item-header">
|
||
<span :class="['feed-dot', typeConfig[item.type].dotClass]"></span>
|
||
<span class="feed-type-label">{{ typeConfig[item.type].label }}</span>
|
||
</div>
|
||
<div class="feed-item-text">{{ item.text }}</div>
|
||
<div class="feed-item-ts">{{ item.ts }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Empty Thinking State -->
|
||
<div v-else class="m-feed">
|
||
<div class="m-feed-title">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="feed-icon">
|
||
<circle cx="12" cy="12" r="9" />
|
||
<path d="M12 8v4l3 3" />
|
||
</svg>
|
||
Live Thinking
|
||
</div>
|
||
<div class="m-feed-empty">
|
||
<span class="empty-text">Keine aktiven Gedanken</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
/* ── Backdrop ──────────────────────────────────────── */
|
||
.modal-backdrop {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 200;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(4, 2, 12, 0.72);
|
||
backdrop-filter: blur(8px);
|
||
animation: backdrop-in 0.2s ease-out;
|
||
}
|
||
|
||
@keyframes backdrop-in {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
|
||
/* ── Panel ─────────────────────────────────────────── */
|
||
.modal-panel {
|
||
width: min(680px, 92vw);
|
||
max-height: 86vh;
|
||
overflow-y: auto;
|
||
border-radius: var(--r);
|
||
position: relative;
|
||
background: linear-gradient(145deg, rgba(14, 12, 28, 0.96), rgba(8, 6, 20, 0.96));
|
||
border: 1px solid var(--line);
|
||
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.6);
|
||
animation: panel-in 0.22s cubic-bezier(0.2, 0.8, 0.3, 1);
|
||
}
|
||
|
||
@keyframes panel-in {
|
||
from {
|
||
opacity: 0;
|
||
transform: scale(0.94) translateY(12px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: scale(1) translateY(0);
|
||
}
|
||
}
|
||
|
||
.modal-panel::-webkit-scrollbar {
|
||
width: 7px;
|
||
}
|
||
|
||
.modal-panel::-webkit-scrollbar-thumb {
|
||
background: rgba(124, 108, 255, 0.22);
|
||
border-radius: 7px;
|
||
border: 2px solid transparent;
|
||
background-clip: padding-box;
|
||
}
|
||
|
||
.modal-panel::-webkit-scrollbar-thumb:hover {
|
||
background: rgba(124, 108, 255, 0.4);
|
||
background-clip: padding-box;
|
||
}
|
||
|
||
.modal-panel::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
/* ── Close Button ──────────────────────────────────── */
|
||
.m-close {
|
||
position: absolute;
|
||
top: 16px;
|
||
right: 16px;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 8px;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--tx-3);
|
||
cursor: pointer;
|
||
display: grid;
|
||
place-items: center;
|
||
transition: background 0.15s, color 0.15s;
|
||
z-index: 3;
|
||
}
|
||
|
||
.m-close:hover {
|
||
background: rgba(124, 108, 255, 0.12);
|
||
color: var(--tx);
|
||
}
|
||
|
||
.close-icon {
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
|
||
/* ── Header ────────────────────────────────────────── */
|
||
.m-head {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 16px;
|
||
padding: 22px 24px 16px;
|
||
padding-right: 56px; /* Platz für Close-Button */
|
||
}
|
||
|
||
.m-av {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 11px;
|
||
flex: 0 0 auto;
|
||
display: grid;
|
||
place-items: center;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-weight: 700;
|
||
font-size: 13px;
|
||
background: var(--grad-soft);
|
||
border: 1px solid var(--line-2);
|
||
color: var(--tx);
|
||
}
|
||
|
||
.m-av-iris {
|
||
background: var(--grad);
|
||
color: #fff;
|
||
box-shadow: var(--glow-purple);
|
||
}
|
||
|
||
.m-av span {
|
||
line-height: 1;
|
||
}
|
||
|
||
.m-head-info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.m-name {
|
||
font-family: 'Space Grotesk', sans-serif;
|
||
font-weight: 700;
|
||
font-size: 16px;
|
||
color: var(--tx);
|
||
line-height: 1.2;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.m-sub {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-top: 6px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.m-role {
|
||
font-family: 'Manrope', sans-serif;
|
||
font-size: 12px;
|
||
color: var(--tx-3);
|
||
}
|
||
|
||
.m-status-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-family: 'Manrope', sans-serif;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
color: var(--tx-2);
|
||
}
|
||
|
||
/* Status dots in pill */
|
||
.dot {
|
||
width: 7px;
|
||
height: 7px;
|
||
border-radius: 50%;
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.dot-work {
|
||
background: var(--st-work);
|
||
box-shadow: 0 0 0 0 rgba(61, 220, 151, 0.55);
|
||
animation: pulse-work 1.8s infinite;
|
||
}
|
||
|
||
.dot-think {
|
||
background: var(--st-think);
|
||
box-shadow: 0 0 0 0 rgba(52, 214, 245, 0.55);
|
||
animation: pulse-think 1.6s infinite;
|
||
}
|
||
|
||
.dot-idle {
|
||
background: var(--st-idle);
|
||
}
|
||
|
||
/* ── Model Area ────────────────────────────────────── */
|
||
.m-model-area {
|
||
flex: 0 0 auto;
|
||
position: relative;
|
||
}
|
||
|
||
.m-model-current {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
gap: 2px;
|
||
}
|
||
|
||
.m-model-label {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 10px;
|
||
color: var(--tx-3);
|
||
letter-spacing: 0.05em;
|
||
}
|
||
|
||
.m-model-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
height: 28px;
|
||
padding: 0 10px;
|
||
border-radius: 7px;
|
||
border: 1px solid var(--line);
|
||
background: rgba(124, 108, 255, 0.06);
|
||
color: var(--tx-2);
|
||
cursor: pointer;
|
||
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 10px;
|
||
font-weight: 500;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.m-model-btn:hover {
|
||
background: rgba(124, 108, 255, 0.12);
|
||
border-color: var(--line-3);
|
||
color: var(--tx);
|
||
}
|
||
|
||
.m-model-name {
|
||
max-width: 140px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.chevron-down {
|
||
width: 13px;
|
||
height: 13px;
|
||
flex: 0 0 auto;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
/* ── Model Dropdown ────────────────────────────────── */
|
||
.m-model-dropdown {
|
||
position: absolute;
|
||
top: 100%;
|
||
right: 0;
|
||
margin-top: 4px;
|
||
min-width: 180px;
|
||
background: linear-gradient(145deg, rgba(16, 14, 32, 0.98), rgba(10, 8, 22, 0.98));
|
||
border: 1px solid var(--line-2);
|
||
border-radius: 10px;
|
||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
|
||
padding: 4px;
|
||
z-index: 10;
|
||
animation: dropdown-in 0.12s ease-out;
|
||
}
|
||
|
||
@keyframes dropdown-in {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-6px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.m-model-option {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
padding: 8px 12px;
|
||
border-radius: 7px;
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--tx-2);
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 11px;
|
||
cursor: pointer;
|
||
transition: background 0.12s, color 0.12s;
|
||
text-align: left;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.m-model-option:hover {
|
||
background: rgba(124, 108, 255, 0.10);
|
||
color: var(--tx);
|
||
}
|
||
|
||
.m-model-option.active {
|
||
color: var(--tx);
|
||
background: rgba(124, 108, 255, 0.14);
|
||
}
|
||
|
||
.option-check {
|
||
width: 16px;
|
||
height: 16px;
|
||
flex: 0 0 auto;
|
||
display: grid;
|
||
place-items: center;
|
||
}
|
||
|
||
.check-icon {
|
||
width: 14px;
|
||
height: 14px;
|
||
color: var(--a-mid);
|
||
}
|
||
|
||
/* ── Metrics Grid ──────────────────────────────────── */
|
||
.m-metrics {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 16px;
|
||
padding: 0 24px 18px;
|
||
}
|
||
|
||
.m-metric {
|
||
background: var(--glass);
|
||
border: 1px solid var(--line);
|
||
border-radius: 10px;
|
||
padding: 14px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
transition: border-color 0.15s;
|
||
}
|
||
|
||
.m-metric:hover {
|
||
border-color: var(--line-2);
|
||
}
|
||
|
||
.m-metric-label {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 9px;
|
||
color: var(--tx-3);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.04em;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.m-metric-value {
|
||
font-family: 'Space Grotesk', sans-serif;
|
||
font-weight: 700;
|
||
font-size: 20px;
|
||
color: var(--tx);
|
||
line-height: 1.1;
|
||
margin-top: 4px;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.m-metric-sub {
|
||
font-family: 'Manrope', sans-serif;
|
||
font-size: 11px;
|
||
color: var(--tx-3);
|
||
margin-top: 2px;
|
||
}
|
||
|
||
/* ── Feed ──────────────────────────────────────────── */
|
||
.m-feed {
|
||
padding: 0 24px 22px;
|
||
}
|
||
|
||
.m-feed-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-family: 'Space Grotesk', sans-serif;
|
||
font-weight: 600;
|
||
font-size: 12px;
|
||
color: var(--tx);
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.feed-icon {
|
||
width: 15px;
|
||
height: 15px;
|
||
color: var(--a-mid);
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.m-feed-items {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
overflow-y: auto;
|
||
max-height: 340px;
|
||
padding-right: 4px;
|
||
}
|
||
|
||
.m-feed-items::-webkit-scrollbar {
|
||
width: 5px;
|
||
}
|
||
|
||
.m-feed-items::-webkit-scrollbar-thumb {
|
||
background: rgba(124, 108, 255, 0.18);
|
||
border-radius: 5px;
|
||
border: 1px solid transparent;
|
||
background-clip: padding-box;
|
||
}
|
||
|
||
.m-feed-items::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
.m-feed-item {
|
||
padding: 11px 14px;
|
||
background: rgba(255, 255, 255, 0.02);
|
||
border: 1px solid rgba(255, 255, 255, 0.04);
|
||
border-radius: 8px;
|
||
transition: background 0.15s;
|
||
}
|
||
|
||
.m-feed-item:hover {
|
||
background: rgba(255, 255, 255, 0.04);
|
||
}
|
||
|
||
.feed-item-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.feed-dot {
|
||
width: 7px;
|
||
height: 7px;
|
||
border-radius: 50%;
|
||
flex: 0 0 auto;
|
||
display: block;
|
||
}
|
||
|
||
.dot-thought {
|
||
background: var(--st-think);
|
||
box-shadow: 0 0 5px rgba(52, 214, 245, 0.4);
|
||
}
|
||
|
||
.dot-action {
|
||
background: var(--a-purple);
|
||
box-shadow: 0 0 5px rgba(181, 87, 246, 0.4);
|
||
}
|
||
|
||
.dot-result {
|
||
background: var(--st-work);
|
||
box-shadow: 0 0 5px rgba(61, 220, 151, 0.4);
|
||
}
|
||
|
||
.feed-type-label {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 9px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
}
|
||
|
||
.dot-thought ~ .feed-type-label {
|
||
color: var(--st-think);
|
||
}
|
||
|
||
.dot-action ~ .feed-type-label {
|
||
color: var(--a-purple);
|
||
}
|
||
|
||
.dot-result ~ .feed-type-label {
|
||
color: var(--st-work);
|
||
}
|
||
|
||
.feed-item-text {
|
||
font-family: 'Manrope', sans-serif;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
color: var(--tx);
|
||
}
|
||
|
||
.feed-item-ts {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 9px;
|
||
color: var(--tx-3);
|
||
margin-top: 4px;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
/* Empty state */
|
||
.m-feed-empty {
|
||
padding: 20px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.empty-text {
|
||
font-family: 'Manrope', sans-serif;
|
||
font-size: 12px;
|
||
color: var(--tx-3);
|
||
font-style: italic;
|
||
}
|
||
</style>
|