Tighten homepage weather summaries
This commit is contained in:
@@ -1222,10 +1222,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.root :global(.home-deb-card) {
|
.root :global(.home-deb-card) {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: minmax(0, 1fr) 120px;
|
align-items: baseline;
|
||||||
gap: 12px;
|
justify-content: space-between;
|
||||||
align-items: center;
|
gap: 10px;
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1259,13 +1259,11 @@
|
|||||||
font-weight: 850;
|
font-weight: 850;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(.home-deb-card svg),
|
|
||||||
.root :global(.home-market-metrics svg) {
|
.root :global(.home-market-metrics svg) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(.home-deb-card polyline),
|
|
||||||
.root :global(.home-market-metrics polyline) {
|
.root :global(.home-market-metrics polyline) {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke: #18e6d4;
|
stroke: #18e6d4;
|
||||||
@@ -1274,10 +1272,6 @@
|
|||||||
stroke-width: 2.2;
|
stroke-width: 2.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(.home-deb-card circle) {
|
|
||||||
fill: #18e6d4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.root :global(.home-card-section) {
|
.root :global(.home-card-section) {
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
}
|
}
|
||||||
@@ -1293,7 +1287,7 @@
|
|||||||
|
|
||||||
.root :global(.home-intraday-chart) {
|
.root :global(.home-intraday-chart) {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 10px 0 22px;
|
padding: 10px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(.home-intraday-chart svg) {
|
.root :global(.home-intraday-chart svg) {
|
||||||
@@ -1320,21 +1314,6 @@
|
|||||||
stroke-width: 1.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) {
|
.root :global(.home-intraday-meta) {
|
||||||
color: rgba(148, 163, 184, 0.9);
|
color: rgba(148, 163, 184, 0.9);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@@ -1345,6 +1324,41 @@
|
|||||||
-webkit-line-clamp: 3;
|
-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) {
|
.root :global(.home-model-stack) {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -222,7 +222,12 @@ type HomeTrendChart = {
|
|||||||
forecastPath: string;
|
forecastPath: string;
|
||||||
legendText: string;
|
legendText: string;
|
||||||
observationDots: Array<{ cx: number; cy: number; key: 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(
|
function projectHomeTrendPoint(
|
||||||
@@ -314,34 +319,56 @@ function buildHomeTrendChart(
|
|||||||
key: `${point.labelTime}-${index}`,
|
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 {
|
return {
|
||||||
forecastPath,
|
forecastPath,
|
||||||
legendText: chartData.legendText,
|
legendText: chartData.legendText,
|
||||||
observationDots,
|
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) {
|
function readNumericField(source: unknown, key: string) {
|
||||||
if (!source || typeof source !== "object") return undefined;
|
if (!source || typeof source !== "object") return undefined;
|
||||||
const value = (source as Record<string, unknown>)[key];
|
const value = (source as Record<string, unknown>)[key];
|
||||||
@@ -512,6 +539,7 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) {
|
|||||||
: locale === "en-US"
|
: locale === "en-US"
|
||||||
? "Pro locked"
|
? "Pro locked"
|
||||||
: "PRO 锁定";
|
: "PRO 锁定";
|
||||||
|
const forecastDays = buildHomeForecastDays(detail, locale);
|
||||||
const keySignals = [
|
const keySignals = [
|
||||||
{
|
{
|
||||||
active: Number(marketEdge) > 0,
|
active: Number(marketEdge) > 0,
|
||||||
@@ -624,10 +652,6 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) {
|
|||||||
<strong>{formatTemperature(debPrediction, symbol)}</strong>
|
<strong>{formatTemperature(debPrediction, symbol)}</strong>
|
||||||
<em>{formatDelta(debPrediction, currentTemp, symbol)}</em>
|
<em>{formatDelta(debPrediction, currentTemp, symbol)}</em>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
{trendChart ? (
|
{trendChart ? (
|
||||||
@@ -648,13 +672,6 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) {
|
|||||||
<circle key={point.key} cx={point.cx} cy={point.cy} r="3.2" />
|
<circle key={point.key} cx={point.cx} cy={point.cy} r="3.2" />
|
||||||
))}
|
))}
|
||||||
</svg>
|
</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>
|
||||||
<div className="home-intraday-meta">
|
<div className="home-intraday-meta">
|
||||||
{trendChart.legendText ||
|
{trendChart.legendText ||
|
||||||
@@ -665,6 +682,20 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : 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">
|
<div className="home-card-section">
|
||||||
<h3>{locale === "en-US" ? "Model stack" : "模型栈"}</h3>
|
<h3>{locale === "en-US" ? "Model stack" : "模型栈"}</h3>
|
||||||
<div className="home-model-stack">
|
<div className="home-model-stack">
|
||||||
|
|||||||
Reference in New Issue
Block a user