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,