Yo this is a classic one. the normalizeColorChannel function is the problem, but not exactly how you’re thinking. it’s because saturation and lightness are always percentages in HSL, even if you write hsl(200, 50, 75) instead of hsl(200, 50%, 75%). your normalizeColorChannel function only treats it as a percentage if it sees the % symbol. so when it gets 50 for saturation, it thinks it’s a 0-255 value and divides it by 255, which is wrong. it should be dividing by 100. you gotta make normalizeColorChannel smarter about what kind of channel it’s parsing. maybe pass in an isPercentageChannel flag.
function normalizeColorChannel(input, isPercentageChannel = false) {
const trimmed = input.trim();
const isPercentage = trimmed.endsWith('%') || isPercentageChannel;
const rawNum = parseFloat(trimmed);
if (Number.isNaN(rawNum)) return 0;
if (isPercentage) {
return Math.min(100, Math.max(0, rawNum)) / 100;
}
return Math.min(255, Math.max(0, rawNum)) / 255;
}
function parseHslString(hslStr) {
const parts = hslStr.replace(/hsla?\(|\)/gi, '').split(',');
if (parts.length < 3) return null;
const hue = parseFloat(parts[0]) % 360;
// Saturation and lightness are always percentages
const sat = normalizeColorChannel(parts[1], true);
const light = normalizeColorChannel(parts[2], true);
return { h: hue, s: sat, l: light };
}
Why:
The modulo operator (%) in JavaScript can return a negative result if the dividend is negative. For example, -30 % 360 is -30. This means a hue like -30 degrees would remain negative instead of being correctly mapped to 330 degrees, causing unpredictable shifts when used in color calculations that expect positive hue values.
The main problem is that normalizeColorChannel() is for RGB values, but you’re using it for HSL saturation and lightness. HSL s and l should be treated as percentages.
Also, % returns negative values for negative hues.
Fix it by using a separate percentage parser and proper hue normalization:
okay, so you’re seeing a few different things in there. it’s definitely tricky when the parsing logic for one color model bleeds into another. good catch on the hue normalization, that’s a classic one. the answer goes up later today!