UNPKG

@poupe/theme-builder

Version:

Design token management and theme generation system for Poupe UI framework

1 lines 82.6 kB
{"version":3,"file":"index.mjs","names":["origColord","pairs","unsafeKeys","Hct"],"sources":["../src/core/utils.ts","../src/core/default-colors.ts","../src/core/types.ts","../src/core/colors.ts","../src/core/formatter.ts","../src/core/mix.ts","../src/core/palettes.ts","../src/core/states.ts","../src/core/index.ts","../src/theme/utils.ts","../src/theme/data.ts","../src/theme/colors.ts","../src/theme/palettes.ts","../src/theme/states.ts","../src/theme/theme.ts","../src/css/css.ts","../src/from-image.ts"],"sourcesContent":["/* re-export */\nexport {\n kebabCase,\n keys,\n pairs,\n unsafeKeys,\n} from '@poupe/css';\n\n/** Converts a number to an unsigned 32-bit integer */\nexport const uint32 = (n: number) => n >>> 0;\n\n/** Converts a number to an unsigned 8-bit integer */\nexport const uint8 = (n: number) => (n >>> 0) & 0xFF;\n\nexport const alphaFromArgb = (argb: number) => uint8(uint32(argb) >> 24);\nexport const redFromArgb = (argb: number) => uint8(uint32(argb) >> 16);\nexport const greenFromArgb = (argb: number) => uint8(uint32(argb) >> 8);\nexport const blueFromArgb = (argb: number) => uint8(uint32(argb));\n","import { type Color } from './types';\n\n/**\n * Complete collection of CSS named colors as defined in the CSS Color Module specifications.\n *\n * This object contains all standard CSS named colors organized by color family,\n * providing a comprehensive palette for color operations and conversions.\n *\n * @remarks\n * - All values are in hexadecimal format (#rrggbb or #rrggbbaa for transparent)\n * - Colors are grouped by visual similarity for easier navigation\n * - Includes all 147 CSS named colors from the CSS Color Module Level 4 specification\n * - Includes the transparent keyword for complete color support\n * - Some colors appear multiple times (e.g., cyan/aqua, fuchsia/magenta) as they represent the same color\n *\n * @example\n * ```typescript\n * const redColor = defaultColors.red; // '#ff0000'\n * const blueColor = defaultColors.blue; // '#0000ff'\n * ```\n */\nexport const defaultColors = {\n // Reds\n indianred: '#cd5c5c',\n lightcoral: '#f08080',\n salmon: '#fa8072',\n darksalmon: '#e9967a',\n crimson: '#dc143c',\n red: '#ff0000',\n firebrick: '#b22222',\n darkred: '#8b0000',\n\n // Pinks\n pink: '#ffc0cb',\n lightpink: '#ffb6c1',\n hotpink: '#ff69b4',\n deeppink: '#ff1493',\n mediumvioletred: '#c71585',\n palevioletred: '#db7093',\n\n // Oranges\n lightsalmon: '#ffa07a',\n coral: '#ff7f50',\n tomato: '#ff6347',\n orangered: '#ff4500',\n darkorange: '#ff8c00',\n orange: '#ffa500',\n\n // Yellows\n gold: '#ffd700',\n yellow: '#ffff00',\n lightyellow: '#ffffe0',\n lemonchiffon: '#fffacd',\n lightgoldenrodyellow: '#fafad2',\n papayawhip: '#ffefd5',\n moccasin: '#ffe4b5',\n peachpuff: '#ffdab9',\n palegoldenrod: '#eee8aa',\n khaki: '#f0e68c',\n darkkhaki: '#bdb76b',\n\n // Purples\n lavender: '#e6e6fa',\n thistle: '#d8bfd8',\n plum: '#dda0dd',\n violet: '#ee82ee',\n orchid: '#da70d6',\n fuchsia: '#ff00ff',\n magenta: '#ff00ff',\n mediumorchid: '#ba55d3',\n mediumpurple: '#9370db',\n rebeccapurple: '#663399',\n blueviolet: '#8a2be2',\n darkviolet: '#9400d3',\n darkorchid: '#9932cc',\n darkmagenta: '#8b008b',\n purple: '#800080',\n indigo: '#4b0082',\n slateblue: '#6a5acd',\n darkslateblue: '#483d8b',\n mediumslateblue: '#7b68ee',\n\n // Greens\n greenyellow: '#adff2f',\n chartreuse: '#7fff00',\n lawngreen: '#7cfc00',\n lime: '#00ff00',\n limegreen: '#32cd32',\n palegreen: '#98fb98',\n lightgreen: '#90ee90',\n mediumspringgreen: '#00fa9a',\n springgreen: '#00ff7f',\n mediumseagreen: '#3cb371',\n seagreen: '#2e8b57',\n forestgreen: '#228b22',\n green: '#008000',\n darkgreen: '#006400',\n yellowgreen: '#9acd32',\n olivedrab: '#6b8e23',\n olive: '#808000',\n darkolivegreen: '#556b2f',\n mediumaquamarine: '#66cdaa',\n darkseagreen: '#8fbc8f',\n lightseagreen: '#20b2aa',\n darkcyan: '#008b8b',\n teal: '#008080',\n\n // Blues/Cyans\n aqua: '#00ffff',\n cyan: '#00ffff',\n lightcyan: '#e0ffff',\n paleturquoise: '#afeeee',\n aquamarine: '#7fffd4',\n turquoise: '#40e0d0',\n mediumturquoise: '#48d1cc',\n darkturquoise: '#00ced1',\n cadetblue: '#5f9ea0',\n steelblue: '#4682b4',\n lightsteelblue: '#b0c4de',\n powderblue: '#b0e0e6',\n lightblue: '#add8e6',\n skyblue: '#87ceeb',\n lightskyblue: '#87cefa',\n deepskyblue: '#00bfff',\n dodgerblue: '#1e90ff',\n cornflowerblue: '#6495ed',\n royalblue: '#4169e1',\n blue: '#0000ff',\n mediumblue: '#0000cd',\n darkblue: '#00008b',\n navy: '#000080',\n midnightblue: '#191970',\n\n // Browns\n cornsilk: '#fff8dc',\n blanchedalmond: '#ffebcd',\n bisque: '#ffe4c4',\n navajowhite: '#ffdead',\n wheat: '#f5deb3',\n burlywood: '#deb887',\n tan: '#d2b48c',\n rosybrown: '#bc8f8f',\n sandybrown: '#f4a460',\n goldenrod: '#daa520',\n darkgoldenrod: '#b8860b',\n peru: '#cd853f',\n chocolate: '#d2691e',\n saddlebrown: '#8b4513',\n sienna: '#a0522d',\n brown: '#a52a2a',\n maroon: '#800000',\n\n // Whites\n white: '#ffffff',\n snow: '#fffafa',\n honeydew: '#f0fff0',\n mintcream: '#f5fffa',\n azure: '#f0ffff',\n aliceblue: '#f0f8ff',\n ghostwhite: '#f8f8ff',\n whitesmoke: '#f5f5f5',\n seashell: '#fff5ee',\n beige: '#f5f5dc',\n oldlace: '#fdf5e6',\n floralwhite: '#fffaf0',\n ivory: '#fffff0',\n antiquewhite: '#faebd7',\n linen: '#faf0e6',\n lavenderblush: '#fff0f5',\n mistyrose: '#ffe4e1',\n\n // Grays\n gainsboro: '#dcdcdc',\n lightgray: '#d3d3d3',\n lightgrey: '#d3d3d3',\n silver: '#c0c0c0',\n darkgray: '#a9a9a9',\n darkgrey: '#a9a9a9',\n gray: '#808080',\n grey: '#808080',\n dimgray: '#696969',\n dimgrey: '#696969',\n lightslategray: '#778899',\n lightslategrey: '#778899',\n slategray: '#708090',\n slategrey: '#708090',\n darkslategray: '#2f4f4f',\n darkslategrey: '#2f4f4f',\n black: '#000000',\n\n // Special colors\n transparent: '#00000000',\n};\n\n/**\n * Converts CSS named color strings to their hexadecimal equivalents.\n *\n * @param c - The color value to process. Can be any valid Color type.\n * @returns The hexadecimal color value if the input is a known CSS named color,\n * otherwise returns the original input unchanged.\n *\n * @example\n * ```typescript\n * withKnownColor('red'); // '#ff0000'\n * withKnownColor('blue'); // '#0000ff'\n * withKnownColor('#abc123'); // '#abc123' (unchanged)\n * withKnownColor('unknown'); // 'unknown' (unchanged)\n * ```\n *\n * @remarks\n * Only processes strings that contain only letters (both uppercase and lowercase).\n * Case-insensitive matching is performed by converting input to lowercase.\n */\nexport function withKnownColor(c: string): string;\nexport function withKnownColor(c: number): number;\nexport function withKnownColor(c: Color): Color;\nexport function withKnownColor(c: Color): Color {\n if (typeof c !== 'string' || !reOnlyLetters.test(c)) {\n return c;\n }\n\n const name = c.toLowerCase();\n if (name in defaultColors) {\n return defaultColors[name as keyof typeof defaultColors];\n }\n\n return c;\n};\n\n/**\n * Regular expression that matches strings containing only letters (uppercase and lowercase).\n * Used to identify potential CSS named color candidates.\n */\nconst reOnlyLetters = /^[a-zA-Z]+$/;\n","import {\n Hct,\n TonalPalette,\n} from '@poupe/material-color-utilities';\n\nimport {\n type AnyColor,\n Colord,\n type RgbColor,\n} from 'colord';\n\nexport {\n DynamicScheme,\n Hct,\n TonalPalette,\n Variant,\n} from '@poupe/material-color-utilities';\n\nexport {\n type AnyColor,\n Colord,\n type HslaColor,\n type HslColor,\n type RgbColor,\n} from 'colord';\n\nexport type ColorMap<K extends string> = Record<K, Hct>;\n\nexport type HexColor = `#${string}`;\n\n/** {@link RgbColor} variant with optional alpha value */\nexport type RgbaColor = RgbColor & { a?: number };\n\n/** destructured {@link Hct} color with optional alpha value */\nexport interface HctColor {\n a?: number\n c: number\n h: number\n t: number\n}\n\n/** ObjectColor represents a destructured Color object */\nexport type ObjectColor = Exclude<AnyColor, string> | HctColor;\n\n/** Color is any accepted color representation */\nexport type Color = Colord | Hct | number | ObjectColor | string;\n\n/**\n * Core Material Design 3 palettes for DynamicSchemeOptions.\n * Contains tonal palettes for the standard Material Design color roles.\n */\nexport type CorePalettes = {\n error?: TonalPalette\n neutral?: TonalPalette\n neutralVariant?: TonalPalette\n primary: TonalPalette\n secondary?: TonalPalette\n tertiary?: TonalPalette\n};\n\n/**\n * Type representing the valid keys for core palettes in the Material Design 3 color system.\n * Derived from the keys of the {@link CorePalettes} type, representing standard color roles.\n */\nexport type CorePaletteKey = keyof CorePalettes;\n\n/**\n * Readonly array of core palette keys used in Material Design 3 color system.\n * Represents the standard color roles that can be defined in a dynamic color scheme.\n */\nexport const corePaletteKeys: Readonly<CorePaletteKey[]> = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'neutral',\n 'neutralVariant',\n 'error',\n];\n\n/**\n * Represents a custom color definition with tones and color groups for light and dark schemes\n * @param name - Optional name for the custom color\n * @param tones - Readonly tonal palette defining color variations\n * @param light - Color group representing the light scheme variant\n * @param dark - Color group representing the dark scheme variant\n */\nexport type CustomColor = {\n dark: Readonly<ColorGroup>\n light: Readonly<ColorGroup>\n name?: string\n tones: TonalPalette\n};\n\n/**\n * Represents a color group with specific color roles in a color scheme.\n * Defines the color and its on-color variants for different contexts and containers.\n * @param color - The custom color\n * @param onColor - The color used for content on top of the custom color\n * @param colorContainer - The container variant of the custom color\n * @param onColorContainer - The color used for content on the color container\n */\nexport type ColorGroup = {\n color: Hct\n colorContainer: Hct\n onColor: Hct\n onColorContainer: Hct\n};\n","import {\n uint8,\n uint32,\n\n alphaFromArgb,\n blueFromArgb,\n greenFromArgb,\n redFromArgb,\n} from './utils';\n\nimport { withKnownColor } from './default-colors';\n\nimport {\n type Color,\n Colord,\n Hct,\n type HctColor,\n type HexColor,\n type HslaColor,\n type ObjectColor,\n type RgbaColor,\n} from './types';\n\n/*\n * colord\n */\nimport {\n extend,\n colord as origColord,\n} from 'colord';\n\n// extend colord with the mix plugin\nimport mixPlugin from 'colord/plugins/mix';\nextend([mixPlugin]);\n\n/**\n * Determines if the given value is an {@link ObjectColor}\n *\n * @param c - The value to check for color object characteristics\n * @returns A boolean indicating whether the input is a color object\n */\nexport const isObjectColor = (c: unknown): boolean => {\n if (c === null || typeof c !== 'object')\n return false;\n else if (c instanceof Hct || c instanceof Colord)\n return false;\n else if (('r' in c && 'g' in c && 'b' in c) ||\n ('h' in c && 'c' in c && 't' in c) ||\n ('h' in c && 's' in c && 'l' in c) ||\n ('h' in c && 's' in c && 'v' in c) ||\n ('h' in c && 'w' in c && 'b' in c) ||\n ('x' in c && 'y' in c && 'z' in c) ||\n ('l' in c && 'a' in c && 'b' in c) ||\n ('l' in c && 'c' in c && 'h' in c) ||\n ('c' in c && 'm' in c && 'y' in c && 'k' in c)\n )\n return true;\n else\n return false;\n};\n\n/**\n * Normalizes an alpha value to a consistent representation between 0 and 1.\n * @param a - Optional alpha value to normalize\n * @returns Normalized alpha value or undefined if input is undefined\n */\nexport function normalizeAlpha(a: number): number;\nexport function normalizeAlpha(a?: number): number | undefined;\nexport function normalizeAlpha(a?: number): number | undefined {\n if (a === undefined) {\n return undefined;\n }\n // Convert 0..255 to 0..1\n const n = a > 1 ? a / 255 : a;\n // Ensure the value is in range [0, 1]\n return Math.min(Math.max(n, 0), 1);\n}\n\n/**\n * Normalizes the alpha value of an object color if present.\n *\n * @param c - The color object to potentially normalize\n * @returns A new color object with a normalized alpha value, or the original object if no alpha is present\n */\nexport function withNormalizedAlpha(c: ObjectColor): ObjectColor {\n if ('a' in c && c.a !== undefined) {\n const a = normalizeAlpha(c.a);\n return {\n ...c,\n a,\n };\n }\n\n return c;\n}\n\n/*\n * RGB factories\n */\n\n/** @returns the RGB number corresponding to the given {@link RgbaColor} */\nexport const rgbFromRgbaColor = (c: RgbaColor): number => {\n const r255 = uint8(c.r);\n const g255 = uint8(c.g);\n const b255 = uint8(c.b);\n\n return uint32(r255 << 16 | g255 << 8 | b255);\n};\n\n/** @returns the decomposed {@link RgbaColor} corresponding to the given {@link HctColor} */\nexport const rgbaFromHctColor = (c: HctColor): RgbaColor => splitArgb(argbFromHctColor(c));\n\n/** @returns the decomposed {@link RgbaColor} corresponding to the given color */\n/**\n * Returns the decomposed RGBA components for any color type.\n * @param c - The color to decompose\n * @returns The RGBA color object with r, g, b, and optional a components\n */\nexport const rgba = (c: Color): RgbaColor => splitArgb(argb(c));\n\n/*\n * ARGB factories\n */\n\n/** @returns the ARGB number corresponding to the given {@link Hct} */\nexport const argbFromHct = (c: Hct) => c.toInt();\n\n/** @returns the ARGB number corresponding to the given {@link RgbaColor} */\nexport const argbFromRgbaColor = (c: RgbaColor): number => {\n const a = normalizeAlpha(c.a) ?? 1;\n const a255 = uint8(Math.round(a * 255));\n\n return uint32(a255 << 24 | rgbFromRgbaColor(c));\n};\n\n/** @returns the ARGB number corresponding to the given {@link HctColor} */\nexport const argbFromHctColor = (c: HctColor): number => {\n const argb = argbFromHct(Hct.from(c.h, c.c, c.t));\n if (c.a === undefined) {\n return argb;\n }\n\n const a = normalizeAlpha(c.a);\n const a255 = uint8(Math.round(a * 255));\n return uint32(a255 << 24 | (argb & 0xFF_FF_FF));\n};\n\n/** @returns the ARGB number corresponding to the given {@link Colord} */\nexport const argbFromColord = (c: Colord) => {\n if (!c.isValid()) {\n throw new Error('Invalid color');\n }\n return argbFromRgbaColor(c.rgba);\n};\n\n/** @returns the ARGB number corresponding to the color string */\nexport const argbFromString = (s: string) => argbFromColord(colordFromString(s));\n\n/** @returns the the decomposed {@link RgbaColor} corresponding to the given ARGB number */\nexport const splitArgb = (argb: number): RgbaColor => {\n const a255 = alphaFromArgb(argb);\n return {\n r: redFromArgb(argb),\n g: greenFromArgb(argb),\n b: blueFromArgb(argb),\n ...(a255 > 0 ? { a: a255 / 255 } : {}),\n };\n};\n\n/** @returns ARGB representation of the given {@link Color}. */\nexport const argb = (c: Color): number => {\n if (c instanceof Hct) {\n return argbFromHct(c);\n } else if (c instanceof Colord) {\n return argbFromRgbaColor(c.rgba);\n } else if (typeof c === 'number') {\n return c;\n } else if (typeof c === 'string') {\n return argbFromString(c);\n } else if ('h' in c && 'c' in c && 't' in c) {\n return argbFromHctColor(c);\n } else {\n return argbFromColord(colord(c));\n }\n};\n\n/*\n * Colord factories\n */\n\n/** @returns {@link Colord} from an ARGB number */\nexport const colordFromArgb = (argb: number) => origColord(splitArgb(argb));\n\n/** @returns {@link Colord} from a {@link Hct} color */\nexport const colordFromHct = (c: Hct) => colordFromArgb(argbFromHct(c));\n\n/** @returns {@link Colord} from a {@link HctColor} */\nexport const colordFromHctColor = (c: HctColor) => colordFromArgb(argbFromHctColor(c));\n\n/** @returns {@link Colord} from a color string, handling known color names */\nexport const colordFromString = (c: string) => {\n const c1 = origColord(withKnownColor(c) as string);\n if (!c1.isValid()) {\n throw new Error(`Invalid color '${c}'`);\n }\n return c1;\n};\n\n/** @returns {@link Colord} from the given {@link Color}. */\nexport const colord = (c: Color): Colord => {\n if (c instanceof Colord) {\n return c;\n } else if (c instanceof Hct) {\n return colordFromHct(c);\n } else if (typeof c === 'number') {\n return colordFromArgb(c);\n } else if (typeof c === 'string') {\n return colordFromString(c);\n } else if ('h' in c && 'c' in c && 't' in c) {\n return colordFromHctColor(c);\n } else if (!isObjectColor(c)) {\n throw new Error('Invalid color');\n } else if ('a' in c && c.a !== undefined) {\n const a = normalizeAlpha(c.a);\n return origColord({ ...c, a });\n } else {\n return origColord(c);\n }\n};\n\n/*\n * Hct factories\n */\n\n/** @returns {@link Hct} from an ARGB number */\nexport const hctFromArgb = (argb: number) => Hct.fromInt(argb);\n\n/** @returns {@link Hct} from an {@link RgbaColor} object */\nexport const hctFromRgbaColor = (c: RgbaColor): Hct => Hct.fromInt(argbFromRgbaColor(c));\n\n/** @returns {@link Hct} from a {@link Colord} object */\nexport const hctFromColord = (c: Colord): Hct => {\n if (!c.isValid()) {\n throw new Error('Invalid color');\n }\n return hctFromRgbaColor(c.rgba);\n};\n\n/** @returns {@link Hct} from a valid CSS color string */\nexport const hctFromString = (s: string): Hct => hctFromColord(colordFromString(s));\n\n/** @returns {@link HctColor} decomposing the given {@link Hct} color. */\nexport const splitHct = (c: Hct): HctColor => {\n return { h: c.hue, c: c.chroma, t: c.tone };\n};\n\n/** @returns {@link Hct} representation of the given {@link Color}. */\nexport const hct = (c: Color): Hct => {\n if (c instanceof Hct) {\n return c;\n } else if (c instanceof Colord) {\n return hctFromColord(c);\n } else if (typeof c === 'number') {\n return hctFromArgb(c);\n } else if (typeof c === 'string') {\n return hctFromString(c);\n } else if ('h' in c && 'c' in c && 't' in c) {\n return Hct.from(c.h, c.c, c.t);\n } else {\n return hctFromColord(colord(c));\n }\n};\n\n/*\n * HSL factories\n */\n\n/** @returns the {@link HslaColor} for the given {@link Colord} */\nexport const hslFromColord = (c: Colord): HslaColor => {\n if (!c.isValid()) {\n throw new Error('Invalid color');\n }\n return c.toHsl();\n};\n\n/** @returns the {@link HslaColor} for the given ARGB number */\nexport const hslFromArgb = (argb: number) => hslFromColord(colord(splitArgb(argb)));\n\n/** @returns the {@link HslaColor} for the given {@link Hct} */\nexport const hslFromHct = (c: Hct): HslaColor => hslFromArgb(argbFromHct(c));\n\n/** @returns the {@link HslaColor} for the given {@link HctColor} */\nexport const hslFromHctColor = (c: HctColor): HslaColor => hslFromColord(colord(rgbaFromHctColor(c)));\n\n/** @returns the {@link HslaColor} for the given CSS color string */\nexport const hslFromString = (s: string): HslaColor => hslFromColord(colordFromString(s));\n\n/** @returns the {@link HslaColor} for the given {@link Color} */\nexport const hsl = (c: Color): HslaColor => {\n if (c instanceof Hct) {\n return hslFromHct(c);\n } else if (c instanceof Colord) {\n return hslFromColord(c);\n } else if (typeof c === 'number') {\n return hslFromArgb(c);\n } else if (typeof c === 'string') {\n return hslFromString(c);\n } else if ('h' in c && 'c' in c && 't' in c) {\n return hslFromHctColor(c);\n } else {\n return hslFromColord(colord(c));\n }\n};\n\n/*\n * Hex factories\n */\n\n/** @returns the Hex RGB Color string for the given {@link Colord} */\nexport const hexFromColord = (c: Colord): HexColor => {\n if (!c.isValid()) {\n throw new Error('Invalid color');\n }\n return c.toHex() as HexColor;\n};\n\n/** @returns the Hex RGB Color string for the given ARGB number */\nexport const hexFromArgb = (argb: number) => hexFromColord(colord(splitArgb(argb)));\n\n/** @returns the Hex RGB Color string for the given {@link Hct} */\nexport const hexFromHct = (c: Hct) => hexFromArgb(argbFromHct(c));\n\n/** @returns the Hex RGB Color string for the given {@link HctColor} */\nexport const hexFromHctColor = (c: HctColor): HexColor => hexFromColord(origColord(rgbaFromHctColor(c)));\n","import {\n type Color,\n Hct,\n type HexColor,\n} from './types';\n\nimport {\n colord,\n rgba,\n\n hexFromArgb,\n hexFromColord,\n hexFromHct,\n} from './colors';\n\n/**\n * Defines the possible color format types for conversion.\n *\n * @remarks\n * Supports predefined formats like 'numbers', 'rgb', 'hsl', 'hex',\n * or a custom formatting function that takes an Hct color and returns a string.\n */\nexport type ColorFormat = 'hex' | 'hsl' | 'numbers' | 'rgb' | ((c: Hct) => string);\n\n/**\n * Converts an HCT color to a specified format.\n *\n * @param v - The color format to convert to. Can be 'numbers', 'rgb', 'hsl', 'hex', or a custom formatting function.\n * @returns A function that converts an HCT color to the specified string format.\n *\n * @remarks defaults to `'rgb'`\n */\nexport function colorFormatter(v: ColorFormat = 'rgb'): ((c: Hct) => string) {\n if (typeof v === 'function') return v;\n\n if (v === 'numbers') {\n return (c: Hct): string => {\n const { r, g, b } = rgba(c);\n return `${r} ${g} ${b}`;\n };\n }\n\n if (v === 'hsl') return hslString;\n if (v === 'hex') return hexString;\n return rgbaString;\n}\n\n/** @returns the Hex RGB Color string for the given {@link Color} */\nexport const hexString = (c: Color): HexColor => {\n if (c instanceof Hct) {\n return hexFromHct(c);\n } else if (typeof c === 'number') {\n return hexFromArgb(c);\n }\n const c1 = colord(c);\n if (!c1.isValid) {\n throw new Error('Invalid color');\n }\n\n return hexFromColord(c1);\n};\n\n/** @returns the HSL or HSLA color string representation of the given {@link Color}, with optional alpha control */\nexport function hslString(c: Color, alpha: boolean = true): string {\n const c1 = colord(c);\n if (!c1.isValid) {\n throw new Error('Invalid color');\n }\n\n const { h, s, l, a: a0 } = c1.toHsl();\n const a = alpha === false ? 1 : a0;\n\n if (a < 1) {\n return `hsla(${h}, ${s}%, ${l}%, ${a})`;\n }\n return `hsl(${h}, ${s}%, ${l}%)`;\n}\n\n/**\n * Converts a color to an RGB or RGBA string representation.\n * @param c - The color to convert\n * @param alpha - Whether to include alpha channel (defaults to true)\n * @returns A CSS-compatible RGB or RGBA string\n */\nexport const rgbaString = (c: Color, alpha: boolean = true): string => {\n const { r, g, b, a: a0 = 1 } = rgba(c);\n const a = alpha === false ? 1 : a0;\n\n if (a < 1) {\n return `rgb(${r} ${g} ${b} / ${a.toFixed(2)})`;\n }\n return `rgb(${r} ${g} ${b})`;\n};\n","import {\n unsafeKeys,\n} from '@poupe/css';\n\nimport {\n type Color,\n Hct,\n} from './types';\n\nimport {\n colord,\n hctFromColord,\n} from './colors';\n\n/** @returns the result of mixing two colors in given ratios */\nexport function makeColorMix(base: Color, other: Color, ratios: number): Hct;\nexport function makeColorMix(base: Color, other: Color, ratios: Array<number>): Hct[];\nexport function makeColorMix<K extends string>(base: Color, other: Color, ratios: Record<K, number>): Record<K, Hct>;\nexport function makeColorMix<K extends string>(base: Color, other: Color, ratios: Array<number> | number | Record<K, number>): Hct | Hct[] | Record<K, Hct> {\n const c0 = colord(base);\n const c1 = colord(other);\n\n if (typeof ratios === 'number') {\n // single value\n const c = c0.mix(c1, ratios);\n return hctFromColord(c);\n }\n\n if (Array.isArray(ratios)) {\n // array\n const out: Hct[] = [];\n for (const r of ratios) {\n const c = c0.mix(c1, r);\n out.push(hctFromColord(c));\n }\n return out;\n }\n\n // named\n const out = {} as Record<K, Hct>;\n for (const k of unsafeKeys(ratios)) {\n const c = c0.mix(c1, ratios[k]);\n out[k] = hctFromColord(c);\n }\n\n return out;\n}\n","import {\n Blend,\n} from '@poupe/material-color-utilities';\n\nimport {\n type Color,\n type CustomColor,\n Hct,\n TonalPalette,\n} from './types';\n\nimport {\n hct,\n} from './colors';\n\n/**\n * Creates a tonal palette from a color with optional harmonization\n *\n * @param color - The base color to create the palette from\n * @param harmonizeTo - Optional target color to harmonize towards.\n * Harmonization shifts the hue of the base color\n * towards the target color while preserving tone.\n * @param isKeyColor - Whether to preserve the exact tone from the input\n * color (true) as key color or derive a tone\n * algorithmically (false).\n * @defaultValue `true`\n * @returns A TonalPalette instance handling tones from 0-100\n */\nexport function makeTonalPalette(color: Color, harmonizeTo?: Hct, isKeyColor: boolean = true): TonalPalette {\n let c = hct(color);\n if (harmonizeTo) {\n const c1 = Blend.harmonize(c.toInt(), harmonizeTo.toInt());\n c = hct(c1);\n }\n\n if (isKeyColor)\n return TonalPalette.fromHct(c);\n\n return TonalPalette.fromHueAndChroma(c.hue, c.chroma);\n}\n\n/**\n * Generates a custom color optionally harmonized to a target color\n *\n * @param color - The base color to transform into a custom color\n * @param harmonizeTo - Optional target color to harmonize towards.\n * Harmonization blends the hue of the base color\n * with the target while maintaining lightness.\n * @param name - Optional name identifier for the custom color\n * @param isKeyColor - Whether to preserve exact tone from input color\n * as key color. @defaultValue `true`.\n * @returns A CustomColor object with light and dark theme variations,\n * including primary color, on-color, container, and\n * on-container variants for each theme\n */\nexport function makeCustomColor(color: Color, harmonizeTo?: Hct, name?: string, isKeyColor: boolean = true): CustomColor {\n const tones = makeTonalPalette(color, harmonizeTo, isKeyColor);\n\n return makeCustomColorFromPalette(tones, name);\n}\n\n/**\n * Generates a custom color from a given tonal palette with predefined\n * light and dark color variations.\n *\n * Light theme uses tones: 40 (color), 100 (onColor), 90 (container),\n * 10 (onContainer). Dark theme uses tones: 80 (color), 20 (onColor),\n * 30 (container), 90 (onContainer). These follow Material Design 3\n * color system guidelines for optimal contrast and accessibility.\n *\n * @param tones - The tonal palette (0-100 tone range) used to derive\n * color variations\n * @param name - Optional name identifier for the custom color\n * @returns A CustomColor object with light and dark color configurations\n * containing color, onColor, colorContainer, and onColorContainer\n * properties for each theme mode\n */\nexport function makeCustomColorFromPalette(tones: TonalPalette, name?: string): CustomColor {\n return {\n name,\n tones,\n light: {\n color: tones.getHct(40),\n onColor: tones.getHct(100),\n colorContainer: tones.getHct(90),\n onColorContainer: tones.getHct(10),\n },\n dark: {\n color: tones.getHct(80),\n onColor: tones.getHct(20),\n colorContainer: tones.getHct(30),\n onColorContainer: tones.getHct(90),\n },\n };\n}\n","import type { Color, Hct } from './types';\nimport { hct } from './colors';\nimport { makeColorMix } from './mix';\n\n/**\n * Material Design 3 state layer opacity values\n * @see https://m3.material.io/foundations/interaction/states/state-layers\n */\nexport const stateLayerOpacities = {\n hover: 0.08,\n focus: 0.12,\n pressed: 0.12,\n dragged: 0.16,\n disabled: 0.12,\n onDisabled: 0.38,\n} as const;\n\nexport type StateLayerOpacity = typeof stateLayerOpacities;\nexport type InteractionState = keyof Omit<StateLayerOpacity, 'disabled' | 'onDisabled'>;\nexport type StateVariants<T extends string> = {\n [K in T as `${K}-disabled` | `${K}-dragged` | `${K}-focus` | `${K}-hover` | `${K}-pressed`]: Hct;\n} & {\n [K in T as `on-${K}-disabled`]: Hct;\n};\n\n/**\n * State color mix parameters for CSS color-mix() function\n */\nexport interface StateColorMixParams {\n /** The base color CSS variable name */\n baseColor: string\n /** The on-color CSS variable name */\n onColor: string\n /** The opacity percentage for mixing (0-100) */\n opacityPercent: number\n /** The state type */\n state: keyof typeof stateLayerOpacities\n}\n\n/**\n * Get CSS color-mix parameters for creating state colors\n * @param colorName - The base color name (e.g., 'primary', 'secondary')\n * @param state - The state type\n * @param prefix - Optional CSS variable prefix (defaults to empty string)\n * @returns Parameters for creating CSS color-mix\n */\nexport function getStateColorMixParams(\n colorName: string,\n state: keyof Omit<StateLayerOpacity, 'onDisabled'>,\n prefix = '',\n): StateColorMixParams {\n const opacity = stateLayerOpacities[state];\n const isOnColor = colorName.startsWith('on-');\n\n // For disabled state on \"on-colors\", we use different opacity\n const actualOpacity = state === 'disabled' && isOnColor ? stateLayerOpacities.onDisabled : opacity;\n\n // Determine base and on-color names\n let baseColor: string;\n let onColor: string;\n\n if (isOnColor) {\n // For on-colors (e.g., 'on-primary'), the base is the color without 'on-' prefix\n baseColor = colorName.replace('on-', '');\n onColor = colorName;\n } else {\n // For base colors (e.g., 'primary'), we mix with the on-color\n baseColor = colorName;\n onColor = `on-${colorName}`;\n }\n\n return {\n state,\n baseColor: prefix ? `${prefix}${baseColor}` : baseColor,\n onColor: prefix ? `${prefix}${onColor}` : onColor,\n opacityPercent: Math.round(actualOpacity * 100),\n };\n}\n\n/**\n * Creates state layer colors by mixing the on-color with the base color at specified opacities\n * Following Material Design 3 state layer principles\n *\n * @param baseColor - The base/background color\n * @param onColor - The on-color (content color that goes on top of the base)\n * @returns Object with state layer colors for each interaction state\n */\nexport function makeStateLayerColors(baseColor: Color, onColor: Color) {\n const base = hct(baseColor);\n const on = hct(onColor);\n\n return makeColorMix(base, on, {\n hover: stateLayerOpacities.hover,\n focus: stateLayerOpacities.focus,\n pressed: stateLayerOpacities.pressed,\n dragged: stateLayerOpacities.dragged,\n disabled: stateLayerOpacities.disabled,\n onDisabled: stateLayerOpacities.onDisabled,\n });\n}\n\n/**\n * Generates state variants for a set of color pairs\n * Each color should have a corresponding on-color\n *\n * @param colors - Object with base colors and their on-colors\n * @returns Object with state variants for each color\n */\nexport function makeStateVariants<K extends string>(\n colors: Record<K, Color>,\n onColors: Record<`on-${K}`, Color>,\n): StateVariants<K> {\n const result: Record<string, Hct> = {};\n\n for (const colorName in colors) {\n const baseColor = colors[colorName];\n const onColorKey = `on-${colorName}` as `on-${K}`;\n const onColor = onColors[onColorKey];\n\n if (!onColor) {\n throw new Error(`Missing on-color for ${colorName}. Expected key: ${onColorKey}`);\n }\n\n const states = makeStateLayerColors(baseColor, onColor);\n\n result[`${colorName}-hover`] = states.hover;\n result[`${colorName}-focus`] = states.focus;\n result[`${colorName}-pressed`] = states.pressed;\n result[`${colorName}-dragged`] = states.dragged;\n result[`${colorName}-disabled`] = states.disabled;\n result[`on-${colorName}-disabled`] = states.onDisabled;\n }\n\n return result as StateVariants<K>;\n}\n","export * from './colors';\n\nexport * from './default-colors';\nexport * from './formatter';\nexport * from './mix';\nexport * from './palettes';\nexport * from './states';\nexport * from './types';\n// re-export\n//\nexport {\n formatCSSRules,\n formatCSSRulesArray,\n generateCSSRules,\n generateCSSRulesArray,\n} from '@poupe/css';\n\n// tools\n//\nexport const hexColorPattern = /^#([\\da-f]{3}|[\\da-f]{6}|[\\da-f]{8})$/i;\nexport const isHexColor = (s: string = '') => !!hexColorPattern.test(s || '');\n","export * from '../core/index';\n\nexport * from '../core/utils';\nexport {\n camelCase,\n} from '@poupe/css';\n\n/**\n * Checks if an object is non-empty (has at least one own property).\n *\n * @param obj - The object to check\n * @returns true if the object exists and has at least one key\n */\nexport function isNonEmpty<T extends Record<string, unknown>>(\n object?: T,\n): object is T {\n return !!object && Object.keys(object).length > 0;\n}\n","import {\n MaterialDynamicColors,\n} from '@poupe/material-color-utilities';\n\nimport {\n type ColorGroup,\n DynamicScheme,\n Hct,\n TonalPalette,\n Variant,\n} from './utils';\n\n// CustomDynamicColor\n//\nexport const customDynamicColors = {\n '{}': (cc: ColorGroup) => cc.color,\n '{}-container': (cc: ColorGroup) => cc.colorContainer,\n 'on-{}': (cc: ColorGroup) => cc.onColor,\n 'on-{}-container': (cc: ColorGroup) => cc.onColorContainer,\n} satisfies Record<string, (cc: ColorGroup) => Hct>;\n\nexport type CustomDynamicColorKey<T extends string> =\n `${T}-container` |\n `${T}` |\n `on-${T}-container` |\n `on-${T}`;\n\n// StandardDynamicColor\n//\n\n/** standardDynamicColors are all dynamic colors defined by Material Design 3 */\nexport const standardDynamicColors = {\n // surface-{}\n 'surface': (ds: DynamicScheme) => ds.surface,\n 'surface-dim': (ds: DynamicScheme) => ds.surfaceDim,\n 'surface-bright': (ds: DynamicScheme) => ds.surfaceBright,\n 'surface-variant': (ds: DynamicScheme) => ds.surfaceVariant,\n 'surface-container-lowest': (ds: DynamicScheme) => ds.surfaceContainerLowest,\n 'surface-container-low': (ds: DynamicScheme) => ds.surfaceContainerLow,\n 'surface-container': (ds: DynamicScheme) => ds.surfaceContainer,\n 'surface-container-high': (ds: DynamicScheme) => ds.surfaceContainerHigh,\n 'surface-container-highest': (ds: DynamicScheme) => ds.surfaceContainerHighest,\n 'inverse-surface': (ds: DynamicScheme) => ds.inverseSurface,\n\n // on-surface-{}\n 'on-surface': (ds: DynamicScheme) => ds.onSurface,\n 'on-surface-dim': (ds: DynamicScheme) => ds.onSurface,\n 'on-surface-bright': (ds: DynamicScheme) => ds.onSurface,\n 'on-surface-variant': (ds: DynamicScheme) => ds.onSurfaceVariant,\n 'on-surface-container-lowest': (ds: DynamicScheme) => ds.onSurface,\n 'on-surface-container-low': (ds: DynamicScheme) => ds.onSurface,\n 'on-surface-container': (ds: DynamicScheme) => ds.onSurface,\n 'on-surface-container-high': (ds: DynamicScheme) => ds.onSurface,\n 'on-surface-container-highest': (ds: DynamicScheme) => ds.onSurface,\n 'on-inverse-surface': (ds: DynamicScheme) => ds.inverseOnSurface,\n\n // primary\n 'primary': (ds: DynamicScheme) => ds.primary,\n 'primary-dim': (ds: DynamicScheme) => ds.primaryDim,\n 'primary-container': (ds: DynamicScheme) => ds.primaryContainer,\n 'primary-fixed': (ds: DynamicScheme) => ds.primaryFixed,\n 'primary-fixed-dim': (ds: DynamicScheme) => ds.primaryFixedDim,\n 'inverse-primary': (ds: DynamicScheme) => ds.inversePrimary,\n // on-primary\n 'on-primary': (ds: DynamicScheme) => ds.onPrimary,\n 'on-primary-container': (ds: DynamicScheme) => ds.onPrimaryContainer,\n 'on-primary-fixed': (ds: DynamicScheme) => ds.onPrimaryFixed,\n 'on-primary-fixed-variant': (ds: DynamicScheme) => ds.onPrimaryFixedVariant,\n\n // secondary\n 'secondary': (ds: DynamicScheme) => ds.secondary,\n 'secondary-dim': (ds: DynamicScheme) => ds.secondaryDim,\n 'secondary-container': (ds: DynamicScheme) => ds.secondaryContainer,\n 'secondary-fixed': (ds: DynamicScheme) => ds.secondaryFixed,\n 'secondary-fixed-dim': (ds: DynamicScheme) => ds.secondaryFixedDim,\n // on-secondary\n 'on-secondary': (ds: DynamicScheme) => ds.onSecondary,\n 'on-secondary-container': (ds: DynamicScheme) => ds.onSecondaryContainer,\n 'on-secondary-fixed': (ds: DynamicScheme) => ds.onSecondaryFixed,\n 'on-secondary-fixed-variant': (ds: DynamicScheme) => ds.onSecondaryFixedVariant,\n\n // tertiary\n 'tertiary': (ds: DynamicScheme) => ds.tertiary,\n 'tertiary-dim': (ds: DynamicScheme) => ds.tertiaryDim,\n 'tertiary-container': (ds: DynamicScheme) => ds.tertiaryContainer,\n 'tertiary-fixed': (ds: DynamicScheme) => ds.tertiaryFixed,\n 'tertiary-fixed-dim': (ds: DynamicScheme) => ds.tertiaryFixedDim,\n // on-tertiary\n 'on-tertiary': (ds: DynamicScheme) => ds.onTertiary,\n 'on-tertiary-container': (ds: DynamicScheme) => ds.onTertiaryContainer,\n 'on-tertiary-fixed': (ds: DynamicScheme) => ds.onTertiaryFixed,\n 'on-tertiary-fixed-variant': (ds: DynamicScheme) => ds.onTertiaryFixedVariant,\n\n // error\n 'error': (ds: DynamicScheme) => ds.error,\n 'error-container': (ds: DynamicScheme) => ds.errorContainer,\n // on-error\n 'on-error': (ds: DynamicScheme) => ds.onError,\n 'on-error-container': (ds: DynamicScheme) => ds.onErrorContainer,\n\n // special\n 'outline': (ds: DynamicScheme) => ds.outline,\n 'outline-variant': (ds: DynamicScheme) => ds.outlineVariant,\n 'shadow': (ds: DynamicScheme) => ds.shadow,\n 'scrim': (ds: DynamicScheme) => ds.scrim,\n} satisfies Record<string, (ds: DynamicScheme) => number>;\n\n/** contentAccentToneDelta is re-exported from MaterialDynamicColors for completeness */\nexport const contentAccentToneDelta = MaterialDynamicColors.contentAccentToneDelta;\n\n/** StandardDynamicColorKey is a type representing all fields in standardDynamicColors */\nexport type StandardDynamicColorKey = keyof typeof standardDynamicColors;\n\n/** standardDynamicColorKeys is an array of all StandardDynamicColorKey values */\nexport const standardDynamicColorKeys = Object.keys(standardDynamicColors) as StandardDynamicColorKey[];\n\n/** standardPaletteKeyColors are all palette key colors defined by MaterialDynamicColors */\nexport const standardPalettes = {\n primary: (ds: DynamicScheme) => ds.primaryPalette,\n secondary: (ds: DynamicScheme) => ds.secondaryPalette,\n tertiary: (ds: DynamicScheme) => ds.tertiaryPalette,\n neutral: (ds: DynamicScheme) => ds.neutralPalette,\n neutralVariant: (ds: DynamicScheme) => ds.neutralVariantPalette,\n} satisfies Record<string, (ds: DynamicScheme) => TonalPalette>;\n\n/** StandardPaletteKey is a type representing all core palettes in standardPalettes */\nexport type StandardPaletteKey = keyof typeof standardPalettes;\n\n/** standardPaletteKeys is an array of all StandardPaletteKey values */\nexport const standardPaletteKeys = Object.keys(standardPalettes) as StandardPaletteKey[];\n\n// DynamicScheme\n//\nexport const standardDynamicSchemes = {\n content: Variant.CONTENT,\n expressive: Variant.EXPRESSIVE,\n fidelity: Variant.FIDELITY,\n monochrome: Variant.MONOCHROME,\n neutral: Variant.NEUTRAL,\n tonalSpot: Variant.TONAL_SPOT,\n vibrant: Variant.VIBRANT,\n rainbow: Variant.RAINBOW,\n fruitSalad: Variant.FRUIT_SALAD,\n} satisfies Record<string, Variant>;\n\nexport type StandardDynamicSchemeKey = keyof typeof standardDynamicSchemes;\n","// imports\n//\nimport type { KebabCase } from 'type-fest';\n\nimport {\n kebabCase,\n pairs,\n unsafeKeys,\n} from '@poupe/css';\n\nimport {\n type Color,\n type CorePalettes,\n DynamicScheme,\n Hct,\n TonalPalette,\n Variant,\n\n hct,\n makeCustomColor,\n makeCustomColorFromPalette,\n} from '../core';\n\nimport {\n type CustomDynamicColorKey,\n type StandardDynamicColorKey,\n type StandardPaletteKey,\n\n customDynamicColors,\n standardDynamicColors,\n standardPalettes,\n} from './data';\n\nimport {\n type ColorOptions,\n} from './types';\n\n// types\n//\n\nexport type StandardDynamicColors = { [K in StandardDynamicColorKey]: Hct };\ntype StandardPaletteColors = { [K in KebabCase<StandardPaletteKey>]: Hct };\ntype StandardPalettes = { [K in KebabCase<StandardPaletteKey>]: TonalPalette };\n\nexport type CustomDynamicColors<T extends string> = { [K in CustomDynamicColorKey<KebabCase<T>>]: Hct };\n\nexport function makeStandardColorsFromScheme(scheme: DynamicScheme) {\n const out = {} as StandardDynamicColors;\n\n for (const [name, fn] of pairs(standardDynamicColors)) {\n out[name] = Hct.fromInt(fn(scheme));\n }\n\n return out;\n}\n\nexport function makeStandardPaletteKeyColorsFromScheme(scheme: DynamicScheme) {\n const out = {} as StandardPaletteColors;\n for (const [kebabName, palette] of pairs(makeStandardPaletteFromScheme(scheme))) {\n out[kebabName] = palette.keyColor;\n }\n return out;\n}\n\nexport function makeStandardPaletteFromScheme(scheme: DynamicScheme) {\n const out = {} as StandardPalettes;\n\n for (const [name, fn] of pairs(standardPalettes)) {\n const kebabName = kebabCase(name) as KebabCase<StandardPaletteKey>;\n out[kebabName] = fn(scheme);\n }\n\n return out;\n}\n\nexport function makeCustomColors<K extends string>(source: Color, colors: Record<K, ColorOptions>) {\n const $source = hct(source);\n\n const colorOptions = {} as Record<KebabCase<K>, ColorOptions>;\n const palettes = {} as Record<KebabCase<K>, TonalPalette>;\n const darkColors = {} as CustomDynamicColors<K>;\n const lightColors = {} as CustomDynamicColors<K>;\n\n for (const [color, options] of pairs(colors)) {\n const kebabName = kebabCase(color) as KebabCase<K>;\n const $color = hct(options.value);\n const harmonize = options.harmonize ?? true;\n\n const { tones, dark, light } = makeCustomColor($color, harmonize ? $source : undefined, kebabName);\n\n colorOptions[kebabName] = options;\n palettes[kebabName] = tones;\n\n for (const [pattern, fn] of Object.entries(customDynamicColors)) {\n const name = pattern.replace('{}', kebabName) as keyof CustomDynamicColors<K>;\n\n darkColors[name] = fn(dark);\n lightColors[name] = fn(light);\n }\n }\n\n return {\n source,\n colors: unsafeKeys(colorOptions),\n colorOptions,\n palettes,\n dark: darkColors,\n light: lightColors,\n };\n}\n\nexport function makeCustomColorsFromPalettes<K extends string>(colors: Record<K, TonalPalette> = {} as Record<K, TonalPalette>) {\n const palettes = {} as Record<KebabCase<K>, TonalPalette>;\n const darkColors = {} as CustomDynamicColors<K>;\n const lightColors = {} as CustomDynamicColors<K>;\n\n for (const [color, tones] of pairs(colors)) {\n const kebabName = kebabCase(color) as KebabCase<K>;\n const { dark, light } = makeCustomColorFromPalette(tones, kebabName);\n\n palettes[kebabName] = tones;\n for (const [pattern, fn] of pairs(customDynamicColors)) {\n const name = pattern.replace('{}', kebabName) as keyof CustomDynamicColors<K>;\n\n darkColors[name] = fn(dark);\n lightColors[name] = fn(light);\n }\n }\n\n return {\n colors: unsafeKeys(palettes),\n palettes,\n dark: darkColors,\n light: lightColors,\n };\n}\n\n/**\n * Creates a dynamic color scheme based on the provided source color, variant, and other parameters.\n * Uses Material Design 2025 specification with phone platform.\n *\n * @param source - The source color in HCT color space\n * @param variant - The color scheme to apply\n * @param contrastLevel - The desired contrast level\n * @param isDark - Whether the scheme is for a dark or light theme\n * @param palettes - Optional color palettes to customize the scheme\n * @returns A configured DynamicScheme instance\n */\nexport function makeDynamicScheme(\n source: Hct,\n variant: Variant,\n contrastLevel: number,\n isDark: boolean,\n palettes: Partial<CorePalettes> = {},\n): DynamicScheme {\n return new DynamicScheme({\n sourceColorHct: source,\n variant,\n contrastLevel,\n isDark,\n primaryPalette: palettes.primary,\n secondaryPalette: palettes.secondary,\n tertiaryPalette: palettes.tertiary,\n neutralPalette: palettes.neutral,\n neutralVariantPalette: palettes.neutralVariant,\n errorPalette: palettes.error,\n specVersion: '2025',\n platform: 'phone',\n });\n}\n","import type {\n ColorOptions,\n CustomColorOptions,\n Palettes,\n ThemeColors,\n} from './types';\n\nimport {\n type Color,\n Colord,\n type CorePaletteKey,\n type CorePalettes,\n Hct,\n TonalPalette,\n\n camelCase,\n corePaletteKeys,\n hct,\n isObjectColor,\n makeTonalPalette,\n pairs,\n unsafeKeys,\n} from './utils';\n\n/**\n * Normalizes a value that could be a direct Color or a partial CustomColorOptions\n * to a partial CustomColorOptions object.\n *\n * @param c - A color, custom color options, or partial custom color options to flatten\n * @returns A normalized (partial) CustomColorOptions object\n */\nexport function flattenPartialColorOptions(c: Color | CustomColorOptions): CustomColorOptions;\nexport function flattenPartialColorOptions(c: Color | CustomColorOptions | Partial<CustomColorOptions> | undefined): CustomColorOptions | Partial<CustomColorOptions>;\nexport function flattenPartialColorOptions(c: Color | CustomColorOptions | Partial<CustomColorOptions> | undefined): CustomColorOptions | Partial<CustomColorOptions> {\n if (c === undefined || c === null) {\n return {};\n } else if (c instanceof Hct || c instanceof Colord) {\n return { value: c };\n } else if (typeof c !== 'object') {\n return { value: c };\n } else if ('value' in c) {\n return c as CustomColorOptions;\n } else if (isObjectColor(c)) {\n return { value: c as Color };\n } else {\n return c as Partial<CustomColorOptions>;\n }\n}\n\n/**\n * Normalizes color input to a CustomColorOptions object.\n * Converts raw Color values to CustomColorOptions with HCT color representation.\n *\n * @param c - Color or CustomColorOptions to normalize\n * @returns Normalized CustomColorOptions with HCT color value\n */\nexport const flattenColorOptions = (c: Color | CustomColorOptions): CustomColorOptions => {\n const c2 = flattenPartialColorOptions(c);\n if (c2.value === undefined) {\n // Partial\n throw new Error('invalid color');\n } else if (c2.value instanceof Hct) {\n // ready\n return c2;\n } else {\n // Color\n return {\n ...c2,\n value: hct(c2.value),\n };\n }\n};\n\n/**\n * Creates a complete theme palette system from a set of palette colors.\n * Processes primary color as the source for harmonization and generates\n * tonal palettes for all defined colors.\n *\n * @typeParam K - Custom color key names\n * @param colors - Complete set of palette colors including primary and optional colors\n * @returns Object containing source color, core palettes, all palettes, and color configurations\n */\nexport const makeThemePalettes = <K extends string>(colors: ThemeColors<K>) => {\n const { colors: $colors } = cookThemeColors(colors);\n const { primary, ...rest } = $colors;\n const { value: primaryValue } = primary;\n const source = hct(primaryValue);\n\n const corePalettes: CorePalettes = {\n primary: makeTonalPalette(source),\n };\n\n type ExtraColorKey = Exclude<keyof typeof $colors, CorePaletteKey>;\n const extraPalettes: Record<ExtraColorKey, TonalPalette> = {};\n\n for (const [name, options] of pairs(rest)) {\n const { value, harmonize = true } = options;\n const tones = makeTonalPalette(value, harmonize ? source : undefined);\n\n if (corePaletteKeys.includes(name as CorePaletteKey)) {\n corePalettes[name as CorePaletteKey] = tones;\n } else {\n extraPalettes[name as ExtraColorKey] = tones;\n }\n }\n\n const palettes: Palettes<ExtraColorKey> = { ...corePalettes, ...extraPalettes };\n\n return {\n source,\n corePalettes,\n extraPalettes,\n palettes,\n colors: $colors as Record<CorePaletteKey | ExtraColorKey, ColorOptions>,\n };\n};\n\nfunction cookThemeColors<K extends string>(colors: ThemeColors<K>) {\n const { primary, ...rest } = colors;\n const out: Record<string, ColorOptions> & { primary: ColorOptions } = {\n primary: flattenColorOptions(primary),\n };\n\n for (const key of unsafeKeys(rest)) {\n const $options = flattenColorOptions(rest[key]);\n const { name = key } = $options;\n out[camelCase(name)] = $options;\n }\n\n return { keys: Object.keys(out), colors: out };\n}\n","import type { KebabCase } from 'type-fest';\nimport { Hct } from '@poupe/material-color-utilities';\n\nimport { makeStateLayerColors } from '../core';\n\nimport type { CustomDynamicColors, StandardDynamicColors } from './colors';\n\n/**\n * Base color keys that support state variants\n */\ntype StandardInteractiveColorKey =\n | 'error' | 'error-container' | 'primary' | 'primary-container' |\n 'secondary' | 'secondary-container' |\n 'surface' | 'surface-variant' | 'tertiary' | 'tertiary-container';\n\n/**\n * State variant types for standard Material Design 3 colors\n */\ntype StandardStateVariants = {\n [K in StandardInteractiveColorKey as `${K}-disabled` | `${K}-dragged` | `${K}-focus` | `${K}-hover` | `${K}-pressed`]: Hct;\n};\n\n/**\n * Generates Material Design 3 state layer variants for standard theme colors\n *\n * @param colors - Standard theme colors generated from scheme\n * @returns Object with state variants for interactive colors\n */\nexport function makeStandardStateVariants(colors: StandardDynamicColors): StandardStateVariants {\n const variants: Record<string, Hct> = {};\n\n // Define color pairs that need state variants\n const colorPairs: Array<[keyof StandardDynamicColors, keyof