{"record":{"id":"71e511f90e1f808b","repo":"iOfficeAI/OfficeCLI","slug":"invalid-color-value-original-hsl-saturation","errorCode":null,"errorMessage":"Invalid color value: '{original}'. HSL saturation/lightness must be expressed as a percentage (e.g. 50%).","messagePattern":"Invalid color value: '(.+?)'\\. HSL saturation/lightness must be expressed as a percentage \\(e\\.g\\. 50%\\)\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Core/ParseHelpers.cs","lineNumber":210,"sourceCode":"    {\n        token = token.Trim();\n        // Strip an optional `deg` suffix (CSS allows it; other angle units\n        // — turn/rad/grad — are out of scope for the input vocabulary we care\n        // about and would just need their own multipliers if asked for).\n        if (token.EndsWith(\"deg\", StringComparison.OrdinalIgnoreCase))\n            token = token[..^3].Trim();\n        if (!double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out var h))\n            throw new ArgumentException($\"Invalid color value: '{original}'. Hue must be a number in degrees.\");\n        h %= 360;\n        if (h < 0) h += 360;\n        return h;\n    }\n\n    private static double ParsePercent01(string token, string original)\n    {\n        token = token.Trim();\n        if (!token.EndsWith('%'))\n            throw new ArgumentException($\"Invalid color value: '{original}'. HSL saturation/lightness must be expressed as a percentage (e.g. 50%).\");\n        if (!double.TryParse(token[..^1], NumberStyles.Float, CultureInfo.InvariantCulture, out var pct)\n            || pct < 0 || pct > 100)\n            throw new ArgumentException($\"Invalid color value: '{original}'. HSL saturation/lightness must be 0%-100%.\");\n        return pct / 100.0;\n    }\n\n    private static (byte R, byte G, byte B) HslToRgb(double h, double s, double l)\n    {\n        // Standard HSL → RGB (CSS Color Module Level 3).\n        double c = (1 - Math.Abs(2 * l - 1)) * s;\n        double hp = h / 60.0;\n        double x = c * (1 - Math.Abs(hp % 2 - 1));\n        double r1 = 0, g1 = 0, b1 = 0;\n        if (hp < 1)      { r1 = c; g1 = x; b1 = 0; }\n        else if (hp < 2) { r1 = x; g1 = c; b1 = 0; }\n        else if (hp < 3) { r1 = 0; g1 = c; b1 = x; }\n        else if (hp < 4) { r1 = 0; g1 = x; b1 = c; }\n        else if (hp < 5) { r1 = x; g1 = 0; b1 = c; }","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/ParseHelpers.cs#L192-L228","documentation":"Thrown by ParsePercent01 when an HSL saturation or lightness token does not end with '%'. The library deliberately requires the percent sign for HSL S/L so that the meaning is unambiguous (CSS requires it too). A bare fraction like 0.5 is rejected even though it is numerically valid.","triggerScenarios":"Passing hsl(120,0.5,0.5) (omitting %); passing hsl(120,50,50) (treating S/L as 0-100 integers without a suffix).","commonSituations":"Programmers used to 0-1 fraction notation; building the hsl string from numeric variables without appending %; copy-paste from a source that used the fraction form.","solutions":["Always append % to saturation and lightness: hsl(120,50%,50%).","When interpolating, format with the suffix: $\"hsl({h},{s}%,{l}%\".","If you hold values as 0-1 fractions, multiply by 100 and add %."],"exampleFix":"// before\nvar c = $\"hsl({h},{s},{l}\")); // s,l are fractions 0..1\nParseColor(c); // throws 289\n\n// after\nvar c = $\"hsl({h},{s*100}%,{l*100}%\");\nParseColor(c);","handlingStrategy":"validation","validationCode":"// ensure S/L always carry '%'\nstatic string Pct(double f01) => $\"{Math.Clamp(f01,0,1)*100}%\";\n// build: $\"hsl({h},{Pct(s)},{Pct(l)})\"","typeGuard":"static bool IsPercentToken(string t) => t.Trim().EndsWith('%');","tryCatchPattern":"try { color = ParseColor(spec); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"expressed as a percentage\"))\n{ /* append % to S/L tokens, retry */ }","preventionTips":["Always append '%' to saturation and lightness.","When holding 0-1 fractions, multiply by 100 and add '%'.","Centralize hsl string formatting in one helper."],"tags":["color","validation","argument","hsl","format"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}