diff --git a/frontend/components/dashboard/Dashboard.module.css b/frontend/components/dashboard/Dashboard.module.css index bdbc9825..e3558c5f 100644 --- a/frontend/components/dashboard/Dashboard.module.css +++ b/frontend/components/dashboard/Dashboard.module.css @@ -1245,9 +1245,30 @@ } .root :global(.forecast-inline-note) { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 10px; + padding: 8px 10px; + border: 1px solid rgba(34, 211, 238, 0.18); + border-radius: 8px; + color: var(--text-secondary); + background: rgba(34, 211, 238, 0.06); + font-size: 12px; line-height: 1.45; } +.root :global(.forecast-inline-note::before) { + content: ""; + width: 8px; + height: 8px; + flex: 0 0 auto; + border-radius: 999px; + background: #22d3ee; + box-shadow: 0 0 0 4px rgba(34, 211, 238, 0.12); + animation: pulseGlow 1.25s ease-in-out infinite; +} + .root :global(.forecast-day) { background: rgba(255, 255, 255, 0.03); border: 1px solid var(--border-subtle); @@ -1287,6 +1308,28 @@ color: var(--accent-cyan); } +.root :global(.forecast-day-sync) { + cursor: wait; + opacity: 0.76; + background: + linear-gradient(90deg, rgba(15, 23, 42, 0.86), rgba(30, 41, 59, 0.72), rgba(15, 23, 42, 0.86)); + background-size: 220% 100%; + animation: panelSkeletonSweep 1.35s ease-in-out infinite; +} + +.root :global(.forecast-day-sync .f-temp) { + color: var(--text-muted); +} + +@keyframes panelSkeletonSweep { + 0% { + background-position: 120% 0; + } + 100% { + background-position: -120% 0; + } +} + @media (max-width: 720px) { .root :global(.forecast-table) { display: flex; diff --git a/frontend/components/dashboard/DetailPanel.tsx b/frontend/components/dashboard/DetailPanel.tsx index b39296b1..9c35b20f 100644 --- a/frontend/components/dashboard/DetailPanel.tsx +++ b/frontend/components/dashboard/DetailPanel.tsx @@ -190,6 +190,12 @@ export function DetailPanel() { const heroSettlementLabel = detail?.current?.settlement_source_label || basicSettlementLabel; const heroAirportLabel = detail?.risk?.airport || basicAirportLabel; + const isDetailCompleting = Boolean( + detail && + (detail.detail_depth !== "full" || + (detail.forecast?.daily?.length ?? 0) <= 1), + ); + const isPanelSyncing = store.loadingState.cityDetail || isDetailCompleting; const blurActiveElement = () => { if (typeof document === "undefined") return; @@ -309,13 +315,17 @@ export function DetailPanel() {