Skip to content

Commit 85c8d1b

Browse files
ericyangpanclaude
andcommitted
feat: optimize brand icon and add color conversion utility
- Simplify icon.svg from 100x100 to 512x512 for better quality - Remove complex DOCTYPE entities, use direct values - Convert OKLCH gradient colors to HEX (#2b7fff, #f6339a) - Add PNG version of icon for broader compatibility - Add oklch-to-hex.mjs utility script for color conversions The optimized SVG is cleaner and more maintainable while preserving the gradient effect and bracket-star design. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 46a09fc commit 85c8d1b

File tree

3 files changed

+151
-134
lines changed

3 files changed

+151
-134
lines changed

public/icon.png

17.9 KB
Loading

public/icon.svg

Lines changed: 12 additions & 134 deletions
Loading

scripts/oklch-to-hex.mjs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* Convert OKLCH color to HEX
3+
* OKLCH -> OKLab -> Linear RGB -> sRGB -> HEX
4+
*/
5+
6+
// Convert OKLCH to OKLab
7+
function oklchToOklab(l, c, h) {
8+
const hRad = (h * Math.PI) / 180;
9+
const a = c * Math.cos(hRad);
10+
const b = c * Math.sin(hRad);
11+
return [l, a, b];
12+
}
13+
14+
// Convert OKLab to Linear RGB
15+
// Using the correct OKLab to LMS to Linear RGB conversion
16+
function oklabToLinearRgb(l, a, b) {
17+
// Convert OKLab to LMS (non-linear)
18+
const l_ = l + 0.3963377774 * a + 0.2158037573 * b;
19+
const m_ = l - 0.1055613458 * a - 0.0638541728 * b;
20+
const s_ = l - 0.0894841775 * a - 1.2914855480 * b;
21+
22+
// Apply non-linearity (cube)
23+
const l_cubed = l_ ** 3;
24+
const m_cubed = m_ ** 3;
25+
const s_cubed = s_ ** 3;
26+
27+
// Convert LMS to Linear RGB
28+
return [
29+
+4.0767416621 * l_cubed - 3.3077115913 * m_cubed + 0.2309699292 * s_cubed,
30+
-1.2684380046 * l_cubed + 2.6097574011 * m_cubed - 0.3413193965 * s_cubed,
31+
-0.0041960863 * l_cubed - 0.7034186147 * m_cubed + 1.7076147010 * s_cubed,
32+
];
33+
}
34+
35+
// Convert Linear RGB to sRGB (gamma correction)
36+
function linearRgbToSrgb(r, g, b) {
37+
const toSRGB = (c) => {
38+
if (c <= 0.0031308) {
39+
return 12.92 * c;
40+
}
41+
return 1.055 * Math.pow(c, 1.0 / 2.4) - 0.055;
42+
};
43+
44+
return [toSRGB(r), toSRGB(g), toSRGB(b)];
45+
}
46+
47+
// Clamp and convert to 0-255
48+
function toByte(value) {
49+
return Math.max(0, Math.min(255, Math.round(value * 255)));
50+
}
51+
52+
// Convert RGB to HEX
53+
function rgbToHex(r, g, b) {
54+
const toHex = (n) => {
55+
const hex = n.toString(16);
56+
return hex.length === 1 ? "0" + hex : hex;
57+
};
58+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
59+
}
60+
61+
// Main conversion function
62+
function oklchToHex(oklchString) {
63+
// Parse oklch(62.3% 0.214 259.815)
64+
const match = oklchString.match(/oklch\(([\d.]+)%\s+([\d.]+)\s+([\d.]+)\)/);
65+
if (!match) {
66+
throw new Error(`Invalid OKLCH format: ${oklchString}`);
67+
}
68+
69+
const l = parseFloat(match[1]) / 100; // Convert percentage to 0-1
70+
const c = parseFloat(match[2]);
71+
const h = parseFloat(match[3]);
72+
73+
// Convert OKLCH to OKLab
74+
const [labL, labA, labB] = oklchToOklab(l, c, h);
75+
76+
// Convert OKLab to Linear RGB
77+
const [linearR, linearG, linearB] = oklabToLinearRgb(labL, labA, labB);
78+
79+
// Convert Linear RGB to sRGB
80+
const [srgbR, srgbG, srgbB] = linearRgbToSrgb(linearR, linearG, linearB);
81+
82+
// Clamp sRGB values to valid range [0, 1]
83+
const clampedR = Math.max(0, Math.min(1, srgbR));
84+
const clampedG = Math.max(0, Math.min(1, srgbG));
85+
const clampedB = Math.max(0, Math.min(1, srgbB));
86+
87+
// Convert to bytes
88+
const r = toByte(clampedR);
89+
const g = toByte(clampedG);
90+
const b = toByte(clampedB);
91+
92+
// Convert to HEX
93+
return rgbToHex(r, g, b);
94+
}
95+
96+
// Test the conversion with debugging
97+
const color1 = "oklch(62.3% 0.214 259.815)";
98+
const color2 = "oklch(65.6% 0.241 354.308)";
99+
100+
console.log(`Converting: ${color1}`);
101+
const hex1 = oklchToHex(color1);
102+
console.log(`Result: ${hex1}`);
103+
104+
// Parse and show intermediate values for debugging
105+
const match1 = color1.match(/oklch\(([\d.]+)%\s+([\d.]+)\s+([\d.]+)\)/);
106+
if (match1) {
107+
const l = parseFloat(match1[1]) / 100;
108+
const c = parseFloat(match1[2]);
109+
const h = parseFloat(match1[3]);
110+
const [labL, labA, labB] = oklchToOklab(l, c, h);
111+
const [linearR, linearG, linearB] = oklabToLinearRgb(labL, labA, labB);
112+
const [srgbR, srgbG, srgbB] = linearRgbToSrgb(linearR, linearG, linearB);
113+
console.log(` OKLab: L=${labL.toFixed(4)}, a=${labA.toFixed(4)}, b=${labB.toFixed(4)}`);
114+
console.log(` Linear RGB: R=${linearR.toFixed(4)}, G=${linearG.toFixed(4)}, B=${linearB.toFixed(4)}`);
115+
console.log(` sRGB: R=${srgbR.toFixed(4)}, G=${srgbG.toFixed(4)}, B=${srgbB.toFixed(4)}`);
116+
}
117+
118+
console.log(`\nConverting: ${color2}`);
119+
const hex2 = oklchToHex(color2);
120+
console.log(`Result: ${hex2}`);
121+
122+
// Parse and show intermediate values for debugging
123+
const match2 = color2.match(/oklch\(([\d.]+)%\s+([\d.]+)\s+([\d.]+)\)/);
124+
if (match2) {
125+
const l = parseFloat(match2[1]) / 100;
126+
const c = parseFloat(match2[2]);
127+
const h = parseFloat(match2[3]);
128+
const [labL, labA, labB] = oklchToOklab(l, c, h);
129+
const [linearR, linearG, linearB] = oklabToLinearRgb(labL, labA, labB);
130+
const [srgbR, srgbG, srgbB] = linearRgbToSrgb(linearR, linearG, linearB);
131+
console.log(` OKLab: L=${labL.toFixed(4)}, a=${labA.toFixed(4)}, b=${labB.toFixed(4)}`);
132+
console.log(` Linear RGB: R=${linearR.toFixed(4)}, G=${linearG.toFixed(4)}, B=${linearB.toFixed(4)}`);
133+
console.log(` sRGB: R=${srgbR.toFixed(4)}, G=${srgbG.toFixed(4)}, B=${srgbB.toFixed(4)}`);
134+
}
135+
136+
console.log(`\nSVG gradient colors:`);
137+
console.log(` <stop offset="0" stop-color="${hex1}" />`);
138+
console.log(` <stop offset="1" stop-color="${hex2}" />`);
139+

0 commit comments

Comments
 (0)