<template>
  <div class="home-page">
    <section class="hero-panel">
      <div class="hero-copy">
        <span class="eyebrow">IoT Monitoring Center</span>
        <h1>物联网监控平台</h1>
        <p>聚合项目、仓库、设备与传感器实时数据，告警优先，状态一屏掌握。</p>
      </div>
      <div class="hero-health">
        <span>系统状态</span>
        <strong>运行正常</strong>
        <em>{{ overview.onlineRate }}% 在线率</em>
      </div>
    </section>

    <section v-if="tickerMessage" :key="tickerKey" class="ws-ticker">
      <div class="ws-ticker-track">
        <span class="ws-ticker-badge">WebSocket</span>
        <strong>实时消息</strong>
        <p>{{ tickerMessage }}</p>
      </div>
    </section>

    <section class="stat-grid">
      <div v-for="item in overviewCards" :key="item.key" :class="['stat-card', item.theme]">
        <div class="stat-icon">{{ item.icon }}</div>
        <div>
          <span>{{ item.label }}</span>
          <strong>{{ item.value }}</strong>
        </div>
      </div>
    </section>

    <section class="dashboard-grid">
      <article class="panel alarm-panel">
        <div class="panel-head">
          <div>
            <span>Alarm Center</span>
            <h2>告警中心</h2>
          </div>
        </div>

        <div class="alarm-visual">
          <div class="trend-card">
            <div class="trend-bars">
              <span v-for="item in alarmTrend" :key="item.day" :style="{ height: item.value + '%' }"></span>
            </div>
            <p>近 7 天告警趋势</p>
            <em>根据统计接口实时展示</em>
          </div>
          <div class="donut-card">
            <div class="donut">
              <strong>{{ exceptionStats.total }}</strong>
              <em class="donut-total-tag">累计告警数</em>
              <span>累计告警</span>
            </div>
            <div class="donut-info">
            <div class="donut-legend">
              <div class="legend-item danger">
                <i></i>
                <span>红色：告警</span>
              </div>
              <div class="legend-item warning">
                <i></i>
                <span>橙色：预警</span>
              </div>
              <div class="legend-item normal">
                <i></i>
                <span>绿色：正常</span>
              </div>
            </div>
            </div>
          </div>
        </div>

        <div class="alarm-list">
          <h3>实时告警</h3>
          <div v-if="realtimeAlarms.length === 0" class="safe-state">
            <i class="dot normal"></i>
            正常，无告警
          </div>
          <div v-for="item in realtimeAlarms" :key="item.id" class="alarm-item">
            <i :class="['dot', item.level]"></i>
            <span>{{ item.message }}</span>
          </div>
        </div>
      </article>

      <article class="panel project-panel">
        <div class="panel-head">
          <div>
            <span>Project Overview</span>
            <h2>项目 / 仓库运行状态</h2>
          </div>
        </div>

        <div class="project-table">
          <div class="project-row project-row-head">
            <span>项目</span>
            <span>仓库</span>
            <span>在线率</span>
            <span>状态</span>
          </div>
          <div
            v-for="item in pagedProjectList"
            :key="item.key"
            class="project-row project-row-link"
            @click="openWarehouseDetail(item)"
          >
            <strong>{{ item.name }}</strong>
            <span>{{ item.warehouseName }}</span>
            <span>{{ item.onlineRate }}</span>
            <i :class="['dot', item.status]"></i>
          </div>
        </div>

        <div class="project-pager">
          <button type="button" :disabled="projectPage <= 1" @click="changeProjectPage(-1)">上一页</button>
          <span>{{ projectPage }} / {{ projectPageTotal }}</span>
          <button type="button" :disabled="projectPage >= projectPageTotal" @click="changeProjectPage(1)">下一页</button>
        </div>
      </article>
    </section>

    <section class="panel realtime-panel">
      <div class="panel-head">
        <div>
          <span>WebSocket Stream</span>
          <h2>实时动态</h2>
        </div>
        <em>传感器上报后实时进入消息流</em>
      </div>

      <div class="stream-layout">
        <div class="event-column">
          <div class="history-head">
            <strong>实时消息</strong>
            <span>{{ leftRealtimeEvents.length }} 条</span>
          </div>
          <div v-if="leftRealtimeEvents.length === 0" class="history-empty">
            等待 WebSocket 实时数据推送
          </div>
          <div v-for="item in leftRealtimeEvents" :key="item.id" :class="['event-row', item.level]">
            <div class="event-main">
              <i></i>
              <span>[{{ item.time }}]</span>
              <p>{{ item.message }}</p>
            </div>
            <div v-if="item.metrics.length" class="event-metrics compact">
              <span v-for="metric in item.metrics" :key="metric.label">
                {{ metric.label }}：{{ metric.value }}
              </span>
            </div>
          </div>
        </div>

        <div class="event-column">
          <div class="history-head">
            <strong>更多消息</strong>
            <span>{{ rightRealtimeEvents.length }} 条</span>
          </div>
          <div v-for="item in rightRealtimeEvents" :key="item.id" :class="['event-row', item.level]">
            <div class="event-main">
              <i></i>
              <span>[{{ item.time }}]</span>
              <p>{{ item.message }}</p>
            </div>
            <div v-if="item.metrics.length" class="event-metrics compact">
              <span v-for="metric in item.metrics" :key="metric.label">
                {{ metric.label }}：{{ metric.value }}
              </span>
            </div>
          </div>
          <div v-if="rightRealtimeEvents.length === 0" class="history-empty">
            等待更多实时消息
          </div>
        </div>
      </div>
    </section>
  </div>
</template>

<script setup name="Index">
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { closeWs, connectWs } from '@/utils/ws'
import { getToken } from '@/utils/auth'
import {
  getHomeAlarmStat,
  getHomeAlarmTrend,
  getHomeAlarmType,
  getHomeDeviceStat,
  getHomeProjectList,
  getHomeStat
} from '@/api/worn/home'

const overview = reactive({
  projectTotal: 0,
  warehouseTotal: 0,
  deviceTotal: 0,
  onlineCount: 0,
  offlineCount: 0,
  onlineRate: 0,
  alarmToday: 0,
  alarmUnhandled: 0
})

const router = useRouter()

const exceptionStats = reactive({
  total: 0
})

const realtimeAlarms = ref([])

const projectList = ref([])

const realtimeEvents = ref([])
const recentEventKeys = new Map()
const projectPage = ref(1)
const tickerMessage = ref('')
const tickerKey = ref(0)
let tickerTimer = null

const DUPLICATE_EVENT_WINDOW = 3000
const PROJECT_PAGE_SIZE = 5
const TICKER_DURATION = 22000

const alarmTrend = ref([])

const overviewCards = computed(() => [
  { key: 'project', icon: 'P', label: '项目总数', value: overview.projectTotal, theme: '' },
  { key: 'warehouse', icon: 'W', label: '仓库总数', value: overview.warehouseTotal, theme: '' },
  { key: 'device', icon: 'D', label: '设备总数', value: overview.deviceTotal, theme: '' },
  { key: 'online', icon: 'ON', label: '在线设备', value: overview.onlineCount, theme: 'online' },
  { key: 'offline', icon: 'OFF', label: '离线设备', value: overview.offlineCount, theme: 'offline' },
  { key: 'rate', icon: '%', label: '设备在线率', value: `${overview.onlineRate}%`, theme: 'rate' },
  { key: 'today', icon: 'A', label: '今日告警', value: overview.alarmToday, theme: 'today' },
  { key: 'pending', icon: '!', label: '未处理告警', value: overview.alarmUnhandled, theme: 'pending' }
])

const projectPageTotal = computed(() => Math.max(1, Math.ceil(projectList.value.length / PROJECT_PAGE_SIZE)))

const pagedProjectList = computed(() => {
  const start = (projectPage.value - 1) * PROJECT_PAGE_SIZE
  return projectList.value.slice(start, start + PROJECT_PAGE_SIZE)
})

const LEFT_STREAM_SIZE = 5

const STREAM_TOTAL_SIZE = 10

const leftRealtimeEvents = computed(() => realtimeEvents.value.slice(0, LEFT_STREAM_SIZE))

const rightRealtimeEvents = computed(() => realtimeEvents.value.slice(LEFT_STREAM_SIZE, STREAM_TOTAL_SIZE))

function handleWsMessage(data) {
  const event = normalizeEvent(data)
  if (isDuplicateRealtimeEvent(event)) {
    return
  }

  pushRealtimeEvent(event)
  playTicker(event)

  if (event.level === 'alarm' || event.level === 'warning') {
    pushRealtimeAlarm(event)
    overview.alarmToday += 1
    overview.alarmUnhandled += 1
    exceptionStats.total += 1
  }
}

function loadHomeData() {
  return Promise.all([
    loadHomeStat(),
    loadDeviceStat(),
    loadAlarmStat(),
    loadAlarmTrend(),
    loadAlarmType(),
    loadProjectPage()
  ])
}

function loadHomeStat() {
  return getHomeStat().then((res) => {
    const data = res.data || {}
    overview.projectTotal = toNumber(data.projectCount, overview.projectTotal)
    overview.warehouseTotal = toNumber(data.warehouseCount, overview.warehouseTotal)
  })
}

function loadDeviceStat() {
  return getHomeDeviceStat().then((res) => {
    const data = res.data || {}
    overview.deviceTotal = toNumber(data.total, overview.deviceTotal)
    overview.onlineCount = toNumber(data.online, overview.onlineCount)
    overview.offlineCount = toNumber(data.offline, overview.offlineCount)
    overview.onlineRate = toNumber(data.onlineRate, overview.onlineRate)
  })
}

function loadAlarmStat() {
  return getHomeAlarmStat().then((res) => {
    const data = res.data || {}
    overview.alarmToday = toNumber(data.today, overview.alarmToday)
    overview.alarmUnhandled = toNumber(data.unhandled, overview.alarmUnhandled)
  })
}

function loadAlarmTrend() {
  return getHomeAlarmTrend().then((res) => {
    const list = Array.isArray(res.data) ? res.data : []
    const maxCount = Math.max(...list.map((item) => toNumber(item.count, 0)), 1)

    alarmTrend.value = list.map((item) => ({
      day: item.date || '',
      value: Math.max(12, Math.round((toNumber(item.count, 0) / maxCount) * 100))
    }))
  })
}

function loadAlarmType() {
  return getHomeAlarmType().then((res) => {
    const list = Array.isArray(res.data) ? res.data : []
    exceptionStats.total = list.reduce((sum, item) => sum + toNumber(item.value, 0), 0)
  })
}

function loadProjectPage() {
  return getHomeProjectList().then((res) => {
    const list = Array.isArray(res.data) ? res.data : []
    projectList.value = list.map(normalizeProject).sort(sortProjectWarehouse)
    if (projectPage.value > projectPageTotal.value) {
      projectPage.value = projectPageTotal.value
    }
  })
}

function changeProjectPage(step) {
  const nextPage = projectPage.value + step
  if (nextPage < 1 || nextPage > projectPageTotal.value) {
    return
  }

  projectPage.value = nextPage
}

function openWarehouseDetail(item) {
  if (!item.deptId) {
    return
  }

  router.push({
    path: '/worn/warehouse-dashboard',
    query: {
      deptId: item.deptId,
      warehouseName: item.warehouseName,
      projectName: item.name,
      deviceSerial: item.deviceSerial || ''
    }
  })
}

function normalizeProject(item, index) {
  const onlineRate = toNumber(item.onlineRate, 0)
  const name = String(item.parentName || item.projectName || item.name || '未命名项目').trim()
  const warehouseName = String(item.warehouseName || item.deptName || '-').trim()

  return {
    key: `${name}-${warehouseName}-${index}`,
    deptId: item.deptId || item.warehouseId || item.id,
    name,
    warehouseName,
    deviceSerial: item.deviceSerial || '',
    onlineRate: `${onlineRate}%`,
    status: onlineRate >= 95 ? 'normal' : 'warning'
  }
}

function sortProjectWarehouse(a, b) {
  const projectCompare = String(a.name).localeCompare(String(b.name), 'zh-Hans-CN', { numeric: true, sensitivity: 'base' })
  if (projectCompare !== 0) {
    return projectCompare
  }

  return String(a.warehouseName).localeCompare(String(b.warehouseName), 'zh-Hans-CN', { numeric: true, sensitivity: 'base' })
}

function toNumber(value, fallback = 0) {
  const number = Number(value)
  return Number.isFinite(number) ? number : fallback
}

function normalizeEvent(data) {
  const now = new Date()
  const type = data?.type || 'unknown'
  const level = getEventLevel(data)
  const deviceName = getRealtimeDeviceLabel(data)
  const deptName = data?.deptName || `部门 ${data?.deptId || ''}`.trim()
  const desc = translateEventValue(data?.eventDesc || data?.statusDesc || data?.event || getTypeName(type))

  return {
    id: `${Date.now()}-${Math.random()}`,
    level,
    time: now.toTimeString().slice(0, 8),
    message: `${deptName} · ${deviceName} ${desc}`,
    metrics: buildMetrics(data),
    fingerprint: buildEventFingerprint(data)
  }
}

function getRealtimeDeviceLabel(data) {
  const rawDeviceName = String(data?.deviceName || '').trim()
  const remark = String(data?.remark || data?.deviceRemark || data?.deviceAlias || '').trim()
  const typeLabel = getRealtimeDeviceTypeName(data)

  if (remark) {
    if (rawDeviceName && rawDeviceName !== remark) {
      return `${remark}（${rawDeviceName}）`
    }
    return remark
  }

  if (typeLabel) {
    if (rawDeviceName) {
      return `${typeLabel}（${rawDeviceName}）`
    }
    return typeLabel
  }

  return rawDeviceName || `设备 ${data?.deviceId || ''}`.trim()
}

function getRealtimeDeviceTypeName(data) {
  const rawType = String(data?.deviceType || data?.type || '').trim().toLowerCase()
  const rawName = String(data?.deviceName || '').trim().toLowerCase()

  if (rawType === 'switch' || rawName.includes('light-switch') || rawName.includes('light switch')) {
    return '智慧照明开关'
  }
  if (rawName.includes('ac-socket') || rawName.includes('ac socket') || rawName.includes('空调')) {
    return '智能空调插座'
  }
  if (rawType === 'socket' || rawName.includes('socket')) {
    return '智能排风'
  }
  if (rawType === 'smoke' || rawName.includes('smoke')) {
    return '烟雾传感器'
  }
  if (rawType === 'water' || rawName.includes('water')) {
    return '水浸传感器'
  }
  if (rawType === 'door' || rawName.includes('door')) {
    return '门禁传感器'
  }
  if (rawType === 'env' || rawName.includes('env')) {
    return '环境传感器'
  }

  return ''
}

function isDuplicateRealtimeEvent(event) {
  const now = Date.now()
  const lastTime = recentEventKeys.get(event.fingerprint)
  recentEventKeys.set(event.fingerprint, now)

  recentEventKeys.forEach((time, key) => {
    if (now - time > DUPLICATE_EVENT_WINDOW) {
      recentEventKeys.delete(key)
    }
  })

  return lastTime && now - lastTime < DUPLICATE_EVENT_WINDOW
}

function buildEventFingerprint(data) {
  return JSON.stringify({
    type: data?.type,
    deviceId: data?.deviceId,
    deptId: data?.deptId,
    event: data?.event,
    eventType: data?.eventType,
    status: data?.status,
    statusDesc: data?.statusDesc,
    temperature: data?.temperature,
    humidity: data?.humidity,
    nh3: data?.nh3,
    h2s: data?.h2s,
    battery: data?.battery,
    concentration: data?.concentration,
    water: data?.water,
    doorStatus: data?.doorStatus,
    tamperStatus: data?.tamperStatus,
    switch1: data?.switch1 ?? data?.switch_1,
    switch2: data?.switch2 ?? data?.switch_2,
    switch3: data?.switch3 ?? data?.switch_3
  })
}

function buildMetrics(data) {
  const metricConfig = [
    ['temperature', '温度', '℃'],
    ['humidity', '湿度', '%'],
    ['nh3', '氨气', 'ppm'],
    ['h2s', '硫化氢', 'ppm'],
    ['battery', '电量', '%'],
    ['concentration', '烟雾浓度', ''],
    ['water', '水浸状态', ''],
    ['doorStatus', '门磁状态', ''],
    ['tamperStatus', '防拆状态', ''],
    ['status', '状态', '']
  ]

  const metrics = metricConfig
    .filter(([key]) => data?.[key] !== undefined && data?.[key] !== null && data?.[key] !== '')
    .filter(([key]) => !isDuplicateStatusMetric(data, key))
    .map(([key, label, unit]) => ({
      label: getMetricLabel(data, key, label),
      value: formatMetricValue(key, data[key], unit)
    }))

  return metrics.concat(buildExtraMetrics(data, metricConfig.map(([key]) => key)))
}

function isDuplicateStatusMetric(data, key) {
  if (key !== 'status') {
    return false
  }

  return (data?.type === 'water' && data?.water !== undefined && data?.water !== null)
    || (data?.type === 'door' && (data?.doorStatus !== undefined || data?.tamperStatus !== undefined))
}

function formatMetricValue(key, value, unit) {
  if (key === 'status') {
    return formatStatusValue(value)
  }
  if (key === 'water') {
    return Number(value) === 1 ? '告警' : '正常'
  }
  if (key === 'doorStatus') {
    return Number(value) === 1 ? '打开' : '关闭'
  }
  if (key === 'tamperStatus') {
    return Number(value) === 1 ? '被拆' : '正常'
  }
  return `${value}${unit}`
}

function getMetricLabel(data, key, fallback) {
  if (key === 'status' && data?.type === 'socket') {
    return '插座状态'
  }
  if (key === 'status' && data?.type === 'smoke') {
    return '烟雾状态'
  }
  if (key === 'status' && data?.type === 'water') {
    return '水浸状态'
  }
  if (key === 'status' && data?.type === 'door') {
    return '门磁状态'
  }
  return fallback
}

function formatStatusValue(value) {
  if (value === 1 || value === '1' || value === 'on') {
    return '通电'
  }
  if (value === 0 || value === '0' || value === 'off') {
    return '断电'
  }

  const statusMap = {
    normal: '正常',
    alarm: '告警',
    warning: '预警',
    offline: '离线',
    open: '打开',
    close: '关闭',
    tamper: '被拆',
    silent: '已消音',
    removed: '被拆除',
    low_battery: '电量低',
    recovery: '恢复'
  }
  return statusMap[value] || value
}

function buildExtraMetrics(data, knownKeys) {
  const hiddenKeys = new Set([
    ...knownKeys,
    'type',
    'deviceId',
    'deviceName',
    'deptId',
    'deptName',
    'eventDesc',
    'statusDesc',
    'time',
    'switch_1_change',
    'switch1_change',
    'switch_2_change',
    'switch2_change',
    'switch_3_change',
    'switch3_change'
  ])

  return Object.keys(data || {})
    .filter((key) => !hiddenKeys.has(key))
    .filter((key) => ['string', 'number', 'boolean'].includes(typeof data[key]))
    .map((key) => ({
      label: getExtraMetricLabel(key),
      value: formatExtraMetricValue(key, data[key])
    }))
}

function formatExtraMetricValue(key, value) {
  if (key === 'event' || key === 'eventType') {
    return translateEventValue(value)
  }
  if (key === 'level') {
    return translateLevelValue(value)
  }
  if (['switch_1', 'switch_2', 'switch_3', 'switch1', 'switch2', 'switch3'].includes(key)) {
    return translateSwitchShort(value)
  }
  return String(value)
}

function translateEventValue(value) {
  if (value === undefined || value === null) {
    return ''
  }

  const event = String(value)
  const normalized = event.trim().toLowerCase()
  const eventMap = {
    alarm: '告警',
    warning: '预警',
    normal: '正常',
    recovery: '恢复正常',
    silent: '已消音',
    removed: '设备被拆除',
    low_battery: '电量低',
    door_open: '门已打开',
    door_close: '门已关闭',
    tamper: '设备被拆',
    socket_on: '插座通电',
    socket_off: '插座断电',
    'testing alarm with normal battery': '测试告警，电量正常',
    'test alarm with normal battery': '测试告警，电量正常',
    'alarm with normal battery': '告警，电量正常',
    'normal battery': '电量正常'
  }

  return eventMap[normalized] || event
}

function translateLevelValue(value) {
  const levelMap = {
    LOW: '低',
    MEDIUM: '中',
    HIGH: '高',
    low: '低',
    medium: '中',
    high: '高'
  }
  return levelMap[value] || String(value)
}

function getExtraMetricLabel(key) {
  const labelMap = {
    event: '事件',
    eventType: '事件类型',
    level: '等级',
    switch_1: '开关1',
    switch_2: '开关2',
    switch_3: '开关3',
    switch1: '开关1',
    switch2: '开关2',
    switch3: '开关3'
  }
  return labelMap[key] || key
}

function translateSwitchShort(value) {
  const normalized = String(value ?? '').trim().toLowerCase()
  if (normalized === '1' || normalized === 'on' || normalized === 'true' || normalized === 'open' || normalized === 'opened') {
    return '开'
  }
  if (normalized === '0' || normalized === 'off' || normalized === 'false' || normalized === 'close' || normalized === 'closed') {
    return '关'
  }
  return String(value ?? '--')
}

function getEventLevel(data) {
  if (data?.type === 'alarm' || data?.eventType === 'alarm' || data?.event === 'alarm') {
    return 'alarm'
  }
  if (data?.eventType === 'warning' || data?.status === 'warning') {
    return 'warning'
  }
  if (data?.status === 'offline') {
    return 'offline'
  }
  return 'normal'
}

function getTypeName(type) {
  const typeMap = {
    env: '环境数据上报',
    smoke: '烟雾数据上报',
    water: '水浸数据上报',
    socket: '智能插座数据上报',
    door: '门禁数据上报',
    alarm: '告警事件'
  }
  return typeMap[type] || '数据上报'
}

function pushRealtimeEvent(event) {
  realtimeEvents.value.unshift(event)
  realtimeEvents.value = realtimeEvents.value.slice(0, STREAM_TOTAL_SIZE)
}

function playTicker(event) {
  const metrics = event.metrics
    .slice(0, 4)
    .map((metric) => `${metric.label}: ${metric.value}`)
    .join('  ')

  tickerMessage.value = metrics ? `${event.message}    ${metrics}` : event.message
  tickerKey.value += 1

  if (tickerTimer) {
    clearTimeout(tickerTimer)
  }

  tickerTimer = setTimeout(() => {
    tickerMessage.value = ''
    tickerTimer = null
  }, TICKER_DURATION)
}

function pushRealtimeAlarm(event) {
  realtimeAlarms.value.unshift({
    id: event.id,
    level: event.level === 'alarm' ? 'danger' : 'warning',
    message: event.message
  })
  realtimeAlarms.value = realtimeAlarms.value.slice(0, 5)
}

onMounted(async () => {
  const token = getToken()

  if (!token || token === 'undefined' || token === 'null' || token.length < 30) {
    console.warn('[Home] token无效，不连接WebSocket')
    return
  }

  try {
    await loadHomeData()
    connectWs(handleWsMessage)
  } catch (error) {
    console.warn('[Home] 棣栭〉鍒濆鍖栧け璐ワ紝璺宠繃 WebSocket 杩炴帴', error)
  }
})

onBeforeUnmount(() => {
  if (tickerTimer) {
    clearTimeout(tickerTimer)
    tickerTimer = null
  }
  closeWs(handleWsMessage)
})
</script>

<style scoped lang="scss">
.home-page {
  --home-left-column: minmax(520px, 0.88fr);
  --home-right-column: minmax(680px, 1.12fr);

  position: relative;
  overflow: hidden;
  min-height: calc(100vh - 84px);
  padding: 24px 28px 32px;
  color: #102033;
  background:
    radial-gradient(circle at 8% 0%, rgba(20, 184, 166, 0.2), transparent 28%),
    radial-gradient(circle at 88% 8%, rgba(14, 165, 233, 0.18), transparent 30%),
    linear-gradient(135deg, #eef8f7 0%, #f7fbff 46%, #eef4ff 100%);
}

.hero-panel,
.panel,
.stat-card {
  position: relative;
  z-index: 1;
}

.hero-panel {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  padding: 26px 30px;
  margin-bottom: 18px;
  color: #fff;
  background:
    linear-gradient(135deg, rgba(8, 47, 73, 0.94), rgba(15, 118, 110, 0.9)),
    radial-gradient(circle at 82% 30%, rgba(125, 211, 252, 0.5), transparent 34%);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 28px;
  box-shadow: 0 24px 60px rgba(8, 47, 73, 0.22);
}

.eyebrow {
  display: block;
  margin-bottom: 8px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.18em;
  color: rgba(204, 251, 241, 0.82);
  text-transform: uppercase;
}

.hero-copy h1 {
  margin: 0;
  font-size: 32px;
  letter-spacing: 0.04em;
}

.hero-copy p {
  max-width: 620px;
  margin: 10px 0 0;
  font-size: 14px;
  color: rgba(240, 253, 250, 0.84);
}

.hero-health {
  display: grid;
  min-width: 190px;
  padding: 18px;
  place-content: center;
  text-align: center;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 22px;

  span,
  em {
    font-size: 12px;
    font-style: normal;
    color: rgba(240, 253, 250, 0.78);
  }

  strong {
    margin: 6px 0;
    font-size: 26px;
  }
}

.stat-grid {
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 18px;
}

.stat-card {
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 82px;
  padding: 16px;
  background: rgba(255, 255, 255, 0.78);
  border: 1px solid rgba(255, 255, 255, 0.9);
  border-radius: 22px;
  box-shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
  backdrop-filter: blur(16px);

  &::after {
    position: absolute;
    right: -24px;
    bottom: -34px;
    width: 92px;
    height: 92px;
    content: "";
    background: rgba(14, 165, 233, 0.08);
    border-radius: 50%;
  }

  span {
    display: block;
    margin-bottom: 6px;
    font-size: 12px;
    color: #64748b;
  }

  strong {
    display: block;
    font-size: 26px;
    line-height: 1;
    color: #0f172a;
  }

  &.online,
  &.offline,
  &.rate,
  &.today,
  &.pending {
    span,
    strong {
      color: #fff;
    }
  }

  &.online { background: linear-gradient(135deg, #059669, #34d399); }
  &.offline { background: linear-gradient(135deg, #475569, #94a3b8); }
  &.rate { background: linear-gradient(135deg, #0f766e, #22d3ee); }
  &.today { background: linear-gradient(135deg, #f97316, #fb7185); }
  &.pending { background: linear-gradient(135deg, #dc2626, #f43f5e); }
}

.stat-icon {
  display: grid;
  width: 38px;
  height: 38px;
  flex: 0 0 38px;
  font-size: 12px;
  font-weight: 900;
  color: #0f172a;
  place-items: center;
  background: rgba(226, 232, 240, 0.82);
  border-radius: 12px;
}

.ws-ticker {
  position: relative;
  z-index: 2;
  overflow: hidden;
  height: 48px;
  margin: -4px 0 18px;
  background:
    linear-gradient(90deg, rgba(240, 253, 250, 0.94), rgba(239, 246, 255, 0.92)),
    radial-gradient(circle at 4% 50%, rgba(20, 184, 166, 0.24), transparent 24%);
  border: 1px solid rgba(20, 184, 166, 0.18);
  border-radius: 18px;
  box-shadow: 0 14px 34px rgba(15, 118, 110, 0.1);

  &::before,
  &::after {
    position: absolute;
    top: 0;
    z-index: 2;
    width: 96px;
    height: 100%;
    pointer-events: none;
    content: "";
  }

  &::before {
    left: 0;
    background: linear-gradient(90deg, rgba(240, 253, 250, 1), transparent);
  }

  &::after {
    right: 0;
    background: linear-gradient(270deg, rgba(239, 246, 255, 1), transparent);
  }
}

.ws-ticker-track {
  position: absolute;
  top: 50%;
  left: 0;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-width: max-content;
  padding: 0 18px;
  white-space: nowrap;
  transform: translate(-110%, -50%);
  animation: wsTickerMove 22s linear forwards;

  strong {
    font-size: 13px;
    color: #0f172a;
  }

  p {
    margin: 0;
    font-size: 13px;
    color: #475569;
  }
}

.ws-ticker-badge {
  padding: 5px 10px;
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.12em;
  color: #0f766e;
  text-transform: uppercase;
  background: rgba(204, 251, 241, 0.9);
  border: 1px solid rgba(20, 184, 166, 0.24);
  border-radius: 999px;
}

@keyframes wsTickerMove {
  0% {
    opacity: 0;
    transform: translate(-110%, -50%);
  }

  10%,
  82% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    transform: translate(calc(100vw + 40px), -50%);
  }
}

.dashboard-grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: var(--home-left-column) var(--home-right-column);
  align-items: stretch;
  gap: 18px;
  margin-bottom: 18px;
}

.panel {
  display: flex;
  flex-direction: column;
  padding: 22px;
  background: rgba(255, 255, 255, 0.84);
  border: 1px solid rgba(255, 255, 255, 0.92);
  border-radius: 28px;
  box-shadow: 0 22px 54px rgba(15, 23, 42, 0.1);
  backdrop-filter: blur(18px);
}

.alarm-panel,
.project-panel {
  min-height: 420px;
}

.panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 18px;

  span {
    font-size: 11px;
    font-weight: 800;
    letter-spacing: 0.12em;
    color: #0f766e;
    text-transform: uppercase;
  }

  h2 {
    margin: 4px 0 0;
    font-size: 18px;
    color: #0f172a;
  }

  strong {
    min-width: 46px;
    padding: 8px 12px;
    font-size: 22px;
    color: #0f766e;
    text-align: center;
    background: #ccfbf1;
    border-radius: 16px;
  }

  em {
    font-size: 12px;
    font-style: normal;
    color: #64748b;
  }
}

.alarm-visual {
  display: grid;
  grid-template-columns: 1.35fr 0.65fr;
  gap: 14px;
  margin-bottom: 14px;
  min-height: 184px;
}

.trend-card,
.donut-card,
.alarm-list,
.project-table {
  background: linear-gradient(135deg, rgba(248, 250, 252, 0.94), rgba(240, 253, 250, 0.72));
  border: 1px solid rgba(226, 232, 240, 0.85);
  border-radius: 20px;
}

.trend-card {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 18px;

  p {
    margin: 14px 0 4px;
    font-size: 13px;
    font-weight: 700;
    color: #334155;
  }

  em {
    font-size: 12px;
    font-style: normal;
    color: #94a3b8;
  }
}

.trend-bars {
  display: flex;
  align-items: flex-end;
  height: 92px;
  gap: 10px;

  span {
    flex: 1;
    min-height: 16px;
    background: linear-gradient(180deg, #fb7185, #f97316);
    border-radius: 999px 999px 8px 8px;
  }
}

.donut-card {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  min-height: 184px;
  padding: 20px 18px;
  gap: 18px;
}

.donut-head {
  display: flex;
  flex-direction: column;
  gap: 4px;
  text-align: left;

  strong {
    font-size: 14px;
    color: #0f172a;
  }

  span {
    font-size: 12px;
    color: #64748b;
  }
}

.donut {
  position: relative;
  display: grid;
  flex: 0 0 110px;
  width: 110px;
  height: 110px;
  place-items: center;
  text-align: center;
  background: conic-gradient(#ef4444 0 34%, #f59e0b 34% 72%, #22c55e 72% 100%);
  border-radius: 50%;

  &::before {
    position: absolute;
    width: 72px;
    height: 72px;
    content: "";
    background: #fff;
    border-radius: 50%;
  }

  strong,
  span {
    position: relative;
    z-index: 1;
  }

  strong {
    font-size: 28px;
    font-weight: 900;
    color: #0f172a;
  }

  .donut-total-tag {
    position: relative;
    z-index: 1;
    margin-top: 2px;
    font-size: 10px;
    font-style: normal;
    font-weight: 700;
    color: #94a3b8;
  }

  span {
    margin-top: 4px;
    font-size: 11px;
    font-weight: 700;
    color: #64748b;
  }
}

.donut-info {
  display: flex;
  flex: 1;
  flex-direction: column;
  justify-content: center;
  gap: 12px;
  min-width: 0;
}

.donut-legend {
  display: grid;
  gap: 10px;
}

.legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 34px;
  padding: 8px 10px;
  font-size: 12px;
  color: #334155;
  background: rgba(255, 255, 255, 0.72);
  border: 1px solid rgba(226, 232, 240, 0.82);
  border-radius: 12px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.86);

  i {
    width: 10px;
    height: 10px;
    border-radius: 50%;
  }

  &.danger i {
    background: #ef4444;
  }

  &.warning i {
    background: #f59e0b;
  }

  &.normal i {
    background: #22c55e;
  }
}

@media (max-width: 1200px) {
  .donut-card {
    flex-direction: column;
    align-items: center;
  }

  .donut-head {
    text-align: center;
  }

  .donut-info {
    width: 100%;
  }
}

.alarm-list {
  flex: 1;
  min-height: 118px;
  padding: 16px;

  h3 {
    margin: 0 0 12px;
    font-size: 14px;
    color: #334155;
  }
}

.safe-state,
.alarm-item {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 10px 12px;
  font-size: 13px;
  color: #15803d;
  background: rgba(240, 253, 244, 0.86);
  border: 1px solid #bbf7d0;
  border-radius: 14px;
}

.alarm-item {
  color: #475569;
  background: #fff;
  border-color: #e2e8f0;

  & + .alarm-item {
    margin-top: 8px;
  }
}

.project-table {
  flex: 1;
  padding: 12px 16px;
}

.project-row {
  display: grid;
  grid-template-columns: minmax(120px, 1fr) minmax(220px, 1.6fr) minmax(80px, 0.55fr) 42px;
  align-items: center;
  min-height: 42px;
  font-size: 13px;
  color: #475569;
  border-bottom: 1px dashed #e2e8f0;

  &:last-child {
    border-bottom: 0;
  }

  strong {
    color: #0f172a;
  }
}

.project-row-link {
  cursor: pointer;
  transition: background 0.2s ease, transform 0.2s ease;

  &:hover {
    background: rgba(20, 184, 166, 0.08);
    transform: translateX(4px);
  }
}

.project-row-head {
  min-height: 34px;
  font-size: 12px;
  font-weight: 800;
  color: #94a3b8;
  cursor: default;

  &:hover {
    background: transparent;
    transform: none;
  }
}

.project-pager {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 14px;
  padding-top: 14px;
  margin-top: auto;

  span {
    font-size: 12px;
    color: #64748b;
  }

  button {
    padding: 6px 14px;
    font-size: 12px;
    color: #0f766e;
    cursor: pointer;
    background: rgba(20, 184, 166, 0.1);
    border: 1px solid rgba(20, 184, 166, 0.2);
    border-radius: 999px;

    &:disabled {
      color: #94a3b8;
      cursor: not-allowed;
      background: #f1f5f9;
      border-color: #e2e8f0;
    }
  }
}

.realtime-panel {
  position: relative;
  z-index: 1;
}

.stream-layout {
  display: grid;
  grid-template-columns: var(--home-left-column) var(--home-right-column);
  align-items: stretch;
  gap: 18px;
}

.event-list {
  display: grid;
  gap: 8px;
  max-height: 286px;
  overflow-y: auto;
}

.empty-realtime {
  display: grid;
  min-height: 128px;
  font-size: 13px;
  color: #94a3b8;
  place-items: center;
  background: rgba(248, 250, 252, 0.72);
  border: 1px dashed #cbd5e1;
  border-radius: 18px;
}

.event-column {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 304px;
  padding: 16px;
  background: rgba(248, 250, 252, 0.68);
  border: 1px solid rgba(226, 232, 240, 0.82);
  border-radius: 20px;
}

.history-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;

  strong {
    font-size: 14px;
    color: #0f172a;
  }

  span {
    font-size: 12px;
    color: #94a3b8;
  }
}

.event-row {
  padding: 10px 12px;
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(240, 253, 250, 0.72));
  border: 1px solid rgba(226, 232, 240, 0.86);
  border-radius: 14px;
}

.history-empty {
  display: grid;
  flex: 1;
  min-height: 96px;
  font-size: 13px;
  color: #94a3b8;
  place-items: center;
  border: 1px dashed #cbd5e1;
  border-radius: 14px;
}

.event-main {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  color: #475569;

  i {
    width: 8px;
    height: 8px;
    flex: 0 0 8px;
    background: #22c55e;
    border-radius: 50%;
  }

  span {
    color: #64748b;
  }

  p {
    flex: 1;
    margin: 0;
    color: #334155;
  }
}

.event-row.alarm .event-main i { background: #ef4444; }
.event-row.warning .event-main i { background: #f59e0b; }
.event-row.offline .event-main i { background: #475569; }

.event-metrics {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-left: 84px;
  margin-top: 10px;

  span {
    padding: 4px 10px;
    font-size: 12px;
    color: #0f766e;
    background: rgba(20, 184, 166, 0.1);
    border: 1px solid rgba(20, 184, 166, 0.18);
    border-radius: 999px;
  }
}

.event-metrics.compact {
  gap: 6px;
  padding-left: 74px;
  margin-top: 8px;

  span {
    padding: 2px 7px;
  }
}

.dot {
  width: 8px;
  height: 8px;
  flex: 0 0 8px;
  background: #22c55e;
  border-radius: 50%;

  &.danger,
  &.alarm {
    background: #ef4444;
  }

  &.warning {
    background: #f59e0b;
  }

  &.normal {
    background: #22c55e;
  }
}

@media (max-width: 1500px) {
  .stat-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .dashboard-grid {
    grid-template-columns: 1fr;
  }

  .stream-layout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 760px) {
  .home-page {
    padding: 16px;
  }

  .hero-panel,
  .alarm-visual {
    grid-template-columns: 1fr;
  }

  .hero-panel {
    display: block;
  }

  .hero-health {
    margin-top: 18px;
  }

  .stat-grid {
    grid-template-columns: 1fr;
  }

  .project-row {
    grid-template-columns: 1.2fr 0.8fr 0.8fr 24px;
  }

  .event-metrics {
    padding-left: 0;
  }

  .event-metrics.compact {
    padding-left: 0;
  }
}
</style>
