import { describe, expect, it } from 'vitest'; import { rgb, wavePalette, WAVE_COLORS } from './wave-anim'; function hsl(color: string) { const [r, g, b] = rgb(color).map((v) => v / 255) as [number, number, number]; const max = Math.max(r, g, b); const min = Math.min(r, g, b); const c = max - min; const l = (max + min) / 2; const h = c === 0 ? 0 : max === r ? (g - b) / c : max === g ? (b - r) / c + 2 : (r - g) / c + 4; return { h: (h * 60 + 360) % 360, s: c === 0 ? 0 : c / (1 - Math.abs(2 * l - 1)), l }; } describe('wave cover palette', () => { it.each([ undefined, [], ['#fff'], ['#000', '#888', '#fff'], ['#010203', '#fefdfc', '#808182'], ['#ff0000', 'invalid', '#0000ff'], ])('uses the default palette for missing, neutral or malformed colors: %j', (colors) => { expect(wavePalette(colors)).toEqual(WAVE_COLORS); }); it('ignores neutral colors and preserves the strongest cover hue', () => { const colors = ['#ffffff', '#007733', '#222222']; const before = [...colors]; expect(hsl(wavePalette(colors)[0]).h).toBeCloseTo(hsl('#007733').h, 0); expect(wavePalette(colors)).toEqual(wavePalette(['#007733', '#222222', '#ffffff'])); expect(colors).toEqual(before); expect(wavePalette(['#fff', '#073', '#222'])).toEqual(wavePalette(colors)); }); it.each([ ['#040e08', '#18281d', '#374450'], // dark, muted cover like the reported case ['#ff0000', '#00ff00', '#0000ff'], // opposing hues must not mix to gray ['#ffff00', '#ffffff', '#000000'], ['#ff0080', '#540029', '#fefefe'], // hue offsets wrap around red ])('keeps a visible, saturated, harmonious palette for %j', (...colors) => { const palette = wavePalette(colors); const base = hsl(palette[0]); for (const color of palette) { expect(color).toMatch(/^#[\da-f]{6}$/i); const { h, s, l } = hsl(color); expect(s).toBeGreaterThanOrEqual(0.70); expect(s).toBeLessThanOrEqual(0.84); expect(l).toBeGreaterThanOrEqual(0.45); expect(l).toBeLessThanOrEqual(0.59); const distance = Math.abs(h - base.h); expect(Math.min(distance, 360 - distance)).toBeLessThan(29); } }); });