<template>
  <div class="dashboard">
    <!-- ===== 系统状态栏：实时连接 / 关键指标 / 时钟 ===== -->
    <section class="status-bar">
      <div class="status-left">
        <span class="status-conn" :class="connState">
          <span class="mg-live-dot" :class="connDotClass" />
          <span>{{ connLabel }}</span>
        </span>
        <span class="status-stat tone-agv">
          <span class="status-stat-label">在线 AGV</span>
          <span class="status-stat-value mg-num">{{ onlineAgv }} <span class="status-stat-total">/ {{ cars.length || '—' }}</span></span>
        </span>
        <span class="status-stat tone-alarm" :class="{ 'has-alarm': alarmCount > 0 }">
          <span class="status-stat-label">告警</span>
          <span class="status-stat-value mg-num">{{ alarmCount }}</span>
        </span>
        <span class="status-stat tone-task">
          <span class="status-stat-label">活跃任务</span>
          <span class="status-stat-value mg-num">{{ activeMissionCount }}</span>
        </span>
      </div>
      <div class="status-right">
        <span class="status-stat tone-clock">
          <span class="status-stat-label">{{ todayLabel }}</span>
          <span class="status-stat-value mg-num">{{ nowText }}</span>
        </span>
      </div>
    </section>

    <!-- ===== 顶部 Hero Banner：标题 + 子描述 + 快捷入口 + 右侧工业插画 ===== -->
    <section class="hero-banner">
      <div class="hero-bg" aria-hidden="true">
        <div class="hero-orb hero-orb-a" />
        <div class="hero-orb hero-orb-b" />
        <div class="hero-grid" />
      </div>
      <div class="hero-left">
        <div class="hero-tag">
          <span class="tag-dot" />
          <span>智能调度 · 一站式平台</span>
        </div>
        <h1 class="hero-title">迷毂智能调度平台</h1>
        <p class="hero-desc">长按图标拖动可交换位置，或点击「添加」新增入口</p>

        <div class="quick-grid" :class="{ 'is-dragging': dragKey }">
          <div
            v-for="(row, rowIdx) in quickEntryRowList"
            :key="rowIdx"
            class="quick-row"
          >
            <button
              v-for="(item, idx) in row"
              :key="item.key"
              type="button"
              class="quick-item"
              :class="{
                primary: item.primary,
                'is-add': item.key === 'add',
                'is-dragging-source': dragKey === item.key,
                'is-drag-hover-target': hoverTargetKey === item.key
              }"
              :data-quick-key="item.key"
              :style="{ animationDelay: `${(rowIdx === 0 ? 0 : quickEntryRows.top.length) + idx * 60}ms` }"
              :title="quickItemTitle(item)"
              @pointerdown="onQuickPointerDown(item, $event)"
              @click="onQuickClick(item)"
            >
              <span
                v-if="item.key !== 'add' && !isMandatoryQuickKey(item.key)"
                class="quick-item-remove"
                title="移除"
                @pointerdown.stop
                @click.stop="onRemoveQuick(item.key)"
              >×</span>
              <span class="quick-icon">
                <el-icon :size="22"><component :is="item.icon" /></el-icon>
              </span>
              <span class="quick-label">{{ item.label }}</span>
            </button>
          </div>
        </div>
      </div>

      <!-- 右侧工业 3D 插画（SVG 绘制紫色风格场景） -->
      <div class="hero-right" aria-hidden="true">
        <div class="hero-glow" />
        <svg class="hero-scene" viewBox="0 0 520 320" xmlns="http://www.w3.org/2000/svg">
          <defs>
            <linearGradient id="floorGrad" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%" stop-color="#3b1e7a" stop-opacity="0.6" />
              <stop offset="100%" stop-color="#0e0a24" stop-opacity="0.95" />
            </linearGradient>
            <linearGradient id="boxGrad" x1="0" y1="0" x2="1" y2="1">
              <stop offset="0%" stop-color="#c4b5fd" />
              <stop offset="100%" stop-color="#7c3aed" />
            </linearGradient>
            <linearGradient id="boxGrad2" x1="0" y1="0" x2="1" y2="1">
              <stop offset="0%" stop-color="#a78bfa" />
              <stop offset="100%" stop-color="#5b21b6" />
            </linearGradient>
            <linearGradient id="agvGrad" x1="0" y1="0" x2="1" y2="0">
              <stop offset="0%" stop-color="#8b5cf6" />
              <stop offset="100%" stop-color="#4c1d95" />
            </linearGradient>
            <radialGradient id="lightGrad" cx="50%" cy="40%" r="60%">
              <stop offset="0%" stop-color="rgba(196,181,253,0.9)" />
              <stop offset="60%" stop-color="rgba(124,58,237,0.25)" />
              <stop offset="100%" stop-color="rgba(124,58,237,0)" />
            </radialGradient>
            <pattern id="gridPat" width="32" height="20" patternUnits="userSpaceOnUse" patternTransform="skewX(-30)">
              <path d="M0 0H32M0 20H32" stroke="rgba(196,181,253,0.18)" stroke-width="1" />
              <path d="M0 0V20M32 0V20" stroke="rgba(196,181,253,0.10)" stroke-width="1" />
            </pattern>
          </defs>

          <!-- 远端光晕 -->
          <ellipse cx="260" cy="120" rx="220" ry="120" fill="url(#lightGrad)" opacity="0.85" />

          <!-- 地面网格（透视） -->
          <path d="M40 280 L480 280 L420 200 L100 200 Z" fill="url(#floorGrad)" />
          <path d="M40 280 L480 280 L420 200 L100 200 Z" fill="url(#gridPat)" opacity="0.85" />

          <!-- 远端货架阵列 -->
          <g class="shelves">
            <g v-for="i in 4" :key="`shelf-${i}`">
              <rect :x="140 + (i - 1) * 60" y="130" width="40" height="70" rx="3" fill="url(#boxGrad2)" opacity="0.85" />
              <rect :x="146 + (i - 1) * 60" y="138" width="28" height="6" fill="rgba(255,255,255,0.18)" />
              <rect :x="146 + (i - 1) * 60" y="152" width="28" height="6" fill="rgba(255,255,255,0.12)" />
              <rect :x="146 + (i - 1) * 60" y="166" width="28" height="6" fill="rgba(255,255,255,0.10)" />
              <rect :x="146 + (i - 1) * 60" y="180" width="28" height="6" fill="rgba(255,255,255,0.08)" />
            </g>
          </g>

          <!-- 输送带 -->
          <g>
            <rect x="60" y="226" width="180" height="14" rx="2" fill="#1a1040" stroke="rgba(196,181,253,0.4)" />
            <line x1="60" y1="232" x2="240" y2="232" stroke="rgba(196,181,253,0.35)" stroke-dasharray="6 4" />
          </g>

          <!-- AGV 小车 1 -->
          <g class="agv agv-1">
            <rect x="100" y="240" width="56" height="22" rx="4" fill="url(#agvGrad)" stroke="rgba(196,181,253,0.55)" />
            <rect x="106" y="234" width="44" height="10" rx="2" fill="rgba(196,181,253,0.85)" />
            <circle cx="112" cy="266" r="5" fill="#1a1040" stroke="#c4b5fd" />
            <circle cx="148" cy="266" r="5" fill="#1a1040" stroke="#c4b5fd" />
            <circle cx="128" cy="232" r="3" fill="#fff">
              <animate attributeName="opacity" values="0.4;1;0.4" dur="1.8s" repeatCount="indefinite" />
            </circle>
          </g>

          <!-- AGV 小车 2 -->
          <g class="agv agv-2">
            <rect x="320" y="248" width="64" height="24" rx="4" fill="url(#agvGrad)" stroke="rgba(196,181,253,0.55)" />
            <rect x="328" y="240" width="48" height="12" rx="2" fill="rgba(196,181,253,0.85)" />
            <circle cx="332" cy="276" r="5" fill="#1a1040" stroke="#c4b5fd" />
            <circle cx="372" cy="276" r="5" fill="#1a1040" stroke="#c4b5fd" />
            <circle cx="352" cy="238" r="3" fill="#fff">
              <animate attributeName="opacity" values="0.4;1;0.4" dur="2.2s" repeatCount="indefinite" />
            </circle>
          </g>

          <!-- 货物堆 -->
          <g>
            <rect x="270" y="232" width="22" height="22" fill="url(#boxGrad)" rx="2" />
            <rect x="270" y="210" width="22" height="22" fill="url(#boxGrad)" rx="2" />
            <rect x="246" y="232" width="22" height="22" fill="url(#boxGrad2)" rx="2" />
          </g>

          <!-- 右侧工业塔 -->
          <g>
            <rect x="420" y="120" width="36" height="120" fill="#1a1040" stroke="rgba(196,181,253,0.55)" />
            <rect x="420" y="120" width="36" height="14" fill="rgba(196,181,253,0.45)" />
            <rect x="424" y="138" width="28" height="6" fill="rgba(196,181,253,0.20)" />
            <rect x="424" y="152" width="28" height="6" fill="rgba(196,181,253,0.20)" />
            <rect x="424" y="166" width="28" height="6" fill="rgba(196,181,253,0.20)" />
            <rect x="424" y="180" width="28" height="6" fill="rgba(196,181,253,0.20)" />
            <rect x="424" y="194" width="28" height="6" fill="rgba(196,181,253,0.20)" />
            <rect x="424" y="208" width="28" height="6" fill="rgba(196,181,253,0.20)" />
            <circle cx="438" cy="115" r="3" fill="#fff">
              <animate attributeName="opacity" values="0.3;1;0.3" dur="1.5s" repeatCount="indefinite" />
            </circle>
          </g>

          <!-- 数据浮标 -->
          <g class="float-data" transform="translate(360,110)">
            <rect x="-30" y="-16" width="80" height="22" rx="11" fill="rgba(255,255,255,0.10)" stroke="rgba(196,181,253,0.55)" />
            <circle cx="-18" cy="-5" r="3" fill="#22c55e" />
            <text x="-10" y="-1" font-size="10" fill="#fff" font-family="monospace">AGV-021 · 92%</text>
          </g>
          <g class="float-data" transform="translate(140,80)">
            <rect x="-30" y="-16" width="92" height="22" rx="11" fill="rgba(255,255,255,0.10)" stroke="rgba(196,181,253,0.55)" />
            <circle cx="-18" cy="-5" r="3" fill="#a855f7" />
            <text x="-10" y="-1" font-size="10" fill="#fff" font-family="monospace">SHELF-A03 · OK</text>
          </g>
        </svg>
      </div>
    </section>

    <!-- ===== A10：双栏分析 + 底栏（任务占比 | 报警信息） ===== -->
    <section class="analytics-grid">
      <el-card shadow="never" class="bento bento-trend">
        <template #header>
          <div class="card-head">
            <span class="card-title">
              <el-icon><TrendCharts /></el-icon>
              24h 任务趋势
            </span>
            <span class="card-tip mg-num">最近 24 小时 · 5 分钟粒度</span>
          </div>
        </template>
        <div ref="trendChartRef" class="trend-chart" />
      </el-card>

      <el-card shadow="never" class="bento bento-fleet">
        <template #header>
          <div class="card-head">
            <span class="card-title">
              <el-icon><Van /></el-icon>
              车队健康
            </span>
            <span class="card-tip mg-num">
              {{ agvTotal }} 台
              <span v-if="agvIsMock" class="mock-tag">·示例</span>
            </span>
          </div>
        </template>
        <div class="fleet-body">
          <div class="agv-row">
            <div class="agv-chart-wrap">
              <div ref="agvChartRef" class="agv-chart" />
              <div class="agv-center">
                <div class="agv-center-value mg-num">{{ agvTotal }}</div>
                <div class="agv-center-label">总台数</div>
              </div>
            </div>
            <ul class="agv-legend">
              <li v-for="s in agvDistribution" :key="s.key">
                <span class="dot" :style="{ background: s.color }" />
                <span class="legend-name">{{ s.label }}</span>
                <span class="legend-value mg-num">{{ s.value }}</span>
                <span class="legend-pct mg-num">{{ agvTotal > 0 ? Math.round((s.value / agvTotal) * 100) : 0 }}%</span>
              </li>
            </ul>
          </div>
          <div class="fleet-stats">
            <div v-for="stat in fleetHealthStats" :key="stat.key" class="fleet-stat" :class="`tone-${stat.tone}`">
              <span class="fleet-stat-value mg-num">{{ stat.value }}</span>
              <span class="fleet-stat-label">{{ stat.label }}</span>
            </div>
          </div>
        </div>
      </el-card>

      <el-card shadow="never" class="bento bento-bottom">
        <div class="bottom-split">
          <div class="bottom-ratio">
            <div class="card-head bottom-head">
              <span class="card-title">
                <el-icon><PieChart /></el-icon>
                各任务占比
              </span>
              <span class="card-tip mg-num">总计 {{ missions.length }} 个任务</span>
            </div>
            <div class="ratio-track" role="img" aria-label="任务占比堆叠条">
              <span
                v-for="r in taskRatios"
                :key="r.name"
                class="ratio-seg"
                :class="`tone-${r.tone}`"
                :style="{ width: `${r.pct}%` }"
                :title="`${r.name} ${r.pct}%`"
              >
                <span v-if="r.pct >= 8" class="ratio-seg-pct mg-num">{{ r.pct }}%</span>
              </span>
            </div>
            <ul class="ratio-legend">
              <li v-for="r in taskRatios" :key="r.name">
                <span class="dot" :class="`tone-${r.tone}`" />
                <span class="legend-name">{{ r.name }}</span>
                <span class="legend-value mg-num">{{ r.pct }}%</span>
              </li>
            </ul>
          </div>

          <div class="bottom-alarms">
            <div class="card-head bottom-head">
              <span class="card-title">
                <el-icon><Bell /></el-icon>
                报警信息
              </span>
              <div class="alarm-head-right">
                <span v-if="alarmCount > 0" class="alarm-active-count mg-num">{{ alarmCount }} 条活跃</span>
                <el-link class="more-link" underline="never" @click="goAlarms">详情 ›</el-link>
              </div>
            </div>
            <div v-if="recentAlarms.length === 0" class="empty-pane compact">
              <p>暂无报警</p>
            </div>
            <ul v-else class="alarm-chip-list">
              <li
                v-for="a in recentAlarms"
                :key="a.id"
                class="alarm-chip"
                :class="[`lvl-${alarmLevelKey(a.level)}`, { cleared: a.status !== 'active' }]"
                :title="a.info"
                @click="goAlarms"
              >
                <span class="alarm-chip-lvl">{{ alarmLevelLabel(a.level) }}</span>
                <span class="alarm-chip-car">{{ a.carName || `AGV-${a.carId}` }}</span>
                <span class="alarm-chip-text">{{ a.info }}</span>
                <span class="alarm-chip-time mg-num">{{ formatAlarmTime(a.lastAt || a.firstAt) }}</span>
              </li>
            </ul>
          </div>
        </div>
      </el-card>
    </section>
  </div>

  <QuickEntryPickerDialog
    v-model="pickerOpen"
    :items="pickerCatalog"
    :pinned-keys="pinnedKeys"
    :can-add="canAddMore"
    @pick="onAddQuick" />

  <Teleport to="body">
    <div
      v-if="dragKey && dragGhostItem"
      class="quick-drag-ghost"
      :style="{ left: `${ghostPos.x}px`, top: `${ghostPos.y}px` }"
    >
      <span class="quick-icon">
        <el-icon :size="22"><component :is="dragGhostItem.icon" /></el-icon>
      </span>
      <span class="quick-label">{{ dragGhostItem.label }}</span>
    </div>
  </Teleport>
</template>

<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import * as echarts from 'echarts'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
  Bell, PieChart, Plus,
  TrendCharts, Van
} from '@element-plus/icons-vue'
import QuickEntryPickerDialog from '@/components/dashboard/QuickEntryPickerDialog.vue'
import { useDashboardQuickEntries } from '@/composables/useDashboardQuickEntries'
import { useQuickEntryDragSwap } from '@/composables/useQuickEntryDragSwap'
import { fetchAlarmFeed } from '@/api/alarm'
import { listCars, listMissions } from '@/api/projection'
import type { VehicleAlarm } from '@/types/alarm'
import type { Car } from '@/types/car'
import type { Mission } from '@/types/mission'

const router = useRouter()

const {
  resolvedEntries,
  pickerCatalog,
  pinnedKeys,
  pickerOpen,
  canAddMore,
  openPicker,
  addKey,
  removeKey,
  swapKeys,
  isMandatoryQuickKey
} = useDashboardQuickEntries()

interface QuickTile {
  key: string
  label: string
  icon: unknown
  hint?: string
  primary?: boolean
  path?: string
}

const displayQuickEntries = computed<QuickTile[]>(() => {
  const items: QuickTile[] = resolvedEntries.value.map((e) => ({
    key: e.key,
    label: e.label,
    icon: e.icon,
    hint: e.hint,
    primary: e.primary,
    path: e.path
  }))
  if (canAddMore.value) {
    items.push({
      key: 'add',
      label: '添加',
      icon: Plus,
      hint: '从菜单添加快捷入口'
    })
  }
  return items
})

/** 上下两行均衡分布：5 个 → 上 3 / 下 2，6 个 → 上 3 / 下 3 */
const quickEntryRows = computed(() => {
  const all = displayQuickEntries.value
  const topCount = Math.ceil(all.length / 2)
  return {
    top: all.slice(0, topCount),
    bottom: all.slice(topCount)
  }
})

const quickEntryRowList = computed(() => {
  const { top, bottom } = quickEntryRows.value
  return bottom.length ? [top, bottom] : [top]
})

const {
  dragKey,
  hoverTargetKey,
  ghostPos,
  onPointerDown: onQuickPointerDown,
  shouldSuppressClick
} = useQuickEntryDragSwap(async (from, to) => {
  await swapKeys(from, to)
  ElMessage.success('已交换位置')
})

const dragGhostItem = computed(() => {
  if (!dragKey.value) return null
  return displayQuickEntries.value.find((i) => i.key === dragKey.value) ?? null
})

function quickItemTitle(item: QuickTile): string {
  if (item.key === 'add') return item.hint ?? item.label
  if (dragKey.value === item.key) return '拖动到目标图标上松开以交换位置'
  return item.hint ?? item.label
}

async function onQuickClick(item: QuickTile) {
  if (shouldSuppressClick()) return

  if (item.key === 'add') {
    openPicker()
    return
  }

  if (item.path) router.push(item.path)
}

async function onAddQuick(key: string) {
  await addKey(key)
}

async function onRemoveQuick(key: string) {
  try {
    await ElMessageBox.confirm('确定从快捷入口移除此项？', '移除快捷入口', {
      type: 'warning',
      confirmButtonText: '移除',
      cancelButtonText: '取消'
    })
    await removeKey(key)
  } catch {
    /* cancel */
  }
}

const cars = ref<Car[]>([])
const missions = ref<Mission[]>([])

const trendChartRef = ref<HTMLDivElement>()
const agvChartRef = ref<HTMLDivElement>()
const alarms = ref<VehicleAlarm[]>([])

function cssVar(name: string, fallback: string): string {
  if (typeof window === 'undefined') return fallback
  const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim()
  return v || fallback
}

// ─── 系统状态栏：实时时钟 + 连接状态 + 关键计数 ───
const nowText = ref('')
const todayLabel = ref('')
let clockTimer: ReturnType<typeof setInterval> | null = null
function tick() {
  const d = new Date()
  todayLabel.value = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
  nowText.value = `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`
}
tick()

const connState = ref<'success' | 'warning' | 'danger'>('success')
const connDotClass = computed(() => `is-${connState.value}`)
const connLabel = computed(() => connState.value === 'success' ? '已连接' : connState.value === 'warning' ? '高延迟' : '断连')

const onlineAgv = computed(() => cars.value.filter((c) => c.state !== 'offline').length)
const activeMissionCount = computed(() =>
  missions.value.filter((m) => m.status === 'running' || m.status === 'assigned').length
)

// ─── 任务占比（横向堆叠条） ───
type RatioTone = 'info' | 'success' | 'warning' | 'idle'
interface TaskRatio { name: string; pct: number; tone: RatioTone }
const taskRatios = computed<TaskRatio[]>(() => {
  const total = missions.value.length || 1
  const buckets: Record<string, { pct: number; tone: RatioTone }> = {
    '货架交接':  { pct: 0, tone: 'info' },
    '搬运/落位': { pct: 0, tone: 'success' },
    'Z1 任务':   { pct: 0, tone: 'warning' },
    '其他':      { pct: 0, tone: 'idle' }
  }
  missions.value.forEach((m) => {
    const t = m.typeName ?? ''
    if (t.includes('shelf') || t.includes('货架')) buckets['货架交接'].pct++
    else if (t.includes('move') || t.includes('搬运') || t.includes('落位')) buckets['搬运/落位'].pct++
    else if (t.includes('z1') || t.includes('Z1')) buckets['Z1 任务'].pct++
    else buckets['其他'].pct++
  })
  let arr = Object.entries(buckets).map(([name, v]) => ({
    name,
    pct: Math.round((v.pct / total) * 100),
    tone: v.tone
  }))
  if (arr.every((x) => x.pct === 0)) {
    arr = [
      { name: '货架交接',  pct: 45, tone: 'info' },
      { name: '搬运/落位', pct: 15, tone: 'success' },
      { name: 'Z1 任务',   pct: 15, tone: 'warning' },
      { name: '其他',      pct: 25, tone: 'idle' }
    ]
  }
  return arr
})

// ─── AGV 状态分布饼图数据 ───
type AgvKey = 'idle' | 'running' | 'charging' | 'paused' | 'fault' | 'offline'
interface AgvSeg { key: AgvKey; label: string; value: number; color: string }

const agvIsMock = computed(() => cars.value.length === 0)
const agvDistribution = computed<AgvSeg[]>(() => {
  const total = cars.value.length || 12
  const counts: Record<AgvKey, number> = { idle: 0, running: 0, charging: 0, paused: 0, fault: 0, offline: 0 }
  cars.value.forEach((c) => { counts[c.state as AgvKey] = (counts[c.state as AgvKey] ?? 0) + 1 })
  const fallback = agvIsMock.value
  return [
    { key: 'running',  label: '执行中', value: fallback ? Math.round(total * 0.45) : counts.running,  color: cssVar('--mg-status-info',    '#3b82f6') },
    { key: 'idle',     label: '空闲',   value: fallback ? Math.round(total * 0.30) : counts.idle,     color: cssVar('--mg-status-success', '#22c55e') },
    { key: 'charging', label: '充电中', value: fallback ? Math.round(total * 0.10) : counts.charging, color: cssVar('--mg-status-warning', '#f59e0b') },
    { key: 'paused',   label: '已暂停', value: fallback ? Math.round(total * 0.05) : counts.paused,   color: cssVar('--mg-status-idle',    '#94a3b8') },
    { key: 'fault',    label: '故障',   value: fallback ? Math.round(total * 0.05) : counts.fault,    color: cssVar('--mg-status-danger',  '#ef4444') },
    { key: 'offline',  label: '离线',   value: fallback ? Math.round(total * 0.05) : counts.offline,  color: cssVar('--mg-text-faint',     '#a8a0c0') }
  ]
})
const agvTotal = computed(() => agvDistribution.value.reduce((s, d) => s + d.value, 0))

type FleetTone = 'success' | 'danger' | 'warning' | 'idle'
interface FleetStat { key: string; label: string; value: number; tone: FleetTone }
const fleetHealthStats = computed<FleetStat[]>(() => {
  const byKey = Object.fromEntries(agvDistribution.value.map((s) => [s.key, s.value])) as Record<string, number>
  return [
    { key: 'online', label: '在线', value: Math.max(0, agvTotal.value - (byKey.offline ?? 0)), tone: 'success' },
    { key: 'fault', label: '故障', value: byKey.fault ?? 0, tone: 'danger' },
    { key: 'charging', label: '充电', value: byKey.charging ?? 0, tone: 'warning' },
    { key: 'idle', label: '空闲', value: byKey.idle ?? 0, tone: 'idle' }
  ]
})

// ─── 24h 任务趋势（5min 粒度，模拟数据；接入真实 telemetry 后替换） ───
const trendSeries = computed<Array<[number, number, number]>>(() => {
  const now = new Date()
  const points: Array<[number, number, number]> = []
  for (let i = 287; i >= 0; i--) {
    const ts = now.getTime() - i * 5 * 60 * 1000
    const phase = (i / 288) * Math.PI * 4
    const completed = Math.round(8 + Math.sin(phase) * 5 + Math.cos(phase * 1.3) * 3 + (Math.random() * 2 - 1))
    const ongoing = Math.round(3 + Math.sin(phase * 1.7) * 2 + Math.cos(phase * 0.6) * 1.5 + (Math.random() * 1 - 0.5))
    points.push([ts, Math.max(0, completed), Math.max(0, ongoing)])
  }
  return points
})

// ─── 报警信息（底部条，替代原待办） ───
type AlarmLevelKey = 'danger' | 'warning' | 'info'
function alarmLevelKey(level: number): AlarmLevelKey {
  if (level >= 2) return 'danger'
  if (level === 1) return 'warning'
  return 'info'
}
function alarmLevelLabel(level: number): string {
  return ({ danger: '严重', warning: '警告', info: '提示' } as const)[alarmLevelKey(level)]
}
function formatAlarmTime(iso: string): string {
  const d = new Date(iso)
  if (Number.isNaN(d.getTime())) return '--:--'
  return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
}
const alarmCount = computed(() => alarms.value.filter((a) => a.status === 'active').length)
const recentAlarms = computed(() => {
  const sorted = [...alarms.value].sort((a, b) => {
    const activeDelta = Number(b.status === 'active') - Number(a.status === 'active')
    if (activeDelta !== 0) return activeDelta
    return new Date(b.lastAt || b.firstAt).getTime() - new Date(a.lastAt || a.firstAt).getTime()
  })
  return sorted.slice(0, 8)
})
function goAlarms() {
  router.push({ name: 'admin-alarms' })
}

// ─── ECharts: 24h 趋势折线图 ───
let trendInst: echarts.ECharts | null = null
function renderTrendChart() {
  if (!trendChartRef.value) return
  trendInst?.dispose()
  trendInst = echarts.init(trendChartRef.value, undefined, { renderer: 'canvas' })
  const colorInfo    = cssVar('--mg-status-info',    '#3b82f6')
  const colorSuccess = cssVar('--mg-status-success', '#22c55e')
  const textMuted    = cssVar('--mg-text-muted',     '#606266')
  const grid         = cssVar('--mg-divider',        'rgba(255,255,255,0.08)')
  const series       = trendSeries.value
  trendInst.setOption({
    backgroundColor: 'transparent',
    grid: { left: 36, right: 16, top: 22, bottom: 28 },
    tooltip: {
      trigger: 'axis',
      axisPointer: { type: 'line', lineStyle: { type: 'dashed', color: textMuted } },
      formatter: (params: unknown) => {
        const arr = params as Array<{ value: [number, number]; color: string; seriesName: string }>
        const time = new Date(arr[0].value[0])
        const tt = `${String(time.getHours()).padStart(2, '0')}:${String(time.getMinutes()).padStart(2, '0')}`
        return arr.map((p) => `<span style="color:${p.color}">●</span> ${p.seriesName}: <b>${p.value[1]}</b>`).join('<br/>')
          + `<br/><span style="color:${textMuted};font-size:11px">${tt}</span>`
      }
    },
    legend: { show: false },
    xAxis: {
      type: 'time',
      axisLine: { lineStyle: { color: grid } },
      axisLabel: {
        color: textMuted, fontSize: 11,
        formatter: (v: number) => {
          const d = new Date(v)
          return `${String(d.getHours()).padStart(2, '0')}:00`
        }
      },
      splitLine: { show: false }
    },
    yAxis: {
      type: 'value',
      axisLine: { show: false },
      axisLabel: { color: textMuted, fontSize: 11, fontFamily: 'JetBrains Mono, monospace' },
      splitLine: { lineStyle: { color: grid, type: 'dashed' } }
    },
    series: [
      {
        name: '已完成', type: 'line', smooth: true, showSymbol: false,
        data: series.map((p) => [p[0], p[1]]),
        lineStyle: { width: 2, color: colorSuccess },
        areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
          colorStops: [{ offset: 0, color: `${colorSuccess}55` }, { offset: 1, color: `${colorSuccess}00` }] } },
        itemStyle: { color: colorSuccess }
      },
      {
        name: '执行中', type: 'line', smooth: true, showSymbol: false,
        data: series.map((p) => [p[0], p[2]]),
        lineStyle: { width: 2, color: colorInfo },
        areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
          colorStops: [{ offset: 0, color: `${colorInfo}55` }, { offset: 1, color: `${colorInfo}00` }] } },
        itemStyle: { color: colorInfo }
      }
    ]
  })
}

// ─── ECharts: AGV 状态分布饼图 ───
let agvInst: echarts.ECharts | null = null
function renderAgvChart() {
  if (!agvChartRef.value) return
  agvInst?.dispose()
  agvInst = echarts.init(agvChartRef.value, undefined, { renderer: 'canvas' })
  const textLight = cssVar('--mg-text-light', '#1a0f3d')
  const isLight = !textLight.startsWith('rgba(255') && textLight.toLowerCase() !== '#fff' && textLight.toLowerCase() !== '#ffffff'
  const ringBorder = isLight ? '#ffffff' : 'rgba(28,14,62,0.95)'
  const data = agvDistribution.value
  agvInst.setOption({
    backgroundColor: 'transparent',
    tooltip: { trigger: 'item', formatter: '{b}: {c} 台 ({d}%)' },
    color: data.map((d) => d.color),
    series: [{
      name: 'AGV 状态', type: 'pie',
      radius: ['62%', '88%'],
      center: ['50%', '50%'],
      avoidLabelOverlap: false,
      itemStyle: {
        borderColor: ringBorder,
        borderWidth: 2,
        borderRadius: 3
      },
      emphasis: {
        itemStyle: {
          shadowBlur: 12,
          shadowColor: 'rgba(124, 58, 237, 0.30)'
        },
        scale: true,
        scaleSize: 4
      },
      label: { show: false },
      labelLine: { show: false },
      data: data.map((d) => ({ name: d.label, value: d.value }))
    }]
  })
}

function onResize() {
  trendInst?.resize()
  agvInst?.resize()
}

onMounted(async () => {
  const [carList, missionList, alarmFeed] = await Promise.all([
    listCars(),
    listMissions(),
    fetchAlarmFeed(100).catch(() => ({ online: false, lastSyncAt: null, alarms: [] as VehicleAlarm[] }))
  ])
  cars.value = carList
  missions.value = missionList
  alarms.value = alarmFeed.alarms
  await nextTick()
  renderTrendChart()
  renderAgvChart()
  window.addEventListener('resize', onResize)
  clockTimer = setInterval(tick, 1000)
})

onUnmounted(() => {
  window.removeEventListener('resize', onResize)
  if (clockTimer) clearInterval(clockTimer)
  trendInst?.dispose(); trendInst = null
  agvInst?.dispose(); agvInst = null
})
</script>

<style scoped>
.dashboard {
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
/* AppShell 给路由页加了 .route-page{flex:1;min-height:0}，会把本页高度钉死在视口。
 * 若不锁定，纵向 flex 会按默认 flex-shrink:1 压扁各区块——hero-banner 又是
 * overflow:hidden，快捷入口就被裁掉。这里固定各区块自然高度，溢出交给 .main 滚动。 */
.dashboard > * {
  flex-shrink: 0;
}

/* ───────────── Hero Banner（深色沉浸背景跨主题响应） ─────────────
 * 设计策略：Hero 区始终是深色（参考 SaaS Dashboard 标杆），但色调跟随主题切换。
 *   - 大块背景用 --mg-bg-aside-* (深色侧栏色)
 *   - 光晕 orb 用 --mg-primary-hover-rgb / --mg-accent-rgb
 *   - 紫色 SVG 工业插画保留为品牌装饰，跨主题不变
 */
.hero-banner {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 480px;
  gap: 24px;
  padding: 28px 32px 22px;
  overflow: hidden;
  border-radius: 18px;
  background:
    radial-gradient(ellipse at 0% 0%, rgba(var(--mg-primary-hover-rgb), 0.50) 0%, transparent 55%),
    radial-gradient(ellipse at 100% 100%, rgba(var(--mg-accent-rgb), 0.45) 0%, transparent 55%),
    linear-gradient(135deg, var(--mg-bg-aside-1) 0%, var(--mg-bg-aside-2) 100%);
  border: 1px solid rgba(var(--mg-accent-rgb), 0.30);
  box-shadow:
    0 18px 44px rgba(0, 0, 0, 0.40),
    0 0 0 1px rgba(255, 255, 255, 0.06) inset,
    0 1px 0 rgba(255, 255, 255, 0.18) inset;
  color: #fff;
}
.hero-banner::before {
  content: '';
  position: absolute;
  top: 0; left: 12%; right: 12%;
  height: 1px;
  background: linear-gradient(90deg, transparent 0%, rgba(var(--mg-accent-rgb), 0.80) 50%, transparent 100%);
  pointer-events: none;
  z-index: 2;
}

/* Banner 内层装饰：紫色光斑 + 极淡科幻网格 */
.hero-bg {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  border-radius: 18px;
}
.hero-orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(80px);
  opacity: 0.55;
  animation: hero-drift 18s ease-in-out infinite;
}
.hero-orb-a {
  width: 360px; height: 360px;
  top: -120px; left: 10%;
  background: radial-gradient(circle,
    rgba(var(--mg-accent-rgb), 0.85) 0%,
    rgba(var(--mg-primary-rgb), 0.85) 55%,
    transparent 85%);
}
.hero-orb-b {
  width: 320px; height: 320px;
  bottom: -120px; right: 22%;
  background: radial-gradient(circle,
    rgba(var(--mg-primary-hover-rgb), 0.80) 0%,
    rgba(var(--mg-primary-rgb), 0.55) 55%,
    transparent 85%);
  animation-delay: -7s;
  opacity: 0.50;
}
.hero-grid {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(var(--mg-accent-rgb), 0.07) 1px, transparent 1px),
    linear-gradient(90deg, rgba(var(--mg-accent-rgb), 0.07) 1px, transparent 1px);
  background-size: 36px 36px;
  mask-image: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.85) 0%, transparent 75%);
  -webkit-mask-image: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.85) 0%, transparent 75%);
  opacity: 0.55;
}
@keyframes hero-drift {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50%      { transform: translate(24px, -28px) scale(1.06); }
}
.hero-left, .hero-right { position: relative; z-index: 1; }
.hero-tag {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 14px;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(var(--mg-accent-rgb), 0.55);
  border-radius: 999px;
  color: #fff;
  font-size: 12px;
  letter-spacing: 1.5px;
  width: max-content;
  box-shadow: 0 0 12px rgba(var(--mg-primary-rgb), 0.30);
}
.tag-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--mg-accent);
  box-shadow: 0 0 10px var(--mg-accent);
  animation: tag-pulse 1.6s ease-in-out infinite;
}
@keyframes tag-pulse {
  0%, 100% { opacity: 0.6; transform: scale(0.85); }
  50%      { opacity: 1;   transform: scale(1.15); }
}
.hero-title {
  margin: 14px 0 8px;
  font-size: 40px;
  font-weight: 700;
  letter-spacing: 5px;
  line-height: 1.1;
  color: #ffffff;
  background: linear-gradient(135deg, #ffffff 0%, var(--mg-accent) 50%, #ffffff 100%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  text-shadow: 0 4px 20px rgba(var(--mg-primary-rgb), 0.60);
  filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.50));
}
.hero-desc {
  margin: 0 0 22px;
  color: rgba(255, 255, 255, 0.85);
  font-size: 13px;
  letter-spacing: 0.8px;
}

/* ───── 快捷入口：上下两行均衡左对齐（最多 16 项 + 添加） ───── */
.quick-grid {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  max-width: 1080px;
}
.quick-row {
  display: flex;
  flex-wrap: nowrap;
  justify-content: flex-start;
  gap: 14px 18px;
}
.quick-item {
  appearance: none;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(var(--mg-accent-rgb), 0.35);
  border-radius: 14px;
  padding: 14px 10px 12px;
  width: 118px;
  flex: 0 0 auto;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  cursor: pointer;
  color: #fff;
  transition: all .25s cubic-bezier(.25, .8, .25, 1);
  position: relative;
  overflow: hidden;
  animation: quick-in .5s ease-out backwards;
  box-shadow:
    0 4px 12px rgba(0, 0, 0, 0.25),
    0 0 0 1px rgba(255, 255, 255, 0.06) inset;
}
.quick-item-remove {
  position: absolute;
  top: 6px;
  right: 8px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.35);
  color: rgba(255, 255, 255, 0.85);
  font-size: 14px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: 0;
  transition: opacity .2s, background .2s;
  z-index: 2;
}
.quick-item:hover .quick-item-remove { opacity: 1; }
.quick-item-remove:hover {
  background: rgba(var(--mg-status-danger-rgb), 0.75);
  color: #fff;
}
.quick-item.is-add .quick-item-remove { display: none; }
.quick-grid.is-dragging {
  user-select: none;
}
.quick-grid.is-dragging .quick-item:not(.is-add) {
  cursor: grabbing;
}
.quick-item.is-dragging-source {
  opacity: 0.35;
  transform: scale(0.96);
}
.quick-item.is-drag-hover-target {
  border-color: rgba(var(--mg-accent-rgb), 0.95);
  background: rgba(var(--mg-primary-rgb), 0.35);
  transform: translateY(-2px) scale(1.05);
  box-shadow:
    0 0 0 2px rgba(var(--mg-accent-rgb), 0.65),
    0 12px 28px rgba(var(--mg-primary-rgb), 0.45);
}
.quick-drag-ghost {
  position: fixed;
  z-index: 9999;
  pointer-events: none;
  transform: translate(-50%, -50%);
  width: 118px;
  padding: 14px 10px 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  border-radius: 14px;
  border: 1px solid rgba(var(--mg-accent-rgb), 0.85);
  background: rgba(var(--mg-primary-rgb), 0.92);
  color: #fff;
  box-shadow:
    0 16px 40px rgba(0, 0, 0, 0.45),
    0 0 0 2px rgba(var(--mg-accent-rgb), 0.5);
}
.quick-drag-ghost .quick-icon {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.12);
}
.quick-drag-ghost .quick-label {
  font-size: 12px;
  font-weight: 500;
  text-align: center;
  line-height: 1.3;
}
@keyframes quick-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.quick-item::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, transparent 30%, rgba(var(--mg-accent-rgb), 0.15) 100%);
  opacity: 0;
  transition: opacity .25s;
}
.quick-item:hover {
  border-color: rgba(var(--mg-accent-rgb), 0.75);
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  transform: translateY(-3px);
  box-shadow:
    0 12px 28px rgba(var(--mg-primary-rgb), 0.45),
    0 0 22px rgba(var(--mg-accent-rgb), 0.40);
}
.quick-item:hover::before { opacity: 1; }
.quick-icon {
  width: 44px; height: 44px;
  border-radius: 12px;
  background: linear-gradient(135deg,
    rgba(var(--mg-primary-hover-rgb), 0.60) 0%,
    rgba(var(--mg-primary-rgb), 0.45) 100%);
  border: 1px solid rgba(var(--mg-accent-rgb), 0.55);
  display: inline-flex; align-items: center; justify-content: center;
  color: #fff;
  box-shadow:
    0 6px 16px rgba(var(--mg-primary-rgb), 0.40),
    0 0 0 1px rgba(255, 255, 255, 0.18) inset;
  transition: all .25s;
}
.quick-item:hover .quick-icon {
  background: linear-gradient(135deg, var(--mg-primary-hover) 0%, var(--mg-primary) 100%);
  box-shadow:
    0 10px 24px rgba(var(--mg-primary-rgb), 0.60),
    0 0 24px rgba(var(--mg-accent-rgb), 0.60),
    0 0 0 1px rgba(255, 255, 255, 0.35) inset;
}
.quick-label {
  width: 100%;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.35;
  text-align: center;
  letter-spacing: 1.2px;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.quick-item.primary {
  border-color: rgba(var(--mg-accent-rgb), 0.75);
  background: linear-gradient(135deg,
    rgba(var(--mg-primary-hover-rgb), 0.45) 0%,
    rgba(var(--mg-primary-rgb), 0.25) 100%);
  box-shadow:
    0 8px 22px rgba(var(--mg-primary-rgb), 0.45),
    0 0 0 1px rgba(var(--mg-accent-rgb), 0.45) inset;
}
.quick-item.primary .quick-icon {
  background: linear-gradient(135deg,
    var(--mg-accent) 0%,
    var(--mg-primary-hover) 60%,
    var(--mg-primary) 100%);
  box-shadow:
    0 8px 20px rgba(var(--mg-primary-rgb), 0.55),
    0 0 28px rgba(var(--mg-accent-rgb), 0.55),
    0 0 0 1px rgba(255, 255, 255, 0.30) inset;
  animation: mg-pulse-glow 3.6s ease-in-out infinite;
}

/* ───── Hero 右侧场景 ───── */
.hero-right {
  position: relative;
  align-self: stretch;
  min-height: 280px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.hero-glow {
  position: absolute;
  inset: 10% 5%;
  background:
    radial-gradient(ellipse at 30% 40%, rgba(var(--mg-primary-hover-rgb), 0.55) 0%, transparent 60%),
    radial-gradient(ellipse at 70% 60%, rgba(var(--mg-accent-rgb), 0.30) 0%, transparent 65%);
  filter: blur(36px);
  pointer-events: none;
}
.hero-scene {
  position: relative;
  width: 100%;
  height: auto;
  max-height: 320px;
  filter: drop-shadow(0 12px 32px rgba(var(--mg-primary-rgb), 0.45));
}
.hero-scene .agv-1 { animation: agv-roll 6s ease-in-out infinite; }
.hero-scene .agv-2 { animation: agv-roll 8s ease-in-out infinite reverse; }
.hero-scene .float-data {
  animation: float-bob 4.5s ease-in-out infinite;
}
.hero-scene .float-data:nth-child(odd) { animation-delay: -2.2s; }
@keyframes agv-roll {
  0%, 100% { transform: translateX(0); }
  50%      { transform: translateX(-20px); }
}
@keyframes float-bob {
  0%, 100% { transform: translate(360px, 110px); }
  50%      { transform: translate(360px, 100px); }
}

/* ═════════════════════════════════════════════════════════════════════
 *  Dashboard 调色系统（v2 · 主题双兼容）
 *  ─ 默认（fame-lavender 浅紫）：白卡片 + 深紫文字 + 紫色描边 + 极淡阴影
 *  ─ industrial-purple 深紫底：深紫玻璃卡片 + 白文字 + 霓虹光晕
 *  ─ 关键修复：之前所有 bento 卡片是 `rgba(255,255,255,0.05)` 几乎透明白，
 *             在浅紫底下完全消失。现改为完全不透明 + 紫色描边 + 明确层级。
 * ═════════════════════════════════════════════════════════════════════ */

/* ───────────── 系统状态栏 · S2 语义色 chips ───────────── */
.status-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 10px 14px;
  border-radius: 14px;
  background: #ffffff;
  border: 1px solid #ebe6f4;
  box-shadow: 0 1px 2px rgba(45, 27, 105, 0.04);
  color: #1a0f3d;
  font-size: 13px;
}
.status-left,
.status-right {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.status-conn,
.status-stat {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border-radius: 999px;
  letter-spacing: 0.2px;
  border: 1px solid transparent;
}
.status-conn {
  font-weight: 700;
  font-size: 12.5px;
}
.status-conn.success {
  color: #0f7a45;
  background: #eaf8f0;
  border-color: #b7e4c8;
}
.status-conn.warning {
  color: #9a6700;
  background: #fff8e8;
  border-color: #f5d98a;
}
.status-conn.danger {
  color: #b42318;
  background: #fef3f2;
  border-color: #fecdca;
}
.status-stat-label {
  font-size: 12px;
  font-weight: 600;
}
.status-stat-value {
  font-weight: 700;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}
.status-stat-total {
  font-size: 12px;
  font-weight: 600;
  opacity: 0.72;
}

/* 在线 AGV · 冷静蓝 */
.status-stat.tone-agv {
  background: #eef4ff;
  border-color: #c7d7fe;
}
.status-stat.tone-agv .status-stat-label { color: #3b5bcc; }
.status-stat.tone-agv .status-stat-value { color: #1d4ed8; }
.status-stat.tone-agv .status-stat-total { color: #6474c4; }

/* 告警 · 浅玫（有活跃时加深） */
.status-stat.tone-alarm {
  background: #f8f0f1;
  border-color: #edd8db;
}
.status-stat.tone-alarm .status-stat-label { color: #9b6b72; }
.status-stat.tone-alarm .status-stat-value { color: #8a5a62; }
.status-stat.tone-alarm.has-alarm {
  background: #fff1f2;
  border-color: #fecdd3;
}
.status-stat.tone-alarm.has-alarm .status-stat-label,
.status-stat.tone-alarm.has-alarm .status-stat-value {
  color: #be123c;
}

/* 活跃任务 · 品牌淡紫 */
.status-stat.tone-task {
  background: #f3eeff;
  border-color: #ddd6fe;
}
.status-stat.tone-task .status-stat-label { color: #6d28d9; }
.status-stat.tone-task .status-stat-value { color: #5b21b6; }

/* 时钟 · 中性灰 */
.status-stat.tone-clock {
  background: #f5f5f7;
  border-color: #e5e5ea;
}
.status-stat.tone-clock .status-stat-label { color: #6b7280; }
.status-stat.tone-clock .status-stat-value { color: #374151; }

/* ───────────── A10 Analytics Grid ───────────── */
.analytics-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

/* 共通 Bento 卡片基类（浅底默认：白卡片 + 紫色描边 + 极淡阴影） */
.bento {
  position: relative;
  border-radius: 14px;
  background: #ffffff;
  border: 1px solid #e2dbf0;
  box-shadow:
    0 1px 2px rgba(45, 27, 105, 0.05),
    0 10px 26px rgba(108, 71, 200, 0.10);
  color: #2d1b69;
  transition: transform .25s cubic-bezier(.25,.8,.25,1), box-shadow .25s, border-color .25s;
  overflow: hidden;
}
.bento:hover {
  transform: translateY(-2px);
  border-color: #c4b5fd;
  box-shadow:
    0 4px 14px rgba(45, 27, 105, 0.10),
    0 18px 40px rgba(108, 71, 200, 0.16);
}

/* el-card 默认 padding 被我们包含 */
.bento :deep(.el-card) {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  color: inherit;
  height: 100%;
}
.bento :deep(.el-card__header) {
  background: transparent !important;
  border-bottom: 1px solid #f0eaf6 !important;
  padding: 14px 18px;
}
.bento :deep(.el-card__body) {
  padding: 16px 18px;
}

/* ───── 卡片标题 ───── */
.card-head {
  display: flex; align-items: center; justify-content: space-between;
}
.card-title {
  display: inline-flex; align-items: center; gap: 8px;
  color: #2d1b69;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.4px;
}
.card-title .el-icon {
  color: #7c3aed;
}
.card-tip {
  font-size: 12px;
  color: #6b5b8e;
  letter-spacing: 0.3px;
  font-weight: 500;
}
.more-link {
  color: #6b5b8e !important;
  font-size: 12.5px;
}
.more-link:hover { color: #7c3aed !important; }

/* ───── 双栏：趋势 | 车队健康 ───── */
.bento-trend,
.bento-fleet {
  min-height: 340px;
  display: flex;
  flex-direction: column;
}
.bento-trend :deep(.el-card),
.bento-fleet :deep(.el-card) {
  display: flex;
  flex-direction: column;
  height: 100%;
}
.bento-trend :deep(.el-card__body),
.bento-fleet :deep(.el-card__body) {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 14px 18px 18px;
}
.trend-chart {
  flex: 1;
  width: 100%;
  height: 100%;
  min-height: 240px;
}

.fleet-body {
  display: flex;
  flex-direction: column;
  gap: 14px;
  flex: 1;
  min-height: 0;
}
.bento-fleet :deep(.el-card__body) { padding: 14px 16px 16px; }

/* 标题角的 示例 标签 */
.mock-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  background: #f0eaf6;
  color: #7c3aed;
  border-radius: 4px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.3px;
  vertical-align: 1px;
}

.agv-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 200px;
  flex: 1;
}

.fleet-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
}
.fleet-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 12px 8px 10px;
  border-radius: 12px;
  background: #faf7fd;
  border: 1px solid #f0eaf6;
}
.fleet-stat-value {
  font-size: 22px;
  font-weight: 700;
  font-family: var(--mg-font-mono);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: #1a0f3d;
}
.fleet-stat-label {
  font-size: 11.5px;
  font-weight: 600;
  color: #6b5b8e;
  letter-spacing: 0.4px;
}
.fleet-stat.tone-success .fleet-stat-value { color: var(--mg-status-success); }
.fleet-stat.tone-danger .fleet-stat-value { color: var(--mg-status-danger); }
.fleet-stat.tone-warning .fleet-stat-value { color: var(--mg-status-warning); }
.fleet-stat.tone-idle .fleet-stat-value { color: var(--mg-status-idle); }

/* 环形图容器 + 中心总数（HTML 叠加，与 echart 分离更好控制） */
.agv-chart-wrap {
  position: relative;
  width: 170px;
  height: 200px;
  flex-shrink: 0;
}
.agv-chart {
  width: 100%;
  height: 100%;
}
.agv-center {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  display: flex; flex-direction: column;
  align-items: center;
  pointer-events: none;
}
.agv-center-value {
  font-size: 28px;
  font-weight: 700;
  color: #1a0f3d;
  font-family: var(--mg-font-mono);
  font-variant-numeric: tabular-nums;
  line-height: 1.05;
  letter-spacing: -0.5px;
}
.agv-center-label {
  font-size: 11px;
  color: #6b5b8e;
  font-weight: 500;
  letter-spacing: 0.5px;
  margin-top: 2px;
}

.agv-legend {
  flex: 1;
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex; flex-direction: column; gap: 4px;
}
.agv-legend li {
  display: grid;
  grid-template-columns: 12px 1fr auto auto;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  color: #3d2b6e;
  padding: 5px 8px;
  border-radius: 6px;
  transition: background .2s;
}
.agv-legend li:hover {
  background: #f5f3ff;
}
.agv-legend .legend-name { color: #4a3d6e; font-weight: 500; }
.agv-legend .legend-value {
  font-weight: 700;
  color: #1a0f3d;
  font-family: var(--mg-font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  min-width: 18px;
}
.agv-legend .legend-pct {
  font-size: 11px;
  color: #9b8eb8;
  font-family: var(--mg-font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  min-width: 32px;
}
.agv-legend .dot {
  width: 10px; height: 10px; border-radius: 50%;
  box-shadow: 0 0 0 2px #ffffff, 0 0 0 3px rgba(0, 0, 0, 0.05);
}

/* ───── 底栏：任务占比 + 报警信息 ───── */
.bento-bottom {
  grid-column: 1 / -1;
}
.bento-bottom :deep(.el-card__body) {
  padding: 16px 18px 18px;
}
.bottom-split {
  display: grid;
  grid-template-columns: minmax(280px, 0.95fr) minmax(320px, 1.05fr);
  gap: 22px 28px;
  align-items: start;
}
.bottom-head {
  margin-bottom: 12px;
}
.alarm-head-right {
  display: inline-flex;
  align-items: center;
  gap: 12px;
}
.alarm-active-count {
  font-size: 12px;
  font-weight: 700;
  color: var(--mg-status-danger);
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(var(--mg-status-danger-rgb), 0.10);
  border: 1px solid rgba(var(--mg-status-danger-rgb), 0.28);
}
.ratio-track {
  display: flex;
  width: 100%;
  height: 32px;
  border-radius: 999px;
  overflow: hidden;
  background: #f0eaf6;
  border: 1px solid #e8e1f0;
  margin-bottom: 14px;
  box-shadow: inset 0 1px 2px rgba(45, 27, 105, 0.04);
}
.ratio-seg {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.4px;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  cursor: default;
  position: relative;
  transition: filter .2s;
}
.ratio-seg:hover { filter: brightness(1.10); }
.ratio-seg.tone-info    { background: linear-gradient(180deg, var(--mg-status-info),    rgba(var(--mg-status-info-rgb),    0.92)); }
.ratio-seg.tone-success { background: linear-gradient(180deg, var(--mg-status-success), rgba(var(--mg-status-success-rgb), 0.92)); }
.ratio-seg.tone-warning { background: linear-gradient(180deg, var(--mg-status-warning), rgba(var(--mg-status-warning-rgb), 0.92)); }
.ratio-seg.tone-idle    { background: linear-gradient(180deg, var(--mg-status-idle),    rgba(var(--mg-status-idle-rgb),    0.92)); }
.ratio-seg-pct {
  font-family: var(--mg-font-mono);
  font-variant-numeric: tabular-nums;
}
.ratio-legend {
  list-style: none; padding: 0; margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 8px 18px;
}
.ratio-legend li {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px;
  color: #3d2b6e;
}
.ratio-legend .dot {
  width: 10px; height: 10px; border-radius: 50%;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.95);
}
.ratio-legend .dot.tone-info    { background: var(--mg-status-info); }
.ratio-legend .dot.tone-success { background: var(--mg-status-success); }
.ratio-legend .dot.tone-warning { background: var(--mg-status-warning); }
.ratio-legend .dot.tone-idle    { background: var(--mg-status-idle); }
.ratio-legend .legend-name {
  flex: 1;
  color: #6b5b8e;
  font-weight: 500;
}
.ratio-legend .legend-value {
  font-weight: 700;
  color: #1a0f3d;
  font-family: var(--mg-font-mono);
  font-variant-numeric: tabular-nums;
}

.empty-pane {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 20px 0 12px;
  color: #6b5b8e;
  font-size: 13px;
}
.empty-pane.compact { padding: 28px 0 16px; }
.empty-pane p { margin: 6px 0 0; font-weight: 500; }

.alarm-chip-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  max-height: 168px;
  overflow-y: auto;
}
.alarm-chip {
  display: inline-grid;
  grid-template-columns: auto auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
  max-width: 100%;
  padding: 8px 10px;
  border-radius: 10px;
  background: #faf7fd;
  border: 1px solid #f0eaf6;
  font-size: 12.5px;
  color: #2d1b69;
  cursor: pointer;
  transition: background .2s, border-color .2s, box-shadow .2s;
}
.alarm-chip:hover {
  background: #f5f3ff;
  border-color: #c4b5fd;
  box-shadow: 0 2px 6px rgba(124, 58, 237, 0.08);
}
.alarm-chip.cleared { opacity: 0.62; }
.alarm-chip-lvl {
  font-size: 11px;
  font-weight: 700;
  padding: 2px 7px;
  border-radius: 999px;
  letter-spacing: 0.3px;
  white-space: nowrap;
}
.alarm-chip.lvl-danger .alarm-chip-lvl {
  background: rgba(var(--mg-status-danger-rgb), 0.12);
  color: var(--mg-status-danger);
  border: 1px solid rgba(var(--mg-status-danger-rgb), 0.32);
}
.alarm-chip.lvl-warning .alarm-chip-lvl {
  background: rgba(var(--mg-status-warning-rgb), 0.12);
  color: var(--mg-status-warning);
  border: 1px solid rgba(var(--mg-status-warning-rgb), 0.32);
}
.alarm-chip.lvl-info .alarm-chip-lvl {
  background: rgba(var(--mg-status-info-rgb), 0.12);
  color: var(--mg-status-info);
  border: 1px solid rgba(var(--mg-status-info-rgb), 0.32);
}
.alarm-chip-car {
  font-weight: 700;
  color: #4a3d6e;
  white-space: nowrap;
}
.alarm-chip-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 500;
  min-width: 0;
}
.alarm-chip-time {
  color: #9b8eb8;
  font-size: 11.5px;
  font-weight: 500;
  white-space: nowrap;
}

/* ───── 响应式 ───── */
@media (max-width: 1280px) {
  .hero-banner  { grid-template-columns: 1fr 380px; }
  .hero-right   { min-height: 240px; }
  .analytics-grid { grid-template-columns: 1fr; }
  .bottom-split { grid-template-columns: 1fr; }
}
@media (max-width: 1024px) {
  .status-bar   { flex-wrap: wrap; }
  .hero-banner  { grid-template-columns: 1fr; }
  .hero-right   { display: none; }
}
@media (max-width: 900px) {
  .quick-item { width: 104px; }
  .quick-row { gap: 10px 12px; flex-wrap: wrap; max-width: 100%; }
  .fleet-stats { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 720px) {
  .quick-item { width: 92px; }
  .agv-row      { flex-direction: column; align-items: stretch; }
  .agv-chart-wrap { width: 100%; height: 220px; }
  .alarm-chip {
    grid-template-columns: auto 1fr auto;
    width: 100%;
  }
  .alarm-chip-car { grid-column: 2; }
  .alarm-chip-text { grid-column: 1 / -1; }
}

/* ═════════════════════════════════════════════════════════════════════
 *  industrial-purple（星云紫深底）主题覆盖
 *  当 :root[data-theme="industrial-purple"] 激活时，把上面的浅底卡片
 *  转为深紫玻璃风 + 白文字 + 霓虹辉光。
 * ═════════════════════════════════════════════════════════════════════ */
:root[data-theme="industrial-purple"] .status-bar {
  background:
    linear-gradient(135deg,
      rgba(38, 24, 78, 0.94) 0%,
      rgba(20, 12, 48, 0.97) 100%);
  border-color: rgba(var(--mg-accent-rgb), 0.28);
  box-shadow:
    0 4px 16px rgba(0, 0, 0, 0.38),
    0 0 0 1px rgba(255, 255, 255, 0.06) inset;
  color: #f4eeff;
}
:root[data-theme="industrial-purple"] .status-conn.success {
  color: #86efac;
  background: rgba(var(--mg-status-success-rgb), 0.22);
  border-color: rgba(var(--mg-status-success-rgb), 0.45);
}
:root[data-theme="industrial-purple"] .status-conn.warning {
  color: #fcd34d;
  background: rgba(var(--mg-status-warning-rgb), 0.22);
  border-color: rgba(var(--mg-status-warning-rgb), 0.45);
}
:root[data-theme="industrial-purple"] .status-conn.danger {
  color: #fca5a5;
  background: rgba(var(--mg-status-danger-rgb), 0.22);
  border-color: rgba(var(--mg-status-danger-rgb), 0.45);
}
:root[data-theme="industrial-purple"] .status-stat.tone-agv {
  background: rgba(59, 130, 246, 0.18);
  border-color: rgba(59, 130, 246, 0.40);
}
:root[data-theme="industrial-purple"] .status-stat.tone-agv .status-stat-label { color: #93c5fd; }
:root[data-theme="industrial-purple"] .status-stat.tone-agv .status-stat-value { color: #bfdbfe; }
:root[data-theme="industrial-purple"] .status-stat.tone-agv .status-stat-total { color: rgba(191, 219, 254, 0.72); }
:root[data-theme="industrial-purple"] .status-stat.tone-alarm {
  background: rgba(244, 63, 94, 0.12);
  border-color: rgba(244, 63, 94, 0.28);
}
:root[data-theme="industrial-purple"] .status-stat.tone-alarm .status-stat-label,
:root[data-theme="industrial-purple"] .status-stat.tone-alarm .status-stat-value { color: #fda4af; }
:root[data-theme="industrial-purple"] .status-stat.tone-alarm.has-alarm {
  background: rgba(244, 63, 94, 0.22);
  border-color: rgba(244, 63, 94, 0.48);
}
:root[data-theme="industrial-purple"] .status-stat.tone-alarm.has-alarm .status-stat-label,
:root[data-theme="industrial-purple"] .status-stat.tone-alarm.has-alarm .status-stat-value { color: #fecdd3; }
:root[data-theme="industrial-purple"] .status-stat.tone-task {
  background: rgba(var(--mg-accent-rgb), 0.18);
  border-color: rgba(var(--mg-accent-rgb), 0.38);
}
:root[data-theme="industrial-purple"] .status-stat.tone-task .status-stat-label { color: #c4b5fd; }
:root[data-theme="industrial-purple"] .status-stat.tone-task .status-stat-value { color: #e9d5ff; }
:root[data-theme="industrial-purple"] .status-stat.tone-clock {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.12);
}
:root[data-theme="industrial-purple"] .status-stat.tone-clock .status-stat-label { color: rgba(255, 255, 255, 0.62); }
:root[data-theme="industrial-purple"] .status-stat.tone-clock .status-stat-value { color: rgba(255, 255, 255, 0.88); }

:root[data-theme="industrial-purple"] .bento {
  background:
    linear-gradient(135deg, rgba(38, 24, 78, 0.78) 0%, rgba(20, 12, 48, 0.86) 100%);
  border-color: rgba(var(--mg-accent-rgb), 0.16);
  box-shadow:
    0 10px 28px rgba(0, 0, 0, 0.45),
    0 0 22px rgba(var(--mg-primary-rgb), 0.16),
    0 0 0 1px rgba(255, 255, 255, 0.04) inset,
    0 1px 0 rgba(255, 255, 255, 0.12) inset;
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  color: #ffffff;
}
:root[data-theme="industrial-purple"] .bento:hover {
  border-color: rgba(var(--mg-accent-rgb), 0.45);
  box-shadow:
    0 18px 40px rgba(0, 0, 0, 0.55),
    0 0 38px rgba(var(--mg-primary-hover-rgb), 0.32),
    0 0 0 1px rgba(var(--mg-accent-rgb), 0.25) inset,
    0 1px 0 rgba(255, 255, 255, 0.18) inset;
}
:root[data-theme="industrial-purple"] .bento :deep(.el-card__header) {
  border-bottom-color: rgba(var(--mg-accent-rgb), 0.14) !important;
}
:root[data-theme="industrial-purple"] .card-title { color: #ffffff; }
:root[data-theme="industrial-purple"] .card-title .el-icon { color: var(--mg-accent); }
:root[data-theme="industrial-purple"] .card-tip { color: rgba(255, 255, 255, 0.70); }
:root[data-theme="industrial-purple"] .more-link { color: rgba(255, 255, 255, 0.70) !important; }
:root[data-theme="industrial-purple"] .more-link:hover { color: var(--mg-accent) !important; }

:root[data-theme="industrial-purple"] .kpi-label { color: rgba(255, 255, 255, 0.72); }
:root[data-theme="industrial-purple"] .kpi-value {
  color: #ffffff;
  text-shadow: 0 0 18px rgba(var(--mg-primary-hover-rgb), 0.35);
}

:root[data-theme="industrial-purple"] .agv-center-value { color: #ffffff; text-shadow: 0 0 18px rgba(var(--mg-primary-hover-rgb), 0.45); }
:root[data-theme="industrial-purple"] .agv-center-label { color: rgba(255, 255, 255, 0.72); }
:root[data-theme="industrial-purple"] .agv-legend li { color: #f4eeff; }
:root[data-theme="industrial-purple"] .agv-legend li:hover { background: rgba(var(--mg-accent-rgb), 0.10); }
:root[data-theme="industrial-purple"] .agv-legend .legend-name { color: rgba(255, 255, 255, 0.78); }
:root[data-theme="industrial-purple"] .agv-legend .legend-value { color: #ffffff; }
:root[data-theme="industrial-purple"] .agv-legend .legend-pct { color: rgba(255, 255, 255, 0.55); }
:root[data-theme="industrial-purple"] .agv-legend .dot { box-shadow: 0 0 8px currentColor; }
:root[data-theme="industrial-purple"] .mock-tag {
  background: rgba(var(--mg-accent-rgb), 0.18);
  color: var(--mg-accent);
}

:root[data-theme="industrial-purple"] .ratio-track {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.08);
}
:root[data-theme="industrial-purple"] .ratio-legend li { color: #f4eeff; }
:root[data-theme="industrial-purple"] .ratio-legend .legend-name { color: rgba(255, 255, 255, 0.72); }
:root[data-theme="industrial-purple"] .ratio-legend .legend-value { color: #ffffff; }
:root[data-theme="industrial-purple"] .ratio-legend .dot { box-shadow: 0 0 8px currentColor; }

:root[data-theme="industrial-purple"] .empty-pane { color: rgba(255, 255, 255, 0.70); }
:root[data-theme="industrial-purple"] .fleet-stat {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.08);
}
:root[data-theme="industrial-purple"] .fleet-stat-value { color: #ffffff; }
:root[data-theme="industrial-purple"] .fleet-stat-label { color: rgba(255, 255, 255, 0.65); }
:root[data-theme="industrial-purple"] .alarm-chip {
  background: rgba(255, 255, 255, 0.03);
  border-color: rgba(255, 255, 255, 0.06);
  color: #f4eeff;
}
:root[data-theme="industrial-purple"] .alarm-chip:hover {
  background: rgba(var(--mg-accent-rgb), 0.08);
  border-color: rgba(var(--mg-accent-rgb), 0.30);
}
:root[data-theme="industrial-purple"] .alarm-chip-car { color: rgba(255, 255, 255, 0.88); }
:root[data-theme="industrial-purple"] .alarm-chip-time { color: rgba(255, 255, 255, 0.55); }
</style>

<style>
/* Outpost：Hero 从「深色霓虹沉浸」翻成纸白工作台（这是首页观感差的主因） */
:root[data-shell="outpost"] .dashboard .hero-banner {
  background: #fffefd !important;
  border: 1px solid rgba(40, 33, 58, 0.08) !important;
  box-shadow:
    0 1px 2px rgba(40, 33, 58, 0.04),
    0 14px 36px rgba(54, 35, 78, 0.08) !important;
  color: #28213a !important;
}
:root[data-shell="outpost"] .dashboard .hero-banner::before {
  background: linear-gradient(90deg, transparent 0%, rgba(117, 67, 232, 0.35) 50%, transparent 100%) !important;
}
:root[data-shell="outpost"] .dashboard .hero-orb,
:root[data-shell="outpost"] .dashboard .hero-grid {
  display: none !important;
}
:root[data-shell="outpost"] .dashboard .hero-tag {
  background: #f6f3fb !important;
  border-color: rgba(117, 67, 232, 0.22) !important;
  color: #5d2ecb !important;
  box-shadow: none !important;
}
:root[data-shell="outpost"] .dashboard .tag-dot {
  box-shadow: none !important;
}
:root[data-shell="outpost"] .dashboard .hero-title {
  background: none !important;
  -webkit-background-clip: unset !important;
  background-clip: unset !important;
  -webkit-text-fill-color: #28213a !important;
  color: #28213a !important;
  text-shadow: none !important;
  filter: none !important;
  letter-spacing: 2px !important;
}
:root[data-shell="outpost"] .dashboard .hero-desc {
  color: #756d85 !important;
}
:root[data-shell="outpost"] .dashboard .quick-item {
  background: #fbf9fd !important;
  border: 1px solid rgba(40, 33, 58, 0.1) !important;
  color: #28213a !important;
  box-shadow: 0 2px 8px rgba(54, 35, 78, 0.06) !important;
}
:root[data-shell="outpost"] .dashboard .quick-item:hover {
  background: #fff !important;
  border-color: rgba(117, 67, 232, 0.35) !important;
  box-shadow: 0 8px 20px rgba(117, 67, 232, 0.12) !important;
}
:root[data-shell="outpost"] .dashboard .quick-label {
  color: #28213a !important;
  text-shadow: none !important;
}
:root[data-shell="outpost"] .dashboard .quick-icon {
  background: linear-gradient(135deg, #7543e8 0%, #9b7cff 100%) !important;
  border-color: transparent !important;
  box-shadow: 0 4px 12px rgba(117, 67, 232, 0.25) !important;
}
:root[data-shell="outpost"] .dashboard .quick-item:hover .quick-icon {
  box-shadow: 0 8px 18px rgba(117, 67, 232, 0.35) !important;
}
:root[data-shell="outpost"] .dashboard .quick-item.primary {
  background: #f6f3fb !important;
  border-color: rgba(117, 67, 232, 0.35) !important;
  box-shadow: 0 4px 14px rgba(117, 67, 232, 0.1) !important;
}
:root[data-shell="outpost"] .dashboard .quick-item.primary .quick-icon {
  animation: none !important;
}
:root[data-shell="outpost"] .dashboard .quick-item-remove {
  background: rgba(40, 33, 58, 0.08) !important;
  color: #756d85 !important;
}
:root[data-shell="outpost"] .dashboard .hero-glow {
  opacity: 0.35 !important;
  filter: blur(48px) !important;
}
:root[data-shell="outpost"] .dashboard .hero-scene {
  filter: drop-shadow(0 10px 24px rgba(117, 67, 232, 0.18)) !important;
}
</style>
