<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import {
  fetchVideoDiscoveryCandidates,
  fetchVideoDiscoveryRun,
  fetchVideoDiscoveryRuns,
  fetchVideoDiscoverySearches,
} from '../api/videoDiscoveryRecords'
import type {
  JsonValue,
  VideoDiscoveryCandidateRecord,
  VideoDiscoveryRunRecord,
  VideoDiscoverySearchRecord,
} from '../types/videoDiscoveryRecords'

type DetailTab = 'searches' | 'candidates'
type JsonRecord = Record<string, unknown>

interface ReadablePoint {
  point: string
  pointType: string
  description: string
}

interface ReadableReferenceVideo {
  videoId: string
  title: string
  points: ReadablePoint[]
}

interface ReadablePointContext {
  bizDt: string
  demandGradeId: string
  demandName: string
  grade: string
  videos: ReadableReferenceVideo[]
}

const PAGE_SIZE = 20
const runs = ref<VideoDiscoveryRunRecord[]>([])
const selected = ref<VideoDiscoveryRunRecord | null>(null)
const searches = ref<VideoDiscoverySearchRecord[]>([])
const candidates = ref<VideoDiscoveryCandidateRecord[]>([])
const runTotal = ref(0)
const searchTotal = ref(0)
const candidateTotal = ref(0)
const runPage = ref(1)
const searchPage = ref(1)
const candidatePage = ref(1)
const tab = ref<DetailTab>('searches')
const bizDt = ref('')
const status = ref('')
const runKeyword = ref('')
const detailKeyword = ref('')
const candidateBucket = ref('')
const loadingRuns = ref(true)
const loadingDetail = ref(false)
const error = ref('')
const expandedSearchId = ref<number | null>(null)
const expandedCandidateId = ref<number | null>(null)
let runRequestId = 0
let detailRequestId = 0

const runPages = computed(() => Math.max(1, Math.ceil(runTotal.value / PAGE_SIZE)))
const detailTotal = computed(() => tab.value === 'searches' ? searchTotal.value : candidateTotal.value)
const detailPage = computed(() => tab.value === 'searches' ? searchPage.value : candidatePage.value)
const detailPages = computed(() => Math.max(1, Math.ceil(detailTotal.value / PAGE_SIZE)))
const pointContext = computed(() => buildPointContext(selected.value?.relevant_points))

onMounted(() => loadRuns())

watch(tab, () => {
  detailKeyword.value = ''
  expandedSearchId.value = null
  expandedCandidateId.value = null
  if (!selected.value) return
  if (tab.value === 'searches' && !searches.value.length) void loadSearches()
  if (tab.value === 'candidates' && !candidates.value.length) void loadCandidates()
})

watch(candidateBucket, () => {
  candidatePage.value = 1
  if (selected.value && tab.value === 'candidates') void loadCandidates()
})

async function loadRuns(preferredRunId?: string) {
  const requestId = ++runRequestId
  loadingRuns.value = true
  error.value = ''
  try {
    const response = await fetchVideoDiscoveryRuns({
      bizDt: bizDt.value.trim(),
      status: status.value,
      keyword: runKeyword.value.trim(),
      limit: PAGE_SIZE,
      offset: (runPage.value - 1) * PAGE_SIZE,
    })
    if (requestId !== runRequestId) return
    runs.value = response.items
    runTotal.value = response.total
    const targetId = preferredRunId || selected.value?.run_id
    const target = response.items.find((item) => item.run_id === targetId) || response.items[0]
    if (target) await selectRun(target)
    else clearSelection()
  } catch (cause) {
    if (requestId === runRequestId) {
      error.value = cause instanceof Error ? cause.message : String(cause)
    }
  } finally {
    if (requestId === runRequestId) loadingRuns.value = false
  }
}

async function selectRun(run: VideoDiscoveryRunRecord) {
  selected.value = run
  searchPage.value = 1
  candidatePage.value = 1
  detailKeyword.value = ''
  candidateBucket.value = ''
  searches.value = []
  candidates.value = []
  expandedSearchId.value = null
  expandedCandidateId.value = null
  await loadActiveDetail()
}

function clearSelection() {
  selected.value = null
  searches.value = []
  candidates.value = []
  searchTotal.value = 0
  candidateTotal.value = 0
}

async function loadActiveDetail() {
  if (tab.value === 'searches') await loadSearches()
  else await loadCandidates()
}

async function loadSearches() {
  if (!selected.value) return
  const requestId = ++detailRequestId
  loadingDetail.value = true
  error.value = ''
  try {
    const response = await fetchVideoDiscoverySearches(selected.value.run_id, {
      keyword: detailKeyword.value.trim(),
      limit: PAGE_SIZE,
      offset: (searchPage.value - 1) * PAGE_SIZE,
    })
    if (requestId !== detailRequestId) return
    searches.value = response.items
    searchTotal.value = response.total
  } catch (cause) {
    if (requestId === detailRequestId) {
      error.value = cause instanceof Error ? cause.message : String(cause)
    }
  } finally {
    if (requestId === detailRequestId) loadingDetail.value = false
  }
}

async function loadCandidates() {
  if (!selected.value) return
  const requestId = ++detailRequestId
  loadingDetail.value = true
  error.value = ''
  try {
    const response = await fetchVideoDiscoveryCandidates(selected.value.run_id, {
      bucket: candidateBucket.value,
      keyword: detailKeyword.value.trim(),
      limit: PAGE_SIZE,
      offset: (candidatePage.value - 1) * PAGE_SIZE,
    })
    if (requestId !== detailRequestId) return
    candidates.value = response.items
    candidateTotal.value = response.total
  } catch (cause) {
    if (requestId === detailRequestId) {
      error.value = cause instanceof Error ? cause.message : String(cause)
    }
  } finally {
    if (requestId === detailRequestId) loadingDetail.value = false
  }
}

function applyRunFilters() {
  runPage.value = 1
  void loadRuns()
}

function resetRunFilters() {
  bizDt.value = ''
  status.value = ''
  runKeyword.value = ''
  runPage.value = 1
  void loadRuns()
}

function applyDetailFilter() {
  if (tab.value === 'searches') searchPage.value = 1
  else candidatePage.value = 1
  void loadActiveDetail()
}

function changeRunPage(delta: number) {
  const next = runPage.value + delta
  if (next < 1 || next > runPages.value) return
  runPage.value = next
  void loadRuns()
}

function changeDetailPage(delta: number) {
  if (tab.value === 'searches') searchPage.value += delta
  else candidatePage.value += delta
  void loadActiveDetail()
}

async function refresh() {
  const runId = selected.value?.run_id
  if (runId) {
    try {
      selected.value = await fetchVideoDiscoveryRun(runId)
    } catch {
      // The list refresh below owns the visible error state.
    }
  }
  await loadRuns(runId)
}

function statusLabel(value: string): string {
  return { running: '运行中', finished: '已完成', failed: '失败' }[value] || value
}

function outcomeLabel(value: VideoDiscoveryRunRecord['outcome_status']): string {
  if (!value) return '待计算'
  return {
    goal_met: '完整达标',
    partial: '部分完成',
    no_match: '无有效结果',
    failed: '技术失败',
  }[value]
}

function bucketLabel(value: string): string {
  return {
    primary: '主推荐',
    rejected: '已淘汰',
    pending_evaluation: '待评估',
  }[value] || value
}

function sourceLabel(value: string): string {
  return {
    demand: '需求词',
    seed: '种子视频',
    point: '内容点',
    tag: '标签',
    author: '作者',
    pagination: '翻页',
    mixed: '混合',
  }[value] || value
}

function formatTime(value: string | null): string {
  if (!value) return '—'
  const date = new Date(value)
  if (Number.isNaN(date.getTime())) return value
  return new Intl.DateTimeFormat('zh-CN', {
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hour12: false,
  }).format(date)
}

function formatDate(value: string | null): string {
  if (!value) return '未标记'
  if (/^\d{8}$/.test(value)) return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6)}`
  return value
}

function formatCount(value: number | null): string {
  if (value === null) return '—'
  if (value >= 100_000_000) return `${(value / 100_000_000).toFixed(1)}亿`
  if (value >= 10_000) return `${(value / 10_000).toFixed(1)}万`
  return new Intl.NumberFormat('zh-CN').format(value)
}

function formatScore(value: number | null): string {
  if (value === null) return '—'
  return value.toFixed(value > 1 ? 2 : 3)
}

function isJsonRecord(value: unknown): value is JsonRecord {
  return typeof value === 'object' && value !== null && !Array.isArray(value)
}

function textValue(value: unknown): string {
  if (value === null || value === undefined) return ''
  return String(value).trim()
}

function normalizePoint(value: unknown): ReadablePoint | null {
  if (!isJsonRecord(value)) return null
  const point = textValue(value.point)
  const description = textValue(value.point_desc)
  if (!point && !description) return null
  return {
    point: point || '未命名点位',
    pointType: textValue(value.point_type),
    description,
  }
}

function buildPointContext(value: JsonValue): ReadablePointContext | null {
  const root = isJsonRecord(value) ? value : {}
  const sourceVideos = Array.isArray(root.reference_videos) ? root.reference_videos : []
  const videos: ReadableReferenceVideo[] = sourceVideos
    .filter(isJsonRecord)
    .map((video) => ({
      videoId: textValue(video.video_id),
      title: textValue(video.title) || '未命名参考视频',
      points: (Array.isArray(video.points) ? video.points : [])
        .map(normalizePoint)
        .filter((point): point is ReadablePoint => point !== null),
    }))
    .filter((video) => video.points.length > 0)

  if (!videos.length) {
    const rawPoints = Array.isArray(value)
      ? value
      : Array.isArray(root.relevant_points)
        ? root.relevant_points
        : []
    const grouped = new Map<string, ReadableReferenceVideo>()
    rawPoints.filter(isJsonRecord).forEach((rawPoint) => {
      const point = normalizePoint(rawPoint)
      if (!point) return
      const videoId = textValue(rawPoint.video_id)
      const title = textValue(rawPoint.video_title) || '未标记参考视频'
      const key = videoId || title
      const group = grouped.get(key) || { videoId, title, points: [] }
      group.points.push(point)
      grouped.set(key, group)
    })
    videos.push(...grouped.values())
  }

  if (!videos.length && !Object.keys(root).length) return null
  return {
    bizDt: textValue(root.biz_dt),
    demandGradeId: textValue(root.demand_grade_id),
    demandName: textValue(root.demand_name),
    grade: textValue(root.grade),
    videos,
  }
}

function pointTypeLabel(value: string): string {
  return {
    inspiration: '灵感点',
    purpose: '目的点',
    key: '关键点',
  }[value] || value || '内容点'
}

function engagementText(video: {
  like_count: number | null
  comment_count: number | null
  collect_count: number | null
  share_count: number | null
}): string {
  return [
    `赞 ${formatCount(video.like_count)}`,
    `评 ${formatCount(video.comment_count)}`,
    `藏 ${formatCount(video.collect_count)}`,
    `转 ${formatCount(video.share_count)}`,
  ].join(' · ')
}

function formatJson(value: JsonValue): string {
  if (value === null || value === undefined || value === '') return '暂无数据'
  if (typeof value === 'string') return value
  return JSON.stringify(value, null, 2)
}

function openCandidate(candidate: VideoDiscoveryCandidateRecord) {
  const url = candidate.content_link?.trim()
  if (url) window.open(url, '_blank', 'noopener,noreferrer')
}
</script>

<template>
  <div class="records-page">
    <header class="page-header">
      <div>
        <div class="eyebrow"><span /> FIND AGENT · DATA TRACE</div>
        <h1>找视频记录</h1>
        <p>沿运行、搜索轨迹和候选视频还原 Find Agent 的完整决策过程。</p>
      </div>
      <button class="refresh-button" type="button" :disabled="loadingRuns" @click="refresh">
        <span :class="{ spinning: loadingRuns }">↻</span>
        刷新数据
      </button>
    </header>

    <form class="filters" @submit.prevent="applyRunFilters">
      <label>
        <span>业务日</span>
        <input v-model="bizDt" inputmode="numeric" maxlength="8" placeholder="YYYYMMDD" />
      </label>
      <label>
        <span>运行状态</span>
        <select v-model="status">
          <option value="">全部状态</option>
          <option value="running">运行中</option>
          <option value="finished">已完成</option>
          <option value="failed">失败</option>
        </select>
      </label>
      <label class="keyword-filter">
        <span>需求 / Run ID</span>
        <input v-model="runKeyword" type="search" placeholder="搜索需求词、种子标题或 Run ID" />
      </label>
      <button class="primary-button" type="submit">查询</button>
      <button class="ghost-button" type="button" @click="resetRunFilters">重置</button>
    </form>

    <p v-if="error" class="error-banner">
      <strong>数据加载失败</strong>
      {{ error }}
    </p>

    <section class="workspace">
      <aside class="run-panel">
        <div class="panel-heading">
          <div>
            <span>video_discovery_run</span>
            <strong>运行记录</strong>
          </div>
          <b>{{ runTotal }}</b>
        </div>

        <div v-if="loadingRuns && !runs.length" class="state"><i class="loader" />加载运行记录…</div>
        <div v-else-if="!runs.length" class="state empty">
          <strong>暂无运行记录</strong>
          <span>尝试调整业务日或状态筛选</span>
        </div>
        <div v-else class="run-list">
          <button
            v-for="run in runs"
            :key="run.run_id"
            type="button"
            class="run-card"
            :class="{ active: selected?.run_id === run.run_id }"
            @click="selectRun(run)"
          >
            <span class="run-card-top">
              <span class="status-pill" :class="`status-${run.status}`">
                <i />{{ statusLabel(run.status) }}
              </span>
              <time>{{ formatTime(run.create_time) }}</time>
            </span>
            <strong>{{ run.demand_word }}</strong>
            <small :title="run.run_id">{{ run.run_id }}</small>
            <span class="run-card-meta">
              <span>{{ formatDate(run.biz_dt) }}</span>
              <span>{{ run.search_count }} 次搜索</span>
              <span>{{ run.candidate_count }} 个候选</span>
            </span>
          </button>
        </div>

        <div class="pagination compact">
          <button type="button" :disabled="runPage <= 1" @click="changeRunPage(-1)">‹</button>
          <span>{{ runPage }} / {{ runPages }}</span>
          <button type="button" :disabled="runPage >= runPages" @click="changeRunPage(1)">›</button>
        </div>
      </aside>

      <main v-if="selected" class="detail-panel">
        <section class="run-summary">
          <div class="summary-main">
            <div class="summary-title">
              <div>
                <span>当前运行 · {{ formatDate(selected.biz_dt) }}</span>
                <h2>{{ selected.demand_word }}</h2>
              </div>
              <span class="status-pill large" :class="`status-${selected.status}`">
                <i />{{ statusLabel(selected.status) }}
              </span>
            </div>
            <p v-if="selected.intent_summary" class="intent">{{ selected.intent_summary }}</p>
            <p v-else class="intent muted">Agent 尚未写入意图总结。</p>
            <div class="identity-row">
              <code>{{ selected.run_id }}</code>
              <span>业务结果：{{ outcomeLabel(selected.outcome_status) }}</span>
              <span>执行次数：{{ selected.attempt_count }}</span>
              <span v-if="selected.demand_grade_id">需求分级 #{{ selected.demand_grade_id }}</span>
              <span v-if="selected.seed_video_id">种子视频 {{ selected.seed_video_id }}</span>
            </div>
            <details v-if="selected.relevant_points || selected.stop_reason" class="run-context" open>
              <summary>查看输入点位与停止原因</summary>
              <div v-if="selected.stop_reason">
                <strong>停止原因</strong>
                <p>{{ selected.stop_reason }}</p>
              </div>
              <div v-if="pointContext">
                <strong>相关点位</strong>
                <div class="point-context">
                  <div
                    v-if="
                      pointContext.demandName
                        || pointContext.grade
                        || pointContext.bizDt
                        || pointContext.demandGradeId
                    "
                    class="point-context-meta"
                  >
                    <span v-if="pointContext.demandName">
                      <small>需求</small>{{ pointContext.demandName }}
                    </span>
                    <span v-if="pointContext.grade">
                      <small>等级</small><b>{{ pointContext.grade }}</b>
                    </span>
                    <span v-if="pointContext.bizDt">
                      <small>业务日</small>{{ formatDate(pointContext.bizDt) }}
                    </span>
                    <span v-if="pointContext.demandGradeId">
                      <small>分级 ID</small>#{{ pointContext.demandGradeId }}
                    </span>
                  </div>
                  <div v-if="pointContext.videos.length" class="reference-video-list">
                    <article
                      v-for="video in pointContext.videos"
                      :key="video.videoId || video.title"
                      class="reference-video-card"
                    >
                      <header>
                        <div>
                          <small>参考视频</small>
                          <h3 :title="video.title">{{ video.title }}</h3>
                        </div>
                        <code v-if="video.videoId">{{ video.videoId }}</code>
                      </header>
                      <div class="readable-point-list">
                        <div
                          v-for="(point, index) in video.points"
                          :key="`${point.point}-${index}`"
                          class="readable-point"
                        >
                          <span
                            class="point-type"
                            :class="`point-type-${point.pointType || 'default'}`"
                          >
                            {{ pointTypeLabel(point.pointType) }}
                          </span>
                          <div>
                            <strong>{{ point.point }}</strong>
                            <p v-if="point.description">{{ point.description }}</p>
                          </div>
                        </div>
                      </div>
                    </article>
                  </div>
                  <p v-else class="no-readable-points">暂无可展示的点位内容。</p>
                </div>
              </div>
            </details>
          </div>

          <div class="metric-grid">
            <article><span>搜索页</span><strong>{{ selected.search_count }}</strong></article>
            <article><span>全部候选</span><strong>{{ selected.candidate_count }}</strong></article>
            <article class="metric-primary"><span>有效主推荐 / 记录</span><strong>{{ selected.valid_primary_count }}<small> / {{ selected.primary_count }}</small></strong></article>
            <article class="metric-rejected"><span>淘汰 / 待评估</span><strong>{{ selected.rejected_count }}<small> / {{ selected.pending_count }}</small></strong></article>
          </div>
        </section>

        <section class="records-card">
          <div class="tabs">
            <button type="button" :class="{ active: tab === 'searches' }" @click="tab = 'searches'">
              搜索轨迹 <b>{{ selected.search_count }}</b>
            </button>
            <button type="button" :class="{ active: tab === 'candidates' }" @click="tab = 'candidates'">
              候选视频 <b>{{ selected.candidate_count }}</b>
            </button>
          </div>

          <form class="detail-filters" @submit.prevent="applyDetailFilter">
            <label class="detail-search">
              <span class="search-icon" />
              <input
                v-model="detailKeyword"
                type="search"
                :placeholder="tab === 'searches' ? '搜索关键词、原因或来源' : '搜索视频 ID、标题或作者'"
              />
            </label>
            <select v-if="tab === 'candidates'" v-model="candidateBucket" aria-label="候选分池">
              <option value="">全部分池</option>
              <option value="primary">主推荐</option>
              <option value="rejected">已淘汰</option>
              <option value="pending_evaluation">待评估</option>
            </select>
            <button type="submit">筛选</button>
            <span class="result-count">共 {{ detailTotal }} 条</span>
          </form>

          <div v-if="loadingDetail" class="state"><i class="loader" />加载记录…</div>
          <div
            v-else-if="tab === 'searches' && !searches.length"
            class="state empty"
          >
            <strong>暂无搜索轨迹</strong>
            <span>Agent 保存搜索页后会显示在这里</span>
          </div>
          <div
            v-else-if="tab === 'candidates' && !candidates.length"
            class="state empty"
          >
            <strong>暂无候选视频</strong>
            <span>当前运行或筛选条件下没有候选</span>
          </div>

          <div v-else-if="tab === 'searches'" class="table-wrap">
            <table class="search-table">
              <thead>
                <tr>
                  <th>ID / 状态</th>
                  <th>搜索词与原因</th>
                  <th>来源</th>
                  <th>供应方 / 分页</th>
                  <th>结果</th>
                  <th>创建时间</th>
                  <th />
                </tr>
              </thead>
              <tbody>
                <template v-for="item in searches" :key="item.id">
                  <tr>
                    <td>
                      <span class="record-id">#{{ item.id }}</span>
                      <span class="mini-status" :class="item.status === 'success' ? 'ok' : 'bad'">
                        {{ item.status === 'success' ? '成功' : '失败' }}
                      </span>
                    </td>
                    <td class="wide-cell">
                      <strong>{{ item.keyword }}</strong>
                      <small>{{ item.query_reason }}</small>
                    </td>
                    <td>
                      <span class="source-pill">{{ sourceLabel(item.source_type) }}</span>
                      <small>{{ item.source_value || '—' }}</small>
                    </td>
                    <td>
                      <strong>{{ item.provider }}</strong>
                      <small>第 {{ item.page_no }} 页 · 游标 {{ item.cursor }}</small>
                    </td>
                    <td>
                      <strong>{{ item.results_count }}</strong>
                      <small>新增 {{ item.new_candidate_count }} · {{ item.has_more ? '有下一页' : '已到底' }}</small>
                    </td>
                    <td class="nowrap">{{ formatTime(item.create_time) }}</td>
                    <td>
                      <button
                        class="expand-button"
                        type="button"
                        :aria-expanded="expandedSearchId === item.id"
                        @click="expandedSearchId = expandedSearchId === item.id ? null : item.id"
                      >
                        {{ expandedSearchId === item.id ? '收起' : '详情' }}
                      </button>
                    </td>
                  </tr>
                  <tr v-if="expandedSearchId === item.id" class="expanded-row">
                    <td colspan="7">
                      <div class="search-videos">
                        <div class="search-videos-heading">
                          <strong>本次搜索视频</strong>
                          <span>{{ item.candidates.length }} 个已入库候选</span>
                        </div>
                        <div v-if="item.candidates.length" class="search-video-grid">
                          <article v-for="video in item.candidates" :key="video.id">
                            <div>
                              <span class="bucket-pill" :class="`bucket-${video.decision_bucket}`">
                                {{ bucketLabel(video.decision_bucket) }}
                              </span>
                              <strong :title="video.title || '未命名视频'">
                                {{ video.title || '未命名视频' }}
                              </strong>
                              <small :title="`${video.author_name || '未知作者'} · ${video.aweme_id}`">
                                {{ video.author_name || '未知作者' }} · {{ video.aweme_id }}
                              </small>
                            </div>
                            <div class="search-video-metrics">
                              <span>{{ formatCount(video.play_count) }} 播放</span>
                              <span>V {{ formatScore(video.value_score) }}</span>
                            </div>
                            <p class="engagement-line" :title="engagementText(video)">
                              {{ engagementText(video) }}
                            </p>
                            <a
                              v-if="video.content_link"
                              :href="video.content_link"
                              target="_blank"
                              rel="noopener noreferrer"
                            >
                              打开视频 ↗
                            </a>
                          </article>
                        </div>
                        <p v-else>本次搜索没有关联的候选视频。</p>
                      </div>
                      <div class="expanded-grid">
                        <div>
                          <strong>检索参数</strong>
                          <dl>
                            <dt>搜索键</dt><dd>{{ item.search_key }}</dd>
                            <dt>内容 / 排序</dt><dd>{{ item.content_type }} · {{ item.sort_type }}</dd>
                            <dt>发布时间</dt><dd>{{ item.publish_time }}</dd>
                            <dt>父搜索</dt><dd>{{ item.parent_search_id ? `#${item.parent_search_id}` : '根搜索' }}</dd>
                            <dt>下一游标</dt><dd>{{ item.next_cursor || '—' }}</dd>
                          </dl>
                          <p v-if="item.error_message" class="error-text">{{ item.error_message }}</p>
                        </div>
                        <div>
                          <strong>供应方状态</strong>
                          <pre>{{ formatJson(item.provider_state) }}</pre>
                        </div>
                      </div>
                    </td>
                  </tr>
                </template>
              </tbody>
            </table>
          </div>

          <div v-else class="table-wrap">
            <table class="candidate-table">
              <thead>
                <tr>
                  <th>ID / 分池</th>
                  <th>视频与作者</th>
                  <th>互动数据</th>
                  <th>R / E / S / V</th>
                  <th>AIGC 计划</th>
                  <th>创建时间</th>
                  <th />
                </tr>
              </thead>
              <tbody>
                <template v-for="item in candidates" :key="item.id">
                  <tr>
                    <td>
                      <span class="record-id">#{{ item.id }}</span>
                      <span class="bucket-pill" :class="`bucket-${item.decision_bucket}`">
                        {{ bucketLabel(item.decision_bucket) }}
                      </span>
                    </td>
                    <td class="wide-cell video-cell">
                      <strong :title="item.title || '未命名视频'">
                        {{ item.title || '未命名视频' }}
                      </strong>
                      <small :title="`${item.author_name || '未知作者'} · ${item.aweme_id}`">
                        {{ item.author_name || '未知作者' }} · {{ item.aweme_id }}
                      </small>
                    </td>
                    <td>
                      <strong>{{ formatCount(item.play_count) }} 播放</strong>
                      <small :title="engagementText(item)">
                        {{ engagementText(item) }}
                      </small>
                    </td>
                    <td>
                      <span class="score-line">
                        <b>R {{ formatScore(item.relevance_score) }}</b>
                        <b>E {{ formatScore(item.elder_score) }}</b>
                        <b>S {{ formatScore(item.share_score) }}</b>
                        <b class="value-score">V {{ formatScore(item.value_score) }}</b>
                      </span>
                    </td>
                    <td>
                      <strong>{{ item.aigc_plan_label || '未分发' }}</strong>
                      <small>{{ item.aigc_crawler_plan_id || '—' }}</small>
                    </td>
                    <td class="nowrap">{{ formatTime(item.create_time) }}</td>
                    <td>
                      <button
                        class="expand-button"
                        type="button"
                        :aria-expanded="expandedCandidateId === item.id"
                        @click="expandedCandidateId = expandedCandidateId === item.id ? null : item.id"
                      >
                        {{ expandedCandidateId === item.id ? '收起' : '详情' }}
                      </button>
                    </td>
                  </tr>
                  <tr v-if="expandedCandidateId === item.id" class="expanded-row">
                    <td colspan="7">
                      <div class="candidate-detail">
                        <div class="decision-block">
                          <strong>决策依据</strong>
                          <p>{{ item.decision_reason || '暂无决策说明' }}</p>
                          <div class="tag-row">
                            <span v-for="tag in Array.isArray(item.tags) ? item.tags : []" :key="String(tag)">
                              {{ tag }}
                            </span>
                          </div>
                          <button
                            v-if="item.content_link"
                            class="link-button"
                            type="button"
                            @click="openCandidate(item)"
                          >
                            打开原视频 ↗
                          </button>
                        </div>
                      </div>
                    </td>
                  </tr>
                </template>
              </tbody>
            </table>
          </div>

          <div v-if="detailTotal" class="pagination">
            <span>第 {{ detailPage }} / {{ detailPages }} 页</span>
            <div>
              <button type="button" :disabled="detailPage <= 1" @click="changeDetailPage(-1)">上一页</button>
              <button type="button" :disabled="detailPage >= detailPages" @click="changeDetailPage(1)">下一页</button>
            </div>
          </div>
        </section>
      </main>

      <main v-else class="detail-panel no-selection">
        <span>⌕</span>
        <strong>选择一次找视频运行</strong>
        <p>运行的搜索轨迹和候选评估将在这里展开。</p>
      </main>
    </section>
  </div>
</template>

<style scoped>
.records-page {
  display: flex;
  min-width: 0;
  max-width: 1680px;
  min-height: 100%;
  margin: 0 auto;
  flex-direction: column;
  gap: 16px;
  color: #242b3a;
}

.page-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  padding: 8px 4px 2px;
}

.eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #737b8c;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: .17em;
}

.eyebrow span {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #6666d4;
  box-shadow: 0 0 0 4px rgba(102, 102, 212, .11);
}

.page-header h1 {
  margin: 8px 0 3px;
  font-size: 27px;
  letter-spacing: -.04em;
}

.page-header p {
  margin: 0;
  color: #8a91a0;
  font-size: 14px;
}

button, input, select { font: inherit; }

.refresh-button,
.primary-button,
.ghost-button {
  height: 38px;
  border-radius: 9px;
  cursor: pointer;
}

.refresh-button {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 0 13px;
  border: 1px solid #dfe2e8;
  background: #fff;
  color: #5d6575;
  font-size: 13px;
  font-weight: 700;
}

.spinning { display: inline-block; animation: spin .7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

.filters {
  display: grid;
  grid-template-columns: 150px 160px minmax(260px, 1fr) auto auto;
  align-items: end;
  gap: 10px;
  padding: 13px 15px;
  border: 1px solid #e3e6eb;
  border-radius: 14px;
  background: rgba(255, 255, 255, .88);
}

.filters label {
  display: grid;
  gap: 5px;
}

.filters label > span {
  color: #858c9b;
  font-size: 12px;
  font-weight: 700;
}

.filters input,
.filters select,
.detail-filters input,
.detail-filters select {
  height: 38px;
  border: 1px solid #dfe2e8;
  border-radius: 8px;
  outline: none;
  background: #fff;
  color: #424a5b;
  font-size: 13px;
}

.filters input { width: 100%; padding: 0 11px; }
.filters select, .detail-filters select { padding: 0 30px 0 10px; }
.filters input:focus, .filters select:focus, .detail-filters input:focus, .detail-filters select:focus {
  border-color: #7474d8;
  box-shadow: 0 0 0 3px rgba(102, 102, 212, .08);
}

.primary-button {
  padding: 0 18px;
  border: 1px solid #5d5dcd;
  background: #6262d2;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
}

.ghost-button {
  padding: 0 13px;
  border: 1px solid #dfe2e8;
  background: #fff;
  color: #7a8190;
  font-size: 13px;
}

.error-banner {
  margin: 0;
  padding: 10px 14px;
  border: 1px solid #f0cdd2;
  border-radius: 10px;
  background: #fff5f6;
  color: #a43d4b;
  font-size: 13px;
}

.error-banner strong { margin-right: 8px; }

.workspace {
  display: grid;
  min-height: 650px;
  flex: 1;
  grid-template-columns: 290px minmax(0, 1fr);
  gap: 14px;
}

.run-panel,
.detail-panel {
  min-width: 0;
  border: 1px solid #e2e5ea;
  border-radius: 15px;
  background: rgba(255, 255, 255, .91);
  box-shadow: 0 8px 26px rgba(34, 42, 61, .035);
}

.run-panel {
  display: flex;
  overflow: hidden;
  flex-direction: column;
}

.panel-heading {
  display: flex;
  min-height: 62px;
  align-items: center;
  justify-content: space-between;
  padding: 0 15px;
  border-bottom: 1px solid #ebedf1;
}

.panel-heading div > span,
.panel-heading div > strong { display: block; }
.panel-heading div > span { color: #a0a6b1; font-size: 11px; font-weight: 700; }
.panel-heading div > strong { margin-top: 3px; font-size: 15px; }
.panel-heading b {
  display: grid;
  min-width: 27px;
  height: 22px;
  place-items: center;
  padding: 0 7px;
  border-radius: 7px;
  background: #f0f0ff;
  color: #5f5fcc;
  font-size: 12px;
}

.run-list {
  min-height: 0;
  padding: 7px;
  flex: 1;
  overflow: auto;
}

.run-card {
  display: flex;
  width: 100%;
  padding: 12px 11px;
  flex-direction: column;
  gap: 6px;
  border: 1px solid transparent;
  border-radius: 10px;
  background: transparent;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.run-card:hover { background: #f7f7fa; }
.run-card.active {
  border-color: #dcdcff;
  background: #f1f1ff;
}

.run-card-top,
.run-card-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
}

.run-card-top time { color: #9aa0ac; font-size: 11px; }
.run-card > strong {
  overflow: hidden;
  font-size: 15px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.run-card > small {
  overflow: hidden;
  color: #9ca2ae;
  font-family: ui-monospace, monospace;
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.run-card-meta {
  justify-content: flex-start;
  color: #7e8594;
  font-size: 11px;
}
.run-card-meta span + span::before { margin-right: 6px; color: #c3c7cf; content: '·'; }

.status-pill,
.bucket-pill {
  display: inline-flex;
  width: fit-content;
  align-items: center;
  gap: 5px;
  padding: 3px 7px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 800;
}
.status-pill i { width: 5px; height: 5px; border-radius: 50%; }
.status-running { background: #fff7df; color: #9a6b08; }
.status-running i { background: #e8a91d; }
.status-finished { background: #eaf8f2; color: #278365; }
.status-finished i { background: #31aa80; }
.status-failed { background: #fff0f1; color: #af4652; }
.status-failed i { background: #d25563; }
.status-pill.large { padding: 5px 9px; font-size: 12px; }

.detail-panel { overflow: hidden; }
.run-summary {
  display: grid;
  padding: 19px;
  grid-template-columns: minmax(0, 1fr) 390px;
  gap: 18px;
  border-bottom: 1px solid #e8eaee;
  background: linear-gradient(145deg, #fff 20%, #f8f8ff);
}
.summary-title {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 15px;
}
.summary-title > div > span { color: #9298a5; font-size: 12px; font-weight: 700; }
.summary-title h2 { margin: 4px 0 0; font-size: 22px; letter-spacing: -.03em; }
.intent { max-width: 800px; margin: 11px 0 10px; color: #586071; font-size: 13px; line-height: 1.7; }
.intent.muted { color: #9ca2ad; }
.identity-row { display: flex; flex-wrap: wrap; gap: 8px; }
.identity-row code, .identity-row span {
  padding: 4px 7px;
  border-radius: 6px;
  background: #f0f2f5;
  color: #707787;
  font-size: 11px;
}
.run-context { margin-top: 10px; font-size: 12px; }
.run-context summary { color: #6262cc; cursor: pointer; }
.run-context strong { display: block; margin-top: 8px; color: #606878; }
.run-context p { margin: 4px 0; color: #737b89; }
.point-context {
  margin-top: 8px;
  padding: 13px;
  border: 1px solid #e2e5eb;
  border-radius: 11px;
  background: rgba(255, 255, 255, .78);
}
.point-context-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 11px;
}
.point-context-meta > span {
  display: inline-flex;
  min-height: 31px;
  align-items: center;
  gap: 7px;
  padding: 5px 9px;
  border-radius: 7px;
  background: #f1f2f6;
  color: #535b6b;
  font-size: 12px;
  font-weight: 650;
}
.point-context-meta small {
  color: #9299a6;
  font-size: 11px;
  font-weight: 600;
}
.point-context-meta b {
  color: #5557c3;
  font-size: 14px;
}
.reference-video-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
  gap: 10px;
}
.reference-video-card {
  min-width: 0;
  overflow: hidden;
  border: 1px solid #e2e5ea;
  border-radius: 10px;
  background: #fff;
}
.reference-video-card > header {
  display: flex;
  min-height: 61px;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  border-bottom: 1px solid #eceef2;
  background: #fafbfc;
}
.reference-video-card > header > div { min-width: 0; }
.reference-video-card header small {
  display: block;
  color: #969daa;
  font-size: 11px;
}
.reference-video-card h3 {
  margin: 3px 0 0;
  overflow: hidden;
  color: #394152;
  font-size: 14px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.reference-video-card header code {
  flex-shrink: 0;
  padding: 4px 7px;
  border-radius: 6px;
  background: #eff0f5;
  color: #707787;
  font-size: 11px;
}
.readable-point-list { display: grid; }
.readable-point {
  display: grid;
  padding: 11px 12px;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 10px;
}
.readable-point + .readable-point { border-top: 1px solid #eff0f3; }
.readable-point .point-type {
  display: inline-flex;
  min-width: 50px;
  min-height: 23px;
  align-items: center;
  justify-content: center;
  padding: 2px 7px;
  border-radius: 999px;
  background: #eef0f4;
  color: #687080;
  font-size: 11px;
  font-weight: 750;
  white-space: nowrap;
}
.readable-point .point-type-inspiration {
  background: #f0edff;
  color: #6860c7;
}
.readable-point .point-type-purpose {
  background: #e9f7f2;
  color: #287b62;
}
.readable-point .point-type-key {
  background: #fff3df;
  color: #97660b;
}
.readable-point > div > strong {
  margin: 1px 0 0;
  color: #41495a;
  font-size: 13px;
}
.readable-point > div > p {
  margin: 6px 0 0;
  color: #6d7585;
  font-size: 12px;
  line-height: 1.7;
  white-space: pre-line;
}
.no-readable-points {
  margin: 0;
  color: #9299a6;
  font-size: 12px;
}
pre {
  max-height: 210px;
  margin: 6px 0 0;
  padding: 10px;
  overflow: auto;
  border: 1px solid #e5e7ec;
  border-radius: 8px;
  background: #f8f9fb;
  color: #596173;
  font: 11px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: pre-wrap;
  word-break: break-word;
}
.metric-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; align-self: start; }
.metric-grid article {
  min-height: 68px;
  padding: 11px 12px;
  border: 1px solid #e5e7ed;
  border-radius: 10px;
  background: rgba(255,255,255,.8);
}
.metric-grid span { display: block; color: #9298a5; font-size: 11px; font-weight: 700; }
.metric-grid strong { display: block; margin-top: 5px; font-size: 19px; letter-spacing: -.04em; }
.metric-grid strong small { color: #999fac; font-size: 14px; }
.metric-grid .metric-primary { border-color: #cceadf; background: #f4fbf8; }
.metric-primary strong { color: #248062; }
.metric-grid .metric-rejected { border-color: #f0dadd; background: #fff9f9; }
.metric-rejected strong { color: #a94c58; }

.records-card { min-width: 0; }
.tabs {
  display: flex;
  height: 49px;
  align-items: flex-end;
  gap: 21px;
  padding: 0 18px;
  border-bottom: 1px solid #e8eaee;
}
.tabs button {
  height: 49px;
  padding: 0 2px;
  border: 0;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: #858c9a;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.tabs button.active { border-bottom-color: #6262d2; color: #4f50bd; }
.tabs b { margin-left: 4px; color: #a1a6b1; font-size: 11px; }

.detail-filters {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 13px;
  border-bottom: 1px solid #eceef2;
  background: #fafbfc;
}
.detail-search { position: relative; width: min(360px, 45%); }
.detail-search input { width: 100%; padding: 0 11px 0 34px; }
.search-icon {
  position: absolute;
  z-index: 1;
  top: 12px;
  left: 12px;
  width: 10px;
  height: 10px;
  border: 1.5px solid #969daa;
  border-radius: 50%;
}
.search-icon::after {
  position: absolute;
  right: -4px;
  bottom: -3px;
  width: 5px;
  height: 1.5px;
  background: #969daa;
  content: '';
  transform: rotate(45deg);
}
.detail-filters > button {
  height: 34px;
  padding: 0 13px;
  border: 1px solid #d7dae1;
  border-radius: 8px;
  background: #fff;
  color: #646c7b;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}
.result-count { margin-left: auto; color: #939aa7; font-size: 12px; }

.table-wrap { width: 100%; overflow-x: auto; }
table { width: 100%; min-width: 1080px; border-collapse: collapse; table-layout: fixed; }
th {
  height: 42px;
  padding: 0 10px;
  border-bottom: 1px solid #e8eaee;
  background: #fff;
  color: #9298a5;
  font-size: 11px;
  font-weight: 700;
  text-align: left;
}
td {
  height: 74px;
  padding: 9px 10px;
  border-bottom: 1px solid #eff0f3;
  color: #5b6373;
  font-size: 12px;
  vertical-align: middle;
}
tbody > tr:not(.expanded-row):hover { background: #fafafe; }
td strong, td small { display: block; }
td strong {
  overflow: hidden;
  color: #41495a;
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
td small {
  max-width: 100%;
  margin-top: 4px;
  overflow: hidden;
  color: #9096a3;
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.search-table th:nth-child(1), .search-table td:nth-child(1) { width: 100px; }
.search-table th:nth-child(2), .search-table td:nth-child(2) { width: 25%; }
.search-table th:nth-child(3), .search-table td:nth-child(3) { width: 15%; }
.search-table th:nth-child(4), .search-table td:nth-child(4) { width: 16%; }
.search-table th:nth-child(5), .search-table td:nth-child(5) { width: 14%; }
.search-table th:nth-child(6), .search-table td:nth-child(6) { width: 130px; }
.search-table th:nth-child(7), .search-table td:nth-child(7) { width: 64px; }
.candidate-table th:nth-child(1), .candidate-table td:nth-child(1) { width: 105px; }
.candidate-table th:nth-child(2), .candidate-table td:nth-child(2) { width: 24%; }
.candidate-table th:nth-child(3), .candidate-table td:nth-child(3) { width: 19%; }
.candidate-table th:nth-child(4), .candidate-table td:nth-child(4) { width: 20%; }
.candidate-table th:nth-child(5), .candidate-table td:nth-child(5) { width: 13%; }
.candidate-table th:nth-child(6), .candidate-table td:nth-child(6) { width: 130px; }
.candidate-table th:nth-child(7), .candidate-table td:nth-child(7) { width: 64px; }
.wide-cell strong { font-size: 13px; }
.record-id {
  display: block;
  margin-bottom: 5px;
  color: #858c9a;
  font-family: ui-monospace, monospace;
  font-size: 11px;
}
.mini-status, .source-pill {
  display: inline-block;
  padding: 2px 6px;
  border-radius: 5px;
  font-size: 11px;
  font-weight: 700;
}
.mini-status.ok { background: #eaf8f2; color: #278365; }
.mini-status.bad { background: #fff0f1; color: #ad4350; }
.source-pill { background: #eef0ff; color: #5f61bc; }
.bucket-primary { background: #e8f8f1; color: #247e61; }
.bucket-rejected { background: #fff0f1; color: #aa4551; }
.bucket-pending_evaluation { background: #fff7df; color: #946708; }
.nowrap { white-space: nowrap; }
.score-line { display: flex; flex-wrap: wrap; gap: 4px; }
.score-line b {
  padding: 3px 5px;
  border-radius: 5px;
  background: #f1f2f5;
  color: #687080;
  font-size: 10px;
}
.score-line .value-score { background: #ececff; color: #5758c2; }
.expand-button {
  padding: 4px 0;
  border: 0;
  background: transparent;
  color: #5e60c8;
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
}
.expanded-row > td { height: auto; padding: 13px; background: #f8f9fc; }
.search-videos {
  margin-bottom: 12px;
  padding: 13px;
  border: 1px solid #dfe2ea;
  border-radius: 10px;
  background: #fff;
}
.search-videos-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}
.search-videos-heading strong { color: #424a5b; font-size: 14px; }
.search-videos-heading span,
.search-videos > p { margin: 0; color: #8b92a0; font-size: 12px; }
.search-video-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}
.search-video-grid article {
  display: grid;
  min-width: 0;
  padding: 11px;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px 12px;
  border: 1px solid #e7e9ee;
  border-radius: 9px;
  background: #fafbfc;
}
.search-video-grid article > div:first-child { min-width: 0; }
.search-video-grid article strong,
.search-video-grid article small { display: block; }
.search-video-grid article strong {
  margin-top: 7px;
  overflow: hidden;
  color: #3f4758;
  font-size: 13px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.search-video-grid article small {
  margin-top: 4px;
  overflow: hidden;
  color: #858c9a;
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.search-video-metrics {
  display: grid;
  align-content: center;
  gap: 3px;
  color: #737b8a;
  font-size: 11px;
  text-align: right;
  white-space: nowrap;
}
.search-video-grid .engagement-line {
  grid-column: 1 / -1;
  margin: 0;
  overflow: hidden;
  color: #626a7a;
  font-size: 12px;
  line-height: 1.5;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: help;
}
.search-video-grid article a {
  width: fit-content;
  color: #5b5dc7;
  font-size: 12px;
  font-weight: 700;
  text-decoration: none;
}
.expanded-grid { display: grid; grid-template-columns: .8fr 1fr; gap: 12px; }
.expanded-grid > div, .decision-block {
  min-width: 0;
  padding: 11px;
  border: 1px solid #e3e6ec;
  border-radius: 9px;
  background: #fff;
}
.expanded-grid > div > strong, .decision-block > strong {
  color: #4b5363;
  font-size: 12px;
}
dl {
  display: grid;
  grid-template-columns: 78px minmax(0, 1fr);
  gap: 6px;
  margin: 8px 0 0;
  font-size: 11px;
}
dt { color: #9ba1ad; }
dd { margin: 0; overflow-wrap: anywhere; color: #606878; }
.error-text { color: #ad4350; }
.candidate-detail { display: block; }
.decision-block p { color: #697181; font-size: 12px; line-height: 1.7; }
.tag-row { display: flex; flex-wrap: wrap; gap: 5px; }
.tag-row span { padding: 3px 6px; border-radius: 5px; background: #f0f1f5; color: #717887; font-size: 11px; }
.link-button {
  margin-top: 10px;
  padding: 5px 8px;
  border: 1px solid #d9daf4;
  border-radius: 6px;
  background: #f5f5ff;
  color: #5a5cc5;
  font-size: 11px;
  cursor: pointer;
}
.state {
  display: flex;
  min-height: 190px;
  align-items: center;
  justify-content: center;
  gap: 8px;
  color: #8d94a2;
  font-size: 13px;
}
.state.empty { flex-direction: column; }
.state.empty strong { color: #666e7d; font-size: 14px; }
.state.empty span { color: #9ba1ad; font-size: 12px; }
.loader {
  width: 13px;
  height: 13px;
  border: 2px solid #dddff1;
  border-top-color: #6262d2;
  border-radius: 50%;
  animation: spin .7s linear infinite;
}
.pagination {
  display: flex;
  min-height: 51px;
  align-items: center;
  justify-content: space-between;
  padding: 8px 13px;
  border-top: 1px solid #eaecf0;
  color: #8b92a0;
  font-size: 12px;
}
.pagination div { display: flex; gap: 6px; }
.pagination button {
  min-width: 54px;
  height: 29px;
  border: 1px solid #dfe2e7;
  border-radius: 7px;
  background: #fff;
  color: #656d7d;
  font-size: 11px;
  cursor: pointer;
}
.pagination button:disabled { opacity: .4; cursor: default; }
.pagination.compact { justify-content: center; gap: 10px; margin-top: auto; }
.pagination.compact button { min-width: 29px; }
.no-selection {
  display: flex;
  min-height: 650px;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  color: #9299a6;
}
.no-selection > span { font-size: 32px; color: #7778d4; }
.no-selection strong { margin-top: 9px; color: #596171; font-size: 16px; }
.no-selection p { margin: 5px 0; font-size: 12px; }

@media (max-width: 1180px) {
  .run-summary { grid-template-columns: 1fr; }
  .metric-grid { grid-template-columns: repeat(4, 1fr); }
  .metric-grid article { min-height: 58px; }
}
</style>
