<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import {
  fetchVideoDiscoveryDemand,
  fetchVideoDiscoveryDemands,
} from '../api/videoDiscovery'
import DemandFeedbackDrawer from '../components/DemandFeedbackDrawer.vue'
import { formatPosteriorDiff } from '../types/category'
import { pickDecodeUrl, videoDetailUrl } from '../utils/videoLinks'
import type {
  DemandFeedbackTarget,
  FeedbackSummary,
  VideoDiscoveryDemand,
  VideoDiscoveryDemandDetail,
  VideoDiscoveryPoint,
  VideoDiscoveryVideo,
} from '../types/videoDiscovery'

type SupplyFilter = 'all' | 'with-video' | 'posterior' | 'no-video'
type PointFilter = 'all' | 'purpose' | 'key' | 'inspiration'

const GRADE_ORDER = ['S', 'A', 'B', 'C', 'D'] as const
const SOURCE_VIDEO_GRADES = new Set(['B', 'C', 'D'])
const POINT_META = {
  purpose: { label: '目的点', short: '目的', symbol: '◎' },
  key: { label: '关键点', short: '关键', symbol: '◆' },
  inspiration: { label: '灵感点', short: '灵感', symbol: '✦' },
} as const

const demands = ref<VideoDiscoveryDemand[]>([])
const bizDt = ref<string | null>(null)
const loading = ref(true)
const error = ref<string | null>(null)
const search = ref('')
const gradeFilter = ref('all')
const supplyFilter = ref<SupplyFilter>('all')
const selectedDemandId = ref<number | null>(null)
const detail = ref<VideoDiscoveryDemandDetail | null>(null)
const detailLoading = ref(false)
const detailError = ref<string | null>(null)
const videoSearch = ref('')
const pointFilter = ref<PointFilter>('all')
const selectedVid = ref<string | null>(null)
const copiedKey = ref<string | null>(null)
const feedbackTarget = ref<DemandFeedbackTarget | null>(null)
const visibleDemandLimit = ref(80)
let detailRequest = 0
let copiedTimer = 0

const gradeCounts = computed(() => {
  const counts: Record<string, number> = { all: demands.value.length }
  for (const grade of GRADE_ORDER) counts[grade] = 0
  for (const item of demands.value) {
    const grade = item.grade?.toUpperCase()
    if (grade in counts) counts[grade] += 1
  }
  return counts
})

const stats = computed(() => {
  const withVideo = demands.value.filter((item) => item.video_count > 0).length
  const videoTotal = demands.value.reduce((sum, item) => sum + item.video_count, 0)
  const posterior = demands.value.filter((item) => item.has_posterior).length
  return { withVideo, videoTotal, posterior }
})

const filteredDemands = computed(() => {
  const keyword = search.value.trim().toLocaleLowerCase()
  return demands.value
    .filter((item) => gradeFilter.value === 'all' || item.grade === gradeFilter.value)
    .filter((item) => {
      if (supplyFilter.value === 'with-video') return item.video_count > 0
      if (supplyFilter.value === 'posterior') return item.has_posterior
      if (supplyFilter.value === 'no-video') return item.video_count === 0
      return true
    })
    .filter((item) => {
      if (!keyword) return true
      const haystack = [
        item.demand_name,
        item.reason ?? '',
        ...item.strategies,
        ...item.category_names,
      ]
        .join(' ')
        .toLocaleLowerCase()
      return haystack.includes(keyword)
    })
    .sort((a, b) => {
      const gradeDiff = gradeRank(a.grade) - gradeRank(b.grade)
      if (gradeDiff !== 0) return gradeDiff
      if (a.video_count !== b.video_count) return b.video_count - a.video_count
      return (b.score ?? -1) - (a.score ?? -1)
    })
})

const selectedDemand = computed(
  () => demands.value.find((item) => item.id === selectedDemandId.value) ?? null,
)

const isSourceOnlyGrade = computed(() =>
  SOURCE_VIDEO_GRADES.has((selectedDemand.value?.grade ?? '').toUpperCase()),
)

const showHitContentPanel = computed(
  () => !isSourceOnlyGrade.value && (selectedVideo.value?.point_count ?? 0) > 0,
)

const visibleDemands = computed(() =>
  filteredDemands.value.slice(0, visibleDemandLimit.value),
)

const pointTypeCounts = computed(() => {
  const counts: Record<PointFilter, number> = {
    all: detail.value?.point_count ?? 0,
    purpose: 0,
    key: 0,
    inspiration: 0,
  }
  for (const video of detail.value?.videos ?? []) {
    for (const point of video.points) {
      if (point.point_type in counts) {
        counts[point.point_type as PointFilter] += 1
      }
    }
  }
  return counts
})

const availablePointFilters = computed<PointFilter[]>(() => [
  'all',
  ...(['purpose', 'key', 'inspiration'] as const).filter(
    (type) => pointTypeCounts.value[type] > 0,
  ),
])

const filteredVideos = computed(() => {
  const keyword = videoSearch.value.trim().toLocaleLowerCase()
  return [...(detail.value?.videos ?? [])]
    .filter((video) => {
      if (pointFilter.value === 'all') return true
      return video.points.some((point) => point.point_type === pointFilter.value)
    })
    .filter((video) => {
      if (!keyword) return true
      const haystack = [
        video.vid,
        video.title ?? '',
        ...video.points.flatMap((point) => [
          point.expanded_text,
          point.point_desc ?? '',
          point.reason,
        ]),
      ]
        .join(' ')
        .toLocaleLowerCase()
      return haystack.includes(keyword)
    })
    .sort((a, b) => b.point_count - a.point_count)
})

const selectedVideo = computed(
  () => detail.value?.videos.find((video) => video.vid === selectedVid.value) ?? null,
)

const selectedPointGroups = computed(() => {
  const groups = new Map<string, VideoDiscoveryPoint[]>()
  for (const point of selectedVideo.value?.points ?? []) {
    const items = groups.get(point.point_type) ?? []
    items.push(point)
    groups.set(point.point_type, items)
  }
  return ['purpose', 'key', 'inspiration']
    .filter((type) => groups.has(type))
    .map((type) => ({
      type,
      meta: pointMeta(type),
      points: groups.get(type) ?? [],
    }))
})

const progressWidth = computed(() => {
  const count = selectedVideo.value?.point_count ?? 0
  return `${Math.min(100, count * 10)}%`
})

const emptyVideoTitle = computed(() =>
  isSourceOnlyGrade.value ? '这个需求暂无关联源视频' : '这个需求暂无命中视频',
)

const emptyVideoHint = computed(() => {
  if ((detail.value?.videos.length ?? 0) > 0) {
    return '清空搜索或切换点位类型'
  }
  return isSourceOnlyGrade.value
    ? 'demand_grade.video_list 中暂无 vid'
    : 'demand_video_expansion 中暂无命中记录'
})

onMounted(async () => {
  try {
    const response = await fetchVideoDiscoveryDemands()
    demands.value = response.items ?? []
    bizDt.value = response.biz_dt
    selectedDemandId.value =
      demands.value.find((item) => item.video_count > 0)?.id ?? demands.value[0]?.id ?? null
  } catch (e) {
    error.value = e instanceof Error ? e.message : String(e)
  } finally {
    loading.value = false
  }
})

watch(selectedDemandId, async (id) => {
  const request = ++detailRequest
  detail.value = null
  selectedVid.value = null
  videoSearch.value = ''
  pointFilter.value = 'all'
  detailError.value = null
  if (id == null) return

  detailLoading.value = true
  try {
    const response = await fetchVideoDiscoveryDemand(id)
    if (request !== detailRequest) return
    detail.value = response
    selectedVid.value =
      response.videos.find((video) => video.point_count > 0)?.vid ??
      response.videos[0]?.vid ??
      null
  } catch (e) {
    if (request !== detailRequest) return
    detailError.value = e instanceof Error ? e.message : String(e)
  } finally {
    if (request === detailRequest) detailLoading.value = false
  }
})

watch(filteredDemands, (items) => {
  if (!items.length) return
  if (!items.some((item) => item.id === selectedDemandId.value)) {
    selectedDemandId.value = items[0].id
  }
})

watch([search, gradeFilter, supplyFilter], () => {
  visibleDemandLimit.value = 80
})

watch(filteredVideos, (videos) => {
  if (!videos.length) {
    selectedVid.value = null
    return
  }
  if (!videos.some((video) => video.vid === selectedVid.value)) {
    selectedVid.value = videos[0].vid
  }
})

function gradeRank(grade: string): number {
  const index = GRADE_ORDER.indexOf(grade as (typeof GRADE_ORDER)[number])
  return index < 0 ? GRADE_ORDER.length : index
}

function gradeClass(grade: string): string {
  return `grade-${grade || 'none'}`
}

function formatDate(raw: string | null): string {
  if (!raw || raw.length !== 8) return raw || '—'
  return `${raw.slice(0, 4)}.${raw.slice(4, 6)}.${raw.slice(6)}`
}

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

function pointMeta(type: string) {
  return (
    POINT_META[type as keyof typeof POINT_META] ?? {
      label: type || '其他点',
      short: type || '其他',
      symbol: '•',
    }
  )
}

function videoPointCount(video: VideoDiscoveryVideo, type: string): number {
  return video.points.filter((point) => point.point_type === type).length
}

function openDemandFeedback() {
  if (!selectedDemand.value) return
  feedbackTarget.value = {
    target_type: 'demand',
    demand_grade_id: selectedDemand.value.id,
    title: selectedDemand.value.demand_name,
    context: [
      { label: '需求', value: selectedDemand.value.demand_name },
      { label: '业务日', value: formatDate(selectedDemand.value.biz_dt) },
      { label: '等级', value: selectedDemand.value.grade },
    ],
    feedback_summary: selectedDemand.value.feedback_summary,
  }
}

function openVideoFeedback(video: VideoDiscoveryVideo) {
  if (!selectedDemand.value) return
  feedbackTarget.value = {
    target_type: 'video',
    demand_grade_id: selectedDemand.value.id,
    video_id: video.vid,
    title: video.title || `视频 ${video.vid}`,
    context: [
      { label: '需求', value: selectedDemand.value.demand_name },
      { label: '视频', value: video.title || `视频 ${video.vid}` },
      { label: '视频 ID', value: video.vid },
    ],
    feedback_summary: video.feedback_summary,
  }
}

function openPointFeedback(point: VideoDiscoveryPoint) {
  if (!selectedDemand.value || !selectedVideo.value) return
  feedbackTarget.value = {
    target_type: 'hit_content',
    demand_grade_id: selectedDemand.value.id,
    video_id: selectedVideo.value.vid,
    demand_video_expansion_id: point.id,
    title: point.expanded_text,
    context: [
      { label: '需求', value: selectedDemand.value.demand_name },
      {
        label: '视频',
        value: selectedVideo.value.title || `视频 ${selectedVideo.value.vid}`,
      },
      { label: pointMeta(point.point_type).label, value: point.expanded_text },
    ],
    feedback_summary: point.feedback_summary,
  }
}

function applyFeedbackSummary(summary: FeedbackSummary) {
  const target = feedbackTarget.value
  if (!target) return
  if (target.target_type === 'demand') {
    const demand = demands.value.find((item) => item.id === target.demand_grade_id)
    if (demand) demand.feedback_summary = summary
    if (detail.value?.id === target.demand_grade_id) {
      detail.value.feedback_summary = summary
    }
    return
  }
  const video = detail.value?.videos.find((item) => item.vid === target.video_id)
  if (!video) return
  if (target.target_type === 'video') {
    video.feedback_summary = summary
    return
  }
  const point = video.points.find(
    (item) => item.id === target.demand_video_expansion_id,
  )
  if (point) point.feedback_summary = summary
}

async function copyText(text: string, key: string) {
  try {
    await navigator.clipboard.writeText(text)
    copiedKey.value = key
    window.clearTimeout(copiedTimer)
    copiedTimer = window.setTimeout(() => {
      copiedKey.value = null
    }, 1600)
  } catch {
    copiedKey.value = null
  }
}

function decodeUrlFor(video: VideoDiscoveryVideo): string | null {
  return pickDecodeUrl(video)
}

function detailUrlFor(video: VideoDiscoveryVideo): string {
  return videoDetailUrl(video.vid, video.video_detail_url)
}
</script>

<template>
  <div class="video-discovery">
    <header class="workspace-header">
      <div>
        <div class="eyebrow">
          <span class="live-dot" />
          内容寻找工作台
        </div>
        <div class="title-row">
          <h1>需求汇总</h1>
          <span class="date-pill">{{ formatDate(bizDt) }}</span>
        </div>
        <p>视频列表与内容命中来自需求拓展结果。</p>
      </div>

      <div class="summary-strip" aria-label="数据概览">
        <div class="summary-item">
          <span class="summary-value">{{ demands.length }}</span>
          <span class="summary-label">今日需求</span>
        </div>
        <div class="summary-divider" />
        <div class="summary-item">
          <span class="summary-value accent">{{ stats.withVideo }}</span>
          <span class="summary-label">有命中视频</span>
        </div>
        <div class="summary-divider" />
        <div class="summary-item">
          <span class="summary-value">{{ stats.videoTotal }}</span>
          <span class="summary-label">命中视频</span>
        </div>
        <div class="summary-divider" />
        <div class="summary-item">
          <span class="summary-value">{{ stats.posterior }}</span>
          <span class="summary-label">有后验数据</span>
        </div>
      </div>
    </header>

    <div v-if="loading" class="page-state">
      <span class="state-spinner" />
      正在整理今日需求汇总…
    </div>
    <div v-else-if="error" class="page-state error-state">
      <strong>数据加载失败</strong>
      <span>{{ error }}</span>
    </div>

    <main v-else class="workspace-grid">
      <section class="panel demand-panel" aria-label="需求队列">
        <div class="panel-head">
          <div>
            <span class="step-index">01</span>
            <h2>选择需求</h2>
          </div>
          <span class="result-count">{{ filteredDemands.length }} 条</span>
        </div>

        <div class="demand-tools">
          <label class="search-box">
            <span class="search-icon" aria-hidden="true" />
            <input
              v-model="search"
              type="search"
              placeholder="搜索需求、分类或策略"
              aria-label="搜索需求、分类或策略"
            />
            <button
              v-if="search"
              type="button"
              class="clear-search"
              aria-label="清空搜索"
              @click="search = ''"
            >
              ×
            </button>
          </label>

          <div class="grade-filter" aria-label="按等级筛选">
            <button
              type="button"
              :class="{ active: gradeFilter === 'all' }"
              @click="gradeFilter = 'all'"
            >
              全部 <span>{{ gradeCounts.all }}</span>
            </button>
            <button
              v-for="grade in GRADE_ORDER"
              :key="grade"
              type="button"
              :class="{ active: gradeFilter === grade }"
              @click="gradeFilter = grade"
            >
              {{ grade }} <span>{{ gradeCounts[grade] }}</span>
            </button>
          </div>

          <select v-model="supplyFilter" class="supply-filter" aria-label="按线索状态筛选">
            <option value="all">全部线索状态</option>
            <option value="with-video">有命中视频</option>
            <option value="posterior">有后验数据</option>
            <option value="no-video">等待寻找视频</option>
          </select>
        </div>

        <div v-if="filteredDemands.length" class="demand-list">
          <button
            v-for="item in visibleDemands"
            :key="item.id"
            type="button"
            class="demand-card"
            :class="{ selected: item.id === selectedDemandId }"
            @click="selectedDemandId = item.id"
          >
            <div class="demand-card-top">
              <span class="grade-badge" :class="gradeClass(item.grade)">{{ item.grade }}</span>
              <span v-if="item.category_names[0]" class="category-name">
                {{ item.category_names[0] }}
              </span>
              <span v-if="item.has_posterior" class="posterior-tag">有后验</span>
            </div>
            <strong>{{ item.demand_name }}</strong>
            <div class="demand-card-bottom">
              <span>需求分 {{ formatScore(item.score) }}</span>
              <span class="dot-sep">·</span>
              <span :class="{ muted: item.video_count === 0 }">
                {{
                  item.video_count
                    ? SOURCE_VIDEO_GRADES.has(item.grade)
                      ? `${item.video_count} 条源视频`
                      : `${item.video_count} 条命中视频`
                    : '暂无视频'
                }}
              </span>
              <span class="card-arrow">›</span>
            </div>
          </button>
          <button
            v-if="visibleDemands.length < filteredDemands.length"
            type="button"
            class="load-more"
            @click="visibleDemandLimit += 80"
          >
            再显示 80 条
            <span>已显示 {{ visibleDemands.length }} / {{ filteredDemands.length }}</span>
          </button>
        </div>
        <div v-else class="empty-state compact">
          <span class="empty-mark">⌕</span>
          <strong>没有符合条件的需求</strong>
          <span>换一个等级或线索状态试试</span>
        </div>
      </section>

      <section class="panel video-panel" aria-label="候选视频">
        <div class="panel-head">
          <div>
            <span class="step-index">02</span>
            <h2>需求拓展视频</h2>
          </div>
          <span v-if="detail" class="result-count">{{ filteredVideos.length }} 条</span>
        </div>

        <div v-if="selectedDemand" class="demand-brief">
          <div class="brief-title-row">
            <span class="grade-badge large" :class="gradeClass(selectedDemand.grade)">
              {{ selectedDemand.grade }}
            </span>
            <div>
              <span class="brief-label">当前寻找需求</span>
              <h3>{{ selectedDemand.demand_name }}</h3>
            </div>
            <div class="brief-actions">
              <button
                type="button"
                class="copy-button"
                @click="copyText(selectedDemand.demand_name, 'demand')"
              >
                {{ copiedKey === 'demand' ? '已复制' : '复制需求' }}
              </button>
              <button type="button" class="feedback-trigger" @click="openDemandFeedback">
                反馈<span v-if="selectedDemand.feedback_summary.count">
                  {{ selectedDemand.feedback_summary.count }}
                </span>
              </button>
            </div>
          </div>

          <p v-if="selectedDemand.reason" class="brief-reason">
            {{ selectedDemand.reason }}
          </p>

          <div class="brief-metrics">
            <div>
              <span>需求得分</span>
              <strong>{{ formatScore(selectedDemand.score) }}</strong>
            </div>
            <div>
              <span>先验热度</span>
              <strong>{{ formatScore(selectedDemand.prior_total_score) }}</strong>
            </div>
            <div>
              <span>后验 ROV diff</span>
              <strong :class="{ pending: !selectedDemand.has_posterior }">
                {{ formatPosteriorDiff(selectedDemand.posterior_rov_avg) }}
              </strong>
            </div>
            <div>
              <span>样本数</span>
              <strong>{{ selectedDemand.posterior_rov_count || '—' }}</strong>
            </div>
          </div>

          <div v-if="selectedDemand.strategies.length" class="strategy-row">
            <span class="strategy-label">来源</span>
            <span
              v-for="strategy in selectedDemand.strategies"
              :key="strategy"
              class="strategy-chip"
            >
              {{ strategy }}
            </span>
          </div>
        </div>

        <div v-if="detailLoading" class="inline-state">
          <span class="state-spinner" />
          正在关联视频与内容点位…
        </div>
        <div v-else-if="detailError" class="inline-state error-state">
          {{ detailError }}
        </div>
        <template v-else-if="detail">
          <div class="video-toolbar">
            <label class="search-box small">
              <span class="search-icon" aria-hidden="true" />
              <input
                v-model="videoSearch"
                type="search"
                placeholder="搜索视频标题、ID 或内容点"
                aria-label="搜索候选视频"
              />
            </label>
            <div v-if="detail.point_count > 0 && !isSourceOnlyGrade" class="point-filter">
              <button
                v-for="filter in availablePointFilters"
                :key="filter"
                type="button"
                :class="{ active: pointFilter === filter }"
                @click="pointFilter = filter"
              >
                {{ filter === 'all' ? '全部点位' : pointMeta(filter).label }}
                <span>{{ pointTypeCounts[filter] }}</span>
              </button>
            </div>
          </div>

          <div v-if="filteredVideos.length" class="video-list">
            <article
              v-for="(video, index) in filteredVideos"
              :key="video.vid"
              class="video-card"
              :class="{ selected: video.vid === selectedVid }"
            >
              <button
                type="button"
                class="video-select"
                @click="selectedVid = video.vid"
              >
                <span class="video-index">{{ String(index + 1).padStart(2, '0') }}</span>
                <div class="video-main">
                  <div class="video-title-row">
                    <strong>{{ video.title || `视频 ${video.vid}` }}</strong>
                    <span v-if="!isSourceOnlyGrade">{{ video.point_count }} 个内容点</span>
                  </div>
                  <code>{{ video.vid }}</code>
                  <div class="video-link-row">
                    <a
                      v-if="decodeUrlFor(video)"
                      :href="decodeUrlFor(video)!"
                      class="video-link"
                      target="_blank"
                      rel="noopener noreferrer"
                      @click.stop
                    >
                      解构结果
                    </a>
                    <a
                      :href="detailUrlFor(video)"
                      class="video-link"
                      target="_blank"
                      rel="noopener noreferrer"
                      @click.stop
                    >
                      视频详情
                    </a>
                  </div>
                  <div v-if="!isSourceOnlyGrade" class="video-point-badges">
                    <span v-if="videoPointCount(video, 'purpose')" class="point-badge purpose">
                      ◎ 目的 {{ videoPointCount(video, 'purpose') }}
                    </span>
                    <span v-if="videoPointCount(video, 'key')" class="point-badge key">
                      ◆ 关键 {{ videoPointCount(video, 'key') }}
                    </span>
                    <span
                      v-if="videoPointCount(video, 'inspiration')"
                      class="point-badge inspiration"
                    >
                      ✦ 灵感 {{ videoPointCount(video, 'inspiration') }}
                    </span>
                  </div>
                </div>
                <span class="video-arrow">›</span>
              </button>
              <button
                type="button"
                class="feedback-trigger video-feedback"
                @click="openVideoFeedback(video)"
              >
                反馈<span v-if="video.feedback_summary.count">
                  {{ video.feedback_summary.count }}
                </span>
              </button>
            </article>
          </div>
          <div v-else class="empty-state">
            <span class="empty-mark">○</span>
            <strong>{{ detail.videos.length ? '没有匹配的视频' : emptyVideoTitle }}</strong>
            <span>{{ emptyVideoHint }}</span>
          </div>
        </template>
      </section>

      <aside class="panel evidence-panel" aria-label="视频内容点位">
        <div class="panel-head">
          <div>
            <span class="step-index">03</span>
            <h2>{{ isSourceOnlyGrade ? '视频信息' : '命中视频内容' }}</h2>
          </div>
          <span v-if="selectedVideo" class="source-label">
            {{ isSourceOnlyGrade ? 'POOL' : 'EXPANSION' }}
          </span>
        </div>

        <template v-if="selectedVideo">
          <div class="selected-video-head">
            <div class="video-kicker">当前视频</div>
            <h3>{{ selectedVideo.title || `视频 ${selectedVideo.vid}` }}</h3>
            <div class="video-id-row">
              <code>{{ selectedVideo.vid }}</code>
              <button
                type="button"
                @click="copyText(selectedVideo.vid, `video-${selectedVideo.vid}`)"
              >
                {{ copiedKey === `video-${selectedVideo.vid}` ? '已复制' : '复制 ID' }}
              </button>
            </div>
            <div class="video-link-row panel-links">
              <a
                v-if="decodeUrlFor(selectedVideo)"
                :href="decodeUrlFor(selectedVideo)!"
                class="video-link"
                target="_blank"
                rel="noopener noreferrer"
              >
                解构结果
              </a>
              <a
                :href="detailUrlFor(selectedVideo)"
                class="video-link"
                target="_blank"
                rel="noopener noreferrer"
              >
                视频详情
              </a>
            </div>
          </div>

          <div v-if="showHitContentPanel" class="coverage-card">
            <div class="coverage-head">
              <span>点位覆盖</span>
              <strong>{{ selectedVideo.point_count }} 个</strong>
            </div>
            <div class="coverage-track">
              <span :style="{ width: progressWidth }" />
            </div>
            <p>结合目的、关键和灵感点判断该视频能否承接当前需求。</p>
          </div>

          <div v-if="showHitContentPanel && selectedPointGroups.length" class="point-groups">
            <section
              v-for="group in selectedPointGroups"
              :key="group.type"
              class="point-group"
              :class="`type-${group.type}`"
            >
              <header>
                <span class="point-symbol">{{ group.meta.symbol }}</span>
                <div>
                  <h4>{{ group.meta.label }}</h4>
                  <p>
                    {{
                      group.type === 'purpose'
                        ? '回答内容为什么值得看'
                        : group.type === 'key'
                          ? '回答内容讲清了什么'
                          : '回答还能向哪里延展'
                    }}
                  </p>
                </div>
                <span class="point-count">{{ group.points.length }}</span>
              </header>

              <article v-for="(point, index) in group.points" :key="point.id" class="point-card">
                <div class="point-number">{{ index + 1 }}</div>
                <div>
                  <strong>{{ point.expanded_text }}</strong>
                  <p v-if="point.point_desc">{{ point.point_desc }}</p>
                  <p v-if="point.reason" class="point-reason">
                    <span>命中原因</span>{{ point.reason }}
                  </p>
                </div>
                <div class="point-actions">
                  <button
                    type="button"
                    class="point-copy"
                    title="复制内容点"
                    :aria-label="`复制内容点：${point.expanded_text}`"
                    @click="
                      copyText(
                        point.expanded_text,
                        `${group.type}-${point.id}`,
                      )
                    "
                  >
                    {{ copiedKey === `${group.type}-${point.id}` ? '✓' : '＋' }}
                  </button>
                  <button
                    type="button"
                    class="feedback-trigger point-feedback"
                    @click="openPointFeedback(point)"
                  >
                    反馈<span v-if="point.feedback_summary.count">
                      {{ point.feedback_summary.count }}
                    </span>
                  </button>
                </div>
              </article>
            </section>
          </div>
          <div v-else-if="isSourceOnlyGrade" class="empty-state evidence-empty">
            <span class="empty-mark">◇</span>
            <strong>暂无命中视频内容</strong>
            <span>B/C/D 等级展示需求池关联源视频，可通过上方链接查看解构结果与视频详情</span>
          </div>
          <div v-else class="empty-state evidence-empty">
            <span class="empty-mark">◇</span>
            <strong>暂无内容点位</strong>
            <span>该视频没有可展示的目的点、关键点或灵感点</span>
          </div>
        </template>
        <div v-else class="empty-state evidence-empty">
          <span class="empty-mark">→</span>
          <strong>选择一条候选视频</strong>
          <span>这里只展示 demand_video_expansion 中实际存在的命中视频和三类拓展点</span>
        </div>
      </aside>
    </main>

    <DemandFeedbackDrawer
      :open="feedbackTarget !== null"
      :target="feedbackTarget"
      @close="feedbackTarget = null"
      @submitted="applyFeedbackSummary"
    />
  </div>
</template>

<style scoped>
.video-discovery {
  --ink: #17211b;
  --muted: #6f7a73;
  --line: #dfe5df;
  --paper: #ffffff;
  --canvas: #f3f5f1;
  --green: #286448;
  --green-soft: #e7f0ea;
  --orange: #c86639;
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
  color: var(--ink);
}

.workspace-header {
  flex-shrink: 0;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 32px;
  padding: 4px 2px 0;
}

.eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 7px;
  color: var(--green);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.16em;
}

.live-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #39a16c;
  box-shadow: 0 0 0 4px rgba(57, 161, 108, 0.12);
}

.title-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.title-row h1 {
  margin: 0;
  color: #16231b;
  font-family: Georgia, 'Songti SC', serif;
  font-size: clamp(26px, 2.2vw, 36px);
  line-height: 1.08;
  letter-spacing: -0.035em;
}

.date-pill {
  padding: 5px 9px 4px;
  border: 1px solid #cad5cc;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.72);
  color: #5d6a62;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
}

.workspace-header p {
  margin: 7px 0 0;
  color: var(--muted);
  font-size: 13px;
}

.summary-strip {
  display: flex;
  align-items: center;
  min-width: 470px;
  padding: 10px 14px;
  border: 1px solid rgba(205, 214, 206, 0.9);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.72);
  box-shadow: 0 8px 28px rgba(32, 52, 40, 0.06);
  backdrop-filter: blur(10px);
}

.summary-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
}

.summary-value {
  color: #253229;
  font-family: Georgia, serif;
  font-size: 20px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.summary-value.accent {
  color: var(--green);
}

.summary-label {
  color: #7d887f;
  font-size: 10px;
  white-space: nowrap;
}

.summary-divider {
  width: 1px;
  height: 27px;
  background: #dde3dd;
}

.workspace-grid {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(280px, 0.82fr) minmax(420px, 1.34fr) minmax(320px, 1fr);
  gap: 12px;
}

.panel {
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--paper);
  box-shadow: 0 12px 32px rgba(24, 45, 31, 0.055);
}

.panel-head {
  min-height: 53px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 0 16px;
  border-bottom: 1px solid #e7ebe7;
}

.panel-head > div {
  display: flex;
  align-items: center;
  gap: 9px;
}

.panel-head h2 {
  margin: 0;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.01em;
}

.step-index {
  color: #9aa49d;
  font-family: Georgia, serif;
  font-size: 11px;
  font-style: italic;
}

.result-count,
.source-label {
  color: #7c887f;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
}

.demand-tools {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 12px 13px;
  border-bottom: 1px solid #edf0ed;
  background: #fbfcfa;
}

.search-box {
  height: 36px;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 0 11px;
  border: 1px solid #d7ded8;
  border-radius: 9px;
  background: #fff;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.search-box:focus-within {
  border-color: #6b957c;
  box-shadow: 0 0 0 3px rgba(53, 110, 78, 0.1);
}

.search-icon {
  width: 12px;
  height: 12px;
  position: relative;
  flex: 0 0 auto;
  border: 1.5px solid #78827b;
  border-radius: 50%;
}

.search-icon::after {
  content: '';
  width: 5px;
  height: 1.5px;
  position: absolute;
  right: -4px;
  bottom: -2px;
  transform: rotate(45deg);
  border-radius: 2px;
  background: #78827b;
}

.search-box input {
  min-width: 0;
  flex: 1;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 12px;
}

.search-box input::placeholder {
  color: #a2aaa4;
}

.clear-search {
  border: 0;
  background: transparent;
  color: #9aa39c;
  cursor: pointer;
}

.grade-filter {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 5px;
}

.grade-filter button,
.point-filter button {
  border: 1px solid #dce2dc;
  border-radius: 7px;
  background: #fff;
  color: #5c675f;
  font-size: 10px;
  font-weight: 700;
  cursor: pointer;
  transition: 0.15s ease;
}

.grade-filter button {
  height: 29px;
  padding: 0 3px;
}

.grade-filter button span,
.point-filter button span {
  color: #a0a9a2;
  font-size: 9px;
  font-weight: 600;
}

.grade-filter button:hover,
.point-filter button:hover {
  border-color: #9bb2a3;
  color: var(--green);
}

.grade-filter button.active,
.point-filter button.active {
  border-color: #315f47;
  background: #315f47;
  color: #fff;
}

.grade-filter button.active span,
.point-filter button.active span {
  color: rgba(255, 255, 255, 0.7);
}

.supply-filter {
  width: 100%;
  height: 31px;
  padding: 0 9px;
  border: 1px solid #dce2dc;
  border-radius: 7px;
  outline: 0;
  background: #fff;
  color: #5d685f;
  font-size: 11px;
}

.demand-list,
.video-list,
.point-groups {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.demand-list {
  padding: 7px;
}

.demand-card {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 11px 11px 10px;
  border: 1px solid transparent;
  border-bottom-color: #edf0ed;
  border-radius: 10px;
  background: transparent;
  color: inherit;
  text-align: left;
  cursor: pointer;
  transition: 0.16s ease;
}

.load-more {
  width: calc(100% - 8px);
  height: 35px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 7px 4px 3px;
  border: 1px dashed #cfd8d1;
  border-radius: 8px;
  background: #fafbfa;
  color: #53705d;
  font-size: 10px;
  font-weight: 700;
  cursor: pointer;
}

.load-more:hover {
  border-color: #94ad9b;
  background: #f2f6f3;
}

.load-more span {
  color: #9aa39c;
  font-size: 9px;
  font-weight: 500;
}

.demand-card:hover {
  border-color: #dce5de;
  background: #f8faf7;
}

.demand-card.selected {
  border-color: #b7cbbb;
  background: var(--green-soft);
  box-shadow: inset 3px 0 #3b7856;
}

.demand-card-top,
.demand-card-bottom {
  display: flex;
  align-items: center;
  gap: 7px;
}

.demand-card strong {
  color: #202b24;
  font-size: 13px;
  line-height: 1.4;
}

.grade-badge {
  min-width: 22px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 6px;
  border-radius: 5px;
  font-size: 10px;
  font-weight: 900;
}

.grade-badge.large {
  min-width: 31px;
  height: 31px;
  border-radius: 8px;
  font-size: 14px;
}

.grade-S {
  background: #f6dfd6;
  color: #a84726;
}

.grade-A {
  background: #faead5;
  color: #a86120;
}

.grade-B {
  background: #ece8c9;
  color: #756917;
}

.grade-C {
  background: #dcebe3;
  color: #2d7150;
}

.grade-D,
.grade-none {
  background: #e9ece9;
  color: #6f7771;
}

.category-name {
  min-width: 0;
  overflow: hidden;
  color: #748077;
  font-size: 10px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.posterior-tag {
  margin-left: auto;
  padding: 2px 6px;
  border-radius: 999px;
  background: #dcece2;
  color: #2f7151;
  font-size: 9px;
  font-weight: 700;
}

.demand-card-bottom {
  color: #7b857e;
  font-size: 10px;
}

.demand-card-bottom .muted {
  color: #a48b78;
}

.dot-sep {
  color: #bbc1bc;
}

.card-arrow {
  margin-left: auto;
  color: #97a199;
  font-family: Georgia, serif;
  font-size: 18px;
}

.demand-brief {
  flex-shrink: 0;
  padding: 15px 17px 13px;
  border-bottom: 1px solid #e6ebe6;
  background:
    radial-gradient(circle at 100% 0, rgba(190, 215, 198, 0.38), transparent 42%),
    #f5f8f4;
}

.brief-title-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.brief-title-row > div {
  min-width: 0;
}

.brief-label {
  display: block;
  margin-bottom: 2px;
  color: #7e8981;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.12em;
}

.brief-title-row h3 {
  margin: 0;
  overflow: hidden;
  color: #1e2b23;
  font-family: Georgia, 'Songti SC', serif;
  font-size: 17px;
  line-height: 1.25;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.copy-button {
  height: 29px;
  padding: 0 9px;
  flex: 0 0 auto;
  border: 1px solid #cbd6cd;
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.76);
  color: #42634e;
  font-size: 10px;
  font-weight: 700;
  cursor: pointer;
}

.brief-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
}

.feedback-trigger {
  min-height: 29px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 0 9px;
  border: 1px solid #b9cbbd;
  border-radius: 7px;
  background: #edf4ef;
  color: #315f47;
  font-size: 10px;
  font-weight: 800;
  white-space: nowrap;
  cursor: pointer;
}

.feedback-trigger:hover {
  border-color: #6d977d;
  background: #e3eee6;
}

.feedback-trigger span {
  min-width: 15px;
  height: 15px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 4px;
  border-radius: 999px;
  background: #315f47;
  color: #fff;
  font-size: 8px;
}

.brief-reason {
  display: -webkit-box;
  margin: 10px 0;
  overflow: hidden;
  color: #647169;
  font-size: 11px;
  line-height: 1.55;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.brief-metrics {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
}

.brief-metrics > div {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 7px 8px;
  border: 1px solid rgba(207, 218, 209, 0.9);
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.7);
}

.brief-metrics span {
  color: #879189;
  font-size: 9px;
}

.brief-metrics strong {
  color: #2d3d32;
  font-family: Georgia, serif;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

.brief-metrics strong.pending {
  color: #9c836e;
  font-family: inherit;
  font-size: 11px;
}

.strategy-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 9px;
}

.strategy-label {
  color: #879189;
  font-size: 9px;
}

.strategy-chip {
  padding: 2px 6px;
  border-radius: 999px;
  background: #e4ebe5;
  color: #56675a;
  font-size: 9px;
  font-weight: 600;
}

.video-toolbar {
  flex-shrink: 0;
  padding: 10px 12px 9px;
  border-bottom: 1px solid #edf0ed;
}

.search-box.small {
  height: 33px;
}

.point-filter {
  display: flex;
  gap: 5px;
  margin-top: 7px;
}

.point-filter button {
  height: 27px;
  padding: 0 8px;
}

.video-list {
  padding: 8px;
}

.video-card {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 3px 5px 3px 3px;
  border: 1px solid transparent;
  border-bottom-color: #ebefeb;
  border-radius: 10px;
  background: transparent;
  color: inherit;
  text-align: left;
  transition: 0.15s ease;
}

.video-select {
  min-width: 0;
  flex: 1;
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr) 16px;
  align-items: center;
  gap: 10px;
  padding: 8px 5px 8px 7px;
  border: 0;
  background: transparent;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.video-feedback {
  min-height: 28px;
  flex: 0 0 auto;
  padding: 0 7px;
}

.video-card:hover {
  border-color: #dfe7e0;
  background: #fafbfa;
}

.video-card.selected {
  border-color: #b8ccbd;
  background: #edf4ef;
}

.video-index {
  width: 27px;
  height: 27px;
  display: grid;
  place-items: center;
  border: 1px solid #d8dfd9;
  border-radius: 50%;
  color: #8a948c;
  font-family: Georgia, serif;
  font-size: 10px;
}

.video-card.selected .video-index {
  border-color: #57906e;
  background: #397354;
  color: #fff;
}

.video-main {
  min-width: 0;
}

.video-title-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.video-title-row strong {
  min-width: 0;
  flex: 1;
  overflow: hidden;
  color: #243028;
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.video-title-row > span {
  flex: 0 0 auto;
  color: #879189;
  font-size: 9px;
}

.video-main code,
.video-id-row code {
  display: block;
  margin: 3px 0 5px;
  overflow: hidden;
  color: #919a93;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 9px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.video-link-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 6px;
}

.video-link-row.panel-links {
  margin-top: 10px;
  margin-bottom: 0;
}

.video-link {
  display: inline-flex;
  align-items: center;
  padding: 2px 7px;
  border: 1px solid #cfd9d2;
  border-radius: 5px;
  background: #fff;
  color: #3a6a50;
  font-size: 9px;
  font-weight: 700;
  text-decoration: none;
  transition: 0.15s ease;
}

.video-link:hover {
  border-color: #8eb19a;
  background: #f3f8f4;
  color: #24553a;
}

.video-point-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

.point-badge {
  padding: 2px 6px;
  border-radius: 5px;
  font-size: 9px;
  font-weight: 700;
}

.point-badge.purpose {
  background: #dfe9f1;
  color: #3c6682;
}

.point-badge.key {
  background: #e6e0ef;
  color: #665083;
}

.point-badge.inspiration {
  background: #f5e5d9;
  color: #a1532e;
}

.video-arrow {
  color: #9ba49d;
  font-family: Georgia, serif;
  font-size: 19px;
}

.evidence-panel {
  background:
    linear-gradient(180deg, rgba(248, 250, 247, 0.96), #fff 150px),
    #fff;
}

.selected-video-head {
  flex-shrink: 0;
  padding: 16px 17px 12px;
}

.video-kicker {
  margin-bottom: 5px;
  color: #758078;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.13em;
}

.selected-video-head h3 {
  margin: 0 0 9px;
  color: #202c24;
  font-family: Georgia, 'Songti SC', serif;
  font-size: 16px;
  line-height: 1.42;
}

.video-id-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.video-id-row code {
  min-width: 0;
  flex: 1;
  margin: 0;
}

.video-id-row button {
  flex: 0 0 auto;
  padding: 3px 7px;
  border: 1px solid #d9dfda;
  border-radius: 5px;
  background: #fff;
  color: #68736b;
  font-size: 9px;
  cursor: pointer;
}

.coverage-card {
  flex-shrink: 0;
  margin: 0 12px 11px;
  padding: 10px 11px;
  border: 1px solid #dce4dd;
  border-radius: 9px;
  background: #f7f9f6;
}

.coverage-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  color: #69756d;
  font-size: 10px;
}

.coverage-head strong {
  color: #315f47;
  font-size: 11px;
}

.coverage-track {
  height: 4px;
  margin: 7px 0;
  overflow: hidden;
  border-radius: 999px;
  background: #dfe5df;
}

.coverage-track span {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, #315f47, #82aa8f);
}

.coverage-card p {
  margin: 0;
  color: #8a948c;
  font-size: 9px;
  line-height: 1.45;
}

.point-groups {
  padding: 0 12px 14px;
}

.point-group + .point-group {
  margin-top: 16px;
}

.point-group > header {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
  margin-bottom: 7px;
}

.point-symbol {
  width: 25px;
  height: 25px;
  display: grid;
  place-items: center;
  border-radius: 7px;
  font-size: 11px;
}

.point-group h4 {
  margin: 0;
  font-size: 11px;
}

.point-group header p {
  margin: 1px 0 0;
  color: #929a94;
  font-size: 8px;
}

.point-count {
  min-width: 19px;
  height: 19px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: #eef1ee;
  color: #788179;
  font-size: 9px;
  font-weight: 800;
}

.type-purpose .point-symbol {
  background: #dfe9f1;
  color: #3c6682;
}

.type-key .point-symbol {
  background: #e6e0ef;
  color: #665083;
}

.type-inspiration .point-symbol {
  background: #f5e5d9;
  color: #a1532e;
}

.point-card {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr) auto;
  align-items: start;
  gap: 8px;
  padding: 9px 7px;
  border-top: 1px solid #edf0ed;
}

.point-number {
  padding-top: 1px;
  color: #a0a8a2;
  font-family: Georgia, serif;
  font-size: 10px;
  font-style: italic;
}

.point-card strong {
  display: block;
  color: #29352d;
  font-size: 11px;
  line-height: 1.45;
}

.point-card p {
  margin: 4px 0 0;
  color: #78827b;
  font-size: 10px;
  line-height: 1.5;
}

.point-card .point-reason {
  padding-top: 6px;
  border-top: 1px dashed #e4e9e5;
  color: #66736a;
}

.point-reason span {
  display: inline-block;
  margin-right: 5px;
  color: #3e6d52;
  font-size: 9px;
  font-weight: 800;
}

.point-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 5px;
}

.point-card .point-copy {
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid #dbe1dc;
  border-radius: 5px;
  background: #fff;
  color: #738078;
  cursor: pointer;
}

.point-card .point-feedback {
  min-height: 23px;
  padding: 0 6px;
  font-size: 9px;
}

.point-card .point-feedback span {
  min-width: 13px;
  height: 13px;
  padding: 0 3px;
  font-size: 7px;
}

.page-state,
.inline-state,
.empty-state {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  color: #7b867e;
  font-size: 12px;
}

.page-state {
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.72);
}

.inline-state {
  flex-direction: column;
}

.error-state {
  color: #a04a35;
}

.error-state span {
  color: #8b756d;
}

.state-spinner {
  width: 18px;
  height: 18px;
  border: 2px solid #dce3dd;
  border-top-color: #3d7556;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

.empty-state {
  flex-direction: column;
  padding: 26px;
  text-align: center;
}

.empty-state.compact {
  padding: 16px;
}

.empty-state strong {
  color: #657069;
  font-size: 12px;
}

.empty-state span:not(.empty-mark) {
  max-width: 260px;
  color: #9aa29c;
  font-size: 10px;
  line-height: 1.5;
}

.empty-mark {
  width: 35px;
  height: 35px;
  display: grid;
  place-items: center;
  border: 1px solid #dfe5e0;
  border-radius: 50%;
  color: #879189;
  font-family: Georgia, serif;
  font-size: 17px;
}

.evidence-empty {
  margin: 10px;
  border: 1px dashed #dce3dd;
  border-radius: 12px;
  background: rgba(249, 250, 248, 0.72);
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

@media (max-width: 1180px) {
  .workspace-grid {
    grid-template-columns: minmax(260px, 0.8fr) minmax(410px, 1.25fr);
  }

  .evidence-panel {
    display: none;
  }
}

@media (max-width: 820px) {
  .video-discovery {
    height: auto;
    min-height: 100%;
  }

  .workspace-header {
    align-items: stretch;
    flex-direction: column;
    gap: 14px;
  }

  .summary-strip {
    min-width: 0;
  }

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

  .panel {
    min-height: 520px;
  }

  .evidence-panel {
    display: flex;
  }
}
</style>
