commit d72f3442b22b963215d8222dbe93cdf0a49f57ad Author: kkfluous Date: Mon Aug 3 19:14:15 2026 +0800 feat: 羚牛氢能全国运营驾驶舱 (Apple Restrained Design & 双语系统) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8173594 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +*.log +node_modules/ +.idea/ +.vscode/ diff --git a/app.js b/app.js new file mode 100644 index 0000000..73838fb --- /dev/null +++ b/app.js @@ -0,0 +1,443 @@ +/** + * 羚牛氢能 - 车辆网络 (Bilingual & Restrained Apple Engine) + * Key: 1868920ac8ff6b6f88dbe9fa2609c183 + * SecurityCode: 0b54a41143bec162788d01deba851340 + */ + +// ========================================== +// BILINGUAL TRANSLATION DICTIONARY +// ========================================== +const i18n = { + zh: { + vehicleModeTitle: '车辆网络', + stationModeTitle: '加氢站网络', + kpiTotalFleet: '车辆总数', + kpiOnlineFleet: '当前在线运行', + kpiDailyMileage: '今日运营里程', + unitVehicles: '辆', + unitStations: '座', + btnVehicle: '车辆', + btnStation: '加氢站', + themeDark: '深色', + themeLight: '浅色', + mapStatusText: '全域实时节点已同步 · 15个省份 · 42座加氢站', + legendPrimary: '核心区域 (>100)', + legendMid: '重点分布 (20-100)', + legendLow: '普通节点 (<20)', + btnResetView: '复位视角', + btn3dView: '3D/2D 视角', + btnNationalView: '全国视图', + mapFooterGis: '高质感地图引擎: 羚牛氢能GIS (AMap 3D Engine)', + mapFooterStatus: '系统状态: 稳定连接 (AES-256)', + hintNational: '视角: 全国运营态势', + hintProvince: '视角: {prov}专向统计', + panelStatusTitle: '车辆运营状态分布', + panelRankTitle: '重点区域运营 TOP 排名', + statusRunning: '运行中', + statusStopped: '静止中', + statusOffline: '离线', + rankByFleet: '按车辆', + rankByDist: '按里程', + onlineText: '在线', + fleetOverview: '📍 {prov}省运营概况', + fleetTotalLabel: '车辆总量', + onlineRunLabel: '在线运行', + unitTail: '台', + descLabel: '说明' + }, + en: { + vehicleModeTitle: 'Vehicle Network', + stationModeTitle: 'H₂ Station Network', + kpiTotalFleet: 'Total Fleet', + kpiOnlineFleet: 'Active Online', + kpiDailyMileage: 'Today Mileage', + unitVehicles: 'units', + unitStations: 'stations', + btnVehicle: 'Fleet', + btnStation: 'H₂ Stations', + themeDark: 'Dark', + themeLight: 'Light', + mapStatusText: 'Real-time Nodes Synced · 15 Provinces · 42 H₂ Stations', + legendPrimary: 'Core (>100)', + legendMid: 'Key (20-100)', + legendLow: 'Standard (<20)', + btnResetView: 'Reset View', + btn3dView: '3D/2D View', + btnNationalView: 'National View', + mapFooterGis: 'Engine: Lingniu H₂ GIS (AMap 3D Engine)', + mapFooterStatus: 'Status: Secure Connected (AES-256)', + hintNational: 'View: National Overview', + hintProvince: 'View: {prov} Focus', + panelStatusTitle: 'Fleet Status Breakdown', + panelRankTitle: 'TOP Regional Operations', + statusRunning: 'Active', + statusStopped: 'Idle', + statusOffline: 'Offline', + rankByFleet: 'By Fleet', + rankByDist: 'By Mileage', + onlineText: 'Online', + fleetOverview: '📍 {prov} Province Overview', + fleetTotalLabel: 'Total Fleet', + onlineRunLabel: 'Active Fleet', + unitTail: 'units', + descLabel: 'Note' + } +}; + +// Province name English mapping +const provinceEnMap = { + '浙江': 'Zhejiang', + '广东': 'Guangdong', + '上海': 'Shanghai', + '江苏': 'Jiangsu', + '新疆': 'Xinjiang', + '湖北': 'Hubei', + '四川': 'Sichuan', + '陕西': 'Shaanxi', + '安徽': 'Anhui', + '重庆': 'Chongqing', + '山东': 'Shandong', + '天津': 'Tianjin', + '河南': 'Henan', + '北京': 'Beijing' +}; + +// ========================================== +// STATE MANAGEMENT (DEFAULT: LIGHT MODE) +// ========================================== +let map = null; +let markerList = []; +let is3DPitch = true; +let currentMode = 'vehicle'; // 'vehicle' | 'station' +let currentTheme = 'theme-white'; // DEFAULT: LIGHT MODE (纯白/浅色模式) +let currentLang = 'zh'; // 'zh' | 'en' +let selectedProvince = null; +let currentRankType = 'fleet'; + +// Province-Level Aggregates for Vehicle Operations +const vehicleGeoNodes = [ + { id: 'zj', name: '浙江', lnglat: [120.155070, 30.274085], count: 322, online: 286, dist: 168420, detail: '322台在途车辆 (在线率 88.8%)' }, + { id: 'gd', name: '广东', lnglat: [113.264385, 23.129112], count: 209, online: 175, dist: 124150, detail: '209台大湾区冷链运力' }, + { id: 'sh', name: '上海', lnglat: [121.473701, 31.230416], count: 54, online: 48, dist: 38200, detail: '54台港口短驳及城配集群' }, + { id: 'js', name: '江苏', lnglat: [118.796877, 32.060255], count: 35, online: 31, dist: 28900, detail: '35台49吨牵引车集群' }, + { id: 'xj', name: '新疆', lnglat: [87.617733, 43.792818], count: 28, online: 21, dist: 31200, detail: '28台重载矿用牵引车' }, + { id: 'hb', name: '湖北', lnglat: [114.305393, 30.593099], count: 27, online: 24, dist: 21800, detail: '27台双飞翼货车' }, + { id: 'sc', name: '四川', lnglat: [104.066541, 30.572269], count: 25, online: 22, dist: 24500, detail: '25台成渝零碳走廊车队' }, + { id: 'sx', name: '陕西', lnglat: [108.948024, 34.263161], count: 19, online: 16, dist: 14200, detail: '19台物流货车' }, + { id: 'ah', name: '安徽', lnglat: [117.283042, 31.861190], count: 19, online: 17, dist: 15600, detail: '19台长途牵引车' }, + { id: 'cq', name: '重庆', lnglat: [106.551556, 29.563009], count: 16, online: 14, dist: 12800, detail: '16台厢式货车' }, + { id: 'sd', name: '山东', lnglat: [117.000923, 36.675807], count: 90, online: 82, dist: 58300, detail: '90台青岛港重卡车队' }, + { id: 'tj', name: '天津', lnglat: [117.200983, 39.084158], count: 6, online: 5, dist: 4200, detail: '6台港口短驳车' }, + { id: 'hn', name: '河南', lnglat: [113.665412, 34.757975], count: 3, online: 3, dist: 2800, detail: '3台冷链物流车' }, + { id: 'bj', name: '北京', lnglat: [116.407526, 39.904030], count: 1, online: 1, dist: 980, detail: '1台平谷干线示范车' } +]; + +// Province-Level Station Data +const stationGeoNodes = [ + { id: 'gd-st', name: '广东', lnglat: [113.264385, 23.129112], count: '12 站', online: 11, detail: '南海撬装站、佛山示范站等 12座' }, + { id: 'sh-st', name: '上海', lnglat: [121.473701, 31.230416], count: '8 站', online: 8, detail: '嘉定70MPa加氢母站等 8座' }, + { id: 'bj-st', name: '北京', lnglat: [116.407526, 39.904030], count: '6 站', online: 6, detail: '大兴国际氢能枢纽站等 6座' }, + { id: 'js-st', name: '江苏', lnglat: [118.796877, 32.060255], count: '5 站', online: 5, detail: '苏州港加氢站等 5座' }, + { id: 'zj-st', name: '浙江', lnglat: [120.155070, 30.274085], count: '4 站', online: 4, detail: '嘉兴嘉善站等 4座' }, + { id: 'sd-st', name: '山东', lnglat: [117.000923, 36.675807], count: '3 站', online: 3, detail: '青岛港加氢站等 3座' }, + { id: 'sc-st', name: '四川', lnglat: [104.066541, 30.572269], count: '2 站', online: 2, detail: '成都皮口加注中心等 2座' }, + { id: 'hb-st', name: '湖北', lnglat: [114.305393, 30.593099], count: '2 站', online: 2, detail: '武汉东湖高新加氢站等 2座' } +]; + +// ========================================== +// INITIALIZATION +// ========================================== +document.addEventListener('DOMContentLoaded', () => { + initClock(); + initAMapInstance(); + renderRankingList(currentRankType); +}); + +function initAMapInstance() { + if (typeof AMap === 'undefined') return; + + map = new AMap.Map('amapContainer', { + zoom: 4.8, + center: [108.948024, 34.263161], + viewMode: '3D', + pitch: 30, + mapStyle: getAMapThemeStyle(currentTheme), + showBuildingBlock: false, + showLabel: true + }); + + map.on('complete', () => { + renderAMapMarkers(); + }); +} + +function getAMapThemeStyle(theme) { + return (theme === 'theme-white') ? 'amap://styles/light' : 'amap://styles/darkblue'; +} + +function getLocalizedProv(name) { + if (currentLang === 'en') { + return provinceEnMap[name] || name; + } + return name; +} + +// Render Restrained Province Markers +function renderAMapMarkers() { + if (!map) return; + + markerList.forEach(m => m.remove()); + markerList = []; + + const nodes = (currentMode === 'vehicle') ? vehicleGeoNodes : stationGeoNodes; + const cyanColor = getThemeColor('--accent-cyan', '#0284C7'); + const cardBg = getThemeColor('--glass-bg', 'rgba(255, 255, 255, 0.85)'); + const textMain = getThemeColor('--text-main', '#1D1D1F'); + const cardBorder = getThemeColor('--glass-border', 'rgba(0, 0, 0, 0.08)'); + const dict = i18n[currentLang]; + + nodes.forEach(node => { + const isSelected = (selectedProvince === node.name); + const dotColor = cyanColor; + const displayName = getLocalizedProv(node.name); + + const markerContent = document.createElement('div'); + markerContent.className = 'province-info-badge-wrap'; + markerContent.innerHTML = ` +
+
+ + ${displayName} + ${node.count} +
+
+ ${dict.onlineText} ${node.online} +
+
+ `; + + const marker = new AMap.Marker({ + position: node.lnglat, + content: markerContent, + offset: new AMap.Pixel(-30, -12), + title: `${displayName}: ${node.count}` + }); + + const overviewTitle = dict.fleetOverview.replace('{prov}', displayName); + + const infoWindow = new AMap.InfoWindow({ + isCustom: true, + content: ` +
+
+ ${overviewTitle} +
+
+
${dict.fleetTotalLabel}: ${node.count}
+
${dict.onlineRunLabel}: ${node.online} ${dict.unitTail}
+
${dict.descLabel}: ${node.detail}
+
+
+ `, + offset: new AMap.Pixel(0, -32) + }); + + marker.on('mouseover', () => infoWindow.open(map, node.lnglat)); + marker.on('mouseout', () => infoWindow.close()); + marker.on('click', () => filterByProvince(node.name)); + + marker.setMap(map); + markerList.push(marker); + }); +} + +// ========================================== +// RIGHT PANEL INTERACTION & RANKING LIST +// ========================================== +function switchRankTab(type) { + currentRankType = type; + document.querySelectorAll('.gtab').forEach(b => b.classList.remove('active')); + event.target.classList.add('active'); + renderRankingList(type); +} + +function renderRankingList(type) { + const box = document.getElementById('rankingListBox'); + if (!box) return; + box.innerHTML = ''; + + let list = [...vehicleGeoNodes]; + if (type === 'fleet') { + list.sort((a, b) => b.count - a.count); + } else { + list.sort((a, b) => b.dist - a.dist); + } + + const dict = i18n[currentLang]; + + list.forEach((item, idx) => { + const div = document.createElement('div'); + div.className = 'rank-glass-row'; + const topClass = idx === 0 ? 'r-top1' : (idx === 1 ? 'r-top2' : (idx === 2 ? 'r-top3' : '')); + const displayName = getLocalizedProv(item.name); + const valText = type === 'fleet' ? `${item.count} ${dict.unitVehicles}` : `${(item.dist / 1000).toFixed(1)}k km`; + + div.innerHTML = ` + ${idx + 1} + ${displayName} + ${valText} + `; + div.onclick = () => filterByProvince(item.name); + box.appendChild(div); + }); +} + +function filterByProvince(provName) { + selectedProvince = provName; + const dict = i18n[currentLang]; + const displayName = getLocalizedProv(provName); + document.getElementById('selectedRegionHint').textContent = dict.hintProvince.replace('{prov}', displayName); + renderAMapMarkers(); + + const match = vehicleGeoNodes.find(n => n.name === provName); + if (match && map) { + map.setZoomAndCenter(6.5, match.lnglat); + } +} + +function clearProvinceFilter() { + selectedProvince = null; + const dict = i18n[currentLang]; + document.getElementById('selectedRegionHint').textContent = dict.hintNational; + resetMapView(); + renderAMapMarkers(); +} + +// ========================================== +// LANGUAGE SWITCHER (ZH / EN) +// ========================================== +function setLanguage(lang) { + currentLang = lang; + + document.querySelectorAll('.lang-switcher-bw .segment-btn').forEach(b => b.classList.remove('active')); + if (lang === 'zh') document.querySelector('.lang-zh-btn')?.classList.add('active'); + if (lang === 'en') document.querySelector('.lang-en-btn')?.classList.add('active'); + + const dict = i18n[lang]; + + // Translate static data-i18n attributes + document.querySelectorAll('[data-i18n]').forEach(el => { + const key = el.getAttribute('data-i18n'); + if (dict[key]) { + el.textContent = dict[key]; + } + }); + + // Dynamic titles & KPIs + if (currentMode === 'vehicle') { + document.getElementById('dashboardTitle').textContent = dict.vehicleModeTitle; + document.getElementById('kpiFleetTotal').innerHTML = `1,024 ${dict.unitVehicles}`; + document.getElementById('kpiFleetOnline').innerHTML = `823 ${dict.unitVehicles} (80.4%)`; + } else { + document.getElementById('dashboardTitle').textContent = dict.stationModeTitle; + document.getElementById('kpiFleetTotal').innerHTML = `42 ${dict.unitStations}`; + document.getElementById('kpiFleetOnline').innerHTML = `39 ${dict.unitStations} (92.8%)`; + } + + // Update selected region hint + if (selectedProvince) { + const displayName = getLocalizedProv(selectedProvince); + document.getElementById('selectedRegionHint').textContent = dict.hintProvince.replace('{prov}', displayName); + } else { + document.getElementById('selectedRegionHint').textContent = dict.hintNational; + } + + renderRankingList(currentRankType); + renderAMapMarkers(); +} + +// ========================================== +// MODE & BLACK/WHITE THEME SWITCHER +// ========================================== +function switchMode(mode) { + currentMode = mode; + const dict = i18n[currentLang]; + + const btnVehicle = document.getElementById('btnModeVehicle'); + const btnStation = document.getElementById('btnModeStation'); + + if (mode === 'vehicle') { + btnVehicle.classList.add('active'); + btnStation.classList.remove('active'); + document.getElementById('dashboardTitle').textContent = dict.vehicleModeTitle; + document.getElementById('kpiFleetTotal').innerHTML = `1,024 ${dict.unitVehicles}`; + document.getElementById('kpiFleetOnline').innerHTML = `823 ${dict.unitVehicles} (80.4%)`; + } else { + btnStation.classList.add('active'); + btnVehicle.classList.remove('active'); + document.getElementById('dashboardTitle').textContent = dict.stationModeTitle; + document.getElementById('kpiFleetTotal').innerHTML = `42 ${dict.unitStations}`; + document.getElementById('kpiFleetOnline').innerHTML = `39 ${dict.unitStations} (92.8%)`; + } + + renderAMapMarkers(); +} + +function setTheme(themeName) { + currentTheme = themeName; + document.body.className = themeName; + + document.querySelectorAll('.bw-btn').forEach(b => b.classList.remove('active')); + if (themeName === 'theme-dark') document.querySelector('.theme-dark-btn')?.classList.add('active'); + if (themeName === 'theme-white') document.querySelector('.theme-white-btn')?.classList.add('active'); + + if (map) { + map.setMapStyle(getAMapThemeStyle(themeName)); + renderAMapMarkers(); + } +} + +function getThemeColor(varName, fallback) { + const val = getComputedStyle(document.body).getPropertyValue(varName).trim(); + return val || fallback; +} + +function resetMapView() { + if (map) { + map.setZoomAndCenter(4.8, [108.948024, 34.263161]); + map.setPitch(30); + } +} + +function togglePitchView() { + if (!map) return; + is3DPitch = !is3DPitch; + map.setPitch(is3DPitch ? 30 : 0); +} + +function initClock() { + const clockTime = document.getElementById('clockTime'); + function update() { + const now = new Date(); + const hrs = String(now.getHours()).padStart(2, '0'); + const mins = String(now.getMinutes()).padStart(2, '0'); + const secs = String(now.getSeconds()).padStart(2, '0'); + if (clockTime) clockTime.textContent = `${hrs}:${mins}:${secs}`; + } + update(); + setInterval(update, 1000); +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..73cd0f0 --- /dev/null +++ b/index.html @@ -0,0 +1,235 @@ + + + + + + 羚牛氢能 - 车辆网络 | Lingniu H2 Executive Cockpit + + + + + + + + + + + + + +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

车辆网络

+ Leitstand +
+
+ + +
+
+ 车辆总数 + 1,024 +
+
+ 当前在线运行 + 823 辆 (80.4%) +
+
+ 今日运营里程 + 512,178 km ↑6.2% +
+
+ + +
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
19:04:00
+
+
+ + +
+ + +
+ + +
+
+ + 全域实时节点已同步 · 15个省份 · 42座加氢站 +
+ +
+ 核心区域 (>100) + 重点分布 (20-100) + 普通节点 (<20) +
+
+ + +
+
+ + +
+ + + +
+
+ + +
+ 高质感地图引擎: 羚牛氢能GIS (AMap 3D Engine) + 系统状态: 稳定连接 (AES-256) + 视角: 全国运营态势 +
+
+ + + + +
+ +
+ + + + diff --git a/logo_light.svg b/logo_light.svg new file mode 100644 index 0000000..d6900ab --- /dev/null +++ b/logo_light.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..abacb39 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "lingniu-truck-map", + "version": "1.0.0", + "description": "羚牛氢能 - 全国氢能物流运营驾驶舱 (Apple Design Spec)", + "main": "index.html", + "scripts": { + "start": "python3 -m http.server 8080", + "dev": "python3 -m http.server 8080" + }, + "keywords": ["lingniu", "hydrogen", "map", "cockpit"], + "author": "Antigravity", + "license": "ISC" +} diff --git a/server.py b/server.py new file mode 100644 index 0000000..d831619 --- /dev/null +++ b/server.py @@ -0,0 +1,22 @@ +import http.server +import socketserver +import socket + +PORT = 8080 + +class ReusableTCPServer(socketserver.TCPServer): + allow_reuse_address = True + +Handler = http.server.SimpleHTTPRequestHandler + +# Explicitly bind to IPv4 address 0.0.0.0 so all LAN devices can connect +try: + with ReusableTCPServer(("0.0.0.0", PORT), Handler) as httpd: + print(f"==================================================") + print(f" 羚牛氢能驾驶舱 - 局域网 (IPv4) 服务已建立") + print(f" 本机访问: http://localhost:{PORT}") + print(f" 局域网访问: http://192.168.110.62:{PORT}") + print(f"==================================================") + httpd.serve_forever() +except Exception as e: + print(f"Server error: {e}") diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..a87733f --- /dev/null +++ b/start.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# 羚牛氢能驾驶舱 - 局域网本地部署启动脚本 + +echo "==================================================" +echo " 羚牛氢能 - 全国氢能物流运营驾驶舱" +echo " 局域网 (LAN) 服务启动中..." +echo "==================================================" + +IP_ADDR=$(ifconfig | grep "inet " | grep -v 127.0.0.1 | head -n 1 | awk '{print $2}') + +echo "本机访问地址: http://localhost:8080" +echo "局域网访问地址: http://${IP_ADDR}:8080" +echo "==================================================" + +python3 -m http.server 8080 diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..87dd53b --- /dev/null +++ b/styles.css @@ -0,0 +1,650 @@ +/* ========================================================================== + 羚牛氢能 - 车辆网络 + Design Philosophy: Apple Restrained Minimalist (极致克制 · 纯净无痕) + ========================================================================== */ + +/* -------------------------------------------------------------------------- + RESTRICTED DUAL-THEME COLOR SYSTEM (纯黑 / 纯白) + -------------------------------------------------------------------------- */ + +/* 1. 纯黑/深色模式 (Minimal Dark Charcoal) */ +.theme-dark { + --bg-app: #0A0B0E; + --bg-radial: radial-gradient(circle at 50% 20%, #12141C 0%, #0A0B0E 100%); + --glass-bg: rgba(20, 22, 28, 0.68); + --glass-border: rgba(255, 255, 255, 0.08); + --glass-hover-border: rgba(255, 255, 255, 0.2); + --glass-shadow: 0 16px 40px rgba(0, 0, 0, 0.4); + --accent-primary: #007143; /* 羚牛品牌绿 */ + --accent-cyan: #38BDF8; /* 柔和冰蓝 */ + --accent-mint: #10B981; + --accent-blue: #3B82F6; + --text-main: #F1F5F9; + --text-muted: #94A3B8; + --text-sub: #64748B; + --pill-bg: rgba(255, 255, 255, 0.06); + --segmented-bg: rgba(255, 255, 255, 0.05); + --map-style: amap://styles/darkblue; +} + +/* 2. 纯白/浅色模式 (Apple Studio Pure White) */ +.theme-white { + --bg-app: #F5F5F7; + --bg-radial: radial-gradient(circle at 50% 20%, #FFFFFF 0%, #F5F5F7 100%); + --glass-bg: rgba(255, 255, 255, 0.82); + --glass-border: rgba(0, 0, 0, 0.08); + --glass-hover-border: rgba(0, 0, 0, 0.18); + --glass-shadow: 0 12px 32px rgba(0, 0, 0, 0.05); + --accent-primary: #007143; + --accent-cyan: #0284C7; + --accent-mint: #059669; + --accent-blue: #2563EB; + --text-main: #1D1D1F; + --text-muted: #6E6E73; + --text-sub: #86868B; + --pill-bg: rgba(0, 0, 0, 0.04); + --segmented-bg: rgba(0, 0, 0, 0.04); + --map-style: amap://styles/light; +} + +/* Elegant Subscript Typographic Tuning for H₂ */ +sub, .h2-sub { + font-size: 0.7em; + line-height: 0; + vertical-align: -0.22em; + font-family: 'JetBrains Mono', -apple-system, sans-serif; + font-weight: 700; + margin-left: 0.5px; + opacity: 0.9; +} + +/* -------------------------------------------------------------------------- + GLOBAL RESET & RESTRAINED GLASS WRAPPER + -------------------------------------------------------------------------- */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + user-select: none; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Helvetica Neue", Arial, sans-serif; + background: var(--bg-app); + background-image: var(--bg-radial); + color: var(--text-main); + height: 100vh; + width: 100vw; + overflow: hidden; + -webkit-font-smoothing: antialiased; + transition: background 0.3s ease, color 0.3s ease; +} + +.liquid-cockpit-wrapper { + display: flex; + flex-direction: column; + height: 100vh; + padding: 12px 16px; + gap: 12px; +} + +/* Restrained Apple Glass Panel */ +.glass-panel { + background: var(--glass-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--glass-border); + border-radius: 16px; + box-shadow: var(--glass-shadow); + transition: border-color 0.25s ease, background 0.25s ease; +} + +.glass-panel:hover { + border-color: var(--glass-hover-border); +} + +/* -------------------------------------------------------------------------- + 1. NAVIGATION HEADER (PROMINENT LOGO) + -------------------------------------------------------------------------- */ +.liquid-header { + display: flex; + align-items: center; + justify-content: space-between; + height: 56px; + padding: 0 18px; + flex-shrink: 0; +} + +.header-left { + display: flex; + align-items: center; + gap: 16px; +} + +.brand-block { + display: flex; + align-items: center; +} + +/* Enlarged Lingniu Logo */ +.brand-logo-svg { + height: 34px; + width: auto; + color: var(--text-main); + transition: transform 0.2s ease; +} + +.brand-logo-svg:hover { + transform: scale(1.03); +} + +.brand-divider { + width: 1px; + height: 22px; + background: var(--glass-border); +} + +.cockpit-title-wrap { + display: flex; + align-items: center; + gap: 8px; +} + +.cockpit-title { + font-size: 16px; + font-weight: 600; + letter-spacing: -0.3px; + color: var(--text-main); +} + +.de-tag { + font-size: 9px; + padding: 1px 6px; + background: var(--pill-bg); + border-radius: 4px; + color: var(--text-muted); + font-weight: 500; +} + +/* Header KPI Capsules */ +.header-kpi-group { + display: flex; + align-items: center; + gap: 10px; +} + +.kpi-glass-capsule { + display: flex; + flex-direction: column; + padding: 4px 14px; + background: var(--pill-bg); + border: 1px solid var(--glass-border); + border-radius: 10px; +} + +.capsule-lbl { + font-size: 9px; + color: var(--text-muted); +} + +.capsule-val { + font-family: 'JetBrains Mono', -apple-system, sans-serif; + font-size: 14px; + font-weight: 700; + color: var(--text-main); + line-height: 1.15; +} + +.capsule-val small { + font-size: 10px; + font-weight: 400; + color: var(--text-muted); +} + +.trend-pill { + font-size: 9px; + font-style: normal; + font-weight: 600; + color: var(--accent-cyan); + margin-left: 2px; +} + +/* Header Controls */ +.header-controls { + display: flex; + align-items: center; + gap: 10px; +} + +.glass-segmented-control, .theme-switcher-bw { + display: flex; + background: var(--segmented-bg); + padding: 2px; + border-radius: 8px; + border: 1px solid var(--glass-border); +} + +.segment-btn, .bw-btn { + display: flex; + align-items: center; + gap: 4px; + padding: 4px 12px; + background: transparent; + border: none; + border-radius: 6px; + color: var(--text-muted); + font-size: 11px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.segment-btn.active, .bw-btn.active { + background: var(--glass-bg); + color: var(--text-main); + font-weight: 600; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); +} + +.bw-svg { + stroke: currentColor; +} + +.time-widget { + font-family: 'JetBrains Mono', monospace; + font-size: 12px; + font-weight: 600; + color: var(--text-muted); +} + +/* -------------------------------------------------------------------------- + 2. MAIN BODY (CENTER MAP CANVAS + RIGHT SIDEBAR) + -------------------------------------------------------------------------- */ +.cockpit-main-grid { + display: grid; + grid-template-columns: 1fr 320px; + gap: 12px; + flex: 1; + min-height: 0; +} + +/* SPATIAL MAP CONTAINER */ +.map-spatial-container { + position: relative; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.map-top-bar { + position: absolute; + top: 12px; + left: 12px; + right: 12px; + display: flex; + align-items: center; + justify-content: space-between; + z-index: 10; + pointer-events: none; +} + +.map-top-bar * { pointer-events: auto; } + +.status-glass-pill { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 12px; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: 20px; + font-size: 11px; + color: var(--text-main); +} + +.pulse-ring { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent-cyan); +} + +.map-legend-glass { + display: flex; + gap: 10px; + padding: 4px 10px; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: 8px; + font-size: 10px; + color: var(--text-muted); +} + +.leg-item { + display: flex; + align-items: center; + gap: 4px; +} + +.dot { + width: 5px; + height: 5px; + border-radius: 50%; +} + +.dot-primary { background: var(--accent-cyan); } +.dot-mid { background: var(--accent-blue); } +.dot-low { background: var(--text-sub); } + +.amap-canvas-box { + flex: 1; + position: relative; + width: 100%; + height: 100%; +} + +#amapContainer { + width: 100%; + height: 100%; +} + +.map-action-controls { + position: absolute; + bottom: 24px; + right: 14px; + display: flex; + gap: 6px; + z-index: 10; +} + +.glass-btn { + padding: 4px 10px; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: 8px; + color: var(--text-main); + font-size: 10px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.glass-btn:hover { + border-color: var(--text-muted); +} + +.map-bottom-info { + height: 24px; + background: rgba(0, 0, 0, 0.15); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 12px; + font-size: 10px; + color: var(--text-muted); + border-top: 1px solid var(--glass-border); +} + +/* RIGHT SIDEBAR */ +.sidebar-operations { + display: flex; + flex-direction: column; + gap: 12px; + min-height: 0; +} + +.sidebar-card { + display: flex; + flex-direction: column; + padding: 12px 14px; + overflow: hidden; +} + +.flex-fill-auto { flex: 1; min-height: 0; } + +.panel-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; + padding-bottom: 6px; + border-bottom: 1px solid var(--glass-border); +} + +.panel-title { + font-size: 12px; + font-weight: 600; + color: var(--text-main); +} + +.panel-tag { + font-size: 9px; + padding: 1px 5px; + background: var(--pill-bg); + border-radius: 4px; + color: var(--text-muted); + font-family: 'JetBrains Mono', monospace; +} + +/* Card 1: Clean Status Cells */ +.status-glass-grid3 { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 6px; + margin-bottom: 8px; +} + +.glass-cell { + padding: 6px 8px; + background: rgba(255, 255, 255, 0.02); + border: 1px solid var(--glass-border); + border-radius: 8px; +} + +.cell-num { + font-family: 'JetBrains Mono', monospace; + font-size: 14px; + font-weight: 700; + color: var(--text-main); + line-height: 1.1; +} + +.cell-num small { + font-size: 9px; + color: var(--text-muted); +} + +.cell-label { + display: flex; + align-items: center; + gap: 3px; + font-size: 9px; + color: var(--text-muted); + margin-top: 2px; +} + +.status-dot { + display: inline-block; + width: 5px; + height: 5px; + border-radius: 50%; +} + +.dot-mint { background: var(--accent-mint); } +.dot-blue { background: var(--accent-blue); } +.dot-sub { background: var(--text-sub); } + +.liquid-progress-bar { + display: flex; + height: 4px; + border-radius: 2px; + overflow: hidden; + background: rgba(0, 0, 0, 0.2); +} + +.seg { height: 100%; } +.seg-running { background: var(--accent-mint); } +.seg-stopped { background: var(--accent-blue); } +.seg-offline { background: var(--text-sub); } + +/* Card 2: TOP Ranking List */ +.glass-tab-control { + display: flex; + gap: 2px; + background: var(--segmented-bg); + padding: 1px; + border-radius: 6px; +} + +.gtab { + padding: 2px 6px; + background: transparent; + border: none; + border-radius: 4px; + font-size: 9px; + color: var(--text-muted); + cursor: pointer; +} + +.gtab.active { + background: var(--glass-bg); + color: var(--text-main); + font-weight: 600; +} + +.liquid-ranking-list { + display: flex; + flex-direction: column; + gap: 5px; + overflow-y: auto; + flex: 1; +} + +.rank-glass-row { + display: flex; + align-items: center; + gap: 10px; + padding: 6px 10px; + border-radius: 8px; + font-size: 11px; + background: rgba(255, 255, 255, 0.01); + border: 1px solid var(--glass-border); + transition: all 0.15s ease; + cursor: pointer; +} + +.rank-glass-row:hover { + background: var(--pill-bg); + border-color: var(--glass-hover-border); +} + +.r-badge { + width: 18px; + height: 18px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + font-size: 9.5px; + font-family: 'JetBrains Mono', monospace; + flex-shrink: 0; +} + +/* Scoped Theme Ranking Badge Colors for High Contrast in Both Modes */ +/* 1. Dark Mode Ranking Badges */ +.theme-dark .r-badge.r-top1 { background: #38BDF8; color: #0A0B0E; font-weight: 800; } +.theme-dark .r-badge.r-top2 { background: rgba(56, 189, 248, 0.2); color: #38BDF8; border: 1px solid rgba(56, 189, 248, 0.3); } +.theme-dark .r-badge.r-top3 { background: rgba(255, 255, 255, 0.1); color: #CBD5E1; } +.theme-dark .r-badge { background: rgba(255, 255, 255, 0.06); color: #64748B; } + +/* 2. Light Mode Ranking Badges (Clear High-Contrast Blue/Green/Dark Gray) */ +.theme-white .r-badge.r-top1 { background: #007143; color: #FFFFFF; font-weight: 800; } +.theme-white .r-badge.r-top2 { background: #0284C7; color: #FFFFFF; font-weight: 700; } +.theme-white .r-badge.r-top3 { background: rgba(0, 0, 0, 0.08); color: #1D1D1F; font-weight: 700; } +.theme-white .r-badge { background: rgba(0, 0, 0, 0.04); color: #86868B; } + +.r-name { + font-weight: 500; + color: var(--text-main); + flex: 1; +} + +.r-val { + font-family: 'JetBrains Mono', monospace; + font-weight: 600; + color: var(--text-main); +} + +/* Custom Scrollbars */ +::-webkit-scrollbar { width: 3px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--glass-border); border-radius: 3px; } + +/* AMAP MAP PROVINCE BUOYS */ +.amap-logo, .amap-copyright { + opacity: 0.2 !important; + filter: invert(0.9) hue-rotate(180deg) !important; +} + +.province-info-badge-wrap { + position: relative; + z-index: 10; +} + +.province-info-badge-wrap:hover { + z-index: 999 !important; +} + +.province-info-card { + display: inline-flex; + flex-direction: column; + padding: 3px 8px; + background: var(--glass-bg); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid var(--glass-border); + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + cursor: pointer; + white-space: nowrap; + transition: transform 0.2s ease; +} + +.province-info-badge-wrap:hover .province-info-card { + transform: scale(1.15); + border-color: var(--accent-cyan) !important; +} + +.p-head { + display: flex; + align-items: center; + gap: 4px; +} + +.p-dot { + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--accent-cyan); +} + +.p-name { + font-size: 10px; + font-weight: 500; + color: var(--text-main); +} + +.p-total { + font-family: 'JetBrains Mono', monospace; + font-size: 12px; + font-weight: 700; + color: var(--accent-cyan); + margin-left: 2px; +} + +.p-sub { + font-size: 8px; + color: var(--text-sub); + margin-top: 1px; +}