dembrandt 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1 -1
- package/lib/colors.js +242 -0
- package/lib/display.js +74 -84
- package/lib/extractors.js +37 -1
- package/package.json +3 -3
package/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import { join } from "path";
|
|
|
20
20
|
program
|
|
21
21
|
.name("dembrandt")
|
|
22
22
|
.description("Extract design tokens from any website")
|
|
23
|
-
.version("0.6.
|
|
23
|
+
.version("0.6.1")
|
|
24
24
|
.argument("<url>")
|
|
25
25
|
.option("--browser <type>", "Browser to use (chromium|firefox)", "chromium")
|
|
26
26
|
.option("--json-only", "Output raw JSON")
|
package/lib/colors.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color Conversion Utilities
|
|
3
|
+
*
|
|
4
|
+
* Converts colors between RGB, LCH, and OKLCH color spaces.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Convert sRGB to linear RGB
|
|
9
|
+
*/
|
|
10
|
+
function srgbToLinear(c) {
|
|
11
|
+
c = c / 255;
|
|
12
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Convert linear RGB to XYZ (D65 illuminant)
|
|
17
|
+
*/
|
|
18
|
+
function linearRgbToXyz(r, g, b) {
|
|
19
|
+
return {
|
|
20
|
+
x: 0.4124564 * r + 0.3575761 * g + 0.1804375 * b,
|
|
21
|
+
y: 0.2126729 * r + 0.7151522 * g + 0.0721750 * b,
|
|
22
|
+
z: 0.0193339 * r + 0.1191920 * g + 0.9503041 * b
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Convert XYZ to Lab (D65 reference white)
|
|
28
|
+
*/
|
|
29
|
+
function xyzToLab(x, y, z) {
|
|
30
|
+
// D65 reference white
|
|
31
|
+
const xn = 0.95047;
|
|
32
|
+
const yn = 1.00000;
|
|
33
|
+
const zn = 1.08883;
|
|
34
|
+
|
|
35
|
+
const f = (t) => t > 0.008856 ? Math.cbrt(t) : (903.3 * t + 16) / 116;
|
|
36
|
+
|
|
37
|
+
const fx = f(x / xn);
|
|
38
|
+
const fy = f(y / yn);
|
|
39
|
+
const fz = f(z / zn);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
l: 116 * fy - 16,
|
|
43
|
+
a: 500 * (fx - fy),
|
|
44
|
+
b: 200 * (fy - fz)
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Convert Lab to LCH
|
|
50
|
+
*/
|
|
51
|
+
function labToLch(l, a, b) {
|
|
52
|
+
const c = Math.sqrt(a * a + b * b);
|
|
53
|
+
let h = Math.atan2(b, a) * (180 / Math.PI);
|
|
54
|
+
if (h < 0) h += 360;
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
l: l,
|
|
58
|
+
c: c,
|
|
59
|
+
h: h
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Convert RGB to LCH
|
|
65
|
+
* @param {number} r - Red (0-255)
|
|
66
|
+
* @param {number} g - Green (0-255)
|
|
67
|
+
* @param {number} b - Blue (0-255)
|
|
68
|
+
* @returns {{ l: number, c: number, h: number }}
|
|
69
|
+
*/
|
|
70
|
+
export function rgbToLch(r, g, b) {
|
|
71
|
+
const lr = srgbToLinear(r);
|
|
72
|
+
const lg = srgbToLinear(g);
|
|
73
|
+
const lb = srgbToLinear(b);
|
|
74
|
+
|
|
75
|
+
const xyz = linearRgbToXyz(lr, lg, lb);
|
|
76
|
+
const lab = xyzToLab(xyz.x, xyz.y, xyz.z);
|
|
77
|
+
return labToLch(lab.l, lab.a, lab.b);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Convert linear RGB to OKLab
|
|
82
|
+
* Uses the OKLab color space for perceptual uniformity
|
|
83
|
+
*/
|
|
84
|
+
function linearRgbToOklab(r, g, b) {
|
|
85
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
86
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
87
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
L: 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
|
|
91
|
+
a: 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
|
|
92
|
+
b: 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Convert OKLab to OKLCH
|
|
98
|
+
*/
|
|
99
|
+
function oklabToOklch(L, a, b) {
|
|
100
|
+
const c = Math.sqrt(a * a + b * b);
|
|
101
|
+
let h = Math.atan2(b, a) * (180 / Math.PI);
|
|
102
|
+
if (h < 0) h += 360;
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
l: L,
|
|
106
|
+
c: c,
|
|
107
|
+
h: h
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Convert RGB to OKLCH
|
|
113
|
+
* @param {number} r - Red (0-255)
|
|
114
|
+
* @param {number} g - Green (0-255)
|
|
115
|
+
* @param {number} b - Blue (0-255)
|
|
116
|
+
* @returns {{ l: number, c: number, h: number }}
|
|
117
|
+
*/
|
|
118
|
+
export function rgbToOklch(r, g, b) {
|
|
119
|
+
const lr = srgbToLinear(r);
|
|
120
|
+
const lg = srgbToLinear(g);
|
|
121
|
+
const lb = srgbToLinear(b);
|
|
122
|
+
|
|
123
|
+
const oklab = linearRgbToOklab(lr, lg, lb);
|
|
124
|
+
return oklabToOklch(oklab.L, oklab.a, oklab.b);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Format LCH values as CSS lch() string
|
|
129
|
+
* @param {{ l: number, c: number, h: number }} lch
|
|
130
|
+
* @param {number} [alpha] - Optional alpha value (0-1)
|
|
131
|
+
* @returns {string}
|
|
132
|
+
*/
|
|
133
|
+
export function formatLch(lch, alpha) {
|
|
134
|
+
const l = Math.round(lch.l * 100) / 100;
|
|
135
|
+
const c = Math.round(lch.c * 100) / 100;
|
|
136
|
+
const h = Math.round(lch.h * 100) / 100;
|
|
137
|
+
|
|
138
|
+
if (alpha !== undefined && alpha < 1) {
|
|
139
|
+
return `lch(${l}% ${c} ${h} / ${alpha})`;
|
|
140
|
+
}
|
|
141
|
+
return `lch(${l}% ${c} ${h})`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Format OKLCH values as CSS oklch() string
|
|
146
|
+
* @param {{ l: number, c: number, h: number }} oklch
|
|
147
|
+
* @param {number} [alpha] - Optional alpha value (0-1)
|
|
148
|
+
* @returns {string}
|
|
149
|
+
*/
|
|
150
|
+
export function formatOklch(oklch, alpha) {
|
|
151
|
+
// OKLCH lightness is 0-1, displayed as percentage
|
|
152
|
+
const l = Math.round(oklch.l * 10000) / 100;
|
|
153
|
+
const c = Math.round(oklch.c * 1000) / 1000;
|
|
154
|
+
const h = Math.round(oklch.h * 100) / 100;
|
|
155
|
+
|
|
156
|
+
if (alpha !== undefined && alpha < 1) {
|
|
157
|
+
return `oklch(${l}% ${c} ${h} / ${alpha})`;
|
|
158
|
+
}
|
|
159
|
+
return `oklch(${l}% ${c} ${h})`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Parse a hex color string and return RGB values
|
|
164
|
+
* @param {string} hex - Hex color (#fff, #ffffff, #ffffffaa)
|
|
165
|
+
* @returns {{ r: number, g: number, b: number, a?: number } | null}
|
|
166
|
+
*/
|
|
167
|
+
export function hexToRgb(hex) {
|
|
168
|
+
if (!hex || !hex.startsWith('#')) return null;
|
|
169
|
+
|
|
170
|
+
// Remove #
|
|
171
|
+
hex = hex.slice(1);
|
|
172
|
+
|
|
173
|
+
// Handle 3-digit hex
|
|
174
|
+
if (hex.length === 3) {
|
|
175
|
+
return {
|
|
176
|
+
r: parseInt(hex[0] + hex[0], 16),
|
|
177
|
+
g: parseInt(hex[1] + hex[1], 16),
|
|
178
|
+
b: parseInt(hex[2] + hex[2], 16)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Handle 6-digit hex
|
|
183
|
+
if (hex.length === 6) {
|
|
184
|
+
return {
|
|
185
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
186
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
187
|
+
b: parseInt(hex.slice(4, 6), 16)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Handle 8-digit hex (with alpha)
|
|
192
|
+
if (hex.length === 8) {
|
|
193
|
+
return {
|
|
194
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
195
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
196
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
197
|
+
a: parseInt(hex.slice(6, 8), 16) / 255
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Convert any supported color format to all formats
|
|
206
|
+
* @param {string} colorString - Color in hex, rgb(), or rgba() format
|
|
207
|
+
* @returns {{ hex: string, rgb: string, lch: string, oklch: string, hasAlpha: boolean } | null}
|
|
208
|
+
*/
|
|
209
|
+
export function convertColor(colorString) {
|
|
210
|
+
let r, g, b, a;
|
|
211
|
+
|
|
212
|
+
// Parse rgba/rgb
|
|
213
|
+
const rgbaMatch = colorString.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
|
214
|
+
if (rgbaMatch) {
|
|
215
|
+
r = parseInt(rgbaMatch[1]);
|
|
216
|
+
g = parseInt(rgbaMatch[2]);
|
|
217
|
+
b = parseInt(rgbaMatch[3]);
|
|
218
|
+
a = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : undefined;
|
|
219
|
+
} else {
|
|
220
|
+
// Try hex
|
|
221
|
+
const rgb = hexToRgb(colorString);
|
|
222
|
+
if (!rgb) return null;
|
|
223
|
+
r = rgb.r;
|
|
224
|
+
g = rgb.g;
|
|
225
|
+
b = rgb.b;
|
|
226
|
+
a = rgb.a;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const hex = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
|
230
|
+
const rgbStr = a !== undefined ? `rgba(${r}, ${g}, ${b}, ${a})` : `rgb(${r}, ${g}, ${b})`;
|
|
231
|
+
|
|
232
|
+
const lchValues = rgbToLch(r, g, b);
|
|
233
|
+
const oklchValues = rgbToOklch(r, g, b);
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
hex: hex.toLowerCase(),
|
|
237
|
+
rgb: rgbStr,
|
|
238
|
+
lch: formatLch(lchValues, a),
|
|
239
|
+
oklch: formatOklch(oklchValues, a),
|
|
240
|
+
hasAlpha: a !== undefined && a < 1
|
|
241
|
+
};
|
|
242
|
+
}
|
package/lib/display.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import chalk from 'chalk';
|
|
9
|
+
import { convertColor } from './colors.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Creates a clickable terminal link using ANSI escape codes
|
|
@@ -94,65 +95,20 @@ function displayFavicons(favicons) {
|
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
function normalizeColorFormat(colorString) {
|
|
97
|
-
//
|
|
98
|
-
const
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
const g = parseInt(rgbaMatch[2]);
|
|
102
|
-
const b = parseInt(rgbaMatch[3]);
|
|
103
|
-
const a = rgbaMatch[4];
|
|
104
|
-
|
|
105
|
-
const hex = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
|
106
|
-
const rgb = a ? `rgba(${r}, ${g}, ${b}, ${a})` : `rgb(${r}, ${g}, ${b})`;
|
|
107
|
-
|
|
108
|
-
return { hex, rgb, hasAlpha: !!a };
|
|
98
|
+
// Use the centralized color conversion utility
|
|
99
|
+
const converted = convertColor(colorString);
|
|
100
|
+
if (converted) {
|
|
101
|
+
return converted;
|
|
109
102
|
}
|
|
110
103
|
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
hex: `#${hexMatch3[1]}${hexMatch3[1]}${hexMatch3[2]}${hexMatch3[2]}${hexMatch3[3]}${hexMatch3[3]}`.toLowerCase(),
|
|
121
|
-
rgb: `rgb(${r}, ${g}, ${b})`,
|
|
122
|
-
hasAlpha: false
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Match 8-digit hex with alpha (#ffffff80, #00ff00ff, etc.)
|
|
127
|
-
const hexMatch8 = colorString.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
|
|
128
|
-
if (hexMatch8) {
|
|
129
|
-
const r = parseInt(hexMatch8[1], 16);
|
|
130
|
-
const g = parseInt(hexMatch8[2], 16);
|
|
131
|
-
const b = parseInt(hexMatch8[3], 16);
|
|
132
|
-
const a = (parseInt(hexMatch8[4], 16) / 255).toFixed(2);
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
hex: `#${hexMatch8[1]}${hexMatch8[2]}${hexMatch8[3]}`.toLowerCase(),
|
|
136
|
-
rgb: `rgba(${r}, ${g}, ${b}, ${a})`,
|
|
137
|
-
hasAlpha: true
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Match 6-digit hex (#ffffff, #f0a0b0, etc.)
|
|
142
|
-
const hexMatch6 = colorString.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
|
|
143
|
-
if (hexMatch6) {
|
|
144
|
-
const r = parseInt(hexMatch6[1], 16);
|
|
145
|
-
const g = parseInt(hexMatch6[2], 16);
|
|
146
|
-
const b = parseInt(hexMatch6[3], 16);
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
hex: colorString.toLowerCase(),
|
|
150
|
-
rgb: `rgb(${r}, ${g}, ${b})`,
|
|
151
|
-
hasAlpha: false
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return { hex: colorString, rgb: colorString, hasAlpha: false };
|
|
104
|
+
// Fallback for unparseable colors
|
|
105
|
+
return {
|
|
106
|
+
hex: colorString,
|
|
107
|
+
rgb: colorString,
|
|
108
|
+
lch: colorString,
|
|
109
|
+
oklch: colorString,
|
|
110
|
+
hasAlpha: false
|
|
111
|
+
};
|
|
156
112
|
}
|
|
157
113
|
|
|
158
114
|
function displayColors(colors) {
|
|
@@ -170,6 +126,8 @@ function displayColors(colors) {
|
|
|
170
126
|
allColors.push({
|
|
171
127
|
hex: formats.hex,
|
|
172
128
|
rgb: formats.rgb,
|
|
129
|
+
lch: formats.lch,
|
|
130
|
+
oklch: formats.oklch,
|
|
173
131
|
hasAlpha: formats.hasAlpha,
|
|
174
132
|
label: role,
|
|
175
133
|
type: 'semantic',
|
|
@@ -181,12 +139,18 @@ function displayColors(colors) {
|
|
|
181
139
|
// Add CSS variables
|
|
182
140
|
if (colors.cssVariables) {
|
|
183
141
|
const limit = 15;
|
|
184
|
-
Object.entries(colors.cssVariables).slice(0, limit).forEach(([name,
|
|
142
|
+
Object.entries(colors.cssVariables).slice(0, limit).forEach(([name, varData]) => {
|
|
185
143
|
try {
|
|
186
|
-
|
|
144
|
+
// Handle both old format (string) and new format (object with value, lch, oklch)
|
|
145
|
+
const colorValue = typeof varData === 'string' ? varData : varData.value;
|
|
146
|
+
const formats = normalizeColorFormat(colorValue);
|
|
147
|
+
|
|
148
|
+
// Use pre-computed LCH/OKLCH from extractor if available
|
|
187
149
|
allColors.push({
|
|
188
150
|
hex: formats.hex,
|
|
189
151
|
rgb: formats.rgb,
|
|
152
|
+
lch: (typeof varData === 'object' && varData.lch) || formats.lch,
|
|
153
|
+
oklch: (typeof varData === 'object' && varData.oklch) || formats.oklch,
|
|
190
154
|
hasAlpha: formats.hasAlpha,
|
|
191
155
|
label: name,
|
|
192
156
|
type: 'variable',
|
|
@@ -208,6 +172,8 @@ function displayColors(colors) {
|
|
|
208
172
|
allColors.push({
|
|
209
173
|
hex: formats.hex,
|
|
210
174
|
rgb: formats.rgb,
|
|
175
|
+
lch: c.lch || formats.lch,
|
|
176
|
+
oklch: c.oklch || formats.oklch,
|
|
211
177
|
hasAlpha: formats.hasAlpha,
|
|
212
178
|
label: '',
|
|
213
179
|
type: 'palette',
|
|
@@ -244,10 +210,11 @@ function displayColors(colors) {
|
|
|
244
210
|
|
|
245
211
|
const uniqueColors = Array.from(colorMap.values());
|
|
246
212
|
|
|
247
|
-
// Display all colors with
|
|
248
|
-
uniqueColors.forEach(({ hex, rgb, label, confidence }, index) => {
|
|
213
|
+
// Display all colors with hex, RGB, LCH, and OKLCH formats
|
|
214
|
+
uniqueColors.forEach(({ hex, rgb, lch, oklch, label, confidence }, index) => {
|
|
249
215
|
const isLast = index === uniqueColors.length - 1;
|
|
250
216
|
const branch = isLast ? '└─' : '├─';
|
|
217
|
+
const indent = isLast ? ' ' : '│ ';
|
|
251
218
|
|
|
252
219
|
try {
|
|
253
220
|
const colorBlock = chalk.bgHex(hex)(' ');
|
|
@@ -256,12 +223,19 @@ function displayColors(colors) {
|
|
|
256
223
|
else if (confidence === 'medium') conf = chalk.hex('#FFB86C')('●');
|
|
257
224
|
else conf = chalk.gray('●'); // low confidence
|
|
258
225
|
|
|
259
|
-
const labelText = label ? chalk.dim(label) : '';
|
|
226
|
+
const labelText = label ? chalk.dim(` ${label}`) : '';
|
|
260
227
|
|
|
261
|
-
//
|
|
262
|
-
console.log(chalk.dim(`│ ${branch}`) + ' ' + `${conf} ${colorBlock} ${hex
|
|
228
|
+
// First line: color swatch, hex, and label
|
|
229
|
+
console.log(chalk.dim(`│ ${branch}`) + ' ' + `${conf} ${colorBlock} ${hex}${labelText}`);
|
|
230
|
+
// Second line: RGB and LCH
|
|
231
|
+
console.log(chalk.dim(`│ ${indent}├─`) + ' ' + chalk.dim('rgb: ') + rgb);
|
|
232
|
+
console.log(chalk.dim(`│ ${indent}├─`) + ' ' + chalk.dim('lch: ') + lch);
|
|
233
|
+
console.log(chalk.dim(`│ ${indent}└─`) + ' ' + chalk.dim('oklch: ') + oklch);
|
|
263
234
|
} catch {
|
|
264
|
-
console.log(chalk.dim(`│ ${branch}`) + ' ' + `${hex
|
|
235
|
+
console.log(chalk.dim(`│ ${branch}`) + ' ' + `${hex} ${label ? chalk.dim(label) : ''}`);
|
|
236
|
+
console.log(chalk.dim(`│ ${indent}├─`) + ' ' + chalk.dim('rgb: ') + rgb);
|
|
237
|
+
console.log(chalk.dim(`│ ${indent}├─`) + ' ' + chalk.dim('lch: ') + lch);
|
|
238
|
+
console.log(chalk.dim(`│ ${indent}└─`) + ' ' + chalk.dim('oklch: ') + oklch);
|
|
265
239
|
}
|
|
266
240
|
});
|
|
267
241
|
|
|
@@ -341,33 +315,49 @@ function displayTypography(typography) {
|
|
|
341
315
|
console.log(chalk.dim(`│ ${indent}${contextBranch}`) + ' ' + chalk.hex('#8BE9FD')(context));
|
|
342
316
|
|
|
343
317
|
styles.forEach((style, styleIndex) => {
|
|
344
|
-
const
|
|
318
|
+
const isStyleLast = styleIndex === styles.length - 1;
|
|
319
|
+
const styleIndent = isFontLast ? ' ' : '│ ';
|
|
320
|
+
const contextIndent = isContextLast ? ' ' : '│ ';
|
|
321
|
+
const styleBranch = isStyleLast ? '└─' : '├─';
|
|
322
|
+
const propIndent = isStyleLast ? ' ' : '│ ';
|
|
323
|
+
|
|
324
|
+
// Main size line
|
|
325
|
+
console.log(chalk.dim(`│ ${styleIndent}${contextIndent}${styleBranch}`) + ' ' + `${style.size}`);
|
|
345
326
|
|
|
327
|
+
// Collect properties
|
|
328
|
+
const props = [];
|
|
346
329
|
if (style.weight && style.weight !== 400) {
|
|
347
|
-
|
|
330
|
+
props.push({ key: 'weight', value: style.weight });
|
|
348
331
|
}
|
|
349
|
-
|
|
350
332
|
if (style.lineHeight) {
|
|
351
333
|
const lh = parseFloat(style.lineHeight);
|
|
352
334
|
let lhLabel = '';
|
|
353
|
-
if (lh <= 1.3) lhLabel = 'tight';
|
|
354
|
-
else if (lh >= 1.6) lhLabel = 'relaxed';
|
|
355
|
-
|
|
335
|
+
if (lh <= 1.3) lhLabel = ' (tight)';
|
|
336
|
+
else if (lh >= 1.6) lhLabel = ' (relaxed)';
|
|
337
|
+
props.push({ key: 'line-height', value: `${style.lineHeight}${lhLabel}` });
|
|
338
|
+
}
|
|
339
|
+
if (style.transform) {
|
|
340
|
+
props.push({ key: 'transform', value: style.transform });
|
|
341
|
+
}
|
|
342
|
+
if (style.spacing) {
|
|
343
|
+
props.push({ key: 'letter-spacing', value: style.spacing });
|
|
344
|
+
}
|
|
345
|
+
if (style.isFluid) {
|
|
346
|
+
props.push({ key: 'fluid', value: 'yes' });
|
|
347
|
+
}
|
|
348
|
+
if (style.fontFeatures) {
|
|
349
|
+
props.push({ key: 'features', value: style.fontFeatures });
|
|
356
350
|
}
|
|
357
351
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const contextIndent = isContextLast ? ' ' : '│ ';
|
|
368
|
-
const styleBranch = isStyleLast ? '└─' : '├─';
|
|
369
|
-
|
|
370
|
-
console.log(chalk.dim(`│ ${styleIndent}${contextIndent}${styleBranch}`) + ' ' + `${style.size}${modifierStr}`);
|
|
352
|
+
// Display properties
|
|
353
|
+
props.forEach((prop, propIndex) => {
|
|
354
|
+
const isLastProp = propIndex === props.length - 1;
|
|
355
|
+
const propBranch = isLastProp ? '└─' : '├─';
|
|
356
|
+
console.log(
|
|
357
|
+
chalk.dim(`│ ${styleIndent}${contextIndent}${propIndent}${propBranch}`) + ' ' +
|
|
358
|
+
chalk.dim(`${prop.key}: `) + `${prop.value}`
|
|
359
|
+
);
|
|
360
|
+
});
|
|
371
361
|
});
|
|
372
362
|
}
|
|
373
363
|
}
|
package/lib/extractors.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import chalk from "chalk";
|
|
9
|
+
import { convertColor } from "./colors.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Main extraction function - orchestrates the entire brand analysis process
|
|
@@ -793,7 +794,7 @@ async function extractLogo(page, url) {
|
|
|
793
794
|
* Analyzes semantic colors, CSS variables, and visual frequency
|
|
794
795
|
*/
|
|
795
796
|
async function extractColors(page) {
|
|
796
|
-
|
|
797
|
+
const result = await page.evaluate(() => {
|
|
797
798
|
// Helper: Convert any color to normalized hex for deduplication
|
|
798
799
|
function normalizeColor(color) {
|
|
799
800
|
const rgbaMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
@@ -1214,6 +1215,41 @@ async function extractColors(page) {
|
|
|
1214
1215
|
cssVariables: filteredCssVariables,
|
|
1215
1216
|
};
|
|
1216
1217
|
});
|
|
1218
|
+
|
|
1219
|
+
// Post-process: add LCH and OKLCH color formats to palette
|
|
1220
|
+
if (result && result.palette) {
|
|
1221
|
+
result.palette = result.palette.map((colorItem) => {
|
|
1222
|
+
const converted = convertColor(colorItem.normalized || colorItem.color);
|
|
1223
|
+
if (converted) {
|
|
1224
|
+
return {
|
|
1225
|
+
...colorItem,
|
|
1226
|
+
lch: converted.lch,
|
|
1227
|
+
oklch: converted.oklch,
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
return colorItem;
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// Post-process: add LCH and OKLCH to CSS variables
|
|
1235
|
+
if (result && result.cssVariables) {
|
|
1236
|
+
const enhancedCssVariables = {};
|
|
1237
|
+
for (const [name, value] of Object.entries(result.cssVariables)) {
|
|
1238
|
+
const converted = convertColor(value);
|
|
1239
|
+
if (converted) {
|
|
1240
|
+
enhancedCssVariables[name] = {
|
|
1241
|
+
value,
|
|
1242
|
+
lch: converted.lch,
|
|
1243
|
+
oklch: converted.oklch,
|
|
1244
|
+
};
|
|
1245
|
+
} else {
|
|
1246
|
+
enhancedCssVariables[name] = { value };
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
result.cssVariables = enhancedCssVariables;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
return result;
|
|
1217
1253
|
}
|
|
1218
1254
|
|
|
1219
1255
|
async function extractTypography(page) {
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dembrandt",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Extract design tokens and brand assets from any website",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"dembrandt": "
|
|
8
|
+
"dembrandt": "index.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|
|
31
|
-
"url": "https://github.com/dembrandt/dembrandt.git"
|
|
31
|
+
"url": "git+https://github.com/dembrandt/dembrandt.git"
|
|
32
32
|
},
|
|
33
33
|
"bugs": {
|
|
34
34
|
"url": "https://github.com/dembrandt/dembrandt/issues"
|