'use client';

import Link from 'next/link';
import { useEffect, useMemo, useState } from 'react';
import { formatToTaipeiTime } from '@/lib/timezone';
import {
  getAllMatches,
  getDailyCard,
  getDailyCardCalendar,
  getSourceHealth,
  type DailyCardCalendarDate,
  type DailyCardItem,
  type DailyCardResponse,
  type MatchListItem,
  type SourceHealthResponse,
} from '@/lib/analytics-api';

type NewsItem = {
  title: string;
  url: string;
  source: string;
  publishedAt: string;
};

type LoadErrors = {
  daily?: string;
  matches?: string;
  news?: string;
  source?: string;
};

type GateState = {
  label: string;
  title: string;
  detail: string;
  tone: 'ready' | 'watch' | 'blocked';
  primaryAction: string;
};

const WATCHLIST_KEY = 'fifa2026-daily-card-watchlist';
const TOURNAMENT_START_DATE = '2026-06-11';

const moduleLinks = [
  { href: '/daily-card', label: '每日作戰室', desc: '單關、串關、同場串關與賠率監控' },
  { href: '/live-score', label: '即時比分中心', desc: '日期切換、即時比分、完賽結果與場地資訊' },
  { href: '/schedule', label: '完整賽程表', desc: '依台北時間查看下一場與已完賽結果' },
  { href: '/battle-room', label: '對戰情報室', desc: '模型勝率、進球分布、環境條件與盤口快照' },
  { href: '/source-health', label: '資料健康總覽', desc: '檢查賽程、比分、新聞與盤口是否即時更新' },
  { href: '/recommendation-performance', label: '賽後校準室', desc: '逐玩法回看命中率並修正模型權重' },
  { href: '/agent-verification', label: 'AI 驗證室', desc: 'Gemini 費用上限、NemoTron 復核與量化閘門' },
  { href: '/news', label: '即時新聞情報', desc: '新聞、傷停與外部事件監控' },
];

function pickKey(item: DailyCardItem): string {
  const normalize = (value: unknown) => String(value ?? '').replace(/\s+/g, ' ').trim();
  return [normalize(item.match_id), normalize(item.market_type), normalize(item.selection)].join('|');
}

function formatTwd(value: number): string {
  return new Intl.NumberFormat('zh-TW', {
    style: 'currency',
    currency: 'TWD',
    maximumFractionDigits: 0,
  }).format(value);
}

function stakeAmountTwd(item: DailyCardItem): number {
  return item.stake_amount_twd ?? Math.round(item.stake_units * (item.unit_size_twd ?? 1000));
}

function itemsFromCard(card: DailyCardResponse | null): DailyCardItem[] {
  if (!card) return [];
  return [...card.safe_singles, ...card.safe_parlays, ...card.sgp_lotteries, ...card.high_risk_singles];
}

function matchDate(match: MatchListItem): string {
  return formatToTaipeiTime(match.kickoff_utc, 'yyyy-MM-dd');
}

function isConditionalPick(item: DailyCardItem): boolean {
  return (
    item.has_market_odds === false ||
    item.selection.includes('預掛條件') ||
    item.selection.includes('參考盤監控') ||
    item.rationale.includes('尚未取得可用即時盤口') ||
    item.rationale.includes('尚未取得完整實盤賠率') ||
    Boolean(item.legs?.some((leg) => leg.selection.includes('預掛條件')))
  );
}

function recommendationText(item: DailyCardItem): string {
  const conditional = isConditionalPick(item);
  if (item.recommendation === 'SAFE_SINGLE') return conditional ? '觀察單關' : '核心單關';
  if (item.recommendation === 'HIGH_RISK_SINGLE') return conditional ? '高賠觀察' : '小注高賠';
  if (item.recommendation === 'SAFE_PARLAY') return conditional ? '觀察串關' : '跨場串關';
  if (item.recommendation === 'SGP_LOTTERY') return conditional ? '同場觀察' : '同場小注';
  return '研究候選';
}

function nextStepText(item: DailyCardItem): string {
  if (isConditionalPick(item) || !item.has_market_odds) {
    return '現在不要下注，先加入賠率監控。等平台開盤後，只有實際賠率達到卡片門檻才考慮。';
  }
  if (item.odds_source_kind === 'reference_market') {
    return '已有台灣運彩參考盤，可先做下注前檢查；但它不是多來源正式盤，下單前仍要確認平台最新盤口。';
  }
  return '可進入下注前檢查。下單前仍要確認同一場、同一玩法、同一選項與賠率門檻。';
}

function pickMatchKey(item: DailyCardItem): string {
  return String(item.match_id ?? item.match_label).trim().toLowerCase();
}

function pickMarketGroup(item: DailyCardItem): string {
  if (item.recommendation === 'SAFE_PARLAY') return '跨場串關';
  if (item.recommendation === 'SGP_LOTTERY') return '同場串關';
  if (item.recommendation === 'HIGH_RISK_SINGLE') return '小注高賠';
  return item.market_type || '單關';
}

function diversifyPicks(items: DailyCardItem[], limit: number): DailyCardItem[] {
  const selected: DailyCardItem[] = [];
  const seen = new Set<string>();
  const matchCounts = new Map<string, number>();
  const matchMarketSeen = new Set<string>();

  const tryAdd = (item: DailyCardItem, maxPerMatch: number, enforceMarketSpread: boolean) => {
    if (selected.length >= limit) return;
    const key = pickKey(item);
    if (seen.has(key)) return;
    const matchKey = pickMatchKey(item);
    const marketKey = `${matchKey}|${pickMarketGroup(item)}`;
    if ((matchCounts.get(matchKey) ?? 0) >= maxPerMatch) return;
    if (enforceMarketSpread && matchMarketSeen.has(marketKey)) return;

    selected.push(item);
    seen.add(key);
    matchCounts.set(matchKey, (matchCounts.get(matchKey) ?? 0) + 1);
    matchMarketSeen.add(marketKey);
  };

  items.forEach((item) => tryAdd(item, 2, true));
  items.forEach((item) => tryAdd(item, 3, false));
  return selected.slice(0, limit);
}

function shortBetInstruction(item: DailyCardItem): string {
  if (item.recommendation === 'SAFE_PARLAY') return '到投注平台選「串關」，把每一腿分開加入；任一腿賠率不達標就整組跳過。';
  if (item.recommendation === 'SGP_LOTTERY') return '到投注平台選「同場串關」，只用小注碼；若平台沒有同樣分線就不要替代。';
  return '到投注平台選「單關」，確認同一場、同一市場、同一選項，再比對最低可接受賠率。';
}

function buildGateState(
  sourceHealth: SourceHealthResponse | null,
  errors: LoadErrors,
  liveCount: number,
  watchCount: number,
): GateState {
  if (errors.source) {
    return {
      label: '資料源異常',
      title: watchCount > 0 ? '正式下注先保留，預掛候選持續監控' : '資料源恢復前只保留研究觀察',
      detail: watchCount > 0
        ? `資料源健康狀態暫時無法讀取，但仍保留 ${watchCount} 組預掛候選。這些不是立即下注單，必須等資料源恢復並達到賠率門檻。`
        : '資料源健康狀態暫時無法讀取，首頁只保留資訊瀏覽，不把不完整資料包裝成高勝率下注。',
      tone: watchCount > 0 ? 'watch' : 'blocked',
      primaryAction: watchCount > 0 ? '查看預掛候選' : '檢查資料源',
    };
  }

  if (!sourceHealth) {
    return {
      label: '同步中',
      title: '正在讀取最新資料',
      detail: '系統正在同步賽程、賽果、新聞與盤口狀態，完成後才會顯示候選。',
      tone: 'watch',
      primaryAction: '稍候刷新',
    };
  }

  if ((sourceHealth.stale_unsettled_matches ?? 0) > 0) {
    return {
      label: '賽果延遲',
      title: watchCount > 0 ? '正式下注暫停，預掛觀察持續更新' : '賽果追上前不硬推下注',
      detail: watchCount > 0
        ? `仍有 ${sourceHealth.stale_unsettled_matches} 場已開賽很久但尚未回寫結果，所以不升級成正式下注推薦；但 ${watchCount} 組日期盤候選仍會持續監控賠率門檻。`
        : `仍有 ${sourceHealth.stale_unsettled_matches} 場已開賽很久但尚未回寫結果。資料追上前，不會硬推下注，只保留資訊與條件監控。`,
      tone: watchCount > 0 ? 'watch' : 'blocked',
      primaryAction: watchCount > 0 ? '查看預掛候選' : '查看賽事中心',
    };
  }

  if (sourceHealth.odds_coverage_status === 'full_market' && liveCount > 0) {
    return {
      label: '可檢查',
      title: '已有實盤候選，可做下注前檢查',
      detail: `目前有 ${liveCount} 組候選具備可比對盤口。仍需確認賠率、分線、傷停與注碼上限後才下單。`,
      tone: 'ready',
      primaryAction: '檢查推薦',
    };
  }

  if (watchCount > 0) {
    return {
      label: '預掛觀察',
      title: '日期盤候選持續推薦，先等賠率達標',
      detail: `已找到 ${watchCount} 組模型候選，但正式盤口不足或仍是比分備援來源。這些會持續推薦為預掛觀察，不是叫你立刻下注；等平台賠率達到門檻再進下注前檢查。`,
      tone: 'watch',
      primaryAction: '查看預掛候選',
    };
  }

  return {
    label: '暫無候選',
    title: '沒有達標候選就不硬推',
    detail: '目前沒有同時通過勝率、最低賠率、資料新鮮度與風險上限的選項。保留資金比硬下注更專業。',
    tone: 'blocked',
    primaryAction: '等待刷新',
  };
}

function gateClass(tone: GateState['tone']): string {
  if (tone === 'ready') return 'border-[#7bcf9b] bg-[#e9f8ef] text-[#167a47]';
  if (tone === 'blocked') return 'border-[#e7a49a] bg-[#fff0e8] text-[#b83822]';
  return 'border-[#e7c462] bg-[#fff7d6] text-[#8a6400]';
}

function weekdayLabel(date: string): string {
  return new Intl.DateTimeFormat('zh-TW', {
    timeZone: 'Asia/Taipei',
    weekday: 'short',
  }).format(new Date(`${date}T00:00:00+08:00`));
}

function recommendationDateLabel(date: string, today: string, tomorrow: string): string {
  if (date === today) return `今天 ${date.slice(5).replace('-', '/')} ${weekdayLabel(date)}`;
  if (date === tomorrow) return `明天 ${date.slice(5).replace('-', '/')} ${weekdayLabel(date)}`;
  return `${date.slice(5).replace('-', '/')} ${weekdayLabel(date)}`;
}

function recommendationDateState(date: string, today: string): string {
  if (date < today) return '已完賽回看';
  if (date === today) return '今日優先';
  return '預售分析';
}

export default function Home() {
  const [dateWindow] = useState(() => {
    const now = new Date();
    const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
    return {
      today: formatToTaipeiTime(now.toISOString(), 'yyyy-MM-dd'),
      tomorrow: formatToTaipeiTime(tomorrow.toISOString(), 'yyyy-MM-dd'),
    };
  });
  const [data, setData] = useState<DailyCardResponse | null>(null);
  const [tomorrowData, setTomorrowData] = useState<DailyCardResponse | null>(null);
  const [dailyCards, setDailyCards] = useState<Record<string, DailyCardResponse>>({});
  const [calendarDates, setCalendarDates] = useState<DailyCardCalendarDate[]>([]);
  const [selectedRecommendationDate, setSelectedRecommendationDate] = useState('');
  const [matches, setMatches] = useState<MatchListItem[]>([]);
  const [news, setNews] = useState<NewsItem[]>([]);
  const [sourceHealth, setSourceHealth] = useState<SourceHealthResponse | null>(null);
  const [watchlist, setWatchlist] = useState<DailyCardItem[]>([]);
  const [feedbackMessage, setFeedbackMessage] = useState('');
  const [loading, setLoading] = useState(true);
  const [errors, setErrors] = useState<LoadErrors>({});
  const [loadedAt, setLoadedAt] = useState('');

  useEffect(() => {
    try {
      const raw = window.localStorage.getItem(WATCHLIST_KEY);
      if (!raw) return;
      const parsed = JSON.parse(raw);
      if (Array.isArray(parsed)) {
        setWatchlist(parsed.filter(Boolean).slice(0, 20));
      }
    } catch {
      window.localStorage.removeItem(WATCHLIST_KEY);
    }
  }, []);

  useEffect(() => {
    let mounted = true;

    async function load() {
      setLoading(true);
      setErrors({});

      const [dailyResult, tomorrowResult, matchesResult, newsResult, sourceResult, calendarResult] = await Promise.allSettled([
        getDailyCard(dateWindow.today),
        getDailyCard(dateWindow.tomorrow),
        getAllMatches(),
        fetch('/api/news', { cache: 'no-store' }).then(async (response) => {
          if (!response.ok) throw new Error(`news status ${response.status}`);
          return response.json() as Promise<{ items?: NewsItem[] }>;
        }),
        getSourceHealth(),
        getDailyCardCalendar(TOURNAMENT_START_DATE),
      ]);

      if (!mounted) return;

      const nextErrors: LoadErrors = {};
      const calendarPayload = calendarResult.status === 'fulfilled' ? calendarResult.value.dates : [];
      const calendarDateList = calendarPayload.map((item) => item.date).sort((a, b) => a.localeCompare(b));
      const pinnedDates = [dateWindow.today, dateWindow.tomorrow].filter((date) => calendarDateList.includes(date));
      const pinnedSet = new Set(pinnedDates);
      const nextCards: Record<string, DailyCardResponse> = {};
      const requestDates = [...pinnedDates, ...calendarDateList.filter((date) => !pinnedSet.has(date))];
      const preferredDate = calendarPayload.find((item) => pinnedDates.includes(item.date) && item.recommendation_count > 0)?.date
        ?? calendarPayload.find((item) => item.date >= dateWindow.today && item.recommendation_count > 0)?.date
        ?? pinnedDates[0]
        ?? requestDates.find((date) => date >= dateWindow.today)
        ?? requestDates[0]
        ?? dateWindow.today;

      if (!mounted) return;

      if (dailyResult.status === 'fulfilled') {
        setData(dailyResult.value);
        nextCards[dateWindow.today] = dailyResult.value;
      }
      else nextErrors.daily = '每日推薦 API 尚未回應';

      if (tomorrowResult.status === 'fulfilled') {
        setTomorrowData(tomorrowResult.value);
        nextCards[dateWindow.tomorrow] = tomorrowResult.value;
      }
      else nextErrors.daily = nextErrors.daily ?? '明日推薦 API 尚未回應';

      if (matchesResult.status === 'fulfilled') setMatches(matchesResult.value);
      else nextErrors.matches = '賽事中心 API 尚未回應';

      if (newsResult.status === 'fulfilled') setNews(newsResult.value.items ?? []);
      else nextErrors.news = '新聞 RSS 暫時無法更新';

      if (sourceResult.status === 'fulfilled') setSourceHealth(sourceResult.value);
      else nextErrors.source = '盤口資料源健康狀態暫時無法讀取';

      if (calendarResult.status === 'fulfilled') setCalendarDates(calendarPayload);
      else nextErrors.daily = nextErrors.daily ?? '日期推薦摘要 API 尚未回應';

      setDailyCards(nextCards);
      setSelectedRecommendationDate((current) => {
        if (requestDates.includes(current)) return current;
        return preferredDate;
      });
      setErrors(nextErrors);
      setLoadedAt(formatToTaipeiTime(new Date().toISOString()));
      setLoading(false);
    }

    load().catch((error) => {
      if (mounted) {
        setErrors({ daily: error instanceof Error ? error.message : '無法取得最新量化資料' });
        setLoading(false);
      }
    });
    const interval = window.setInterval(() => {
      load().catch(() => undefined);
    }, 60_000);

    return () => {
      mounted = false;
      window.clearInterval(interval);
    };
  }, [dateWindow.today, dateWindow.tomorrow]);

  useEffect(() => {
    if (!selectedRecommendationDate || dailyCards[selectedRecommendationDate]) return undefined;

    let mounted = true;
    getDailyCard(selectedRecommendationDate)
      .then((card) => {
        if (!mounted) return;
        setDailyCards((current) => ({ ...current, [selectedRecommendationDate]: card }));
      })
      .catch(() => {
        if (!mounted) return;
        setErrors((current) => ({ ...current, daily: '此日期推薦卡片暫時無法載入' }));
      });

    return () => {
      mounted = false;
    };
  }, [dailyCards, selectedRecommendationDate]);

  const matchesByDate = useMemo(() => {
    const grouped = new Map<string, MatchListItem[]>();
    matches.forEach((match) => {
      const date = matchDate(match);
      if (date < TOURNAMENT_START_DATE) return;
      grouped.set(date, [...(grouped.get(date) ?? []), match]);
    });
    return grouped;
  }, [matches]);

  const chronologicalDates = useMemo(() => {
    return calendarDates.map((item) => item.date).sort((a, b) => a.localeCompare(b));
  }, [calendarDates]);

  const calendarByDate = useMemo(() => {
    return new Map(calendarDates.map((item) => [item.date, item]));
  }, [calendarDates]);

  const recommendationDateOptions = useMemo(() => {
    const pinned = [dateWindow.today, dateWindow.tomorrow].filter((date) => calendarByDate.has(date));
    const pinnedSet = new Set(pinned);
    return [...pinned, ...chronologicalDates.filter((date) => !pinnedSet.has(date))];
  }, [calendarByDate, chronologicalDates, dateWindow.today, dateWindow.tomorrow]);

  const activeRecommendationDate = selectedRecommendationDate || recommendationDateOptions[0] || dateWindow.today;
  const selectedCard = dailyCards[activeRecommendationDate]
    ?? (activeRecommendationDate === dateWindow.tomorrow ? tomorrowData : null)
    ?? (activeRecommendationDate === dateWindow.today ? data : null);
  const recommendationItems = useMemo(() => itemsFromCard(selectedCard), [selectedCard]);
  const selectedDateSummary = calendarByDate.get(activeRecommendationDate);
  const selectedMatchCount = selectedDateSummary?.match_count ?? matchesByDate.get(activeRecommendationDate)?.length ?? selectedCard?.matched_matches ?? 0;
  const dateSummaries = useMemo(() => {
    return recommendationDateOptions.map((date) => {
      const card = dailyCards[date]
        ?? (date === dateWindow.today ? data : null)
        ?? (date === dateWindow.tomorrow ? tomorrowData : null);
      const items = itemsFromCard(card);
      const summary = calendarByDate.get(date);
      return {
        date,
        matchCount: summary?.match_count ?? matchesByDate.get(date)?.length ?? card?.matched_matches ?? 0,
        recommendationCount: summary?.recommendation_count ?? items.length,
        liveCount: summary?.live_count ?? items.filter((item) => item.has_market_odds).length,
      };
    });
  }, [calendarByDate, dailyCards, data, dateWindow.today, dateWindow.tomorrow, matchesByDate, recommendationDateOptions, tomorrowData]);

  const topPicks = useMemo<DailyCardItem[]>(() => {
    const rank = (items: DailyCardItem[]) =>
      [...items].sort((a, b) => {
        const aLiveBonus = a.has_market_odds ? 18 : 0;
        const bLiveBonus = b.has_market_odds ? 18 : 0;
        const aScore = aLiveBonus + (a.confidence_score ?? a.win_prob) + (a.ev_percent * 0.22) - (a.stake_units * 0.8);
        const bScore = bLiveBonus + (b.confidence_score ?? b.win_prob) + (b.ev_percent * 0.22) - (b.stake_units * 0.8);
        return bScore - aScore;
      });

    const singles = rank(recommendationItems.filter((item) => item.recommendation === 'SAFE_SINGLE'));
    const parlays = rank(recommendationItems.filter((item) => item.recommendation === 'SAFE_PARLAY'));
    const sameGame = rank(recommendationItems.filter((item) => item.recommendation === 'SGP_LOTTERY'));
    const highRisk = rank(recommendationItems.filter((item) => item.recommendation === 'HIGH_RISK_SINGLE'));
    const balanced = [...singles.slice(0, 3), ...parlays.slice(0, 2), ...sameGame.slice(0, 1), ...highRisk.slice(0, 2)];
    const used = new Set(balanced.map(pickKey));
    const remaining = rank(recommendationItems).filter((item) => !used.has(pickKey(item)));
    return diversifyPicks([...balanced, ...remaining], 10);
  }, [recommendationItems]);

  const categorySummary = useMemo(() => {
    const count = (type: DailyCardItem['recommendation']) => recommendationItems.filter((item) => item.recommendation === type).length;
    return [
      { label: '單關', value: count('SAFE_SINGLE') },
      { label: '跨場串關', value: count('SAFE_PARLAY') },
      { label: '同場串關', value: count('SGP_LOTTERY') },
      { label: '小注高賠', value: count('HIGH_RISK_SINGLE') },
    ];
  }, [recommendationItems]);

  const nextMatches = useMemo(() => {
    const now = Date.now();
    return matches
      .filter((match) => new Date(match.kickoff_utc).getTime() >= now - 1000 * 60 * 120)
      .sort((a, b) => new Date(a.kickoff_utc).getTime() - new Date(b.kickoff_utc).getTime())
      .slice(0, 5);
  }, [matches]);

  const watchlistKeys = useMemo(() => new Set(watchlist.map(pickKey)), [watchlist]);
  const liveRecommendationCount = recommendationItems.filter((item) => item.has_market_odds).length;
  const watchOnlyRecommendationCount = recommendationItems.length - liveRecommendationCount;
  const gate = buildGateState(sourceHealth, errors, liveRecommendationCount, watchOnlyRecommendationCount);

  const latestResultLabel = sourceHealth?.latest_result_synced_at ? formatToTaipeiTime(sourceHealth.latest_result_synced_at) : '尚無賽果時間';
  const latestNewsLabel = sourceHealth?.news_status?.run_at ? formatToTaipeiTime(sourceHealth.news_status.run_at) : '尚無新聞排程';
  const latestFixturesLabel = sourceHealth?.fixtures_status?.run_at ? formatToTaipeiTime(sourceHealth.fixtures_status.run_at) : '尚無賽程排程';
  const oddsCoverageLabel = sourceHealth?.odds_coverage_status === 'full_market'
    ? '完整實盤'
    : sourceHealth?.odds_coverage_status === 'reference_market'
      ? '台灣運彩參考盤'
    : sourceHealth?.odds_coverage_status === 'limited_scoreboard_fallback'
      ? '比分備援，盤口不足'
      : sourceHealth?.odds_coverage_status === 'no_upcoming_market'
        ? '未來賽事無實盤'
        : '資料不足';
  const activeDateLabel = recommendationDateLabel(activeRecommendationDate, dateWindow.today, dateWindow.tomorrow);
  const activeDateState = recommendationDateState(activeRecommendationDate, dateWindow.today);
  const activeDateRecommendationCount = selectedDateSummary?.recommendation_count ?? recommendationItems.length;
  const activeDateLiveCount = selectedDateSummary?.live_count ?? liveRecommendationCount;
  const activeDateWatchCount = Math.max(activeDateRecommendationCount - activeDateLiveCount, 0);
  const activeDateSummaryText = `${activeDateLabel}｜${selectedMatchCount} 場比賽、${activeDateRecommendationCount} 筆候選，其中 ${activeDateLiveCount} 筆可做下注前檢查、${activeDateWatchCount} 筆先監控賠率。`;

  const healthItems = [
    {
      label: '資料可用性',
      ok: Boolean(sourceHealth) && (sourceHealth?.stale_unsettled_matches ?? 0) === 0 && !errors.source,
      detail: errors.source ?? `${oddsCoverageLabel}；賽果 ${latestResultLabel}，逾時未更新 ${sourceHealth?.stale_unsettled_matches ?? 0} 場`,
    },
    {
      label: '推薦狀態',
      ok: liveRecommendationCount > 0,
      detail: `${liveRecommendationCount} 組可檢查、${watchOnlyRecommendationCount} 組只監控；目前日期掃描 ${selectedMatchCount} 場`,
    },
    {
      label: '賽程與新聞',
      ok: matches.length > 0 && news.length > 0 && !errors.matches && !errors.news,
      detail: `賽程 ${matches.length} 場，排程 ${latestFixturesLabel}；新聞 ${news.length} 則，排程 ${latestNewsLabel}`,
    },
  ];

  function handleTrack(item: DailyCardItem) {
    const key = pickKey(item);
    if (watchlistKeys.has(key)) {
      setFeedbackMessage(`已在清單中：${item.match_label} ｜ ${item.selection}`);
      return;
    }
    const next = [item, ...watchlist].slice(0, 20);
    setWatchlist(next);
    setFeedbackMessage(`已加入${isConditionalPick(item) || !item.has_market_odds ? '賠率監控清單' : '下注前檢查清單'}：${item.match_label} ｜ ${item.selection}。最低賠率 ${item.target_odds.toFixed(2)}，參考上限 ${formatTwd(stakeAmountTwd(item))}。`);
    try {
      window.localStorage.setItem(WATCHLIST_KEY, JSON.stringify(next));
    } catch {
      setFeedbackMessage(`已加入本頁清單，但瀏覽器暫時無法永久保存：${item.match_label} ｜ ${item.selection}`);
    }
  }

  function handleRemoveTracked(item: DailyCardItem) {
    const key = pickKey(item);
    const next = watchlist.filter((tracked) => pickKey(tracked) !== key);
    setWatchlist(next);
    setFeedbackMessage(`已移除監控：${item.match_label} ｜ ${item.selection}`);
    try {
      window.localStorage.setItem(WATCHLIST_KEY, JSON.stringify(next));
    } catch {
      setFeedbackMessage('已從本頁清單移除；瀏覽器永久保存狀態稍後會在重新整理後同步。');
    }
  }

  return (
    <div className="space-y-8">
      <section className="rounded-[2rem] border border-[#e7c89b] bg-[#fff8e6]/95 p-6 shadow-[0_18px_60px_rgba(125,42,21,0.12)] md:p-8">
        <div className="flex flex-col gap-3 md:flex-row md:items-end md:justify-between">
          <div>
            <p className="dot-matrix text-xs font-semibold text-[#b83822]">首頁第一屏 / {dateWindow.today} - {dateWindow.tomorrow}</p>
            <h2 className="mt-2 text-3xl font-black text-[#3f2f25] md:text-5xl">下一批可檢查投注候選</h2>
            <p className="mt-3 max-w-3xl text-sm leading-7 text-[#6f4f3c]">
              一打開首頁先看這裡：系統會優先顯示今天與明天，再依日期列出後續可分析賽事。清單會自動分散場次與玩法，避免同一場比賽洗版；若資料不足，卡片會標成「預掛觀察」，只代表先盯賠率門檻，不是叫你立刻下注。
            </p>
            <p className="mt-3 rounded-2xl border border-[#eadcb9] bg-white/75 px-4 py-3 text-sm font-bold text-[#5f4330]">
              目前主顯示日期：<span className="text-[#b83822]">{activeRecommendationDate}</span>
              。當日賽事 {selectedMatchCount} 場，推薦 {recommendationItems.length} 組。
            </p>
          </div>
          <div className="flex flex-wrap gap-2 text-xs font-bold">
            <span className="rounded-full bg-[#e9f8ef] px-3 py-1 text-[#167a47]">可檢查 {liveRecommendationCount} 組</span>
            <span className="rounded-full bg-[#fff7d6] px-3 py-1 text-[#8a6400]">預掛監控 {watchOnlyRecommendationCount} 組</span>
            <span className="rounded-full bg-[#fff0e2] px-3 py-1 text-[#b83822]">60 秒刷新</span>
            <span className="rounded-full bg-white px-3 py-1 text-[#5f4330]">首頁最多每場優先顯示 2 組</span>
          </div>
        </div>

        <div className="mt-4 flex flex-wrap gap-2 text-xs font-black">
          {dateSummaries.map((item) => {
            const pinnedLabel = item.date === dateWindow.today ? '今天' : item.date === dateWindow.tomorrow ? '明天' : '';
            return (
              <button
                key={item.date}
                type="button"
                onClick={() => setSelectedRecommendationDate(item.date)}
                className={`rounded-full border px-3 py-1.5 text-left transition ${
                  activeRecommendationDate === item.date
                    ? 'border-[#7d2a15] bg-[#7d2a15] text-white'
                    : 'border-[#d8b58c] bg-white/75 text-[#5f4330] hover:border-[#b83822]'
                }`}
              >
                {item.date}{pinnedLabel ? ` ${pinnedLabel}` : ''}｜{item.matchCount} 場｜{item.recommendationCount} 推
                {item.liveCount > 0 ? `｜${item.liveCount} 可檢查` : ''}
              </button>
            );
          })}
        </div>

        <div className="mt-4 flex flex-wrap gap-2 text-xs font-black">
          {categorySummary.map((item) => (
            <span key={item.label} className="rounded-full border border-[#d8b58c] bg-white/75 px-3 py-1 text-[#5f4330]">
              {item.label} {item.value} 組
            </span>
          ))}
        </div>

        {feedbackMessage ? (
          <p role="status" className="mt-4 rounded-2xl border border-[#9bd8b0] bg-[#e9f8ef] p-3 text-sm font-bold text-[#167a47]">
            {feedbackMessage}
          </p>
        ) : null}

        <section className="mt-5 rounded-3xl border border-[#eadcb9] bg-white/70 p-4">
          <div className="flex flex-col gap-2 md:flex-row md:items-start md:justify-between">
            <div>
              <p className="dot-matrix text-xs font-bold text-[#b83822]">我的賠率監控清單</p>
              <h3 className="mt-1 text-xl font-black text-[#3f2f25]">
                {watchlist.length ? `已追蹤 ${watchlist.length} 組候選` : '還沒有加入候選'}
              </h3>
              <p className="mt-1 text-sm leading-6 text-[#7a5b46]">
                加入後不是立刻下注，而是集中檢查來源、最低賠率、玩法與注碼上限。台灣盤參考價與模型推算門檻會分開標示。
              </p>
            </div>
            <Link href="/daily-card" className="rounded-full border border-[#d8b58c] bg-[#fff8e6] px-4 py-2 text-sm font-black text-[#7d2a15] transition hover:border-[#b83822]">
              打開完整檢查清單
            </Link>
          </div>
          {watchlist.length ? (
            <div className="mt-4 grid gap-3 lg:grid-cols-2">
              {watchlist.slice(0, 4).map((item) => {
                const conditional = isConditionalPick(item) || !item.has_market_odds;
                return (
                  <article key={`watch-${pickKey(item)}`} className="rounded-2xl border border-[#e7c89b] bg-[#fff8e6]/90 p-4">
                    <div className="flex flex-wrap items-center gap-2">
                      <span className={`rounded-full px-2 py-1 text-[11px] font-black ${conditional ? 'bg-[#fff7d6] text-[#8a6400]' : 'bg-[#e9f8ef] text-[#167a47]'}`}>
                        {conditional ? '先監控' : '可檢查'}
                      </span>
                      {item.odds_source_label ? (
                        <span className="rounded-full border border-[#d8b58c] bg-white px-2 py-1 text-[11px] font-black text-[#5f4330]">{item.odds_source_label}</span>
                      ) : null}
                    </div>
                    <p className="mt-2 text-sm font-black text-[#3f2f25]">{item.match_label}</p>
                    <p className="mt-1 text-xs leading-5 text-[#7a5b46]">{item.market_type}｜{item.selection}</p>
                    <div className="mt-3 grid grid-cols-3 gap-2 text-xs text-[#7a5b46]">
                      <p>門檻<br /><b className="text-[#b83822]">{item.target_odds.toFixed(2)}</b></p>
                      <p>信心<br /><b className="text-[#167a47]">{typeof item.confidence_score === 'number' ? item.confidence_score.toFixed(1) : item.win_prob.toFixed(1)}</b></p>
                      <p>上限<br /><b className="text-[#8a6400]">{formatTwd(stakeAmountTwd(item))}</b></p>
                    </div>
                    <button
                      type="button"
                      className="mt-3 rounded-full border border-[#d8b58c] bg-white px-3 py-1.5 text-xs font-black text-[#7d2a15] transition hover:border-[#b83822]"
                      onClick={() => handleRemoveTracked(item)}
                    >
                      移除這筆
                    </button>
                  </article>
                );
              })}
            </div>
          ) : (
            <p className="mt-4 rounded-2xl border border-dashed border-[#d8b58c] bg-[#fff8e6]/80 p-4 text-sm leading-6 text-[#7a5b46]">
              看到候選卡片時，先按「加入賠率監控」或「加入下注前檢查」。清單會保留在此瀏覽器中，方便你逐筆核對，不用重新找。
            </p>
          )}
        </section>

        {loading ? (
          <p className="mt-6 dot-matrix dot-matrix-loading text-sm text-[#7a5b46]">正在同步日期賽事與多玩法推薦...</p>
        ) : topPicks.length ? (
          <div className="mt-6 grid gap-4 lg:grid-cols-3">
            {topPicks.slice(0, 9).map((item) => {
              const conditional = isConditionalPick(item) || !item.has_market_odds;
              const inList = watchlistKeys.has(pickKey(item));
              return (
                <article key={`hero-${pickKey(item)}-${item.recommendation}`} className="rounded-3xl border border-[#e7c89b] bg-white/80 p-5 shadow-[0_14px_36px_rgba(125,42,21,0.10)]">
                  <div className="flex flex-wrap items-center gap-2">
                    <span className="dot-matrix text-xs font-bold text-[#b83822]">{recommendationText(item)}</span>
                    <span className={`rounded-full border px-2 py-1 text-[11px] font-black ${conditional ? 'border-[#e7c462] bg-[#fff7d6] text-[#8a6400]' : 'border-[#7bcf9b] bg-[#e9f8ef] text-[#167a47]'}`}>
                      {conditional ? '預掛觀察' : '下注前檢查'}
                    </span>
                    {typeof item.confidence_score === 'number' ? <span className="rounded-full bg-[#e9f8ef] px-2 py-1 text-[11px] font-black text-[#167a47]">信心分數 {item.confidence_score.toFixed(1)}</span> : null}
                    {item.odds_source_label ? <span className="rounded-full border border-[#d8b58c] bg-white px-2 py-1 text-[11px] font-black text-[#5f4330]">{item.odds_source_label}</span> : null}
                  </div>
                  <h3 className="mt-3 text-lg font-black text-[#3f2f25]">{item.match_label}</h3>
                  <p className="mt-1 text-xs font-bold text-[#7a5b46]">台北日期：{activeDateLabel}</p>
                  <p className="mt-2 text-sm font-bold text-[#b83822]">{item.market_type}｜{item.selection}</p>
                  <p className="mt-2 text-xs leading-5 text-[#8a6b58]">同一場比賽可能有不同玩法；不同玩法的賠率不能互相比大小，只能和該玩法自己的最低門檻比較。</p>
                  <p className="mt-2 rounded-2xl border border-[#eadcb9] bg-[#fff8e6] p-3 text-xs font-bold leading-5 text-[#5f4330]">{shortBetInstruction(item)}</p>
                  <div className="mt-4 grid gap-2 text-sm text-[#5f4330]">
                    <p>最低可接受賠率：<span className="font-black text-[#b83822]">{item.target_odds.toFixed(2)}</span></p>
                    <p>模型勝率：<span className="font-black text-[#167a47]">{item.win_prob.toFixed(2)}%</span></p>
                    <p>參考上限：<span className="font-black text-[#8a6400]">{formatTwd(stakeAmountTwd(item))}</span></p>
                  </div>
                  <p className="mt-3 rounded-2xl bg-[#fff8e6] p-3 text-xs leading-5 text-[#6f4f3c]">
                    {nextStepText(item)} 平台賠率必須大於或等於 {item.target_odds.toFixed(2)}；低於這個數字就直接跳過。信心分數是綜合評分，不是命中率。
                  </p>
                  <div className="mt-4 flex flex-col gap-2 sm:flex-row">
                    <button
                      type="button"
                      aria-pressed={inList}
                      className={`rounded-full px-4 py-2 text-sm font-black text-white transition ${inList ? 'bg-[#167a47]' : 'bg-[#d1432d] hover:bg-[#b83822]'}`}
                      onClick={() => handleTrack(item)}
                    >
                      {inList ? '已加入清單' : conditional ? '加入賠率監控' : '加入下注前檢查'}
                    </button>
                    <Link href="/daily-card" className="rounded-full border border-[#d8b58c] bg-white px-4 py-2 text-center text-sm font-bold text-[#5f4330] transition hover:border-[#b83822] hover:text-[#7d2a15]">
                      完整分析
                    </Link>
                  </div>
                </article>
              );
            })}
          </div>
        ) : (
          <div className="mt-6 rounded-3xl border border-dashed border-[#d8b58c] bg-white/65 p-5">
            <p className="text-sm leading-7 text-[#6f4f3c]">目前沒有通過模型勝率、最低賠率、資料新鮮度與倉位上限的候選。專業推薦不是硬湊數量，沒有優勢就保留資金，等下一輪資料刷新。</p>
          </div>
        )}
      </section>

      <section className="relative overflow-hidden rounded-[2rem] border border-[#e7c89b] bg-[#fff8e6]/95 p-6 shadow-[0_18px_60px_rgba(125,42,21,0.12)] md:p-8">
        <div className="absolute inset-y-0 right-0 w-1/2 bg-[radial-gradient(circle_at_center,rgba(209,67,45,0.16),transparent_55%)]" />
        <div className="relative grid gap-8 lg:grid-cols-[1.08fr_0.92fr]">
          <div>
            <p className="dot-matrix text-xs font-semibold text-[#b83822]">台北即時投注研究台 / {activeDateState} / {activeRecommendationDate}</p>
            <h2 className="mt-4 max-w-3xl text-4xl font-black leading-tight text-[#3f2f25] md:text-6xl">
              世界盃下注推薦主控台
            </h2>
            <p className="mt-4 max-w-2xl text-base leading-7 text-[#6f4f3c]">
              首頁先顯示可研究的投注候選，再判斷能不能進場。{activeDateSummaryText} 資料不足時只開監控，不用漂亮空殼假裝高勝率。
            </p>
            <div className={`mt-6 rounded-3xl border p-5 ${gateClass(gate.tone)}`}>
              <div className="flex flex-wrap items-center gap-2">
                <span className="rounded-full bg-white/70 px-3 py-1 text-xs font-black">{gate.label}</span>
                <span className="text-xs font-semibold">{loadedAt || '同步中'}</span>
              </div>
              <h3 className="mt-3 text-2xl font-black">{gate.title}</h3>
              <p className="mt-2 text-sm leading-6">{gate.detail}</p>
              <div className="mt-4 flex flex-wrap gap-2 text-xs font-bold">
                <span className="rounded-full bg-white/65 px-3 py-1">正式盤口：{sourceHealth?.odds_coverage_status === 'full_market' ? '已具備' : '不足'}</span>
                <span className="rounded-full bg-white/65 px-3 py-1">逾時賽果：{sourceHealth?.stale_unsettled_matches ?? 0} 場</span>
                <span className="rounded-full bg-white/65 px-3 py-1">目前日期：{activeDateLabel}</span>
                <span className="rounded-full bg-white/65 px-3 py-1">監控清單：{watchlist.length} 筆</span>
              </div>
            </div>
            <div className="mt-6 flex flex-wrap gap-3">
              <Link href="/daily-card" className="rounded-full bg-[#d1432d] px-5 py-3 text-sm font-black text-white transition hover:bg-[#b83822]">
                {gate.primaryAction}
              </Link>
              <Link href="/market-coverage" className="rounded-full border border-[#d8b58c] bg-white/70 px-5 py-3 text-sm font-semibold text-[#5f4330] transition hover:border-[#b83822] hover:text-[#7d2a15]">
                檢查盤口覆蓋
              </Link>
              <Link href="/recommendation-readiness" className="rounded-full border border-[#d8b58c] bg-white/70 px-5 py-3 text-sm font-semibold text-[#5f4330] transition hover:border-[#1a9a57] hover:text-[#1a9a57]">
                推薦就緒度
              </Link>
            </div>
          </div>

          <div className="rounded-3xl border border-[#e7c89b] bg-white/75 p-5 shadow-inner">
            <div className="flex items-center justify-between">
              <p className="dot-matrix text-sm font-semibold text-[#7d2a15]">專業檢查順序</p>
              <span className="rounded-full bg-[#fff0e2] px-3 py-1 text-xs text-[#7a5b46]">60 秒刷新</span>
            </div>
            <div className="mt-4 grid gap-3">
              {[
                ['1', '先看狀態', '若是只監控或資料延遲，就不要直接下注。'],
                ['2', '確認玩法', '單關、串關、同場串關要分開看，不混成同一種推薦。'],
                ['3', '比對賠率', '平台賠率低於最低門檻就跳過，不能硬追。'],
                ['4', '控制注碼', '只用卡片建議單位，尤其高賠與同場串關要小注。'],
              ].map(([step, title, detail]) => (
                <article key={step} className="rounded-2xl border border-[#eadcb9] bg-[#fff8e6]/80 p-4">
                  <div className="flex gap-3">
                    <span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[#7d2a15] text-sm font-black text-white">{step}</span>
                    <div>
                      <p className="font-bold text-[#5f4031]">{title}</p>
                      <p className="mt-1 text-xs leading-5 text-[#7a5b46]">{detail}</p>
                    </div>
                  </div>
                </article>
              ))}
            </div>
          </div>
        </div>
      </section>

      <section className="grid gap-4 md:grid-cols-4">
        {[
          { label: '可檢查候選', value: selectedCard ? liveRecommendationCount : '-', helper: '同日實盤資料' },
          { label: '監控候選', value: selectedCard ? watchOnlyRecommendationCount : '-', helper: '同日等賠率' },
          { label: '串關玩法', value: selectedCard ? recommendationItems.filter((item) => ['SAFE_PARLAY', 'SGP_LOTTERY'].includes(item.recommendation)).length : '-', helper: '跨場/同場' },
          { label: '近期賽事', value: nextMatches.length || '-', helper: '台北時間排序' },
        ].map((item) => (
          <article key={item.label} className="panel-glow rounded-2xl p-5">
            <p className="text-xs font-semibold tracking-[0.2em] text-[#8a6b58]">{item.helper}</p>
            <p className="mt-3 text-3xl font-black text-[#7d2a15]">{item.value}</p>
            <p className="mt-1 text-sm text-[#6f4f3c]">{item.label}</p>
          </article>
        ))}
      </section>

      <section className="grid gap-6 lg:grid-cols-[1.38fr_0.62fr]">
        <div className="space-y-4">
          <div className="flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
            <div>
              <p className="dot-matrix text-xs text-[#b83822]">依日期優先檢查 / {activeDateState}</p>
              <h2 className="text-2xl font-black text-[#3f2f25]">{activeDateLabel} 多玩法候選清單</h2>
            <p className="mt-2 text-sm text-[#7a5b46]">{activeDateSummaryText} 詳細下注方式與白話分析請進每日作戰室逐張檢查。</p>
            </div>
            <Link href="/daily-card" className="text-sm font-semibold text-[#b83822] hover:text-[#7d2a15]">看完整作戰室</Link>
          </div>

          {feedbackMessage ? (
            <p role="status" className="rounded-2xl border border-[#9bd8b0] bg-[#e9f8ef] p-3 text-sm font-bold text-[#167a47]">
              {feedbackMessage}
            </p>
          ) : null}

          {loading ? (
            <p className="dot-matrix dot-matrix-loading text-sm text-[#7a5b46]">同步市場即時資料中...</p>
          ) : topPicks.length ? (
            <div className="grid gap-4 md:grid-cols-2">
              {topPicks.map((item) => {
                const conditional = isConditionalPick(item) || !item.has_market_odds;
                const inList = watchlistKeys.has(pickKey(item));
                return (
                  <article key={`${pickKey(item)}-${item.recommendation}`} className="rounded-3xl border border-[#e7c89b] bg-[#fff8e6] p-5 shadow-[0_18px_48px_rgba(125,42,21,0.10)]">
                    <div className="flex flex-wrap items-center gap-2">
                      <span className="dot-matrix text-xs font-bold text-[#b83822]">{recommendationText(item)}</span>
                      <span className={`rounded-full border px-2 py-1 text-[11px] font-bold ${conditional ? 'border-[#e7c462] bg-[#fff7d6] text-[#8a6400]' : 'border-[#7bcf9b] bg-[#e9f8ef] text-[#167a47]'}`}>
                        {conditional ? '先監控' : '可檢查'}
                      </span>
                      {typeof item.confidence_score === 'number' ? <span className="rounded-full bg-white/70 px-2 py-1 text-[11px] font-bold text-[#167a47]">信心分數 {item.confidence_score.toFixed(1)}</span> : null}
                      <span className="rounded-full bg-white/70 px-2 py-1 text-[11px] font-bold text-[#7a5b46]">{activeDateLabel}</span>
                      {item.confidence_band ? <span className="rounded-full bg-white/70 px-2 py-1 text-[11px] font-bold text-[#7d2a15]">{item.confidence_band}</span> : null}
                    </div>
                    <h3 className="mt-3 text-xl font-black text-[#3f2f25]">{item.match_label}</h3>
                    <p className="mt-1 text-sm text-[#7a5b46]">{item.market_type} ｜ {item.selection}</p>
                    <p className="mt-2 rounded-2xl border border-[#eadcb9] bg-white/70 p-3 text-xs font-bold leading-5 text-[#5f4330]">{shortBetInstruction(item)}</p>
                    <div className="mt-4 grid gap-2 text-sm text-[#5f4330] md:grid-cols-3">
                      <p>模型勝率 <span className="font-black text-[#167a47]">{item.win_prob.toFixed(2)}%</span></p>
                      <p>最低賠率 <span className="font-black text-[#b83822]">{item.target_odds.toFixed(2)}</span></p>
                      <p>參考上限 <span className="font-black text-[#8a6400]">{formatTwd(stakeAmountTwd(item))}</span></p>
                    </div>
                    <p className="mt-3 rounded-2xl bg-white/70 p-3 text-sm leading-6 text-[#6f4f3c]">
                      {nextStepText(item)} 平台賠率必須大於或等於 {item.target_odds.toFixed(2)}；低於這個數字就直接跳過。信心分數是綜合評分，不是命中率。
                    </p>
                    <div className="mt-4 flex flex-col gap-2 sm:flex-row">
                      <button
                        type="button"
                        aria-pressed={inList}
                        className={`rounded-full px-4 py-2 text-sm font-black text-white transition ${inList ? 'bg-[#167a47]' : 'bg-[#d1432d] hover:bg-[#b83822]'}`}
                        onClick={() => handleTrack(item)}
                      >
                        {inList ? '已加入清單' : conditional ? '加入賠率監控' : '加入下注前檢查'}
                      </button>
                      <Link href="/daily-card" className="rounded-full border border-[#d8b58c] bg-white/70 px-4 py-2 text-center text-sm font-bold text-[#5f4330] transition hover:border-[#b83822] hover:text-[#7d2a15]">
                        看完整分析
                      </Link>
                    </div>
                  </article>
                );
              })}
            </div>
          ) : (
            <div className="panel-glow rounded-2xl border-dashed p-6">
              <p className="text-sm leading-7 text-[#6f4f3c]">目前沒有通過勝率、期望值、資料新鮮度與注碼上限的候選。沒有優勢就不硬推單，這才是專業紀律。</p>
              <p className="mt-2 text-xs text-[#8a6b58]">{errors.daily ?? selectedCard?.summary ?? '等待下一次資料刷新。'}</p>
            </div>
          )}
        </div>

        <aside className="space-y-4">
          <section className="panel-glow rounded-2xl p-5">
            <h3 className="dot-matrix text-sm font-bold text-[#7d2a15]">資料源健康</h3>
            <div className="mt-4 space-y-3">
              {healthItems.map((item) => (
                <article key={item.label} className="rounded-2xl border border-[#eadcb9] bg-white/70 p-4">
                  <div className="flex items-center gap-2">
                    <span className={`status-led ${item.ok ? 'status-led-ok' : 'status-led-warn'}`} />
                    <p className="text-sm font-bold text-[#3f2f25]">{item.label}</p>
                  </div>
                  <p className="mt-2 text-xs leading-5 text-[#7a5b46]">{item.detail}</p>
                </article>
              ))}
            </div>
          </section>

          <section className="panel-glow rounded-2xl p-5">
            <div className="flex items-center justify-between">
              <h3 className="dot-matrix text-sm font-bold text-[#7d2a15]">近期賽程</h3>
              <Link href="/schedule" className="text-xs font-bold text-[#b83822] hover:text-[#7d2a15]">完整賽程</Link>
            </div>
            <div className="mt-4 space-y-3">
              {nextMatches.length ? nextMatches.map((match) => (
                <Link key={match.match_id} href={`/matches/${match.match_id}`} className="block rounded-2xl border border-[#eadcb9] bg-white/70 p-3 transition hover:border-[#b83822]">
                  <p className="text-sm font-bold text-[#3f2f25]">{match.home_team} vs {match.away_team}</p>
                  <p className="mt-1 text-xs text-[#7a5b46]">{formatToTaipeiTime(match.kickoff_utc, 'MM/dd HH:mm')} ｜ {match.status} ｜ {match.venue_city || '場地待定'}</p>
                </Link>
              )) : (
                <p className="text-sm text-[#7a5b46]">{errors.matches ?? '尚無可顯示賽程。'}</p>
              )}
            </div>
          </section>

          <section className="panel-glow rounded-2xl p-5">
            <div className="flex items-center justify-between">
              <h3 className="dot-matrix text-sm font-bold text-[#7d2a15]">新聞脈搏</h3>
              <Link href="/news" className="text-xs font-bold text-[#b83822] hover:text-[#7d2a15]">新聞頁</Link>
            </div>
            <div className="mt-4 space-y-3">
              {news.slice(0, 5).map((item) => (
                <a key={item.url} href={item.url} target="_blank" rel="noreferrer" className="block rounded-2xl border border-[#eadcb9] bg-white/70 p-3 transition hover:border-[#b83822]">
                  <p className="text-sm font-semibold leading-5 text-[#3f2f25]">{item.title}</p>
                  <p className="mt-2 text-xs text-[#7a5b46]">{item.source} ｜ {item.publishedAt ? formatToTaipeiTime(item.publishedAt, 'MM/dd HH:mm') : '時間待定'}</p>
                </a>
              ))}
              {!news.length ? <p className="text-sm text-[#7a5b46]">{errors.news ?? '尚無新聞資料。'}</p> : null}
            </div>
          </section>
        </aside>
      </section>

      <section className="panel-glow rounded-2xl p-6">
        <h2 className="dot-matrix text-xl text-[#7d2a15]">核心頁面入口</h2>
        <p className="mt-2 text-sm text-[#7a5b46]">首頁不再把所有工具一次攤開；這裡只保留主流程，其他進階工具放在上方工具箱。</p>
        <div className="mt-4 grid gap-3 md:grid-cols-2 lg:grid-cols-4">
          {moduleLinks.map((item) => (
            <Link key={item.href} href={item.href} className="rounded-2xl border border-[#eadcb9] bg-white/70 p-4 transition hover:border-[#b83822] hover:bg-[#fff0e2]">
              <p className="font-bold text-[#3f2f25]">{item.label}</p>
              <p className="mt-2 text-xs leading-5 text-[#7a5b46]">{item.desc}</p>
            </Link>
          ))}
        </div>
      </section>
    </div>
  );
}
