Spaces:
Sleeping
Sleeping
| /** | |
| * src/utils/color.js | |
| * Color conversion utilities for CSS → Figma RGB (0-1 range). | |
| */ | |
| const NAMED_COLORS = { | |
| aliceblue: '#f0f8ff', | |
| antiquewhite: '#faebd7', | |
| aqua: '#00ffff', | |
| aquamarine: '#7fffd4', | |
| azure: '#f0ffff', | |
| beige: '#f5f5dc', | |
| bisque: '#ffe4c4', | |
| black: '#000000', | |
| blanchedalmond: '#ffebcd', | |
| blue: '#0000ff', | |
| blueviolet: '#8a2be2', | |
| brown: '#a52a2a', | |
| burlywood: '#deb887', | |
| cadetblue: '#5f9ea0', | |
| chartreuse: '#7fff00', | |
| chocolate: '#d2691e', | |
| coral: '#ff7f50', | |
| cornflowerblue: '#6495ed', | |
| cornsilk: '#fff8dc', | |
| crimson: '#dc143c', | |
| cyan: '#00ffff', | |
| darkblue: '#00008b', | |
| darkcyan: '#008b8b', | |
| darkgoldenrod: '#b8860b', | |
| darkgray: '#a9a9a9', | |
| darkgreen: '#006400', | |
| darkgrey: '#a9a9a9', | |
| darkkhaki: '#bdb76b', | |
| darkmagenta: '#8b008b', | |
| darkolivegreen: '#556b2f', | |
| darkorange: '#ff8c00', | |
| darkorchid: '#9932cc', | |
| darkred: '#8b0000', | |
| darksalmon: '#e9967a', | |
| darkseagreen: '#8fbc8f', | |
| darkslate50: '#483d8b', | |
| darkslateblue: '#483d8b', | |
| darkslategray: '#2f4f4f', | |
| darkslategrey: '#2f4f4f', | |
| darkturquoise: '#00ced1', | |
| darkviolet: '#9400d3', | |
| deeppink: '#ff1493', | |
| deepskyblue: '#00bfff', | |
| dimgray: '#696969', | |
| dimgrey: '#696969', | |
| dodgerblue: '#1e90ff', | |
| firebrick: '#b22222', | |
| floralwhite: '#fffaf0', | |
| forestgreen: '#228b22', | |
| fuchsia: '#ff00ff', | |
| gainsboro: '#dcdcdc', | |
| ghostwhite: '#f8f8ff', | |
| gold: '#ffd700', | |
| goldenrod: '#daa520', | |
| gray: '#808080', | |
| green: '#008000', | |
| greenyellow: '#adff2f', | |
| grey: '#808080', | |
| honeydew: '#f0fff0', | |
| hotpink: '#ff69b4', | |
| indianred: '#cd5c5c', | |
| indigo: '#4b0082', | |
| ivory: '#fffff0', | |
| khaki: '#f0e68c', | |
| lavender: '#e6e6fa', | |
| lavenderblush: '#fff0f5', | |
| lawngreen: '#7cfc00', | |
| lemonchiffon: '#fffacd', | |
| lightblue: '#add8e6', | |
| lightcoral: '#f08080', | |
| lightcyan: '#e0ffff', | |
| lightgoldenrodyellow: '#fafad2', | |
| lightgray: '#d3d3d3', | |
| lightgreen: '#90ee90', | |
| lightgrey: '#d3d3d3', | |
| lightpink: '#ffb6c1', | |
| lightsalmon: '#ffa07a', | |
| lightseagreen: '#20b2aa', | |
| lightskyblue: '#87cefa', | |
| lightslategray: '#778899', | |
| lightslategrey: '#778899', | |
| lightsteelblue: '#b0c4de', | |
| lightyellow: '#ffffe0', | |
| lime: '#00ff00', | |
| limegreen: '#32cd32', | |
| linen: '#faf0e6', | |
| magenta: '#ff00ff', | |
| maroon: '#800000', | |
| mediumaquamarine: '#66cdaa', | |
| mediumblue: '#0000cd', | |
| mediumorchid: '#ba55d3', | |
| mediumpurple: '#9370db', | |
| mediumseagreen: '#3cb371', | |
| mediumslateblue: '#7b68ee', | |
| mediumspringgreen: '#00fa9a', | |
| mediumturquoise: '#48d1cc', | |
| mediumvioletred: '#c71585', | |
| midnightblue: '#191970', | |
| mintcream: '#f5fffa', | |
| mistyrose: '#ffe4e1', | |
| moccasin: '#ffe4b5', | |
| navajowhite: '#ffdead', | |
| navy: '#000080', | |
| oldlace: '#fdf5e6', | |
| olive: '#808000', | |
| olivedrab: '#6b8e23', | |
| orange: '#ffa500', | |
| orangered: '#ff4500', | |
| orchid: '#da70d6', | |
| palegoldenrod: '#eee8aa', | |
| palegreen: '#98fb98', | |
| paleturquoise: '#afeeee', | |
| palevioletred: '#db7093', | |
| papayawhip: '#ffefd5', | |
| peachpuff: '#ffdab9', | |
| peru: '#cd853f', | |
| pink: '#ffc0cb', | |
| plum: '#dda0dd', | |
| powderblue: '#b0e0e6', | |
| purple: '#800080', | |
| rebeccapurple: '#663399', | |
| red: '#ff0000', | |
| rosybrown: '#bc8f8f', | |
| royalblue: '#4169e1', | |
| saddlebrown: '#8b4513', | |
| salmon: '#fa8072', | |
| sandybrown: '#f4a460', | |
| seagreen: '#2e8b57', | |
| seashell: '#fff5ee', | |
| sienna: '#a0522d', | |
| silver: '#c0c0c0', | |
| skyblue: '#87ceeb', | |
| slateblue: '#6a5acd', | |
| slategray: '#708090', | |
| slategrey: '#708090', | |
| snow: '#fffafa', | |
| springgreen: '#00ff7f', | |
| steelblue: '#4682b4', | |
| tan: '#d2b48c', | |
| teal: '#008080', | |
| thistle: '#d8bfd8', | |
| tomato: '#ff6347', | |
| turquoise: '#40e0d0', | |
| violet: '#ee82ee', | |
| wheat: '#f5deb3', | |
| white: '#ffffff', | |
| whitesmoke: '#f5f5f5', | |
| yellow: '#ffff00', | |
| yellowgreen: '#9acd32', | |
| transparent: '#00000000', | |
| }; | |
| /** | |
| * Convert hex color to Figma RGBA object. | |
| * Supports: #rgb, #rgba, #rrggbb, #rrggbbaa | |
| * @param {string} hex - e.g. "#c9a84c" or "#fff" | |
| * @returns {{ r: number, g: number, b: number, a: number }} | |
| */ | |
| export function hexToFigmaRGB(hex) { | |
| const clean = hex.replace('#', ''); | |
| if (clean.length === 3 || clean.length === 4) { | |
| const r = parseInt(clean[0] + clean[0], 16) / 255; | |
| const g = parseInt(clean[1] + clean[1], 16) / 255; | |
| const b = parseInt(clean[2] + clean[2], 16) / 255; | |
| const a = clean.length === 4 ? parseInt(clean[3] + clean[3], 16) / 255 : 1; | |
| return { r, g, b, a }; | |
| } | |
| const r = parseInt(clean.substring(0, 2), 16) / 255; | |
| const g = parseInt(clean.substring(2, 4), 16) / 255; | |
| const b = parseInt(clean.substring(4, 6), 16) / 255; | |
| const a = clean.length === 8 ? parseInt(clean.substring(6, 8), 16) / 255 : 1; | |
| return { r, g, b, a }; | |
| } | |
| /** | |
| * Convert CSS rgba() string to Figma RGBA. | |
| * Supports standard commas syntax, modern spaces syntax, and percentages. | |
| * @param {string} rgba - e.g. "rgba(201, 168, 76, 0.3)" or "rgba(200 255 0 / 0.5)" | |
| * @returns {{ r: number, g: number, b: number, a: number }} | |
| */ | |
| export function rgbaStringToFigma(rgba) { | |
| const normalized = rgba.trim().toLowerCase(); | |
| const matches = normalized.match(/[\d.]+%?/g) || []; | |
| if (matches.length < 3) { | |
| return { r: 0, g: 0, b: 0, a: 1 }; | |
| } | |
| const parseComponent = (val, max) => { | |
| if (val.endsWith('%')) { | |
| return (parseFloat(val) / 100) * max; | |
| } | |
| return parseFloat(val); | |
| }; | |
| const rVal = parseComponent(matches[0], 255); | |
| const gVal = parseComponent(matches[1], 255); | |
| const bVal = parseComponent(matches[2], 255); | |
| let aVal = 1; | |
| if (matches.length >= 4) { | |
| const aPart = matches[3]; | |
| if (aPart.endsWith('%')) { | |
| aVal = parseFloat(aPart) / 100; | |
| } else { | |
| aVal = parseFloat(aPart); | |
| } | |
| } | |
| return { | |
| r: Math.max(0, Math.min(255, rVal)) / 255, | |
| g: Math.max(0, Math.min(255, gVal)) / 255, | |
| b: Math.max(0, Math.min(255, bVal)) / 255, | |
| a: Math.max(0, Math.min(1, aVal)) | |
| }; | |
| } | |
| /** | |
| * Convert HSL HSL/HSLA color values to RGB. | |
| */ | |
| function hslToRgb(h, s, l) { | |
| h = h % 360; | |
| if (h < 0) h += 360; | |
| s = Math.max(0, Math.min(100, s)) / 100; | |
| l = Math.max(0, Math.min(100, l)) / 100; | |
| const c = (1 - Math.abs(2 * l - 1)) * s; | |
| const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); | |
| const m = l - c / 2; | |
| let r = 0, g = 0, b = 0; | |
| if (h >= 0 && h < 60) { | |
| r = c; g = x; b = 0; | |
| } else if (h >= 60 && h < 120) { | |
| r = x; g = c; b = 0; | |
| } else if (h >= 120 && h < 180) { | |
| r = 0; g = c; b = x; | |
| } else if (h >= 180 && h < 240) { | |
| r = 0; g = x; b = c; | |
| } else if (h >= 240 && h < 300) { | |
| r = x; g = 0; b = c; | |
| } else if (h >= 300 && h < 360) { | |
| r = c; g = 0; b = x; | |
| } | |
| return { | |
| r: r + m, | |
| g: g + m, | |
| b: b + m | |
| }; | |
| } | |
| /** | |
| * Convert HSL/HSLA string to Figma RGBA. | |
| */ | |
| export function hslStringToFigma(hslStr) { | |
| const normalized = hslStr.trim().toLowerCase(); | |
| const matches = normalized.match(/[\d.]+(?:%|deg)?/g) || []; | |
| if (matches.length < 3) { | |
| return { r: 0, g: 0, b: 0, a: 1 }; | |
| } | |
| const hVal = parseFloat(matches[0]); | |
| const sVal = parseFloat(matches[1]); | |
| const lVal = parseFloat(matches[2]); | |
| let aVal = 1; | |
| if (matches.length >= 4) { | |
| const aPart = matches[3]; | |
| if (aPart.endsWith('%')) { | |
| aVal = parseFloat(aPart) / 100; | |
| } else { | |
| aVal = parseFloat(aPart); | |
| } | |
| } | |
| const rgb = hslToRgb(hVal, sVal, lVal); | |
| return { | |
| r: rgb.r, | |
| g: rgb.g, | |
| b: rgb.b, | |
| a: Math.max(0, Math.min(1, aVal)) | |
| }; | |
| } | |
| /** | |
| * Parse any CSS color string → Figma RGBA. | |
| * Handles: hex, rgba(), rgb(), hsl(), hsla(), named colors | |
| */ | |
| export function cssColorToFigma(color) { | |
| if (!color) { | |
| return { r: 0, g: 0, b: 0, a: 0 }; | |
| } | |
| const clean = color.trim().toLowerCase(); | |
| if (clean === 'transparent' || clean === 'none') { | |
| return { r: 0, g: 0, b: 0, a: 0 }; | |
| } | |
| if (clean.startsWith('#')) { | |
| const parsed = hexToFigmaRGB(clean); | |
| return { r: parsed.r, g: parsed.g, b: parsed.b, a: parsed.a ?? 1 }; | |
| } | |
| if (clean.startsWith('rgb')) { | |
| return rgbaStringToFigma(clean); | |
| } | |
| if (clean.startsWith('hsl')) { | |
| return hslStringToFigma(clean); | |
| } | |
| if (NAMED_COLORS[clean]) { | |
| const hex = NAMED_COLORS[clean]; | |
| if (hex === '#00000000') { | |
| return { r: 0, g: 0, b: 0, a: 0 }; | |
| } | |
| const parsed = hexToFigmaRGB(hex); | |
| return { r: parsed.r, g: parsed.g, b: parsed.b, a: parsed.a ?? 1 }; | |
| } | |
| // Fallback | |
| return { r: 0, g: 0, b: 0, a: 1 }; | |
| } | |
| /** | |
| * Build a Figma solid paint object. | |
| */ | |
| export function solidPaint(cssColor, opacity = 1) { | |
| const { r, g, b, a } = cssColorToFigma(cssColor); | |
| return { | |
| type: 'SOLID', | |
| color: { r, g, b }, | |
| opacity: opacity * a, | |
| }; | |
| } | |