Compare commits

...

7 Commits

Author SHA1 Message Date
rayd1o
33734b08b8 release: bump version to 0.76.14
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
2026-10-02 02:56:14 +08:00
rayd1o
bbe47c5759 release: bump version to 0.76.13
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
2026-10-02 02:43:55 +08:00
rayd1o
361bf258e4 release: bump version to 0.76.12
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
2026-10-02 02:33:36 +08:00
rayd1o
83c552f9b2 release: bump version to 0.76.11
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
2026-10-02 02:21:20 +08:00
rayd1o
15fed1e121 release: bump version to 0.76.10
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
2026-10-02 02:10:26 +08:00
rayd1o
f4514b588e release: bump version to 0.76.9
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
2026-10-02 01:56:05 +08:00
rayd1o
9523f2bb83 release: bump version to 0.76.8
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
2026-10-02 01:41:46 +08:00
10 changed files with 196 additions and 42 deletions

View File

@@ -1 +1 @@
0.76.7
0.76.14

View File

@@ -8,6 +8,98 @@ This project follows the repository versioning rule:
- `improvement` -> `+0.0.1`(bugfix + 小功能混合)
- `bugfix` -> `+0.0.1`
## [0.76.14] — 2026-10-02
Released: 2026-10-02
### Highlights
- 修复卡片扫光从中途出现的问题,让反射从卡片左边界完整扫到右边界。
### Added / Fixed / Improved
- 卡片反射改用内部 `background-position` 扫描,不再使用卡片外部 transform 起始。
- ticker 上下边线保持静态,横向扫光独立运行。
---
## [0.76.13] — 2026-10-02
Released: 2026-10-02
### Highlights
- 修正 breaking hover 强度基准,hover 会在自身 breaking 高亮上继续增强。
### Added / Fixed / Improved
- ticker hover 从 breaking 原始色相和强度继续增强,不再回落到普通新闻强度。
- breaking 卡片边框 hover 同样相对自身等级增强,保持红/橙/黄颜色连续。
---
## [0.76.12] — 2026-10-02
Released: 2026-10-02
### Highlights
- 修复 breaking ticker 和黄卡 hover 被白色混合漂淡的问题。
### Added / Fixed / Improved
- hover 使用共享强度参数与等级色相,玻璃底复用深色 HUD surface。
- 红色 ticker 保持红色边线,黄色卡片保持黄色 tint,不再被白色边框稀释。
---
## [0.76.11] — 2026-10-02
Released: 2026-10-02
### Highlights
- 修正 breaking 卡片整卡扫光层级,并将 ticker hover 亮度对齐共享 HUD 面板。
### Added / Fixed / Improved
- 卡片扫光覆层位于内容之上,整张卡片 hover/focus 时才扫过。
- ticker 上下边线 hover 使用共享 HUD 边框 hover 强度,breaking 只替换色相。
---
## [0.76.10] — 2026-10-02
Released: 2026-10-02
### Highlights
- 将 breaking 卡片扫光限制为当前 hover/focus 卡片,避免所有卡片同时动态闪烁。
### Added / Fixed / Improved
- 卡片反射层默认关闭,只在当前交互卡片上启动对应等级颜色扫光。
- ticker 的全宽 breaking 扫光保持独立,不影响卡片浏览。
---
## [0.76.9] — 2026-10-02
Released: 2026-10-02
### Highlights
- 修复 breaking 卡片黄色 hover 变淡,并补齐 ticker 上下边线的 hover 高亮反馈。
### Added / Fixed / Improved
- 卡片 hover 使用不透明等级色相和统一混合强度,红/橙/黄表现一致。
- ticker hover 只强化上下两条边,普通新闻使用蓝色,breaking 使用对应等级颜色。
---
## [0.76.8] — 2026-10-02
Released: 2026-10-02
### Highlights
- 修复展开新闻面板时后台 ticker / realtime 更新导致卡片重建和 hover 抖动的问题。
### Added / Fixed / Improved
- 面板浏览期间冻结卡片 DOM,后台更新只写入最新 payload。
- 关闭面板时一次性应用待处理新闻,保持滚动位置和 hover 稳定。
- 展开面板时暂停隐藏 ticker,手动刷新仍可立即更新面板内容。
---
## [0.76.7] — 2026-10-02
Released: 2026-10-02

View File

@@ -444,7 +444,7 @@ The current state machine keeps data, ticker visibility, panel lifecycle and sel
- `getNewsBreakingState()` derives the data state from `items` and `cruise_items`, ignores expired stories, and ranks `critical > breaking > watch`. The expiry timestamp only schedules the next recomputation.
- A ticker viewport watcher runs every 120 ms and chooses the item with the greatest viewport overlap, breaking ties by distance to the viewport center. Only when that current item has a non-`none` level does the ticker switch its horizontal rules to `has-breaking-*`; it returns to neutral as soon as the current item is not breaking.
- The panel lifecycle is `closed → opening → open → closing → closed`. Opening and closing control morphing, visibility and ticker hiding; they do not mutate the breaking data.
- The panel lifecycle is `closed → opening → open → closing → closed`. Opening and closing control morphing, visibility and ticker hiding; they do not mutate the breaking data. During `opening/open`, the current card DOM stays stable while incoming payloads are deferred and applied once on close, so realtime updates cannot rebuild a hovered card.
- `selectedCruiseStoryId` is independent of breaking severity. Selection, focus, hover and close only change the selected story and reading position; close also clears the selection and filter popover.
- The expanded panel always keeps its blue glass surface. Breaking colors remain on the matching story cards, so opening the panel never turns the entire glass panel red; the ticker can still show a red signal when the currently scrolling story is breaking.

View File

@@ -577,7 +577,7 @@ Earth 原有 Roboto Condensed 字体已随 `assets/fonts/` 本地分发,许可
- 数据态由 `getNewsBreakingState()` 从 `items` 与 `cruise_items` 合并计算,只接受未过期项目,并按 `critical > breaking > watch` 排序;到期时间只负责安排下一次重新计算。
- ticker 的显示态由滚动视口 watcher 每 120ms 取视口内重叠最多、距离中心最近的当前项目。只有当前项目的 breaking 等级非 `none` 时,ticker 的上下细线才切换为对应的 `has-breaking-*` 颜色;当前项目离开后立即恢复中性。
- 面板生命周期 `panelPhase` 只有 `closed → opening → open → closing → closed`。打开/关闭只控制形变、可见性和 ticker 的隐藏,不改变 breaking 数据态。
- 面板生命周期 `panelPhase` 只有 `closed → opening → open → closing → closed`。打开/关闭只控制形变、可见性和 ticker 的隐藏,不改变 breaking 数据态;进入 `opening/open` 后保留当前卡片 DOM,后台 payload 先暂存,关闭时再一次性应用,避免 hover 卡片被实时更新重建。
- `selectedCruiseStoryId` 独立于 breaking 等级。选择、聚焦、悬停和关闭只更新选中项与阅读位置;关闭时清空选中项及筛选弹层。
- 展开面板始终使用蓝色玻璃底。breaking 只在对应新闻卡片上使用等级颜色,因此点开面板不会把整块玻璃染成红色,之后滚动到 breaking 新闻时仍可让 ticker 显示红色提示。

View File

@@ -16,12 +16,19 @@
## Current Version
- `main` 当前主线历史推导到:`0.16.5`
- `dev` 当前开发分支历史推导到:`0.76.7`
- `dev` 当前开发分支历史推导到:`0.76.14`
## Timeline
| Version | Type | Branch | Commit | Summary |
| --- | --- | --- | --- | --- |
| `0.76.14` | bugfix | `dev` | `v0.76.14` | 修复卡片扫光起点,改为从卡片左边界到右边界的内部背景扫描 |
| `0.76.13` | bugfix | `dev` | `v0.76.13` | breaking hover 以自身高亮为基准继续增强,修复红 ticker/卡片变暗 |
| `0.76.12` | bugfix | `dev` | `v0.76.12` | 修复 breaking ticker 与黄卡 hover 被白色混合漂淡,保留等级色相 |
| `0.76.11` | bugfix | `dev` | `v0.76.11` | breaking 卡片整卡扫光及 ticker hover 强度对齐共享 HUD |
| `0.76.10` | bugfix | `dev` | `v0.76.10` | breaking 卡片扫光仅在 hover/focus 时启动,避免全列表同时闪烁 |
| `0.76.9` | bugfix | `dev` | `v0.76.9` | 统一 breaking 卡片 hover 强度并增加 ticker 上下边线 hover 高亮 |
| `0.76.8` | bugfix | `dev` | `v0.76.8` | 展开新闻面板时冻结卡片 DOM,隔离 ticker/realtime 更新并修复 hover 抖动 |
| `0.76.7` | bugfix | `dev` | `v0.76.7` | 统一 breaking 卡片 hover 行为并通过卡片等级变量注入颜色 tint |
| `0.76.6` | bugfix | `dev` | `v0.76.6` | breaking ticker 全域扫光闪烁,新闻列表首卡增加顶部呼吸空间 |
| `0.76.5` | bugfix | `dev` | `v0.76.5` | breaking 玻璃反射式警灯脉冲、等级颜色统一及新闻卡片内容高度修复 |

View File

@@ -1,6 +1,6 @@
{
"name": "planet-frontend",
"version": "0.76.7",
"version": "0.76.14",
"private": true,
"packageManager": "bun@1",
"dependencies": {

View File

@@ -4,7 +4,10 @@
--news-ticker-width: min(calc(820px * var(--hud-scale)), 58vw);
--news-ticker-edge-fade: calc(96px * var(--hud-scale));
--news-ticker-rule-color: var(--hud-border);
--news-ticker-line-color: var(--news-ticker-rule-color);
--news-ticker-hover-rule-color: var(--hud-border-hover);
--news-breaking-color: rgba(255, 166, 102, 0.46);
--news-breaking-hue: rgb(255 166 102);
position: absolute;
top: calc(var(--hud-offset) + calc(2px * var(--hud-scale)));
left: 50%;
@@ -42,15 +45,16 @@
z-index: -1;
pointer-events: none;
background:
linear-gradient(90deg, transparent, var(--news-ticker-rule-color) var(--news-ticker-edge-fade), var(--news-ticker-rule-color) calc(100% - var(--news-ticker-edge-fade)), transparent) top / 100% 1px no-repeat,
linear-gradient(90deg, transparent, var(--news-ticker-rule-color) var(--news-ticker-edge-fade), var(--news-ticker-rule-color) calc(100% - var(--news-ticker-edge-fade)), transparent) bottom / 100% 1px no-repeat,
linear-gradient(90deg, transparent, var(--news-ticker-line-color) var(--news-ticker-edge-fade), var(--news-ticker-line-color) calc(100% - var(--news-ticker-edge-fade)), transparent) top / 100% 1px no-repeat,
linear-gradient(90deg, transparent, var(--news-ticker-line-color) var(--news-ticker-edge-fade), var(--news-ticker-line-color) calc(100% - var(--news-ticker-edge-fade)), transparent) bottom / 100% 1px no-repeat,
linear-gradient(90deg, rgba(17, 31, 53, 0), rgba(17, 31, 53, 0.84) var(--news-ticker-edge-fade), rgba(7, 17, 31, 0.84) calc(100% - var(--news-ticker-edge-fade)), rgba(7, 17, 31, 0));
}
.earth-news-ticker::after {
content: "";
position: absolute;
inset: calc(-8px * var(--hud-scale)) calc(-14px * var(--hud-scale));
inset: 0 calc(-14px * var(--hud-scale));
clip-path: inset(calc(3px * var(--hud-scale)) 0);
z-index: 0;
pointer-events: none;
opacity: 0;
@@ -64,7 +68,7 @@
transparent 66%,
transparent 100%
) 140% 0 / 220% 100% no-repeat;
filter: blur(calc(8px * var(--hud-scale)));
filter: blur(calc(4px * var(--hud-scale)));
mix-blend-mode: screen;
}
@@ -76,24 +80,42 @@
.earth-news-ticker.has-breaking-watch::after,
.earth-news-ticker.has-breaking-breaking::after,
.earth-news-ticker.has-breaking-critical::after {
animation: earthNewsBreakingGlassPulse 2.4s ease-in-out infinite;
animation: earthNewsBreakingGlassSweep 2.4s linear infinite;
}
.earth-news-ticker.has-breaking-watch {
--news-breaking-color: rgba(255, 213, 128, 0.42);
--news-breaking-hue: rgb(255 213 128);
--news-ticker-rule-color: var(--news-breaking-color);
}
.earth-news-ticker.has-breaking-breaking {
--news-breaking-color: rgba(255, 151, 78, 0.5);
--news-breaking-hue: rgb(255 151 78);
--news-ticker-rule-color: var(--news-breaking-color);
}
.earth-news-ticker.has-breaking-critical {
--news-breaking-color: rgba(255, 95, 95, 0.56);
--news-breaking-hue: rgb(255 95 95);
--news-ticker-rule-color: var(--news-breaking-color);
}
.earth-news-ticker.has-breaking-watch,
.earth-news-ticker.has-breaking-breaking,
.earth-news-ticker.has-breaking-critical {
--news-ticker-hover-rule-color: color-mix(
in srgb,
var(--news-breaking-hue) 68%,
transparent
);
}
.earth-news-ticker:hover,
.earth-news-ticker:focus-visible {
--news-ticker-line-color: var(--news-ticker-hover-rule-color);
}
.earth-news-ticker__region {
position: relative;
z-index: 3;
@@ -265,18 +287,13 @@
}
}
@keyframes earthNewsBreakingGlassPulse {
0%,
100% {
opacity: 0;
@keyframes earthNewsBreakingGlassSweep {
from {
opacity: 0.68;
background-position: 140% 0;
}
18%,
74% {
opacity: 0.76;
}
50% {
opacity: 0.9;
to {
opacity: 0.68;
background-position: -40% 0;
}
}
@@ -284,15 +301,15 @@
@keyframes earthNewsBreakingCardReflection {
0% {
opacity: 0;
transform: translateX(-125%);
background-position: -100% 0;
}
16%,
64% {
10%,
88% {
opacity: 0.58;
}
100% {
opacity: 0;
transform: translateX(125%);
background-position: 100% 0;
}
}
@@ -537,7 +554,8 @@
}
.news-story-card {
--news-hover-tint: rgba(214, 235, 255, 0.16);
--news-hover-color: rgb(214 235 255);
--news-hover-border-strength: 24%;
position: relative;
overflow: hidden;
flex: 0 0 auto;
@@ -567,12 +585,12 @@
}
.news-story-card:hover {
border-color: color-mix(in srgb, var(--news-hover-tint) 76%, rgba(214, 235, 255, 0.16));
border-color: color-mix(in srgb, var(--news-hover-color) var(--news-hover-border-strength), transparent);
background:
linear-gradient(
180deg,
color-mix(in srgb, var(--news-hover-tint) 30%, rgba(255, 255, 255, 0.05)),
color-mix(in srgb, var(--news-hover-tint) 24%, rgba(92, 151, 218, 0.03))
color-mix(in srgb, var(--news-hover-color) 16%, var(--hud-surface-top)),
color-mix(in srgb, var(--news-hover-color) 12%, var(--hud-surface-bottom))
);
transform: translateY(-1px);
}
@@ -591,9 +609,15 @@
0 0 18px rgba(255, 184, 77, 0.12);
}
.news-story-card--breaking-watch,
.news-story-card--breaking-breaking,
.news-story-card--breaking-critical {
--news-hover-border-strength: 68%;
}
.news-story-card--breaking-watch {
--news-breaking-color: rgba(255, 213, 128, 0.42);
--news-hover-tint: var(--news-breaking-color);
--news-hover-color: rgb(255 213 128);
border-color: var(--news-breaking-color);
background:
linear-gradient(180deg, rgba(255, 248, 220, 0.08), rgba(255, 184, 77, 0.05));
@@ -601,7 +625,7 @@
.news-story-card--breaking-breaking {
--news-breaking-color: rgba(255, 151, 78, 0.5);
--news-hover-tint: var(--news-breaking-color);
--news-hover-color: rgb(255 151 78);
border-color: var(--news-breaking-color);
background:
linear-gradient(180deg, rgba(255, 205, 150, 0.1), rgba(255, 135, 80, 0.07));
@@ -609,7 +633,7 @@
.news-story-card--breaking-critical {
--news-breaking-color: rgba(255, 95, 95, 0.56);
--news-hover-tint: var(--news-breaking-color);
--news-hover-color: rgb(255 95 95);
border-color: var(--news-breaking-color);
background:
linear-gradient(180deg, rgba(255, 155, 155, 0.11), rgba(205, 58, 58, 0.08));
@@ -621,15 +645,25 @@
content: "";
position: absolute;
inset: 0;
z-index: 0;
z-index: 2;
pointer-events: none;
background: linear-gradient(
105deg,
transparent 32%,
var(--news-breaking-color) 49%,
transparent 66%
);
) -100% 0 / 200% 100% no-repeat;
mix-blend-mode: screen;
opacity: 0;
animation: none;
}
.news-story-card--breaking-watch:hover::after,
.news-story-card--breaking-watch:focus-visible::after,
.news-story-card--breaking-breaking:hover::after,
.news-story-card--breaking-breaking:focus-visible::after,
.news-story-card--breaking-critical:hover::after,
.news-story-card--breaking-critical:focus-visible::after {
animation: earthNewsBreakingCardReflection 3.2s ease-in-out infinite;
}
@@ -721,9 +755,12 @@
.earth-news-ticker.has-breaking-watch::after,
.earth-news-ticker.has-breaking-breaking::after,
.earth-news-ticker.has-breaking-critical::after,
.news-story-card--breaking-watch::after,
.news-story-card--breaking-breaking::after,
.news-story-card--breaking-critical::after {
.news-story-card--breaking-watch:hover::after,
.news-story-card--breaking-watch:focus-visible::after,
.news-story-card--breaking-breaking:hover::after,
.news-story-card--breaking-breaking:focus-visible::after,
.news-story-card--breaking-critical:hover::after,
.news-story-card--breaking-critical:focus-visible::after {
animation: none;
opacity: 0.2;
}

View File

@@ -61,6 +61,7 @@ let selectedCruiseStoryId = null;
let morphTimer = null;
let morphFrame = null;
let panelPhase = "closed";
let deferredPanelPayload = null;
let breakingExpiryTimer = null;
let tickerBreakingTimer = null;
let newsRealtimeSocket = null;
@@ -215,6 +216,8 @@ function finishHudOpen(hud) {
}
function finishHudClose(hud, ticker, restoreRect = null) {
const pendingPayload = deferredPanelPayload;
deferredPanelPayload = null;
panelPhase = "closed";
hud.classList.add("hud-panel-hidden");
hud.classList.remove("is-morphing");
@@ -230,6 +233,14 @@ function finishHudClose(hud, ticker, restoreRect = null) {
hud.style.transform = "";
}
ticker?.classList.remove("is-hidden");
setTickerPlayback(true);
if (pendingPayload) renderPayload(pendingPayload, { forcePanelRender: true });
}
function setTickerPlayback(running) {
const { tickerTrack } = getElements();
if (!(tickerTrack instanceof HTMLElement)) return;
tickerTrack.style.animationPlayState = running ? "" : "paused";
}
function setNewsHudOpen(open, { highlightId = null } = {}) {
@@ -258,6 +269,7 @@ function setNewsHudOpen(open, { highlightId = null } = {}) {
hud.classList.add("is-morphing");
setHudRectWithoutTransition(hud, tickerRect, 1);
ticker?.classList.add("is-hidden");
setTickerPlayback(false);
morphFrame = requestAnimationFrame(() => {
morphFrame = null;
setHudRect(hud, targetRect, 1);
@@ -814,7 +826,7 @@ function renderEmptyState(message) {
renderTicker({ items: [], focus: payload?.focus || { region: "global" } });
}
function renderPayload(nextPayload) {
function renderPayload(nextPayload, { forcePanelRender = false } = {}) {
payload = nextPayload;
reconcileNewsSourceFilters(nextPayload);
const {
@@ -847,6 +859,12 @@ function renderPayload(nextPayload) {
syncFilterSummaries(nextPayload);
applyNewsBreakingShellState(nextPayload);
if (!forcePanelRender && (panelPhase === "opening" || panelPhase === "open")) {
deferredPanelPayload = nextPayload;
return;
}
if (forcePanelRender) deferredPanelPayload = null;
if (!board || !status || !focusLabel || !focusCoords || !sourceCount || !regionChip) {
if (document.body.classList.contains("layout-mode-mobile")) {
// Mobile page omits the region chip shell, but the rest of the page is still renderable.
@@ -1103,7 +1121,7 @@ async function fetchNews(lat, lon, context = {}) {
return response.json();
}
async function refreshNews(lat, lon, { silent = false } = {}) {
async function refreshNews(lat, lon, { silent = false, forcePanelRender = false } = {}) {
const targetRegion = inferRegion(lat, lon);
const categorySignature = getNewsCategorySignature();
const sourceSignature = getNewsSourceSignatureForFetch(lat, lon);
@@ -1129,7 +1147,7 @@ async function refreshNews(lat, lon, { silent = false } = {}) {
if (requestToken !== activeRefreshToken) {
return nextPayload;
}
renderPayload(nextPayload);
renderPayload(nextPayload, { forcePanelRender });
lastFetchAt = Date.now();
lastCategorySignature = getNewsCategorySignature();
lastSourceSignature = getNewsSourceSignature(nextPayload);
@@ -1369,7 +1387,7 @@ export function initNewsPanel() {
const refreshBtn = document.getElementById(id);
refreshBtn?.addEventListener("click", async () => {
try {
await refreshNews(lastFocus?.lat, lastFocus?.lon);
await refreshNews(lastFocus?.lat, lastFocus?.lon, { forcePanelRender: true });
showStatusMessage(earthMessage("status.newsRefresh", { ok: true }), "info");
} catch {
showStatusMessage(earthMessage("status.newsRefresh", { ok: false }), "error");

View File

@@ -1,6 +1,6 @@
[project]
name = "planet"
version = "0.76.7"
version = "0.76.14"
description = "智能星球计划 - 态势感知系统"
requires-python = ">=3.14"
dependencies = [

2
uv.lock generated
View File

@@ -757,7 +757,7 @@ wheels = [
[[package]]
name = "planet"
version = "0.76.7"
version = "0.76.14"
source = { virtual = "." }
dependencies = [
{ name = "aiofiles" },