diff --git a/VERSION b/VERSION index 894542aa..1b58cc10 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.26.2 +0.27.0 diff --git a/backend/app/services/tv_streams.py b/backend/app/services/tv_streams.py index d5fcd7c5..7e28b3e1 100644 --- a/backend/app/services/tv_streams.py +++ b/backend/app/services/tv_streams.py @@ -21,7 +21,7 @@ DEFAULT_TV_SETTINGS = { "auto_fallback": True, "sources": [ { - "id": DEFAULT_TV_SOURCE_ID, + "id": "cctv4", "name": "CCTV-4 中文国际", "provider": "CCTV", "region": "China", diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 122f5ec9..180543e6 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,31 @@ This project follows the repository versioning rule: - `feature` -> `+0.1.0` - `bugfix` -> `+0.0.1` +## 0.27.0 + +Released: 2026-04-14 + +### Highlights + +- 全面重构 Earth HUD 布局:品牌面板、图层控制面板、信息详情卡片各自独立,支持拖拽与折叠,信息卡片改为跟随点击位置悬浮显示。 +- 新增图层控制面板(Layer Panel),海缆、卫星、地形、BGP 等图层集中管理,支持关键字搜索过滤。 +- Earth 大气层渲染升级,引入 Fresnel 着色器双层辉光效果和深度遮挡球体。 + +### ✨ Features + +- 新增独立 Layer Panel(`js/controls.js`, `css/layer-panel.css`),图层开关、搜索过滤、折叠收起,取代原工具栏弹出菜单 +- 信息详情面板(info-panel)改为点击时定位到鼠标附近(`js/info-card.js`),悬停改为轻量 tooltip,降低视觉干扰 +- Earth 材质重构(`js/earth.js`, `js/constants.js`):新增 `EARTH_MATERIAL_CONFIG`,Fresnel 内外大气层辉光、深度遮挡球体,纹理加载独立为 `loadEarthTexture()` + +### 🔧 Improvements + +- Earth Stats 面板改为 2 列 KPI 网格布局,支持拖拽和关闭(`css/earth-stats.css`) +- 数据加载改为分步串行(登陆点 → 海缆 → 卫星 → BGP → 纹理),每步之间 yield 帧,改善视觉渐现体验(`js/main.js`) +- 图层按钮状态更新逻辑统一至 `updateLayerButtonState()`,消除重复实现 +- 海缆状态识别新增 `active` 枚举值(兼容旧 `In Service`) + +--- + ## 0.26.1 Released: 2026-04-12 diff --git a/docs/version-history.md b/docs/version-history.md index 566fafe2..e1cf4469 100644 --- a/docs/version-history.md +++ b/docs/version-history.md @@ -16,12 +16,13 @@ ## Current Version - `main` 当前主线历史推导到:`0.16.5` -- `dev` 当前开发分支历史推导到:`0.26.2` +- `dev` 当前开发分支历史推导到:`0.27.0` ## Timeline | Version | Type | Branch | Commit | Summary | | --- | --- | --- | --- | --- | +| `0.27.0` | feature | `dev` | — | Earth HUD 重构:图层面板、信息卡片悬浮定位、Fresnel 大气层渲染 | | `0.0.1-beta` | bootstrap | `main` | `e7033775` | first commit | | `0.1.0` | feature | `main` | `6cb4398f` | Modularize 3D Earth page with ES Modules | | `0.2.0` | feature | `main` | `aaae6a53` | Add cable graph service and data collectors | diff --git a/frontend/package.json b/frontend/package.json index 3d6e099c..9b4570d2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "planet-frontend", - "version": "0.26.2", + "version": "0.27.0", "private": true, "packageManager": "bun@1", "dependencies": { diff --git a/frontend/public/earth/css/earth-stats.css b/frontend/public/earth/css/earth-stats.css index 60f4e2c8..6affa4c0 100644 --- a/frontend/public/earth/css/earth-stats.css +++ b/frontend/public/earth/css/earth-stats.css @@ -1,26 +1,133 @@ -/* earth-stats */ +/* earth-stats.css — compact KPI grid panel */ .hud-panel-stats { - bottom: var(--hud-offset); + top: var(--hud-offset); right: var(--hud-offset); - border-radius: var(--hud-radius); - padding: var(--hud-panel-padding); - width: calc(264px * var(--hud-scale)); + border-radius: 0; /* square / angular — matches layer panel */ + padding: 0; + width: min(calc(240px * var(--hud-scale)), calc(100vw - 32px)); z-index: 10; - font-size: var(--hud-font-size); + overflow: hidden; } -.hud-panel-stats .stats-item { - margin-bottom: 0; - padding: calc(9px * var(--hud-scale)) 0; - border-bottom: 1px solid rgba(214, 229, 245, 0.06); +/* ── Thin drag bar ────────────────────────────────────────────── */ + +.stats-drag-bar { + display: flex; + align-items: center; + justify-content: space-between; + padding: calc(8px * var(--hud-scale)) calc(12px * var(--hud-scale)); + border-bottom: 1px solid var(--hud-line); + cursor: grab; + user-select: none; } -.hud-panel-stats .stats-item:last-child { +.stats-drag-bar:active { + cursor: grabbing; +} + +.stats-kicker { + color: var(--hud-text-soft); + font-size: calc(0.64rem * var(--hud-scale)); + letter-spacing: 0.16em; + text-transform: uppercase; +} + +/* Reuse hud-panel-close — just override size to match kicker line */ +.stats-drag-bar .hud-panel-close { + width: calc(20px * var(--hud-scale)); + height: calc(20px * var(--hud-scale)); + min-width: calc(20px * var(--hud-scale)); +} + +.stats-drag-bar .hud-panel-close .material-symbols-rounded { + font-size: calc(12px * var(--hud-scale)); +} + +/* ── 2-column KPI grid ────────────────────────────────────────── */ + +.stats-grid { + display: grid; + grid-template-columns: 1fr 1fr; +} + +.stat-cell { + display: flex; + flex-direction: column; + gap: calc(2px * var(--hud-scale)); + padding: calc(10px * var(--hud-scale)) calc(12px * var(--hud-scale)); + border-right: 1px solid var(--hud-line); + border-bottom: 1px solid var(--hud-line); +} + +/* Right column cells: remove right border */ +.stat-cell:nth-child(even) { + border-right: none; +} + +/* Bottom row cells: remove bottom border */ +.stat-cell:nth-last-child(-n+2) { border-bottom: none; - padding-bottom: 0; } -.hud-panel-stats .stats-item:first-of-type { - padding-top: 0; +.stat-num { + color: var(--hud-title); + font-size: calc(1.3rem * var(--hud-scale)); + font-weight: 700; + letter-spacing: -0.01em; + line-height: 1.1; + font-variant-numeric: tabular-nums; +} + +/* Smaller number for multi-word values like "256/314" */ +.stat-num--sm { + font-size: calc(0.94rem * var(--hud-scale)); + font-weight: 600; + letter-spacing: 0; +} + +.stat-label { + color: var(--hud-text-soft); + font-size: calc(0.62rem * var(--hud-scale)); + letter-spacing: 0.1em; + text-transform: uppercase; + line-height: 1.2; +} + +/* ── BGP status footer ────────────────────────────────────────── */ + +.stats-footer { + display: flex; + align-items: center; + gap: calc(6px * var(--hud-scale)); + padding: calc(7px * var(--hud-scale)) calc(12px * var(--hud-scale)); + border-top: 1px solid var(--hud-line); +} + +.stats-footer-dot { + flex-shrink: 0; + width: calc(5px * var(--hud-scale)); + height: calc(5px * var(--hud-scale)); + border-radius: 50%; + background: var(--hud-accent); + box-shadow: 0 0 5px var(--hud-accent); +} + +.stats-footer-text { + color: var(--hud-text-muted); + font-size: calc(0.7rem * var(--hud-scale)); + line-height: 1.3; + flex: 1 1 auto; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* ── Layout-expanded: slide off-screen ───────────────────────── */ + +.earth-app.layout-expanded .hud-panel-stats:not([data-dragged="true"]) { + top: var(--hud-offset); + right: var(--hud-offset); + transform: translate(calc(100% - var(--hud-offset)), calc(-100% + var(--hud-offset))); } diff --git a/frontend/public/earth/css/hud.css b/frontend/public/earth/css/hud.css index d10c81e5..38d8ad74 100644 --- a/frontend/public/earth/css/hud.css +++ b/frontend/public/earth/css/hud.css @@ -94,9 +94,9 @@ height: calc(var(--hud-title-size) * 1.24); min-width: calc(var(--hud-title-size) * 1.24); padding: 0; - border: 1px solid rgba(220, 234, 248, 0.08); - border-radius: 999px; - background: rgba(255, 255, 255, 0.04); + border: 1px solid transparent; + border-radius: calc(4px * var(--hud-scale)); + background: transparent; color: var(--hud-text-muted); display: inline-flex; align-items: center; @@ -119,7 +119,6 @@ background: rgba(255, 255, 255, 0.08); border-color: rgba(225, 239, 255, 0.14); color: var(--hud-accent-strong); - transform: translateY(-1px); } .hud-panel.is-dragging { @@ -453,26 +452,8 @@ } } -.earth-app.layout-expanded .hud-panel-info { - top: var(--hud-offset); - left: var(--hud-offset); - transform: translate(calc(-100% + var(--hud-offset)), calc(-100% + var(--hud-offset))); -} - -.earth-app.layout-expanded .hud-panel-coordinates { - top: var(--hud-offset); - right: var(--hud-offset); - transform: translate(calc(100% - var(--hud-offset)), calc(-100% + var(--hud-offset))); -} - -.earth-app.layout-expanded .hud-panel-legend { - left: var(--hud-offset); - bottom: var(--hud-offset); - transform: translate(calc(-100% + var(--hud-offset)), calc(100% - var(--hud-offset))); -} - -.earth-app.layout-expanded .hud-panel-stats { - right: var(--hud-offset); - bottom: var(--hud-offset); - transform: translate(calc(100% - var(--hud-offset)), calc(100% - var(--hud-offset))); -} +/* .earth-left-column layout-expanded rule lives in info-panel.css */ +/* .hud-panel-legend layout-expanded rule lives in legend.css */ +/* .hud-panel-stats layout-expanded rule lives in earth-stats.css */ +/* .hud-panel-layers layout-expanded rule lives in layer-panel.css */ +/* .hud-panel-tv layout-expanded rule lives in tv-panel.css */ diff --git a/frontend/public/earth/css/info-panel.css b/frontend/public/earth/css/info-panel.css index e7ba5cb6..05f82979 100644 --- a/frontend/public/earth/css/info-panel.css +++ b/frontend/public/earth/css/info-panel.css @@ -1,146 +1,175 @@ -/* info-panel */ +/* info-panel.css — brand panel + detail card */ -.hud-panel-info { +/* ── Left column wrapper ──────────────────────────────────────── */ + +.earth-left-column { + position: absolute; top: var(--hud-offset); left: var(--hud-offset); - border-radius: var(--hud-radius); - padding: calc(22px * var(--hud-scale)); - width: calc(336px * var(--hud-scale)); + display: flex; + flex-direction: column; z-index: 10; - font-size: var(--hud-font-size); + pointer-events: none; + /* Width is set by the widest child (brand or info card) */ + max-width: min(calc(340px * var(--hud-scale)), calc(100vw - 32px)); } -.hud-panel-info .earth-brand { - margin-bottom: calc(12px * var(--hud-scale)); +.earth-left-column > * { + position: relative; /* override .hud-panel position:absolute */ + pointer-events: auto; + width: 100%; + box-sizing: border-box; +} + +/* ── Brand panel ──────────────────────────────────────────────── */ + +.hud-panel-brand { + border-radius: 0; + padding: calc(12px * var(--hud-scale)) calc(14px * var(--hud-scale)); display: flex; - align-items: flex-start; + align-items: center; + justify-content: center; + /* Reserve full panel height before brand images load */ + min-height: calc(66px * var(--hud-scale)); +} + +.hud-panel-brand .earth-brand { + display: flex; + align-items: center; gap: calc(10px * var(--hud-scale)); min-width: 0; } -.hud-panel-info .earth-brand__logo { +.hud-panel-brand .earth-brand__logo { display: block; flex: 0 0 auto; - width: min(calc(148px * var(--hud-scale)), 41%); - height: auto; + width: calc(128px * var(--hud-scale)); + height: calc(128px * var(--hud-scale)); object-fit: contain; - image-rendering: auto; } -.hud-panel-info .earth-brand__copy { +.hud-panel-brand .earth-brand__copy { display: flex; flex: 1 1 auto; min-width: 0; flex-direction: column; justify-content: center; - gap: calc(8px * var(--hud-scale)); + gap: calc(5px * var(--hud-scale)); } -.hud-panel-info .earth-brand__title { +.hud-panel-brand .earth-brand__title { display: block; - width: min(100%, calc(196px * var(--hud-scale))); + width: min(100%, calc(160px * var(--hud-scale))); max-width: 100%; height: auto; + min-height: calc(20px * var(--hud-scale)); object-fit: contain; - image-rendering: auto; } -.hud-panel-info .earth-brand__meta { - text-align: left; +.hud-panel-brand .earth-brand__meta { display: flex; flex-direction: column; - align-items: flex-start; - gap: calc(4px * var(--hud-scale)); + gap: calc(2px * var(--hud-scale)); + width: fit-content; } -.hud-panel-info .earth-brand__subtitle { +.hud-panel-brand .earth-brand__subtitle { color: var(--hud-text-muted); - font-size: calc(0.84rem * var(--hud-scale)); - line-height: 1.38; + font-size: calc(0.74rem * var(--hud-scale)); + line-height: 1.3; font-weight: 500; - letter-spacing: 0.015em; + letter-spacing: 0.01em; + /* Prevent text from pushing brand wider than logo column */ + width: fit-content; + max-width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } -.hud-panel-info .earth-brand__description { +.hud-panel-brand .earth-brand__description { color: var(--hud-text-soft); - font-size: calc(0.66rem * var(--hud-scale)); - line-height: 1.35; + font-size: calc(0.6rem * var(--hud-scale)); + line-height: 1.3; letter-spacing: 0.08em; + width: fit-content; + max-width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } -.hud-panel-info .earth-brand--en .earth-brand__logo { - width: min(calc(140px * var(--hud-scale)), 39%); +.hud-panel-brand .earth-brand--en .earth-brand__title { + width: min(100%, calc(172px * var(--hud-scale))); } -.hud-panel-info .earth-brand--en .earth-brand__title { - width: min(100%, calc(208px * var(--hud-scale))); -} - -.hud-panel-info .earth-brand--en .earth-brand__meta { - gap: calc(3px * var(--hud-scale)); -} - -.hud-panel-info .earth-brand--en .earth-brand__subtitle, -.hud-panel-info .earth-brand--en .earth-brand__description { +.hud-panel-brand .earth-brand--en .earth-brand__subtitle, +.hud-panel-brand .earth-brand--en .earth-brand__description { font-family: "Roboto Condensed", "Arial Narrow", "Trebuchet MS", "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; } -.hud-panel-info .earth-brand--en .earth-brand__subtitle { - line-height: 1.26; - letter-spacing: 0.01em; -} +/* ── Info detail panel (floating, positioned near click by JS) ── */ -.hud-panel-info .earth-brand--en .earth-brand__description { - line-height: 1.24; - letter-spacing: 0.045em; -} - -/* Info Card - Unified details panel (inside info-panel) */ -.info-card { - display: none; - margin-top: var(--hud-gap-lg); - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.04), transparent 48%), - rgba(255, 255, 255, 0.025); - border-radius: calc(18px * var(--hud-scale)); - border: 1px solid rgba(214, 229, 245, 0.08); - box-shadow: - inset 0 1px 0 rgba(255, 255, 255, 0.05), - 0 12px 28px rgba(0, 0, 0, 0.14); +.hud-panel-info { + position: absolute; + z-index: 50; + width: min(calc(300px * var(--hud-scale)), calc(100vw - 32px)); + border-radius: 0; padding: 0; overflow: hidden; + /* Start hidden */ + opacity: 0; + transform: scale(0.94) translateY(4px); + pointer-events: none; + transition: + opacity 0.22s ease, + transform 0.22s ease; +} + +.hud-panel-info.is-visible { + opacity: 1; + transform: scale(1) translateY(0); pointer-events: auto; } -.info-card.no-border { - background: transparent; - border: none; +/* ── Info Card ────────────────────────────────────────────────── */ + +.info-card { + display: block; } .info-card-header { display: flex; align-items: center; - padding: calc(12px * var(--hud-scale)) calc(14px * var(--hud-scale)); + padding: calc(10px * var(--hud-scale)) calc(12px * var(--hud-scale)); background: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.015)); border-bottom: 1px solid var(--hud-line); - gap: var(--hud-gap-sm); + gap: var(--hud-gap-xs); } .info-card-icon { - font-size: calc(18px * var(--hud-scale)); + font-size: calc(16px * var(--hud-scale)); + flex-shrink: 0; } .info-card-header h3 { flex: 1; margin: 0; - font-size: calc(0.96rem * var(--hud-scale)); + font-size: calc(0.92rem * var(--hud-scale)); color: var(--hud-title); font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.info-card-close { + flex-shrink: 0; } .info-card-content { - padding: calc(12px * var(--hud-scale)) calc(14px * var(--hud-scale)); - max-height: 40vh; + padding: calc(8px * var(--hud-scale)) calc(12px * var(--hud-scale)); + max-height: 58vh; overflow-y: auto; scrollbar-width: thin; scrollbar-color: rgba(160, 186, 216, 0.34) transparent; @@ -148,7 +177,7 @@ } .info-card-content::-webkit-scrollbar { - width: 6px; + width: 4px; } .info-card-content::-webkit-scrollbar-track { @@ -160,15 +189,11 @@ border-radius: 999px; } -.info-card-content::-webkit-scrollbar-thumb:hover { - background: linear-gradient(180deg, rgba(226, 236, 246, 0.28), rgba(136, 164, 196, 0.34)); -} - .info-card-property { display: flex; justify-content: space-between; align-items: flex-start; - padding: calc(8px * var(--hud-scale)) 0; + padding: calc(6px * var(--hud-scale)) 0; border-bottom: 1px solid rgba(214, 229, 245, 0.06); pointer-events: auto; gap: var(--hud-gap-sm); @@ -180,10 +205,11 @@ .info-card-label { color: var(--hud-text-soft); - font-size: calc(0.72rem * var(--hud-scale)); + font-size: calc(0.68rem * var(--hud-scale)); letter-spacing: 0.1em; text-transform: uppercase; cursor: pointer; + flex-shrink: 0; transition: color 0.18s ease; } @@ -194,35 +220,34 @@ .info-card-value { color: var(--hud-text); font-weight: 600; - font-size: calc(0.88rem * var(--hud-scale)); + font-size: calc(0.82rem * var(--hud-scale)); line-height: 1.45; text-align: right; - max-width: calc(190px * var(--hud-scale)); + max-width: calc(180px * var(--hud-scale)); word-break: break-word; } -/* Cable type */ -.info-card.cable { - border-color: rgba(255, 200, 0, 0.4); -} - +/* Type-specific header accent colors */ .info-card.cable .info-card-header { - background: rgba(255, 200, 0, 0.15); -} - -.info-card.cable .info-card-header h3 { - color: #ffc800; -} - -/* Satellite type */ -.info-card.satellite { - border-color: rgba(0, 229, 255, 0.4); + background: rgba(255, 200, 0, 0.12); + border-bottom-color: rgba(255, 200, 0, 0.15); } +.info-card.cable .info-card-header h3 { color: #ffc800; } .info-card.satellite .info-card-header { - background: rgba(0, 229, 255, 0.15); + background: rgba(0, 229, 255, 0.12); + border-bottom-color: rgba(0, 229, 255, 0.15); } +.info-card.satellite .info-card-header h3 { color: #00e5ff; } -.info-card.satellite .info-card-header h3 { - color: #00e5ff; +.info-card.bgp .info-card-header { + background: rgba(120, 180, 255, 0.12); + border-bottom-color: rgba(120, 180, 255, 0.15); +} +.info-card.bgp .info-card-header h3 { color: var(--hud-accent-strong); } + +/* ── Layout-expanded: slide left column off-screen ────────────── */ + +.earth-app.layout-expanded .earth-left-column { + transform: translate(calc(-100% + var(--hud-offset)), 0); } diff --git a/frontend/public/earth/css/layer-panel.css b/frontend/public/earth/css/layer-panel.css new file mode 100644 index 00000000..1a88dc31 --- /dev/null +++ b/frontend/public/earth/css/layer-panel.css @@ -0,0 +1,260 @@ +/* layer-panel.css — layer toggle panel (below brand, in left column) */ + +.hud-panel-layers { + /* Lives inside .earth-left-column — position is relative via column rule */ + border-radius: 0; + padding: 0; + width: 100%; + z-index: 10; + overflow: hidden; + margin-top: calc(6px * var(--hud-scale)); +} + +/* ── Header / drag handle ─────────────────────────────────────── */ + +.layer-panel-header { + display: flex; + align-items: center; + gap: calc(6px * var(--hud-scale)); + padding: calc(8px * var(--hud-scale)) calc(10px * var(--hud-scale)); + border-bottom: 1px solid var(--hud-line); + cursor: grab; + user-select: none; +} + +.layer-panel-header:active { + cursor: grabbing; +} + +.layer-panel-icon { + flex-shrink: 0; + font-size: calc(15px * var(--hud-scale)); + color: var(--hud-text-soft); + line-height: 1; + font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20; + pointer-events: none; +} + +.layer-panel-title { + flex: 1 1 auto; + margin: 0; + color: var(--hud-title); + font-size: calc(0.82rem * var(--hud-scale)); + font-weight: 600; + letter-spacing: 0.04em; + line-height: 1.2; +} + +/* ── Collapse / generic icon button ──────────────────────────── */ + +.layer-panel-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: calc(22px * var(--hud-scale)); + height: calc(22px * var(--hud-scale)); + min-width: calc(22px * var(--hud-scale)); + padding: 0; + border: none; + border-radius: calc(4px * var(--hud-scale)); + background: transparent; + color: var(--hud-text-muted); + cursor: pointer; + flex-shrink: 0; + transition: background 0.14s ease, color 0.14s ease; +} + +.layer-panel-btn:hover { + background: rgba(255, 255, 255, 0.07); + color: var(--hud-text); +} + +.layer-panel-btn .material-symbols-rounded { + font-size: calc(14px * var(--hud-scale)); + line-height: 1; + pointer-events: none; + transition: transform 0.22s ease; +} + +/* Chevron rotates when collapsed */ +.layer-panel--collapsed .layer-panel-btn .material-symbols-rounded { + transform: rotate(180deg); +} + +/* ── Search bar ───────────────────────────────────────────────── */ + +.layer-panel-search { + display: flex; + align-items: center; + gap: calc(5px * var(--hud-scale)); + padding: calc(6px * var(--hud-scale)) calc(10px * var(--hud-scale)); + border-bottom: 1px solid var(--hud-line); +} + +.layer-panel-search-icon { + flex-shrink: 0; + font-size: calc(14px * var(--hud-scale)); + color: var(--hud-text-soft); + line-height: 1; + font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20; + pointer-events: none; +} + +.layer-panel-search-input { + flex: 1 1 auto; + min-width: 0; + border: none; + background: transparent; + color: var(--hud-text); + font-size: calc(0.78rem * var(--hud-scale)); + line-height: 1.4; + outline: none; + padding: 0; +} + +.layer-panel-search-input::placeholder { + color: var(--hud-text-soft); +} + +/* ── Empty search state ───────────────────────────────────────── */ + +.layer-panel-empty { + padding: calc(12px * var(--hud-scale)) calc(10px * var(--hud-scale)); + color: var(--hud-text-soft); + font-size: calc(0.78rem * var(--hud-scale)); + text-align: center; +} + +/* ── Collapsible body ─────────────────────────────────────────── */ + +.layer-panel-body { + overflow: hidden; + max-height: 600px; + transition: max-height 0.24s ease, opacity 0.18s ease; + opacity: 1; +} + +.layer-panel--collapsed .layer-panel-body { + max-height: 0; + opacity: 0; +} + +/* ── Layer rows ───────────────────────────────────────────────── */ + +.layer-panel-list { + display: flex; + flex-direction: column; +} + +.layer-row { + display: flex; + align-items: center; + gap: calc(8px * var(--hud-scale)); + padding: calc(9px * var(--hud-scale)) calc(10px * var(--hud-scale)); + border-bottom: 1px solid var(--hud-line); + transition: background 0.14s ease; +} + +.layer-row:last-child { + border-bottom: none; +} + +.layer-row:hover { + background: rgba(255, 255, 255, 0.03); +} + +.layer-row-icon { + flex-shrink: 0; + font-size: calc(15px * var(--hud-scale)); + color: var(--hud-text-soft); + line-height: 1; + font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20; + transition: color 0.18s ease; +} + +/* Icon brightens when layer is active */ +.layer-row:has(.layer-row-toggle.active) .layer-row-icon { + color: var(--hud-accent); +} + +.layer-row-copy { + flex: 1 1 auto; + min-width: 0; + display: flex; + flex-direction: column; + gap: calc(1px * var(--hud-scale)); +} + +.layer-row-label { + color: var(--hud-text); + font-size: calc(0.82rem * var(--hud-scale)); + font-weight: 500; + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + transition: color 0.18s ease; +} + +.layer-row:has(.layer-row-toggle.active) .layer-row-label { + color: var(--hud-title); +} + +.layer-row-meta { + color: var(--hud-text-soft); + font-size: calc(0.58rem * var(--hud-scale)); + letter-spacing: 0.08em; + text-transform: uppercase; + line-height: 1.2; +} + +/* ── Toggle switch ────────────────────────────────────────────── */ + +.layer-row-toggle { + flex-shrink: 0; + position: relative; + width: calc(34px * var(--hud-scale)); + height: calc(20px * var(--hud-scale)); + padding: 0; + border: none; + background: transparent; + cursor: pointer; +} + +.layer-row-toggle-track { + display: block; + position: absolute; + inset: 0; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(215, 229, 242, 0.12); + transition: background 0.18s ease, border-color 0.18s ease; +} + +/* Thumb */ +.layer-row-toggle-track::after { + content: ""; + position: absolute; + top: calc(2px * var(--hud-scale)); + left: calc(2px * var(--hud-scale)); + width: calc(14px * var(--hud-scale)); + height: calc(14px * var(--hud-scale)); + border-radius: 50%; + background: #c8d8ea; + box-shadow: 0 2px 6px rgba(1, 8, 18, 0.3); + transition: transform 0.18s ease, background 0.18s ease; +} + +/* Active (ON) state */ +.layer-row-toggle.active .layer-row-toggle-track { + background: linear-gradient(180deg, rgba(143, 185, 255, 0.72), rgba(104, 147, 221, 0.78)); + border-color: rgba(223, 236, 252, 0.28); +} + +.layer-row-toggle.active .layer-row-toggle-track::after { + background: #f0f6ff; + transform: translateX(calc(14px * var(--hud-scale))); +} + +/* Layout-expanded: layer panel slides off with .earth-left-column — no + individual rule needed since the whole column translates together. */ diff --git a/frontend/public/earth/css/legend.css b/frontend/public/earth/css/legend.css index 1c975035..3993eb43 100644 --- a/frontend/public/earth/css/legend.css +++ b/frontend/public/earth/css/legend.css @@ -1,85 +1,177 @@ -/* legend */ +/* legend.css — compact tab-strip legend */ .hud-panel-legend { bottom: var(--hud-offset); left: var(--hud-offset); - border-radius: var(--hud-radius); - padding: var(--hud-panel-padding); - width: calc(236px * var(--hud-scale)); + border-radius: 0; + padding: 0; + width: min(calc(200px * var(--hud-scale)), calc(100vw - 32px)); z-index: 10; - font-size: var(--hud-font-size); + overflow: hidden; } -.hud-panel-legend .legend-title { - margin-bottom: 0; -} +/* ── Drag bar ─────────────────────────────────────────────────── */ -.hud-panel-legend .legend-list { - display: flex; - flex-direction: column; - gap: calc(9px * var(--hud-scale)); - max-height: calc(214px * var(--hud-scale)); - overflow-y: auto; - padding-right: calc(4px * var(--hud-scale)); - scrollbar-width: thin; - scrollbar-color: rgba(160, 186, 216, 0.32) transparent; -} - -.hud-panel-legend .legend-list::-webkit-scrollbar { - width: 6px; -} - -.hud-panel-legend .legend-list::-webkit-scrollbar-track { - background: transparent; -} - -.hud-panel-legend .legend-list::-webkit-scrollbar-thumb { - background: linear-gradient(180deg, rgba(210, 225, 242, 0.2), rgba(126, 154, 185, 0.28)); - border-radius: 999px; -} - -.hud-panel-legend .legend-item { +.legend-bar { display: flex; align-items: center; - gap: calc(10px * var(--hud-scale)); - padding: calc(9px * var(--hud-scale)) calc(10px * var(--hud-scale)); - border-radius: calc(14px * var(--hud-scale)); - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.035), transparent), - rgba(255, 255, 255, 0.018); - border: 1px solid rgba(214, 229, 245, 0.07); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035); + justify-content: space-between; + gap: calc(6px * var(--hud-scale)); + padding: calc(6px * var(--hud-scale)) calc(8px * var(--hud-scale)); + border-bottom: 1px solid var(--hud-line); + cursor: grab; + user-select: none; } -.hud-panel-legend .legend-color { - width: calc(16px * var(--hud-scale)); - height: calc(16px * var(--hud-scale)); - border-radius: 999px; - margin-right: 0; - box-shadow: - inset 0 1px 0 rgba(255, 255, 255, 0.28), - 0 0 0 3px rgba(255, 255, 255, 0.04); +.legend-bar:active { + cursor: grabbing; } -.hud-panel-legend .legend-item span { +/* ── Mode tabs ────────────────────────────────────────────────── */ + +.legend-tabs { + display: flex; + gap: calc(2px * var(--hud-scale)); + flex: 1 1 auto; + min-width: 0; +} + +.legend-tab { + padding: calc(3px * var(--hud-scale)) calc(7px * var(--hud-scale)); + border-radius: calc(4px * var(--hud-scale)); + border: 1px solid transparent; + background: transparent; + color: var(--hud-text-muted); + font-size: calc(0.68rem * var(--hud-scale)); + font-family: inherit; + letter-spacing: 0.08em; + cursor: pointer; + transition: background 0.14s ease, color 0.14s ease, border-color 0.14s ease; + white-space: nowrap; +} + +.legend-tab:hover { + background: rgba(255, 255, 255, 0.06); color: var(--hud-text); - font-size: calc(0.84rem * var(--hud-scale)); - font-weight: 500; - line-height: 1.35; } -.legend-color-americas { - background-color: #ff4444; +.legend-tab--active { + background: rgba(120, 180, 255, 0.12); + border-color: rgba(120, 180, 255, 0.2); + color: var(--hud-accent-strong); } -.legend-color-au-a { - background-color: #44ff44; +/* ── Bar action buttons ───────────────────────────────────────── */ + +.legend-bar-actions { + display: flex; + align-items: center; + gap: calc(2px * var(--hud-scale)); + flex-shrink: 0; } -.legend-color-au-b { - background-color: #4444ff; +.legend-bar-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: calc(20px * var(--hud-scale)); + height: calc(20px * var(--hud-scale)); + min-width: calc(20px * var(--hud-scale)); + padding: 0; + border: none; + border-radius: calc(4px * var(--hud-scale)); + background: transparent; + color: var(--hud-text-muted); + cursor: pointer; + transition: background 0.14s ease, color 0.14s ease; } -.legend-color-default { - background-color: #ffff44; +.legend-bar-btn:hover { + background: rgba(255, 255, 255, 0.07); + color: var(--hud-text); +} + +.legend-bar-btn .material-symbols-rounded { + font-size: calc(13px * var(--hud-scale)); + line-height: 1; + font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20; + pointer-events: none; +} + +/* Collapse chevron */ +#legend-collapse .material-symbols-rounded { + transition: transform 0.22s ease; +} + +.legend--collapsed #legend-collapse .material-symbols-rounded { + transform: rotate(180deg); +} + +/* ── Collapsible list body ────────────────────────────────────── */ + +.legend-body { + max-height: calc(220px * var(--hud-scale)); + overflow: hidden; + transition: + max-height 0.26s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.2s ease; + opacity: 1; +} + +.legend--collapsed .legend-body { + max-height: 0; + opacity: 0; + pointer-events: none; +} + +/* ── Item list ────────────────────────────────────────────────── */ + +.legend-list { + display: flex; + flex-direction: column; + padding: calc(4px * var(--hud-scale)) 0; + overflow-y: auto; + max-height: calc(220px * var(--hud-scale)); + scrollbar-width: thin; + scrollbar-color: rgba(160, 186, 216, 0.28) transparent; +} + +.legend-list::-webkit-scrollbar { width: 3px; } +.legend-list::-webkit-scrollbar-track { background: transparent; } +.legend-list::-webkit-scrollbar-thumb { + background: rgba(160, 186, 216, 0.26); + border-radius: 999px; +} + +.legend-item { + display: flex; + align-items: center; + gap: calc(8px * var(--hud-scale)); + padding: calc(5px * var(--hud-scale)) calc(10px * var(--hud-scale)); +} + +.legend-dot { + flex-shrink: 0; + width: calc(7px * var(--hud-scale)); + height: calc(7px * var(--hud-scale)); + border-radius: 50%; + box-shadow: 0 0 4px currentColor; +} + +.legend-label { + color: var(--hud-text); + font-size: calc(0.78rem * var(--hud-scale)); + font-weight: 400; + line-height: 1.3; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* ── Layout-expanded ──────────────────────────────────────────── */ + +.earth-app.layout-expanded .hud-panel-legend:not([data-dragged="true"]) { + left: var(--hud-offset); + bottom: var(--hud-offset); + transform: translate(calc(-100% + var(--hud-offset)), calc(100% - var(--hud-offset))); } diff --git a/frontend/public/earth/css/toolbar.css b/frontend/public/earth/css/toolbar.css index 104cfdc1..f6a83b82 100644 --- a/frontend/public/earth/css/toolbar.css +++ b/frontend/public/earth/css/toolbar.css @@ -388,20 +388,106 @@ gap: 8px; } -.earth-info-toolbar .earth-toolbar-btn:nth-child(1) { - animation-delay: 0.34s; +.earth-info-toolbar { + width: min(280px, calc(100vw - 36px)); + padding: 12px; + border-radius: 22px; + background: + radial-gradient(circle at top, rgba(255, 255, 255, 0.12), transparent 34%), + linear-gradient(180deg, rgba(16, 29, 48, 0.96), rgba(8, 18, 33, 0.94)); + border: 1px solid rgba(211, 228, 246, 0.14); + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.28), + inset 0 1px 0 rgba(255, 255, 255, 0.08); + backdrop-filter: blur(18px) saturate(135%); + -webkit-backdrop-filter: blur(18px) saturate(135%); } -.earth-info-toolbar .earth-toolbar-btn:nth-child(2) { - animation-delay: 0.18s; +.earth-layer-toolbar-header { + width: 100%; + padding: 2px 4px 8px; + border-bottom: 1px solid rgba(201, 225, 247, 0.08); + margin-bottom: 2px; } -.earth-info-toolbar .earth-toolbar-btn:nth-child(3) { - animation-delay: 0.1s; +.earth-layer-toolbar-title { + display: block; + color: var(--hud-accent-strong); + font-size: 0.86rem; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; } -.earth-info-toolbar .earth-toolbar-btn:nth-child(4) { - animation-delay: 0s; +.earth-layer-toolbar-subtitle { + display: block; + margin-top: 4px; + color: var(--hud-text-soft); + font-size: 0.68rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.earth-layer-btn { + width: 100%; + min-width: 0; + min-height: 52px; + height: auto; + border-radius: 16px; + padding: 12px 14px; + justify-content: space-between; + align-items: center; + gap: 12px; + overflow: hidden; + animation: none; +} + +.earth-layer-btn__copy { + min-width: 0; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 3px; +} + +.earth-layer-btn__label { + color: var(--hud-text); + font-size: 0.92rem; + font-weight: 600; + line-height: 1.15; +} + +.earth-layer-btn__meta { + color: var(--hud-text-soft); + font-size: 0.68rem; + letter-spacing: 0.08em; + text-transform: uppercase; + line-height: 1.2; +} + +.earth-layer-btn__state { + flex: 0 0 auto; + min-width: 42px; + padding: 5px 10px; + border-radius: 999px; + border: 1px solid rgba(201, 225, 247, 0.12); + color: var(--hud-text-soft); + font-size: 0.67rem; + font-weight: 700; + letter-spacing: 0.12em; + text-align: center; + text-transform: uppercase; + background: rgba(255, 255, 255, 0.04); +} + +.earth-layer-btn.active .earth-layer-btn__state { + color: #dff4ff; + border-color: rgba(220, 240, 255, 0.24); + background: rgba(131, 197, 255, 0.14); +} + +.earth-layer-btn .earth-toolbar-tooltip { + display: none; } .earth-zoom-toolbar .earth-zoom-btn, @@ -458,6 +544,7 @@ border-top-color: rgba(77, 184, 255, 0.4); } + .earth-app.layout-expanded .earth-toolbar-group { bottom: 18px; transform: translateX(-50%); diff --git a/frontend/public/earth/css/tv-panel.css b/frontend/public/earth/css/tv-panel.css index b85b7b0d..73e6b1c7 100644 --- a/frontend/public/earth/css/tv-panel.css +++ b/frontend/public/earth/css/tv-panel.css @@ -1,10 +1,11 @@ /* tv-panel */ .hud-panel-tv { - top: calc(96px * var(--hud-scale)); - right: calc(92px * var(--hud-scale)); - width: min(calc(460px * var(--hud-scale)), calc(100vw - 32px)); - min-width: calc(360px * var(--hud-scale)); + bottom: var(--hud-offset); + right: var(--hud-offset); + width: calc(420px * var(--hud-scale)); + max-width: calc(100vw - 32px); + min-width: calc(300px * var(--hud-scale)); min-height: calc(340px * var(--hud-scale)); padding: calc(18px * var(--hud-scale)); display: flex; @@ -73,6 +74,18 @@ transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease; } +.tv-panel-action--icon { + padding: calc(9px * var(--hud-scale)); + border-radius: calc(10px * var(--hud-scale)); +} + +.tv-panel-action--icon .material-symbols-rounded { + font-size: calc(18px * var(--hud-scale)); + line-height: 1; + font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20; + pointer-events: none; +} + .tv-panel-action:hover:not(:disabled) { background: rgba(255, 255, 255, 0.08); border-color: rgba(225, 239, 255, 0.2); @@ -184,39 +197,11 @@ user-select: none; } -.earth-app.layout-expanded .hud-panel-tv { - top: calc(50% + 12px); - left: 50%; - right: auto; - width: min(calc(760px * var(--hud-scale)), calc(100vw - 48px)); - min-height: calc(410px * var(--hud-scale)); - transform: translateX(-50%); +.earth-app.layout-expanded .hud-panel-tv:not([data-dragged="true"]) { + bottom: var(--hud-offset); + right: var(--hud-offset); + transform: translate(calc(100% - var(--hud-offset)), calc(100% - var(--hud-offset))); } -@media (max-width: 900px) { - .hud-panel-tv { - left: 16px; - right: 16px; - top: auto; - bottom: 16px; - width: auto; - min-width: 0; - min-height: min(48vh, 360px); - } - - .tv-panel-controls { - flex-wrap: wrap; - } - - .tv-panel-actions { - width: 100%; - } - - .tv-panel-action { - flex: 1 1 0; - } - - .tv-panel-resize-handle { - display: none; - } -} +/* TV panel keeps its fixed width on all screen sizes. + Responsive stretching removed — width only changes if user manually resizes. */ diff --git a/frontend/public/earth/index.html b/frontend/public/earth/index.html index 8cd83cb8..267833d0 100644 --- a/frontend/public/earth/index.html +++ b/frontend/public/earth/index.html @@ -32,10 +32,10 @@ - + @@ -50,17 +50,111 @@
-
-
- +
+
+
+
+ +
+ +
+ layers + 图层 + +
+ + +
+ + + + +
+
+ landscape +
+ 地形 + Terrain +
+ +
+
+ satellite_alt +
+ 卫星 + Satellites +
+ +
+
+ timeline +
+ 轨迹 + Trails +
+ +
+
+ cable +
+ 海缆 + Subsea Cables +
+ +
+
+ hub +
+ BGP观测 + Routing Signals +
+ +
+
+ + + +
+
+
+ + +
-
+
🛰️

详情

+
-
@@ -82,52 +176,12 @@ 自动旋转 -
- -
- - - - - - -
-
+
-
-
-

坐标信息

- -
-
- 经度: - 0.00° -
-
- 纬度: - 0.00° -
-
缩放: 1.0x
-
鼠标位置: 无
-
-
-

线缆图例

- + +
+
+ + + +
+
+ + +
-
-
-
- Americas II -
-
-
- AU Aleutian A -
-
-
- AU Aleutian B -
-
-
- 其他电缆 -
+ +
+
-
-

地球信息

+ +
+ 全球态势
-
- 电缆系统: - 0个 + + +
+
+ + 海缆系统 +
+
+ + 登陆点 +
+
+ + 在轨卫星 +
+
+ + BGP 事件 +
+
+ + BGP 观测站 +
+
+ + 运行中 +
-
- 状态: - - -
-
- 登陆点: - 0个 -
-
- 地形: - 开启 -
-
- 卫星: - 0 颗 -
-
- BGP事件: - 0 条 -
-
- 观测站: - 0 个 -
-
- BGP态势: - 暂无观测数据 -
-
- 视角距离: - 300 km -
-
- 纹理质量: - 8K 卫星图 + + + + + + + +
-
+

新闻直播

@@ -280,8 +312,12 @@
- - + +
@@ -334,6 +370,16 @@
视图
+