<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-divider" />
        <span class="status-stat">
          <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-divider" />
        <span class="status-stat">
          <span class="status-stat-label">告警</span>
          <span class="status-stat-value mg-num" :class="{ 'is-danger': alarmCount > 0 }">{{ alarmCount }}</span>
        </span>
        <span class="status-divider" />
        <span class="status-stat">
          <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">
          <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">
          <button
            v-for="(item, idx) in quickEntries"
            :key="item.key"
            class="quick-item"
            :class="{ primary: item.primary }"
            :style="{ animationDelay: `${idx * 60}ms` }"
            :title="item.hint ?? item.label"
            @click="onQuickClick(item)">
            <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>

      <!-- 右侧工业 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>

    <!-- ===== Bento Grid 主体（KPI 6 + 趋势 + AGV 分布 + 任务占比 + 待办） ===== -->
    <section class="bento-grid">
      <!-- KPI ×6 -->
      <div class="bento bento-kpi" v-for="kpi in kpis" :key="kpi.key">
        <div class="kpi-accent" :class="`tone-${kpi.tone}`" />
        <div class="kpi-body">
          <div class="kpi-row1">
            <el-icon :size="18" class="kpi-icon-sm" :class="`tone-${kpi.tone}`"><component :is="kpi.icon" /></el-icon>
            <span class="kpi-label">{{ kpi.label }}</span>
          </div>
          <div class="kpi-row2">
            <span class="kpi-value mg-num">{{ kpi.value }}</span>
            <span class="kpi-trend" :class="`trend-${kpi.trend.dir}`">
              <span class="kpi-trend-arrow">{{ kpi.trend.arrow }}</span>
              <span class="kpi-trend-text mg-num">{{ kpi.trend.text }}</span>
            </span>
          </div>
        </div>
        <svg class="kpi-spark" viewBox="0 0 100 32" preserveAspectRatio="none" aria-hidden="true">
          <defs>
            <linearGradient :id="`spark-grad-${kpi.key}`" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%" :stop-color="kpi.tone === 'danger' ? 'var(--mg-status-danger)' : kpi.tone === 'warning' ? 'var(--mg-status-warning)' : 'var(--mg-primary-hover)'" stop-opacity="0.50" />
              <stop offset="100%" :stop-color="kpi.tone === 'danger' ? 'var(--mg-status-danger)' : kpi.tone === 'warning' ? 'var(--mg-status-warning)' : 'var(--mg-primary-hover)'" stop-opacity="0" />
            </linearGradient>
          </defs>
          <path :d="kpi.sparkArea" :fill="`url(#spark-grad-${kpi.key})`" />
          <path :d="kpi.sparkLine" fill="none" stroke-width="1.5"
                :stroke="kpi.tone === 'danger' ? 'var(--mg-status-danger)' : kpi.tone === 'warning' ? 'var(--mg-status-warning)' : 'var(--mg-primary-hover)'" />
        </svg>
      </div>

      <!-- 24h 任务趋势折线图 -->
      <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>

      <!-- AGV 状态分布饼图 -->
      <el-card shadow="never" class="bento bento-agv">
        <template #header>
          <div class="card-head">
            <span class="card-title">
              <el-icon><Van /></el-icon>
              AGV 状态分布
            </span>
            <span class="card-tip mg-num">{{ agvTotal }} 台<span v-if="agvIsMock" class="mock-tag">·示例</span></span>
          </div>
        </template>
        <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>
      </el-card>

      <!-- 任务占比 横向堆叠条 -->
      <el-card shadow="never" class="bento bento-ratio">
        <template #header>
          <div class="card-head">
            <span class="card-title">
              <el-icon><PieChart /></el-icon>
              各任务占比
            </span>
            <span class="card-tip mg-num">总计 {{ missions.length }} 个任务</span>
          </div>
        </template>
        <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>
      </el-card>

      <!-- 待办事项 -->
      <el-card shadow="never" class="bento bento-todo">
        <template #header>
          <div class="card-head">
            <span class="card-title">
              <el-icon><Bell /></el-icon>
              待办事项
            </span>
            <el-link class="more-link" underline="never">详情 ›</el-link>
          </div>
        </template>

        <el-tabs v-model="activeTab" class="todo-tabs">
          <el-tab-pane v-for="t in todoTabs" :key="t.name" :name="t.name">
            <template #label>
              <span class="tab-label">
                {{ t.label }}
                <em class="tab-count" :class="t.tone">{{ t.count }}</em>
              </span>
            </template>
            <div v-if="t.count === 0" class="empty-pane">
              <svg width="120" height="80" viewBox="0 0 120 80" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
                <defs>
                  <linearGradient id="empGrad" x1="0" y1="0" x2="1" y2="1">
                    <stop offset="0%" stop-color="var(--mg-primary-hover)" />
                    <stop offset="100%" stop-color="var(--mg-primary-active)" />
                  </linearGradient>
                </defs>
                <ellipse cx="60" cy="68" rx="40" ry="6" fill="rgba(var(--mg-accent-rgb),0.15)" />
                <rect x="34" y="28" width="52" height="32" rx="4" fill="url(#empGrad)" opacity="0.8" />
                <rect x="40" y="34" width="40" height="3" rx="1.5" fill="rgba(255,255,255,0.4)" />
                <rect x="40" y="42" width="30" height="3" rx="1.5" fill="rgba(255,255,255,0.30)" />
                <rect x="40" y="50" width="36" height="3" rx="1.5" fill="rgba(255,255,255,0.20)" />
                <circle cx="80" cy="22" r="6" fill="var(--mg-accent)" opacity="0.85" />
                <path d="M77 22h6M80 19v6" stroke="var(--mg-bg-aside-2)" stroke-width="1.5" stroke-linecap="round" />
              </svg>
              <p>暂无数据</p>
            </div>
            <ul v-else class="todo-list">
              <li v-for="(it, i) in t.items ?? []" :key="i">
                <span class="todo-pill" :class="t.tone">{{ t.label }}</span>
                <span class="todo-text">{{ it.text }}</span>
                <span class="todo-time">{{ it.time }}</span>
              </li>
            </ul>
          </el-tab-pane>
        </el-tabs>
      </el-card>
    </section>
  </div>
</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 } from 'element-plus'
import {
  Avatar, Bell, Box, Coordinate, Cpu, Lightning,
  List, Operation, PieChart, Plus, Setting,
  Tools, TrendCharts, Van, Warning
} from '@element-plus/icons-vue'
import { listCars, listMissions, listSites, listTracks } from '@/api/projection'
import type { Site, Track } from '@/types/map'
import type { Car } from '@/types/car'
import type { Mission } from '@/types/mission'

const router = useRouter()

const sites = ref<Site[]>([])
const tracks = ref<Track[]>([])
const cars = ref<Car[]>([])
const missions = ref<Mission[]>([])

const trendChartRef = ref<HTMLDivElement>()
const agvChartRef = ref<HTMLDivElement>()
const activeTab = ref('alarm')

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
)

// ─── 快捷入口 ───
interface QuickEntry { key: string; label: string; icon: unknown; hint?: string; primary?: boolean; to?: string; action?: () => void }
const quickEntries = computed<QuickEntry[]>(() => [
  { key: 'platform-config', label: '平台配置', icon: Setting, primary: true, hint: '进入地图编辑 / 平台搭建（map-editor）', to: '/admin/map-editor' },
  { key: 'mission', label: '任务编排', icon: Operation, to: '/admin/missions' },
  { key: 'cars', label: 'AGV 配置', icon: Van, to: '/admin/cars' },
  { key: 'auth', label: '权限配置', icon: Avatar, to: '/admin/config/auth' },
  { key: 'system', label: '系统配置', icon: Tools, to: '/admin/config/system' },
  { key: 'ops', label: '异常处理', icon: Warning, to: '/admin/config/ops' },
  { key: 'tasks', label: '任务管理', icon: List, to: '/admin/config/task' },
  { key: 'add', label: '添加', icon: Plus, hint: '自定义快捷入口（待实现）', action: () => { ElMessage.info('自定义快捷入口 — 即将在 v1.8 中开放') } }
])
function onQuickClick(item: QuickEntry) {
  if (item.action) item.action()
  else if (item.to) router.push(item.to)
}

// ─── KPI（每张卡片含 trend + sparkline） ───
type KpiTone = 'info' | 'success' | 'warning' | 'danger' | 'idle'
interface KpiTrend { dir: 'up' | 'down' | 'flat'; arrow: '↑' | '↓' | '→'; text: string }
interface Kpi {
  key: string; label: string; value: number; icon: unknown; tone: KpiTone
  trend: KpiTrend; sparkLine: string; sparkArea: string
}

function makeSpark(seed: number, len = 16): { line: string; area: string } {
  const pts: number[] = []
  let v = 50 + ((seed * 7) % 30)
  for (let i = 0; i < len; i++) {
    v += Math.sin((i + seed) * 0.55) * 6 + ((i * seed) % 5) - 2
    v = Math.max(8, Math.min(96, v))
    pts.push(v)
  }
  const w = 100, h = 32
  const step = w / (len - 1)
  const line = pts.map((p, i) =>
    `${i === 0 ? 'M' : 'L'}${(i * step).toFixed(2)},${(h - (p / 100) * h * 0.85 - h * 0.05).toFixed(2)}`
  ).join(' ')
  const area = `${line} L${w},${h} L0,${h} Z`
  return { line, area }
}

const estimateShelves  = computed(() => Math.max(12, Math.round(sites.value.length * 0.7)))
const estimateCharger  = computed(() => Math.max(6,  Math.round(cars.value.length * 0.27)))
const estimateUsedSlot = computed(() => Math.max(265, sites.value.length * 12))
const estimateFreeSlot = computed(() => Math.max(10,  Math.round(estimateUsedSlot.value * 0.04)))

function buildTrend(prev: number, curr: number): KpiTrend {
  if (prev === 0 && curr === 0) return { dir: 'flat', arrow: '→', text: '0%' }
  const diff = curr - prev
  if (Math.abs(diff) < 0.5) return { dir: 'flat', arrow: '→', text: '0%' }
  const pct = prev === 0 ? 100 : Math.round((diff / prev) * 100)
  if (diff > 0) return { dir: 'up',   arrow: '↑', text: `${pct}%` }
  return            { dir: 'down', arrow: '↓', text: `${Math.abs(pct)}%` }
}

const kpis = computed<Kpi[]>(() => {
  const raw: Array<Omit<Kpi, 'sparkLine' | 'sparkArea'>> = [
    { key: 'devices', label: '设备数',     value: cars.value.length,        icon: Cpu,        tone: 'info',    trend: buildTrend(cars.value.length - 2,    cars.value.length) },
    { key: 'sites',   label: '工作站数',   value: sites.value.length,       icon: Coordinate, tone: 'success', trend: buildTrend(sites.value.length - 1,   sites.value.length) },
    { key: 'shelves', label: '货架数',     value: estimateShelves.value,    icon: Box,        tone: 'info',    trend: buildTrend(estimateShelves.value - 5, estimateShelves.value) },
    { key: 'charger', label: '充电桩数',   value: estimateCharger.value,    icon: Lightning,  tone: 'warning', trend: { dir: 'flat', arrow: '→', text: '0%' } },
    { key: 'used',    label: '已用储位数', value: estimateUsedSlot.value,   icon: Operation,  tone: 'success', trend: buildTrend(estimateUsedSlot.value - 12, estimateUsedSlot.value) },
    { key: 'free',    label: '空余储位数', value: estimateFreeSlot.value,   icon: Operation,  tone: 'idle',    trend: buildTrend(estimateFreeSlot.value + 3,  estimateFreeSlot.value) }
  ]
  return raw.map((k, i) => {
    const sp = makeSpark(i + 1)
    return { ...k, sparkLine: sp.line, sparkArea: sp.area }
  })
})

// ─── 任务占比（横向堆叠条） ───
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))

// ─── 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
})

// ─── 待办事项 Tabs ───
// E12 修复：每条 item 改为对象 `{ text, time }`，time 在 ref 初始化时一次性生成；
// 之前模板里直接调 `formatTime()` 含 Math.random()，每次 reactivity tick 都重新渲染随机时间，
// 用户体验上像数据在闪。time 此处仅用于"示例数据"展示，等接入真实告警源后改为 server 提供的时间戳。
interface TodoItem { text: string; time: string }
interface TodoTab { name: string; label: string; count: number; tone: 'danger' | 'warning' | 'info' | 'success'; items?: TodoItem[] }

function mockTime(): string {
  const d = new Date(Date.now() - Math.random() * 1000 * 60 * 60 * 6)
  return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
}

const todoTabs = ref<TodoTab[]>([
  { name: 'alarm',   label: '故障告警', count: 0, tone: 'danger' },
  { name: 'abnTask', label: '异常任务', count: 4, tone: 'warning',
    items: [
      { text: 'AGV-021 路径阻塞 · 等待解锁',  time: mockTime() },
      { text: 'M-1024 调度超时',              time: mockTime() },
      { text: 'M-1027 取货失败',              time: mockTime() },
      { text: '搬运任务 #913 因充电中断',     time: mockTime() }
    ] },
  { name: 'comm',    label: '通信异常', count: 0, tone: 'info' },
  { name: 'server',  label: '服务器报警', count: 2, tone: 'warning',
    items: [
      { text: 'SimpleScheduler · CPU 持续 85%', time: mockTime() },
      { text: 'WebAPI · 90% 内存使用率',         time: mockTime() }
    ] }
])

const alarmCount = computed(() =>
  todoTabs.value.filter((t) => t.tone === 'danger' || t.tone === 'warning').reduce((s, t) => s + t.count, 0)
)

// ─── 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 () => {
  ;[sites.value, tracks.value, cars.value, missions.value] = await Promise.all([
    listSites(), listTracks(), listCars(), listMissions()
  ])
  void tracks.value
  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;
}

/* ───────────── 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;
}

/* ───── 快捷入口栅格（按截图 8 项，两行 4 列） ───── */
.quick-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px 18px;
  max-width: 540px;
}
.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;
  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;
}
@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 {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 1.2px;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}
.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)` 几乎透明白，
 *             在浅紫底下完全消失。现改为完全不透明 + 紫色描边 + 明确层级。
 * ═════════════════════════════════════════════════════════════════════ */

/* ───────────── 系统状态栏（实时连接 + 在线 AGV + 告警 + 时钟） ───────────── */
.status-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: 12px 20px;
  border-radius: 12px;
  /* 浅紫白底 + 左侧紫色色条 + 明显紫色描边阴影（视觉跳出主区背景） */
  background: linear-gradient(135deg, #ffffff 0%, #fbf9ff 100%);
  border: 1px solid #d8d0e8;
  border-left: 3px solid #7c3aed;
  box-shadow:
    0 1px 3px rgba(45, 27, 105, 0.06),
    0 4px 14px rgba(124, 58, 237, 0.08);
  color: #1a0f3d;
  font-size: 13px;
}
.status-left,
.status-right {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.status-conn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 12px;
  border-radius: 999px;
  background: #c4b5fd;
  border: 1px solid #8b6ff0;
  font-weight: 600;
  letter-spacing: 0.4px;
  font-size: 12px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    0 1px 3px rgba(124, 58, 237, 0.15);
}
.status-conn.success {
  color: var(--mg-status-success);
  background: rgba(var(--mg-status-success-rgb), 0.32);
  border-color: rgba(var(--mg-status-success-rgb), 0.70);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    0 1px 3px rgba(var(--mg-status-success-rgb), 0.25);
}
.status-conn.warning {
  color: var(--mg-status-warning);
  background: rgba(var(--mg-status-warning-rgb), 0.32);
  border-color: rgba(var(--mg-status-warning-rgb), 0.70);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    0 1px 3px rgba(var(--mg-status-warning-rgb), 0.25);
}
.status-conn.danger {
  color: var(--mg-status-danger);
  background: rgba(var(--mg-status-danger-rgb), 0.32);
  border-color: rgba(var(--mg-status-danger-rgb), 0.70);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    0 1px 3px rgba(var(--mg-status-danger-rgb), 0.25);
}
.status-divider {
  width: 1px; height: 14px;
  background: #a78bfa;
}
.status-stat {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 4px 10px;
  border-radius: 999px;
  background: #c8b8f5;
  border: 1px solid #9d86e6;
  letter-spacing: 0.4px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    0 1px 3px rgba(124, 58, 237, 0.12);
}
.status-stat-label {
  color: #4a3a6d;
  font-size: 12px;
  font-weight: 600;
}
.status-stat-value {
  color: #1f0d54;
  font-weight: 700;
  font-size: 14px;
}
.status-stat-value.is-danger {
  color: var(--mg-status-danger);
}
.status-stat-total {
  font-size: 12px;
  color: #9b8eb8;
  font-weight: 500;
}

/* ───────────── Bento Grid 主体 ───────────── */
.bento-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: minmax(0, auto);
  gap: 16px;
}

/* 共通 Bento 卡片基类（浅底默认：白卡片 + 紫色描边 + 极淡阴影） */
.bento {
  position: relative;
  border-radius: 14px;
  background: #ffffff;
  border: 1px solid #e8e1f0;
  box-shadow:
    0 1px 3px rgba(45, 27, 105, 0.04),
    0 4px 12px rgba(124, 58, 237, 0.04);
  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 12px rgba(45, 27, 105, 0.08),
    0 12px 28px rgba(124, 58, 237, 0.12);
}

/* 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; }

/* ───── KPI ×6（每个 col-span 2，共占 12 列一行） ───── */
.bento-kpi {
  grid-column: span 2;
  padding: 16px 18px 14px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-height: 116px;
}
.kpi-accent {
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  height: 3px;
  background: linear-gradient(90deg, var(--mg-primary-hover), var(--mg-accent) 50%, transparent);
  opacity: 0.85;
}
.kpi-accent.tone-info    { background: linear-gradient(90deg, var(--mg-status-info),    rgba(var(--mg-status-info-rgb),    0.20) 80%, transparent); }
.kpi-accent.tone-success { background: linear-gradient(90deg, var(--mg-status-success), rgba(var(--mg-status-success-rgb), 0.20) 80%, transparent); }
.kpi-accent.tone-warning { background: linear-gradient(90deg, var(--mg-status-warning), rgba(var(--mg-status-warning-rgb), 0.20) 80%, transparent); }
.kpi-accent.tone-danger  { background: linear-gradient(90deg, var(--mg-status-danger),  rgba(var(--mg-status-danger-rgb),  0.20) 80%, transparent); }
.kpi-accent.tone-idle    { background: linear-gradient(90deg, var(--mg-status-idle),    rgba(var(--mg-status-idle-rgb),    0.20) 80%, transparent); }

.kpi-body {
  display: flex; flex-direction: column; gap: 6px;
  z-index: 1;
}
.kpi-row1 {
  display: flex; align-items: center; gap: 8px;
}
.kpi-icon-sm {
  color: var(--mg-text-muted);
}
.kpi-icon-sm.tone-info    { color: var(--mg-status-info); }
.kpi-icon-sm.tone-success { color: var(--mg-status-success); }
.kpi-icon-sm.tone-warning { color: var(--mg-status-warning); }
.kpi-icon-sm.tone-danger  { color: var(--mg-status-danger); }
.kpi-icon-sm.tone-idle    { color: var(--mg-status-idle); }

.kpi-label {
  font-size: 12px;
  color: #6b5b8e;
  letter-spacing: 0.5px;
  font-weight: 500;
}
.kpi-row2 {
  display: flex; align-items: baseline; gap: 8px;
}
.kpi-value {
  font-size: 30px;
  font-weight: 700;
  color: #1a0f3d;
  font-family: var(--mg-font-mono);
  font-variant-numeric: tabular-nums;
  line-height: 1.05;
  letter-spacing: -0.5px;
}
.kpi-trend {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: 11px;
  font-weight: 600;
  padding: 2px 6px;
  border-radius: 999px;
}
.kpi-trend.trend-up   { background: rgba(var(--mg-status-success-rgb), 0.16); color: var(--mg-status-success); }
.kpi-trend.trend-down { background: rgba(var(--mg-status-danger-rgb),  0.16); color: var(--mg-status-danger); }
.kpi-trend.trend-flat { background: rgba(var(--mg-status-idle-rgb),    0.16); color: var(--mg-status-idle); }
.kpi-trend-text {
  font-family: var(--mg-font-mono);
  font-variant-numeric: tabular-nums;
}
.kpi-spark {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 32px;
  width: 100%;
  pointer-events: none;
  opacity: 0.65;
}

/* ───── 24h 趋势卡（占 8 列） ───── */
.bento-trend {
  grid-column: span 8;
  min-height: 280px;
  display: flex; flex-direction: column;
}
.bento-trend :deep(.el-card) {
  display: flex; flex-direction: column;
}
.bento-trend :deep(.el-card__body) {
  flex: 1;
  display: flex;
  padding: 14px 18px 18px;
}
.trend-chart {
  flex: 1;
  width: 100%;
  height: 100%;
  min-height: 220px;
}

/* ───── AGV 状态饼图（占 4 列） ───── */
.bento-agv {
  grid-column: span 4;
  min-height: 280px;
}
.bento-agv :deep(.el-card__body) { padding: 14px 16px 18px; }

/* 标题角的 示例 标签 */
.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: 220px;
}

/* 环形图容器 + 中心总数（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);
}

/* ───── 任务占比（横向堆叠条；占 7 列） ───── */
.bento-ratio {
  grid-column: span 7;
}
.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;
}

/* ───── 待办事项（占 5 列） ───── */
.bento-todo {
  grid-column: span 5;
}
.bento-todo :deep(.el-card__body) {
  padding-top: 8px;
}
.todo-tabs :deep(.el-tabs__header) { margin-bottom: 8px; }
.todo-tabs :deep(.el-tabs__nav-wrap::after) { display: none; }
.todo-tabs :deep(.el-tabs__item) {
  color: #6b5b8e !important;
  font-weight: 500;
}
.todo-tabs :deep(.el-tabs__item:hover) {
  color: #7c3aed !important;
}
.todo-tabs :deep(.el-tabs__item.is-active) {
  color: #7c3aed !important;
  font-weight: 700;
}
.todo-tabs :deep(.el-tabs__active-bar) {
  background-color: #7c3aed !important;
  height: 3px !important;
  border-radius: 2px;
}
.tab-label {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px;
}
.tab-count {
  display: inline-flex; align-items: center; justify-content: center;
  font-style: normal;
  min-width: 20px; height: 18px;
  padding: 0 6px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  font-family: var(--mg-font-mono);
  font-variant-numeric: tabular-nums;
}
.tab-count.danger  { background: rgba(var(--mg-status-danger-rgb),  0.18); color: var(--mg-status-danger);  border: 1px solid rgba(var(--mg-status-danger-rgb),  0.40); }
.tab-count.warning { background: rgba(var(--mg-status-warning-rgb), 0.18); color: var(--mg-status-warning); border: 1px solid rgba(var(--mg-status-warning-rgb), 0.40); }
.tab-count.info    { background: rgba(var(--mg-status-info-rgb),    0.18); color: var(--mg-status-info);    border: 1px solid rgba(var(--mg-status-info-rgb),    0.40); }
.tab-count.success { background: rgba(var(--mg-status-success-rgb), 0.18); color: var(--mg-status-success); border: 1px solid rgba(var(--mg-status-success-rgb), 0.40); }

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

.todo-list {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 8px;
  max-height: 220px;
  overflow-y: auto;
  padding-right: 4px;
}
.todo-list li {
  display: grid;
  grid-template-columns: 90px 1fr auto;
  gap: 12px;
  align-items: center;
  padding: 10px 12px;
  background: #faf7fd;
  border: 1px solid #f0eaf6;
  border-radius: 10px;
  font-size: 13px;
  color: #2d1b69;
  transition: background .2s, border-color .2s, transform .2s, box-shadow .2s;
}
.todo-list li:hover {
  background: #f5f3ff;
  border-color: #c4b5fd;
  transform: translateX(2px);
  box-shadow: 0 2px 6px rgba(124, 58, 237, 0.08);
}
.todo-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 500;
}
.todo-pill {
  text-align: center;
  font-size: 11px;
  padding: 3px 8px;
  border-radius: 999px;
  letter-spacing: 0.4px;
  font-weight: 700;
}
.todo-pill.danger  { background: rgba(var(--mg-status-danger-rgb),  0.10); color: var(--mg-status-danger);  border: 1px solid rgba(var(--mg-status-danger-rgb),  0.32); }
.todo-pill.warning { background: rgba(var(--mg-status-warning-rgb), 0.10); color: var(--mg-status-warning); border: 1px solid rgba(var(--mg-status-warning-rgb), 0.32); }
.todo-pill.info    { background: rgba(var(--mg-status-info-rgb),    0.10); color: var(--mg-status-info);    border: 1px solid rgba(var(--mg-status-info-rgb),    0.32); }
.todo-pill.success { background: rgba(var(--mg-status-success-rgb), 0.10); color: var(--mg-status-success); border: 1px solid rgba(var(--mg-status-success-rgb), 0.32); }
.todo-time {
  color: #9b8eb8;
  font-size: 11.5px;
  font-family: var(--mg-font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}

/* ───── 响应式 ───── */
@media (max-width: 1440px) {
  .bento-kpi    { grid-column: span 3; }
  .bento-trend  { grid-column: span 8; }
  .bento-agv    { grid-column: span 4; }
  .bento-ratio  { grid-column: span 7; }
  .bento-todo   { grid-column: span 5; }
}
@media (max-width: 1280px) {
  .hero-banner  { grid-template-columns: 1fr 380px; }
  .hero-right   { min-height: 240px; }
  .bento-kpi    { grid-column: span 4; }
  .bento-trend  { grid-column: span 12; }
  .bento-agv    { grid-column: span 6; }
  .bento-ratio  { grid-column: span 6; }
  .bento-todo   { grid-column: span 12; }
}
@media (max-width: 1024px) {
  .status-bar   { flex-wrap: wrap; }
  .hero-banner  { grid-template-columns: 1fr; }
  .hero-right   { display: none; }
  .bento-kpi    { grid-column: span 6; }
  .bento-agv    { grid-column: span 12; }
  .bento-ratio  { grid-column: span 12; }
}
@media (max-width: 720px) {
  .quick-grid   { grid-template-columns: repeat(2, 1fr); }
  .bento-kpi    { grid-column: span 12; }
  .agv-row      { flex-direction: column; align-items: stretch; }
  .agv-chart    { width: 100%; height: 220px; }
}

/* ═════════════════════════════════════════════════════════════════════
 *  industrial-purple（星云紫深底）主题覆盖
 *  当 :root[data-theme="industrial-purple"] 激活时，把上面的浅底卡片
 *  转为深紫玻璃风 + 白文字 + 霓虹辉光。
 * ═════════════════════════════════════════════════════════════════════ */
:root[data-theme="industrial-purple"] .status-bar {
  /* 与 bento 卡片同级的深紫玻璃底，避免浅底 + 白字对比不足 */
  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.38);
  border-left-color: var(--mg-accent);
  box-shadow:
    0 4px 16px rgba(0, 0, 0, 0.38),
    0 0 0 1px rgba(255, 255, 255, 0.06) inset,
    0 1px 0 rgba(255, 255, 255, 0.08) inset;
  backdrop-filter: blur(12px) saturate(140%);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  color: #f4eeff;
}
:root[data-theme="industrial-purple"] .status-conn {
  background: rgba(var(--mg-primary-hover-rgb), 0.42);
  border-color: rgba(var(--mg-accent-rgb), 0.45);
  color: #f4eeff;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.10),
    0 1px 4px rgba(0, 0, 0, 0.28);
}
:root[data-theme="industrial-purple"] .status-conn.success {
  color: #86efac;
  background: rgba(var(--mg-status-success-rgb), 0.28);
  border-color: rgba(var(--mg-status-success-rgb), 0.58);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.12),
    0 1px 4px rgba(var(--mg-status-success-rgb), 0.30);
}
:root[data-theme="industrial-purple"] .status-conn.warning {
  color: #fcd34d;
  background: rgba(var(--mg-status-warning-rgb), 0.28);
  border-color: rgba(var(--mg-status-warning-rgb), 0.58);
}
:root[data-theme="industrial-purple"] .status-conn.danger {
  color: #fca5a5;
  background: rgba(var(--mg-status-danger-rgb), 0.28);
  border-color: rgba(var(--mg-status-danger-rgb), 0.58);
}
:root[data-theme="industrial-purple"] .status-stat {
  background: rgba(var(--mg-primary-hover-rgb), 0.35);
  border-color: rgba(var(--mg-accent-rgb), 0.38);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 1px 3px rgba(0, 0, 0, 0.22);
}
:root[data-theme="industrial-purple"] .status-divider { background: rgba(var(--mg-accent-rgb), 0.48); }
:root[data-theme="industrial-purple"] .status-stat-label { color: rgba(255, 255, 255, 0.82); }
:root[data-theme="industrial-purple"] .status-stat-value { color: #ffffff; }
:root[data-theme="industrial-purple"] .status-stat-total { color: rgba(255, 255, 255, 0.62); }
:root[data-theme="industrial-purple"] .status-stat-value.is-danger { color: #fca5a5; }

: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"] .todo-tabs :deep(.el-tabs__item) { color: rgba(255, 255, 255, 0.65) !important; }
:root[data-theme="industrial-purple"] .todo-tabs :deep(.el-tabs__item:hover),
:root[data-theme="industrial-purple"] .todo-tabs :deep(.el-tabs__item.is-active) { color: #ffffff !important; }
:root[data-theme="industrial-purple"] .todo-tabs :deep(.el-tabs__active-bar) { background-color: var(--mg-accent) !important; }
:root[data-theme="industrial-purple"] .empty-pane { color: rgba(255, 255, 255, 0.70); }
:root[data-theme="industrial-purple"] .todo-list li {
  background: rgba(255, 255, 255, 0.03);
  border-color: rgba(255, 255, 255, 0.06);
  color: #f4eeff;
}
:root[data-theme="industrial-purple"] .todo-list li:hover {
  background: rgba(var(--mg-accent-rgb), 0.08);
  border-color: rgba(var(--mg-accent-rgb), 0.30);
}
:root[data-theme="industrial-purple"] .todo-time { color: rgba(255, 255, 255, 0.55); }
</style>
