diff --git a/src/components/EffortPanel/EffortPanel.tsx b/src/components/EffortPanel/EffortPanel.tsx
index 3006b8bcd..6846868ad 100644
--- a/src/components/EffortPanel/EffortPanel.tsx
+++ b/src/components/EffortPanel/EffortPanel.tsx
@@ -123,10 +123,16 @@ export function EffortPanel({ appStateEffort, onDone }: Props): React.ReactNode
Effort
{envActive && {`⚠ CLAUDE_CODE_EFFORT_LEVEL=${envRaw} overrides this session`}}
- {rippleActive ? : }
-
- ←/→ adjust · Enter confirm · Esc cancel
-
+ {rippleActive ? (
+
+ ) : (
+ <>
+
+
+ ←/→ adjust · Enter confirm · Esc cancel
+
+ >
+ )}
);
}
@@ -218,6 +224,11 @@ function RippleContent({ renderRow, cursor }: RippleContentProps): React.ReactNo
x: segmentTextStartX(cursorIdx, SUBLABEL_ULTRACODE.length),
color: COLOR_LABEL_DEFAULT,
};
+ const hintOverlay: Overlay = {
+ text: '←/→ adjust · Enter confirm · Esc cancel',
+ x: 0,
+ color: COLOR_LABEL_DEFAULT,
+ };
// 各行 y 坐标(相对震源 RIPPLE_SOURCE_Y = 档位名行)
// y=-3: Faster/Smarter
@@ -225,6 +236,7 @@ function RippleContent({ renderRow, cursor }: RippleContentProps): React.ReactNo
// y=-1: ▲
// y=0: 档位名(震源)
// y=1: 副标签
+ // y=2: 快捷键行(y 方向延伸覆盖到底部)
return (
<>
@@ -232,6 +244,7 @@ function RippleContent({ renderRow, cursor }: RippleContentProps): React.ReactNo
+
>
);
}
diff --git a/src/components/EffortPanel/__tests__/rippleAnimation.test.ts b/src/components/EffortPanel/__tests__/rippleAnimation.test.ts
index 6de044b0c..8407a2cc8 100644
--- a/src/components/EffortPanel/__tests__/rippleAnimation.test.ts
+++ b/src/components/EffortPanel/__tests__/rippleAnimation.test.ts
@@ -11,11 +11,11 @@ import {
describe('intensityToColor', () => {
test('intensity=0 → 最暗档(不再是 transparent,作面板底色)', () => {
- expect(intensityToColor(0)).toBe('#0a0d1a')
+ expect(intensityToColor(0)).toBe('#1a1f3a')
})
test('intensity < 0 钳到 0 → 最暗档', () => {
- expect(intensityToColor(-0.5)).toBe('#0a0d1a')
+ expect(intensityToColor(-0.5)).toBe('#1a1f3a')
})
test('intensity > 0 → 永远是 #hex 颜色字符串(不返回 transparent)', () => {
@@ -95,17 +95,26 @@ describe('computeRippleCells', () => {
).toEqual([])
})
- test('震源点处颜色为最亮档(dist=0,falloff=1,intensity 高)', () => {
- const cells = computeRippleCells({
+ test('震源点 time=0 时为波谷(最暗档),time 推进后出现亮档', () => {
+ // dist=0,time=0 时 phase = -0 = 0,sin(0)=0 → wave=0 → intensity=0 → 最暗档
+ const t0 = computeRippleCells({
y: 5,
width: 11,
time: 0,
sourceX: 5,
sourceY: 5,
})
- // 震源在 (5,5),dist=0,falloff=1,dist<6 触发高频涟漪叠加
- // 波峰附近颜色应较高档(非最暗)
- expect(cells[5].color).not.toBe('#0a0d1a')
+ expect(t0[5].color).toBe('#1a1f3a')
+
+ // time 推进,phase 变化,震源会扫过波峰
+ const t1 = computeRippleCells({
+ y: 5,
+ width: 11,
+ time: 1500,
+ sourceX: 5,
+ sourceY: 5,
+ })
+ expect(t1[5].color).not.toBe('#1a1f3a')
})
test('覆盖半径扩大:dist=65(左侧远端)仍有非最暗颜色', () => {
@@ -132,7 +141,7 @@ describe('computeRippleCells', () => {
sourceX: 65,
sourceY: 0,
})
- const nonDarkest = t1.filter(c => c.color !== '#0a0d1a')
+ const nonDarkest = t1.filter(c => c.color !== '#1a1f3a')
expect(nonDarkest.length).toBeGreaterThan(0)
})
diff --git a/src/components/EffortPanel/rippleAnimation.ts b/src/components/EffortPanel/rippleAnimation.ts
index b560ba5b2..97e4c9da7 100644
--- a/src/components/EffortPanel/rippleAnimation.ts
+++ b/src/components/EffortPanel/rippleAnimation.ts
@@ -20,14 +20,15 @@
* "暗紫蓝海洋"作为底色,波峰在底色上流动。这样波纹颜色变化更明显,
* 波谷也有暗色(不会"消失")。
*
- * 波峰最高升到 suggestion (#5769F7),避免与文字 overlay(也用 suggestion 系)
- * 同色互相吞噬。文字用更亮的高光色(#a3b5ff)保持对比。
+ * 最暗档用 #1a1f3a(紫黑,亮度 ~12%),不是纯黑——避免远端波谷
+ * 看起来像"硬黑边"。波峰最高升到 suggestion (#5769F7),避免与
+ * 文字 overlay(也用 suggestion 系)同色互相吞噬。
*/
const RIPPLE_COLOR_STOPS = [
- '#0a0d1a', // 0.00 ~ 0.14 — 最暗,波谷底色
- '#15182b', // 0.14 ~ 0.28
- '#1f2543', // 0.28 ~ 0.42
- '#2a3360', // 0.42 ~ 0.56
+ '#1a1f3a', // 0.00 ~ 0.14 — 最暗(紫黑底色,非纯黑)
+ '#1f2543', // 0.14 ~ 0.28
+ '#252c55', // 0.28 ~ 0.42
+ '#2e3870', // 0.42 ~ 0.56
'#3a4582', // 0.56 ~ 0.70
'#4a5bb0', // 0.70 ~ 0.84
'#5769F7', // 0.84 ~ 1.00 — suggestion (波峰)
@@ -37,7 +38,7 @@ const RIPPLE_COLOR_STOPS = [
* 强度(任意实数)→ 颜色字符串。
*
* 钳到 [0, 1],按 RIPPLE_COLOR_STOPS 分级。永不返回 transparent。
- * intensity=0 → 最暗档(#0a0d1a,作为面板底色)。
+ * intensity=0 → 最暗档(#1a1f3a,作为面板底色)。
*/
export function intensityToColor(intensity: number): string {
const v = intensity < 0 ? 0 : intensity > 1 ? 1 : intensity
@@ -98,18 +99,17 @@ const RIPPLE_BG_CHAR = ' '
/**
* 计算面板某一行 y 的完整波纹 cell 列表。
*
- * 波纹数学(v2 — 调慢 + 扩大覆盖):
+ * 波纹数学(v3 — 简化,移除震源高频涟漪):
* dx = x - sourceX
* dy = (y - sourceY) * 1.5 (y 方向视觉拉伸,行高 > 字宽)
* dist = sqrt(dx² + dy²)
* phase = dist * 0.35 - time * 0.004 (速度调慢至原 1/3)
* wave = max(0, sin(phase))
- * falloff = max(0, 1 - dist / 90) (覆盖半径扩到 90,让左侧 dist=65 也可见)
+ * falloff = max(0, 1 - dist / 90) (覆盖半径扩到 90)
* intensity = wave * falloff
- * 震源附近 (dist < 6):叠加高频涟漪 max(intensity, 0.5 + 0.5*sin(time*0.02 - dist*1.2))
*
* 每位置强度经 intensityToColor → 颜色字符串(永不 transparent),写入 cell。
- * 即使 intensity=0(波谷)也得到最暗档 #0a0d1a 作为面板底色。
+ * 即使 intensity=0(波谷)也得到最暗档 #1a1f3a 作为面板底色。
*
* @returns 长度严格等于 width 的 Cell 数组
*/
@@ -135,13 +135,7 @@ export function computeRippleCells(args: {
// 距离衰减(覆盖半径扩到 90:原 40)
const falloff = Math.max(0, 1 - dist / 90)
- let intensity = wave * falloff
-
- // 震源附近高频涟漪
- if (dist < 6) {
- const ripple = 0.5 + 0.5 * Math.sin(time * 0.02 - dist * 1.2)
- if (ripple > intensity) intensity = ripple
- }
+ const intensity = wave * falloff
cells[x] = {
char: RIPPLE_BG_CHAR,