From ce25530938995b02592d2fca5b7e653d9a45140a Mon Sep 17 00:00:00 2001 From: zhangyan Date: Wed, 25 Mar 2026 19:47:39 +0800 Subject: [PATCH] Refine compass and ruler overlay --- communication-guidelines.md | 96 +++++++++ miniprogram/engine/map/mapEngine.ts | 22 ++- miniprogram/pages/map/map.ts | 294 +++++++++++++++++++++++++++- miniprogram/pages/map/map.wxml | 22 ++- miniprogram/pages/map/map.wxss | 114 ++++++++++- 5 files changed, 534 insertions(+), 14 deletions(-) create mode 100644 communication-guidelines.md diff --git a/communication-guidelines.md b/communication-guidelines.md new file mode 100644 index 0000000..eda02fb --- /dev/null +++ b/communication-guidelines.md @@ -0,0 +1,96 @@ +# 沟通协作建议 + +这份文档用于约定后续在 UI 微调、交互细改、规则补充时,怎样沟通最有效,减少来回修改。 + +## 1. 需求描述的推荐格式 + +后续尽量按下面 4 个点描述需求: + +### 改动对象 +明确指出这次只改什么。 + +例如: +- 比例尺和指北针顶部数字之间的距离 +- 某个按钮的高亮状态 +- 某条提示文案 + +### 不要改 +明确指出哪些相邻元素不要动。 + +例如: +- 不要改顶部数字和小箭头之间的距离 +- 不要动比例尺刻度算法 +- 不要改地图引擎逻辑 + +### 目标效果 +说明你想达到什么视觉或交互结果。 + +例如: +- 更靠近一点 +- 改成 4 到 5 像素的空隙 +- 只在可点击时高亮 + +### 验证标准 +说明你用什么标准判断“改对了”。 + +例如: +- 看起来贴近,但不要重叠 +- 指北针仍然盖住比例尺 +- 缩放时要实时变化,不要手势结束后再跳 + +## 2. 推荐的需求表达模板 + +可以直接按这个模板发: + +```text +改动对象: +不要改: +目标: +验证标准: +``` + +例如: + +```text +改动对象:比例尺和顶部角度数字之间的距离 +不要改:顶部角度数字和小箭头之间的距离 +目标:再近一点 +验证标准:看起来大约 4~5px,不重叠 +``` + +## 3. 为什么这样最有效 + +很多来回修改,通常不是功能做不了,而是: + +- 一次需求里混了两层甚至三层改动 +- 没说清楚“哪块不要动” +- 验收标准只有感觉,没有边界 + +一旦把“改什么”和“不要改什么”拆开,误改概率会明显下降。 + +## 4. 开发执行的约定 + +后续默认按下面方式执行: + +- 先复述这次只改哪一层 +- 真实执行时只改这一层 +- 不顺手带改别的部分 +- 改完明确说明: + - 改了什么 + - 没改什么 + +## 5. 最适合哪些场景 + +这套方式尤其适合: + +- UI 间距微调 +- 按钮状态和图标切换 +- HUD 显示调整 +- 地图表现修正 +- 规则字段补充 + +## 6. 一句话原则 + +后续最有效的协作方式是: + +**需求把边界说死,修改一次只动一层。** diff --git a/miniprogram/engine/map/mapEngine.ts b/miniprogram/engine/map/mapEngine.ts index 44cc5e1..7ea63b6 100644 --- a/miniprogram/engine/map/mapEngine.ts +++ b/miniprogram/engine/map/mapEngine.ts @@ -23,7 +23,7 @@ const RENDER_MODE = 'Single WebGL Pipeline' const PROJECTION_MODE = 'WGS84 -> WorldTile -> Camera -> Screen' const MAP_NORTH_OFFSET_DEG = 0 let MAGNETIC_DECLINATION_DEG = -6.91 -let MAGNETIC_DECLINATION_TEXT = '6.91掳 W' +let MAGNETIC_DECLINATION_TEXT = '6.91˚ W' const MIN_ZOOM = 15 const MAX_ZOOM = 20 const DEFAULT_ZOOM = 17 @@ -129,6 +129,7 @@ export interface MapEngineViewState { tileTranslateX: number tileTranslateY: number tileSizePx: number + previewScale: number stageWidth: number stageHeight: number stageLeft: number @@ -232,6 +233,8 @@ const VIEW_SYNC_KEYS: Array = [ 'mapName', 'configStatusText', 'zoom', + 'centerTileX', + 'centerTileY', 'rotationDeg', 'rotationText', 'rotationMode', @@ -263,6 +266,11 @@ const VIEW_SYNC_KEYS: Array = [ 'networkFetchCount', 'cacheHitRateText', 'tileSizePx', + 'previewScale', + 'stageWidth', + 'stageHeight', + 'stageLeft', + 'stageTop', 'statusText', 'gpsTracking', 'gpsTrackingText', @@ -393,12 +401,16 @@ function formatRotationText(rotationDeg: number): string { return `${Math.round(normalizeRotationDeg(rotationDeg))}deg` } +function normalizeDegreeDisplayText(text: string): string { + return text.replace(/[°掳•]/g, '˚') +} + function formatHeadingText(headingDeg: number | null): string { if (headingDeg === null) { return '--' } - return `${Math.round(normalizeRotationDeg(headingDeg))}掳` + return `${Math.round(normalizeRotationDeg(headingDeg))}˚` } function formatDevicePoseText(pose: 'upright' | 'tilted' | 'flat'): string { @@ -1011,6 +1023,7 @@ export class MapEngine { tileTranslateX: 0, tileTranslateY: 0, tileSizePx: 0, + previewScale: 1, stageWidth: 0, stageHeight: 0, stageLeft: 0, @@ -2114,7 +2127,7 @@ export class MapEngine { applyRemoteMapConfig(config: RemoteMapConfig): void { MAGNETIC_DECLINATION_DEG = config.magneticDeclinationDeg - MAGNETIC_DECLINATION_TEXT = config.magneticDeclinationText + MAGNETIC_DECLINATION_TEXT = normalizeDegreeDisplayText(config.magneticDeclinationText) this.minZoom = config.minZoom this.maxZoom = config.maxZoom this.defaultZoom = config.defaultZoom @@ -3168,6 +3181,9 @@ export class MapEngine { this.previewScale = scale this.previewOriginX = originX this.previewOriginY = originY + this.setState({ + previewScale: scale, + }, true) } resetPreviewState(): void { diff --git a/miniprogram/pages/map/map.ts b/miniprogram/pages/map/map.ts index 549cff3..96687b5 100644 --- a/miniprogram/pages/map/map.ts +++ b/miniprogram/pages/map/map.ts @@ -18,11 +18,24 @@ type CompassLabelData = { radius: number className: string } +type ScaleRulerMinorTickData = { + key: string + topPx: number + long: boolean +} +type ScaleRulerMajorMarkData = { + key: string + topPx: number + label: string +} type SideButtonMode = 'all' | 'left' | 'right' | 'hidden' type SideActionButtonState = 'muted' | 'default' | 'active' +type CenterScaleRulerAnchorMode = 'screen-center' | 'compass-center' type MapPageData = MapEngineViewState & { showDebugPanel: boolean showGameInfoPanel: boolean + showCenterScaleRuler: boolean + centerScaleRulerAnchorMode: CenterScaleRulerAnchorMode statusBarHeight: number topInsetHeight: number hudPanelIndex: number @@ -45,12 +58,23 @@ type MapPageData = MapEngineViewState & { sideButton2Class: string sideButton4Class: string sideButton11Class: string + sideButton13Class: string + sideButton14Class: string sideButton16Class: string + centerScaleRulerVisible: boolean + centerScaleRulerCenterXPx: number + centerScaleRulerZeroYPx: number + centerScaleRulerHeightPx: number + centerScaleRulerAxisBottomPx: number + centerScaleRulerZeroVisible: boolean + centerScaleRulerZeroLabel: string + centerScaleRulerMinorTicks: ScaleRulerMinorTickData[] + centerScaleRulerMajorMarks: ScaleRulerMajorMarkData[] showLeftButtonGroup: boolean showRightButtonGroups: boolean showBottomDebugButton: boolean } -const INTERNAL_BUILD_VERSION = 'map-build-232' +const INTERNAL_BUILD_VERSION = 'map-build-252' const CLASSIC_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/classic-sequential.json' const SCORE_O_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/score-o.json' let mapEngine: MapEngine | null = null @@ -132,7 +156,7 @@ function getSideActionButtonClass(state: SideActionButtonState): string { return 'map-side-button map-side-button--default' } -function buildSideButtonState(data: Pick) { +function buildSideButtonState(data: Pick) { const sideButton2State: SideActionButtonState = !data.gpsLockAvailable ? 'muted' : data.gpsLockEnabled @@ -140,6 +164,12 @@ function buildSideButtonState(data: Pick= 1000) { + const distanceKm = distanceMeters / 1000 + const formatted = distanceKm >= 10 ? distanceKm.toFixed(0) : distanceKm.toFixed(1) + return `${formatted.replace(/\.0$/, '')} km` + } + + return `${Math.round(distanceMeters)} m` +} + +function buildCenterScaleRulerPatch(data: Pick) { + if (!data.showCenterScaleRuler) { + return { + centerScaleRulerVisible: false, + centerScaleRulerCenterXPx: 0, + centerScaleRulerZeroYPx: 0, + centerScaleRulerHeightPx: 0, + centerScaleRulerAxisBottomPx: 0, + centerScaleRulerZeroVisible: false, + centerScaleRulerZeroLabel: '0 m', + centerScaleRulerMinorTicks: [] as ScaleRulerMinorTickData[], + centerScaleRulerMajorMarks: [] as ScaleRulerMajorMarkData[], + } + } + + if (!data.stageWidth || !data.stageHeight) { + return { + centerScaleRulerVisible: false, + centerScaleRulerCenterXPx: 0, + centerScaleRulerZeroYPx: 0, + centerScaleRulerHeightPx: 0, + centerScaleRulerAxisBottomPx: 0, + centerScaleRulerZeroVisible: false, + centerScaleRulerZeroLabel: '0 m', + centerScaleRulerMinorTicks: [] as ScaleRulerMinorTickData[], + centerScaleRulerMajorMarks: [] as ScaleRulerMajorMarkData[], + } + } + + const topPadding = 12 + const rpxUnitPx = getRpxUnitInPx() + const compassBottomPaddingPx = 248 * rpxUnitPx + const compassDialRadiusPx = (196 * rpxUnitPx) / 2 + const compassHeadingOverlayHeightPx = 40 * rpxUnitPx + const compassOcclusionPaddingPx = 10 * rpxUnitPx + const zeroYPx = data.centerScaleRulerAnchorMode === 'compass-center' + ? Math.round(data.stageHeight - compassBottomPaddingPx - compassDialRadiusPx) + : Math.round(data.stageHeight / 2) + const fallbackHeight = Math.max(zeroYPx - topPadding, 160) + const coveredBottomPx = data.centerScaleRulerAnchorMode === 'compass-center' + ? Math.round(compassDialRadiusPx + compassHeadingOverlayHeightPx + compassOcclusionPaddingPx) + : 0 + + if ( + !data.tileSizePx + || !Number.isFinite(data.zoom) + || !Number.isFinite(data.centerTileY) + ) { + return { + centerScaleRulerVisible: true, + centerScaleRulerCenterXPx: Math.round(data.stageWidth / 2), + centerScaleRulerZeroYPx: zeroYPx, + centerScaleRulerHeightPx: fallbackHeight, + centerScaleRulerAxisBottomPx: coveredBottomPx, + centerScaleRulerZeroVisible: data.centerScaleRulerAnchorMode !== 'compass-center', + centerScaleRulerZeroLabel: '0 m', + centerScaleRulerMinorTicks: [] as ScaleRulerMinorTickData[], + centerScaleRulerMajorMarks: [] as ScaleRulerMajorMarkData[], + } + } + + const centerLat = worldTileYToLat(data.centerTileY + 0.5, data.zoom) + const metersPerTile = Math.cos(centerLat * Math.PI / 180) * 40075016.686 / Math.pow(2, data.zoom) + const metersPerPixel = metersPerTile / data.tileSizePx + const effectivePreviewScale = Number.isFinite(data.previewScale) && data.previewScale > 0 ? data.previewScale : 1 + const effectiveMetersPerPixel = metersPerPixel / effectivePreviewScale + const rulerHeight = Math.floor(zeroYPx - topPadding) + + if (!Number.isFinite(effectiveMetersPerPixel) || effectiveMetersPerPixel <= 0 || rulerHeight < 120) { + return { + centerScaleRulerVisible: true, + centerScaleRulerCenterXPx: Math.round(data.stageWidth / 2), + centerScaleRulerZeroYPx: zeroYPx, + centerScaleRulerHeightPx: Math.max(rulerHeight, fallbackHeight), + centerScaleRulerAxisBottomPx: coveredBottomPx, + centerScaleRulerZeroVisible: data.centerScaleRulerAnchorMode !== 'compass-center', + centerScaleRulerZeroLabel: '0 m', + centerScaleRulerMinorTicks: [] as ScaleRulerMinorTickData[], + centerScaleRulerMajorMarks: [] as ScaleRulerMajorMarkData[], + } + } + + const labelDistanceMeters = getNiceDistanceMeters(effectiveMetersPerPixel * 80) + const minorDistanceMeters = labelDistanceMeters / 8 + const minorStepPx = minorDistanceMeters / effectiveMetersPerPixel + const visibleTopLimitPx = rulerHeight - coveredBottomPx + const minorTicks: ScaleRulerMinorTickData[] = [] + const majorMarks: ScaleRulerMajorMarkData[] = [] + + for (let index = 1; index <= 200; index += 1) { + const topPx = Math.round(rulerHeight - index * minorStepPx) + if (topPx < 0) { + break + } + if (topPx >= visibleTopLimitPx) { + continue + } + + const isHalfMajor = index % 4 === 0 + const isLabelMajor = index % 8 === 0 + minorTicks.push({ + key: `minor-${index}`, + topPx, + long: isHalfMajor, + }) + + if (isLabelMajor) { + majorMarks.push({ + key: `major-${index}`, + topPx, + label: formatScaleDistanceLabel((index / 8) * labelDistanceMeters), + }) + } + } + + return { + centerScaleRulerVisible: true, + centerScaleRulerCenterXPx: Math.round(data.stageWidth / 2), + centerScaleRulerZeroYPx: zeroYPx, + centerScaleRulerHeightPx: rulerHeight, + centerScaleRulerAxisBottomPx: coveredBottomPx, + centerScaleRulerZeroVisible: data.centerScaleRulerAnchorMode !== 'compass-center', + centerScaleRulerZeroLabel: '0 m', + centerScaleRulerMinorTicks: minorTicks, + centerScaleRulerMajorMarks: majorMarks, + } +} + function buildEmptyGameInfoSnapshot(): MapEngineGameInfoSnapshot { return { title: '当前游戏', @@ -170,10 +372,12 @@ Page({ data: { showDebugPanel: false, showGameInfoPanel: false, + showCenterScaleRuler: false, statusBarHeight: 0, topInsetHeight: 12, hudPanelIndex: 0, configSourceText: '顺序赛配置', + centerScaleRulerAnchorMode: 'screen-center', gameInfoTitle: '当前游戏', gameInfoSubtitle: '未开始', gameInfoLocalRows: [], @@ -246,12 +450,23 @@ Page({ mapPulseFxClass: '', stageFxVisible: false, stageFxClass: '', + centerScaleRulerVisible: false, + centerScaleRulerCenterXPx: 0, + centerScaleRulerZeroYPx: 0, + centerScaleRulerHeightPx: 0, + centerScaleRulerAxisBottomPx: 0, + centerScaleRulerZeroVisible: false, + centerScaleRulerZeroLabel: '0 m', + centerScaleRulerMinorTicks: [], + centerScaleRulerMajorMarks: [], compassTicks: buildCompassTicks(), compassLabels: buildCompassLabels(), ...buildSideButtonVisibility('left'), ...buildSideButtonState({ sideButtonMode: 'left', showGameInfoPanel: false, + showCenterScaleRuler: false, + centerScaleRulerAnchorMode: 'screen-center', skipButtonEnabled: false, gameSessionStatus: 'idle', gpsLockEnabled: false, @@ -298,6 +513,7 @@ Page({ this.setData({ ...nextData, + ...buildCenterScaleRulerPatch(mergedData), ...buildSideButtonState(mergedData), }) @@ -391,11 +607,24 @@ Page({ ...buildSideButtonState({ sideButtonMode: 'left', showGameInfoPanel: false, + showCenterScaleRuler: false, + centerScaleRulerAnchorMode: 'screen-center', skipButtonEnabled: false, gameSessionStatus: 'idle', gpsLockEnabled: false, gpsLockAvailable: false, }), + ...buildCenterScaleRulerPatch({ + ...(mapEngine.getInitialData() as MapPageData), + showCenterScaleRuler: false, + centerScaleRulerAnchorMode: 'screen-center', + stageWidth: 0, + stageHeight: 0, + topInsetHeight: Math.max(statusBarHeight + 12, menuButtonBottom + 20), + zoom: 0, + centerTileY: 0, + tileSizePx: 0, + }), }) }, @@ -807,10 +1036,19 @@ Page({ } const snapshot = mapEngine.getGameInfoSnapshot() + const localRows = snapshot.localRows.concat([ + { label: '比例尺开关', value: this.data.showCenterScaleRuler ? '开启' : '关闭' }, + { label: '比例尺锚点', value: this.data.centerScaleRulerAnchorMode === 'compass-center' ? '指北针圆心' : '屏幕中心' }, + { label: '比例尺可见', value: this.data.centerScaleRulerVisible ? 'true' : 'false' }, + { label: '比例尺中心X', value: `${this.data.centerScaleRulerCenterXPx}px` }, + { label: '比例尺零点Y', value: `${this.data.centerScaleRulerZeroYPx}px` }, + { label: '比例尺高度', value: `${this.data.centerScaleRulerHeightPx}px` }, + { label: '比例尺主刻度数', value: String(this.data.centerScaleRulerMajorMarks.length) }, + ]) this.setData({ gameInfoTitle: snapshot.title, gameInfoSubtitle: snapshot.subtitle, - gameInfoLocalRows: snapshot.localRows, + gameInfoLocalRows: localRows, gameInfoGlobalRows: snapshot.globalRows, }) }, @@ -823,6 +1061,8 @@ Page({ ...buildSideButtonState({ sideButtonMode: this.data.sideButtonMode, showGameInfoPanel: true, + showCenterScaleRuler: this.data.showCenterScaleRuler, + centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode, skipButtonEnabled: this.data.skipButtonEnabled, gameSessionStatus: this.data.gameSessionStatus, gpsLockEnabled: this.data.gpsLockEnabled, @@ -837,6 +1077,8 @@ Page({ ...buildSideButtonState({ sideButtonMode: this.data.sideButtonMode, showGameInfoPanel: false, + showCenterScaleRuler: this.data.showCenterScaleRuler, + centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode, skipButtonEnabled: this.data.skipButtonEnabled, gameSessionStatus: this.data.gameSessionStatus, gpsLockEnabled: this.data.gpsLockEnabled, @@ -878,6 +1120,8 @@ Page({ ...buildSideButtonState({ sideButtonMode: nextMode, showGameInfoPanel: this.data.showGameInfoPanel, + showCenterScaleRuler: this.data.showCenterScaleRuler, + centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode, skipButtonEnabled: this.data.skipButtonEnabled, gameSessionStatus: this.data.gameSessionStatus, gpsLockEnabled: this.data.gpsLockEnabled, @@ -909,6 +1153,8 @@ Page({ ...buildSideButtonState({ sideButtonMode: this.data.sideButtonMode, showGameInfoPanel: false, + showCenterScaleRuler: this.data.showCenterScaleRuler, + centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode, skipButtonEnabled: this.data.skipButtonEnabled, gameSessionStatus: this.data.gameSessionStatus, gpsLockEnabled: this.data.gpsLockEnabled, @@ -923,6 +1169,8 @@ Page({ ...buildSideButtonState({ sideButtonMode: this.data.sideButtonMode, showGameInfoPanel: this.data.showGameInfoPanel, + showCenterScaleRuler: this.data.showCenterScaleRuler, + centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode, skipButtonEnabled: this.data.skipButtonEnabled, gameSessionStatus: this.data.gameSessionStatus, gpsLockEnabled: this.data.gpsLockEnabled, @@ -931,6 +1179,46 @@ Page({ }) }, + handleToggleCenterScaleRuler() { + const nextEnabled = !this.data.showCenterScaleRuler + this.data.showCenterScaleRuler = nextEnabled + const mergedData = { + ...this.data, + showCenterScaleRuler: nextEnabled, + } as MapPageData + + this.setData({ + showCenterScaleRuler: nextEnabled, + ...buildCenterScaleRulerPatch(mergedData), + ...buildSideButtonState(mergedData), + }) + }, + + handleToggleCenterScaleRulerAnchor() { + if (!this.data.showCenterScaleRuler) { + return + } + + const nextAnchorMode: CenterScaleRulerAnchorMode = this.data.centerScaleRulerAnchorMode === 'screen-center' + ? 'compass-center' + : 'screen-center' + this.data.centerScaleRulerAnchorMode = nextAnchorMode + const mergedData = { + ...this.data, + centerScaleRulerAnchorMode: nextAnchorMode, + } as MapPageData + + this.setData({ + centerScaleRulerAnchorMode: nextAnchorMode, + ...buildCenterScaleRulerPatch(mergedData), + ...buildSideButtonState(mergedData), + }) + + if (this.data.showGameInfoPanel) { + this.syncGameInfoPanelSnapshot() + } + }, + handleDebugPanelTap() {}, }) diff --git a/miniprogram/pages/map/map.wxml b/miniprogram/pages/map/map.wxml index d3ccf8b..76d2d35 100644 --- a/miniprogram/pages/map/map.wxml +++ b/miniprogram/pages/map/map.wxml @@ -37,11 +37,25 @@ + + + + + + {{centerScaleRulerZeroLabel}} + + {{item.label}} + + - {{sensorHeadingText}} - + + {{sensorHeadingText}} + + + + @@ -93,8 +107,8 @@ 12 - 13 - 14 + 13 + 14 15 diff --git a/miniprogram/pages/map/map.wxss b/miniprogram/pages/map/map.wxss index 225a87f..016a07e 100644 --- a/miniprogram/pages/map/map.wxss +++ b/miniprogram/pages/map/map.wxss @@ -97,6 +97,86 @@ z-index: 4; } +.map-stage__overlay-center-layer { + position: absolute; + inset: 0; + pointer-events: none; + z-index: 4; +} + +.center-scale-ruler { + position: absolute; + width: 220rpx; + transform: translate(-50%, -100%); + pointer-events: none; + z-index: 12; +} + +.center-scale-ruler__axis { + position: absolute; + left: 50%; + top: 0; + bottom: 0; + width: 4rpx; + margin-left: -2rpx; + border-radius: 999rpx; + background: rgba(19, 20, 18, 0.92); +} + +.center-scale-ruler__arrow { + position: absolute; + left: 50%; + top: -34rpx; + width: 0; + height: 0; + margin-left: -14rpx; + border-left: 14rpx solid transparent; + border-right: 14rpx solid transparent; + border-bottom: 34rpx solid rgba(19, 20, 18, 0.96); +} + +.center-scale-ruler__tick { + position: absolute; + left: 50%; + width: 18rpx; + height: 4rpx; + margin-left: -9rpx; + margin-top: -2rpx; + border-radius: 999rpx; + background: rgba(19, 20, 18, 0.82); +} + +.center-scale-ruler__tick--major { + width: 32rpx; + margin-left: -16rpx; + height: 5rpx; + margin-top: -2.5rpx; + background: rgba(12, 14, 12, 0.96); +} + +.center-scale-ruler__tick--zero { + width: 34rpx; + margin-left: -17rpx; +} + +.center-scale-ruler__label { + position: absolute; + left: 50%; + margin-left: 18rpx; + transform: translateY(-50%); + font-size: 22rpx; + line-height: 1; + font-weight: 800; + color: rgba(19, 20, 18, 0.96); + text-shadow: 0 1rpx 0 rgba(255, 255, 255, 0.28); + white-space: nowrap; +} + +.center-scale-ruler__label--zero { + transform: translateY(-24%); + font-size: 24rpx; +} + .map-stage__topbar { display: flex; align-items: flex-start; @@ -329,14 +409,38 @@ align-items: center; gap: 6rpx; flex-shrink: 0; + position: relative; + z-index: 20; +} + +.compass-widget__heading-wrap { + display: flex; + flex-direction: column; + align-items: center; + gap: 0; + position: relative; + z-index: 21; } .compass-widget__heading { - font-size: 14rpx; + min-width: 96rpx; + padding: 6rpx 12rpx; + font-size: 20rpx; line-height: 1; font-weight: 600; color: rgba(32, 42, 34, 0.72); text-shadow: 0 1rpx 0 rgba(255, 255, 255, 0.35); + text-align: center; +} + +.compass-widget__edge-arrow-wrap { + display: flex; + align-items: center; + justify-content: center; + width: 40rpx; + height: 20rpx; + margin-top: -2rpx; + margin-bottom: -4rpx; } .compass-widget__dial { @@ -519,8 +623,6 @@ .compass-widget__edge-arrow { width: 0; height: 0; - margin-top: -2rpx; - margin-bottom: -4rpx; border-left: 8rpx solid transparent; border-right: 8rpx solid transparent; border-top: 14rpx solid rgba(58, 49, 37, 0.72); @@ -530,12 +632,16 @@ .compass-widget__hint { max-width: 196rpx; - font-size: 14rpx; + padding: 4rpx 10rpx; + border-radius: 999rpx; + background: rgba(219, 238, 212, 0.94); + font-size: 16rpx; line-height: 1.3; color: #d62828; text-align: center; font-weight: 700; text-shadow: 0 1rpx 0 rgba(255, 255, 255, 0.24); + box-shadow: 0 2rpx 8rpx rgba(22, 48, 32, 0.08); } .race-panel-swiper { position: absolute;