{"version":3,"file":"color-depth.d.ts","sourceRoot":"","sources":["../src/color-depth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,KAAK,GAAG,IAAI,CAAC;AAIpD,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,UAAU,CAyBjF;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AA6ED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAwBrG;AAED,wBAAgB,QAAQ,IAAI,MAAM,CAEjC","sourcesContent":["/**\n * Color depth detection and SGR emission.\n *\n * Covers cavekit-terminal-blend R7: emit truecolor when the terminal supports it,\n * 256-color when it doesn't, and 16-color as a last resort so sequences degrade\n * cleanly under tmux, screen, and linux-console.\n */\n\nexport type ColorDepth = \"truecolor\" | \"256\" | \"16\";\n\nlet cachedDepth: ColorDepth | undefined;\n\nexport function detectColorDepth(env: NodeJS.ProcessEnv = process.env): ColorDepth {\n\tif (cachedDepth !== undefined && env === process.env) return cachedDepth;\n\n\tconst colorterm = (env.COLORTERM || \"\").toLowerCase();\n\tconst term = (env.TERM || \"\").toLowerCase();\n\tconst tmux = Boolean(env.TMUX);\n\n\tlet depth: ColorDepth;\n\n\tif (term === \"linux\") {\n\t\tdepth = \"16\";\n\t} else if (colorterm === \"truecolor\" || colorterm === \"24bit\") {\n\t\t// tmux without explicit truecolor indicator downgrades to 256.\n\t\tdepth = tmux && term.includes(\"screen\") && !colorterm ? \"256\" : \"truecolor\";\n\t} else if (tmux && !colorterm) {\n\t\t// tmux without truecolor indicator: 256 max per R7.\n\t\tdepth = term.includes(\"256color\") ? \"256\" : \"16\";\n\t} else if (term.includes(\"256color\") || term.includes(\"256colour\")) {\n\t\tdepth = \"256\";\n\t} else {\n\t\tdepth = \"16\";\n\t}\n\n\tif (env === process.env) cachedDepth = depth;\n\treturn depth;\n}\n\nexport function resetColorDepthCache(): void {\n\tcachedDepth = undefined;\n}\n\nfunction parseHex(hex: string): { r: number; g: number; b: number } | null {\n\tif (!hex) return null;\n\tconst v = hex.startsWith(\"#\") ? hex.slice(1) : hex;\n\tif (!/^[0-9a-fA-F]{6}$/.test(v)) return null;\n\treturn {\n\t\tr: parseInt(v.slice(0, 2), 16),\n\t\tg: parseInt(v.slice(2, 4), 16),\n\t\tb: parseInt(v.slice(4, 6), 16),\n\t};\n}\n\n/** Convert 0-255 channel to 0-5 cube index (6x6x6 cube in 256-color). */\nfunction channelTo6(v: number): number {\n\tif (v < 48) return 0;\n\tif (v < 115) return 1;\n\treturn Math.floor((v - 35) / 40);\n}\n\n/** Map (r,g,b) 0-255 to an xterm 256-color index (16-231 cube or 232-255 grayscale). */\nfunction rgbTo256(r: number, g: number, b: number): number {\n\t// Prefer grayscale ramp if channels are close together.\n\tconst max = Math.max(r, g, b);\n\tconst min = Math.min(r, g, b);\n\tif (max - min < 8) {\n\t\tconst avg = (r + g + b) / 3;\n\t\tif (avg < 8) return 16; // black in cube\n\t\tif (avg > 248) return 231; // white in cube\n\t\treturn 232 + Math.min(23, Math.floor((avg - 8) / 10));\n\t}\n\tconst ri = channelTo6(r);\n\tconst gi = channelTo6(g);\n\tconst bi = channelTo6(b);\n\treturn 16 + 36 * ri + 6 * gi + bi;\n}\n\n/**\n * Standard 16-color ANSI palette (approximate RGB values used for nearest-match).\n * Base 0-7 map to SGR 30-37 (fg) / 40-47 (bg); bright 8-15 map to 90-97 / 100-107.\n */\nconst ANSI16: { r: number; g: number; b: number }[] = [\n\t{ r: 0, g: 0, b: 0 }, // black\n\t{ r: 205, g: 0, b: 0 }, // red\n\t{ r: 0, g: 205, b: 0 }, // green\n\t{ r: 205, g: 205, b: 0 }, // yellow\n\t{ r: 0, g: 0, b: 238 }, // blue\n\t{ r: 205, g: 0, b: 205 }, // magenta\n\t{ r: 0, g: 205, b: 205 }, // cyan\n\t{ r: 229, g: 229, b: 229 }, // white\n\t{ r: 127, g: 127, b: 127 }, // bright black\n\t{ r: 255, g: 0, b: 0 }, // bright red\n\t{ r: 0, g: 255, b: 0 }, // bright green\n\t{ r: 255, g: 255, b: 0 }, // bright yellow\n\t{ r: 92, g: 92, b: 255 }, // bright blue\n\t{ r: 255, g: 0, b: 255 }, // bright magenta\n\t{ r: 0, g: 255, b: 255 }, // bright cyan\n\t{ r: 255, g: 255, b: 255 }, // bright white\n];\n\nfunction rgbTo16(r: number, g: number, b: number): number {\n\tlet best = 0;\n\tlet bestDist = Infinity;\n\tfor (let i = 0; i < ANSI16.length; i++) {\n\t\tconst p = ANSI16[i];\n\t\tconst dr = r - p.r;\n\t\tconst dg = g - p.g;\n\t\tconst db = b - p.b;\n\t\tconst dist = dr * dr + dg * dg + db * db;\n\t\tif (dist < bestDist) {\n\t\t\tbestDist = dist;\n\t\t\tbest = i;\n\t\t}\n\t}\n\treturn best;\n}\n\n/**\n * Emit the SGR escape sequence to set the given color as foreground or background\n * at the given color depth. Returns \"\" for invalid input (empty/undefined hex).\n */\nexport function hexToSgr(hex: string | undefined | null, depth: ColorDepth, kind: \"fg\" | \"bg\"): string {\n\tif (!hex) return \"\";\n\tconst rgb = parseHex(hex);\n\tif (!rgb) return \"\";\n\n\tif (depth === \"truecolor\") {\n\t\tconst code = kind === \"fg\" ? 38 : 48;\n\t\treturn `\\x1b[${code};2;${rgb.r};${rgb.g};${rgb.b}m`;\n\t}\n\n\tif (depth === \"256\") {\n\t\tconst idx = rgbTo256(rgb.r, rgb.g, rgb.b);\n\t\tconst code = kind === \"fg\" ? 38 : 48;\n\t\treturn `\\x1b[${code};5;${idx}m`;\n\t}\n\n\t// 16-color\n\tconst idx = rgbTo16(rgb.r, rgb.g, rgb.b);\n\tif (idx < 8) {\n\t\tconst base = kind === \"fg\" ? 30 : 40;\n\t\treturn `\\x1b[${base + idx}m`;\n\t}\n\tconst base = kind === \"fg\" ? 90 : 100;\n\treturn `\\x1b[${base + (idx - 8)}m`;\n}\n\nexport function sgrReset(): string {\n\treturn \"\\x1b[0m\";\n}\n"]}