Tighten homepage weather summaries
This commit is contained in:
@@ -1222,10 +1222,10 @@
|
||||
}
|
||||
|
||||
.root :global(.home-deb-card) {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 120px;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
@@ -1259,13 +1259,11 @@
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.root :global(.home-deb-card svg),
|
||||
.root :global(.home-market-metrics svg) {
|
||||
width: 100%;
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
.root :global(.home-deb-card polyline),
|
||||
.root :global(.home-market-metrics polyline) {
|
||||
fill: none;
|
||||
stroke: #18e6d4;
|
||||
@@ -1274,10 +1272,6 @@
|
||||
stroke-width: 2.2;
|
||||
}
|
||||
|
||||
.root :global(.home-deb-card circle) {
|
||||
fill: #18e6d4;
|
||||
}
|
||||
|
||||
.root :global(.home-card-section) {
|
||||
padding: 12px 14px;
|
||||
}
|
||||
@@ -1293,7 +1287,7 @@
|
||||
|
||||
.root :global(.home-intraday-chart) {
|
||||
position: relative;
|
||||
padding: 10px 0 22px;
|
||||
padding: 10px 0 0;
|
||||
}
|
||||
|
||||
.root :global(.home-intraday-chart svg) {
|
||||
@@ -1320,21 +1314,6 @@
|
||||
stroke-width: 1.6;
|
||||
}
|
||||
|
||||
.root :global(.home-intraday-axis) {
|
||||
position: absolute;
|
||||
inset: auto 0 0;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.root :global(.home-intraday-axis span) {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
transform: translateX(-50%);
|
||||
color: rgba(148, 163, 184, 0.82);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.root :global(.home-intraday-meta) {
|
||||
color: rgba(148, 163, 184, 0.9);
|
||||
font-size: 11px;
|
||||
@@ -1345,6 +1324,41 @@
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
|
||||
.root :global(.home-card-section.forecast) {
|
||||
border-color: rgba(99, 102, 241, 0.24);
|
||||
}
|
||||
|
||||
.root :global(.home-forecast-grid) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.root :global(.home-forecast-item) {
|
||||
min-width: 0;
|
||||
padding: 10px 8px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.14);
|
||||
border-radius: 12px;
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.root :global(.home-forecast-item span) {
|
||||
display: block;
|
||||
color: rgba(148, 163, 184, 0.84);
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.root :global(.home-forecast-item strong) {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
color: #f8fafc;
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.root :global(.home-model-stack) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -222,7 +222,12 @@ type HomeTrendChart = {
|
||||
forecastPath: string;
|
||||
legendText: string;
|
||||
observationDots: Array<{ cx: number; cy: number; key: string }>;
|
||||
tickLabels: Array<{ key: string; label: string; x: number }>;
|
||||
};
|
||||
|
||||
type HomeForecastDay = {
|
||||
key: string;
|
||||
label: string;
|
||||
maxTemp: number;
|
||||
};
|
||||
|
||||
function projectHomeTrendPoint(
|
||||
@@ -314,34 +319,56 @@ function buildHomeTrendChart(
|
||||
key: `${point.labelTime}-${index}`,
|
||||
};
|
||||
});
|
||||
const tickLabels = chartData.tickLabels
|
||||
.map((label, index) => {
|
||||
if (!label) return null;
|
||||
const minutes = Number.parseInt(String(chartData.times[index] || "0").split(":")[0] || "0", 10) * 60;
|
||||
const projected = projectHomeTrendPoint(
|
||||
minutes,
|
||||
chartData.min,
|
||||
chartData.xMin,
|
||||
chartData.xMax,
|
||||
chartData.min,
|
||||
chartData.max,
|
||||
);
|
||||
return {
|
||||
key: `${label}-${index}`,
|
||||
label,
|
||||
x: projected.cx,
|
||||
};
|
||||
})
|
||||
.filter((item): item is { key: string; label: string; x: number } => item != null);
|
||||
|
||||
return {
|
||||
forecastPath,
|
||||
legendText: chartData.legendText,
|
||||
observationDots,
|
||||
tickLabels,
|
||||
};
|
||||
}
|
||||
|
||||
function buildHomeForecastDays(
|
||||
detail?: CityDetail | null,
|
||||
locale = "zh-CN",
|
||||
): HomeForecastDay[] {
|
||||
if (!detail) return [];
|
||||
const todayLabel = locale === "en-US" ? "Today" : "今天";
|
||||
const tomorrowLabel = locale === "en-US" ? "Tomorrow" : "明天";
|
||||
const formatter = new Intl.DateTimeFormat(locale === "en-US" ? "en-US" : "zh-CN", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
});
|
||||
const rows = Array.isArray(detail.forecast?.daily) ? detail.forecast.daily : [];
|
||||
const byDate = new Map<string, number>();
|
||||
|
||||
if (detail.local_date && Number.isFinite(Number(detail.forecast?.today_high))) {
|
||||
byDate.set(detail.local_date, Number(detail.forecast?.today_high));
|
||||
}
|
||||
rows.forEach((row) => {
|
||||
if (!row?.date || !Number.isFinite(Number(row.max_temp))) return;
|
||||
if (!byDate.has(row.date)) {
|
||||
byDate.set(row.date, Number(row.max_temp));
|
||||
}
|
||||
});
|
||||
|
||||
return [...byDate.entries()].slice(0, 4).map(([date, maxTemp], index) => {
|
||||
const parsed = new Date(`${date}T00:00:00`);
|
||||
const label =
|
||||
index === 0
|
||||
? todayLabel
|
||||
: index === 1
|
||||
? tomorrowLabel
|
||||
: Number.isNaN(parsed.getTime())
|
||||
? date
|
||||
: formatter.format(parsed);
|
||||
return {
|
||||
key: date,
|
||||
label,
|
||||
maxTemp,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function readNumericField(source: unknown, key: string) {
|
||||
if (!source || typeof source !== "object") return undefined;
|
||||
const value = (source as Record<string, unknown>)[key];
|
||||
@@ -512,6 +539,7 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) {
|
||||
: locale === "en-US"
|
||||
? "Pro locked"
|
||||
: "PRO 锁定";
|
||||
const forecastDays = buildHomeForecastDays(detail, locale);
|
||||
const keySignals = [
|
||||
{
|
||||
active: Number(marketEdge) > 0,
|
||||
@@ -624,10 +652,6 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) {
|
||||
<strong>{formatTemperature(debPrediction, symbol)}</strong>
|
||||
<em>{formatDelta(debPrediction, currentTemp, symbol)}</em>
|
||||
</div>
|
||||
<svg viewBox="0 0 110 34" aria-hidden="true">
|
||||
<polyline points="4,26 22,22 38,14 55,20 72,16 88,8 106,10" />
|
||||
<circle cx="88" cy="8" r="2.5" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{trendChart ? (
|
||||
@@ -648,13 +672,6 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) {
|
||||
<circle key={point.key} cx={point.cx} cy={point.cy} r="3.2" />
|
||||
))}
|
||||
</svg>
|
||||
<div className="home-intraday-axis">
|
||||
{trendChart.tickLabels.map((tick) => (
|
||||
<span key={tick.key} style={{ left: `${tick.x}px` }}>
|
||||
{tick.label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="home-intraday-meta">
|
||||
{trendChart.legendText ||
|
||||
@@ -665,6 +682,20 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{forecastDays.length ? (
|
||||
<div className="home-card-section forecast">
|
||||
<h3>{locale === "en-US" ? "Multi-day forecast" : "多日预报"}</h3>
|
||||
<div className="home-forecast-grid">
|
||||
{forecastDays.map((day) => (
|
||||
<div key={day.key} className="home-forecast-item">
|
||||
<span>{day.label}</span>
|
||||
<strong>{formatTemperature(day.maxTemp, symbol)}</strong>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="home-card-section">
|
||||
<h3>{locale === "en-US" ? "Model stack" : "模型栈"}</h3>
|
||||
<div className="home-model-stack">
|
||||
|
||||
Reference in New Issue
Block a user