UX 审查收尾:AI 解读过渡标记 + 极端温度视觉强化

P2-17:AI 快速判断→完整解读过渡标记
- AiEvidencePanel 新增 useTransitionMarker hook
- AI 从 loading→ready 后显示"✓ Updated/已更新"徽标(4 秒后消失)
- 绿色动画徽标 fadeUpIn,避免用户以为信息没变

P2-18:极端温度视觉强化
- globals.css 新增 temp-extreme-hot(橙红辉光 ≥40°C/≥104°F)
- globals.css 新增 temp-extreme-cold(冰蓝辉光 ≤-5°C/≤23°F)
- PanelSections Hero 温度值自动应用极端温度样式

Tested: npx tsc --noEmit
This commit is contained in:
2569718930@qq.com
2026-05-10 17:44:45 +08:00
parent 341825747f
commit fe681503ae
2 changed files with 32 additions and 0 deletions
@@ -1027,6 +1027,18 @@
color: var(--color-accent-primary);
}
.root :global(.scan-ai-updated-badge) {
display: inline-block;
padding: 2px 8px;
border-radius: 999px;
background: rgba(34, 197, 94, 0.12);
color: #4ade80;
font-size: 10px;
font-weight: 700;
margin-bottom: 6px;
animation: fadeUpIn 0.3s ease;
}
.root :global(.scan-ai-raw-metar) {
margin-top: 12px;
padding-top: 12px;
@@ -1,5 +1,6 @@
"use client";
import { useEffect, useState } from "react";
import type {
AiCityForecastPayload,
AiCityForecastState,
@@ -13,6 +14,18 @@ function confidenceBadge(confidence: string | null, isEn: boolean) {
return { label: isEn ? "Low" : "低", tone: "low" };
}
function useTransitionMarker(status: string, hasContent: boolean) {
const [showUpdated, setShowUpdated] = useState(false);
useEffect(() => {
if (status === "ready" && hasContent) {
setShowUpdated(true);
const id = setTimeout(() => setShowUpdated(false), 4000);
return () => clearTimeout(id);
}
}, [status, hasContent]);
return showUpdated;
}
export function AiEvidencePanel({
aiBullets,
aiCityForecast,
@@ -62,6 +75,8 @@ export function AiEvidencePanel({
? `${formatTemperatureValue(aiRangeLow, tempSymbol, { digits: 0 })} ~ ${formatTemperatureValue(aiRangeHigh, tempSymbol, { digits: 0 })}`
: null;
const showUpdatedBadge = useTransitionMarker(aiForecast.status, Boolean(aiCityForecast));
return (
<section className="scan-ai-city-section scan-ai-city-ai-read">
<div className="scan-ai-city-section-title">
@@ -143,6 +158,11 @@ export function AiEvidencePanel({
</>
) : aiForecast.status === "ready" && aiCityForecast ? (
<>
{showUpdatedBadge ? (
<span className="scan-ai-updated-badge">
{isEn ? "✓ Updated" : "✓ 已更新"}
</span>
) : null}
<p className="scan-ai-weather-summary">
{aiRuleEvidenceMode ? aiRuleEvidenceText : aiReadCompleteText}
</p>