dembrandt 0.7.1 → 0.7.2
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/extractors.js +65 -26
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import { join } from "path";
|
|
|
21
21
|
program
|
|
22
22
|
.name("dembrandt")
|
|
23
23
|
.description("Extract design tokens from any website")
|
|
24
|
-
.version("0.7.
|
|
24
|
+
.version("0.7.2")
|
|
25
25
|
.argument("<url>")
|
|
26
26
|
.option("--browser <type>", "Browser to use (chromium|firefox)", "chromium")
|
|
27
27
|
.option("--json-only", "Output raw JSON")
|
package/lib/extractors.js
CHANGED
|
@@ -448,7 +448,7 @@ export async function extractBranding(
|
|
|
448
448
|
await page.mouse.move(0, 0).catch(() => { });
|
|
449
449
|
|
|
450
450
|
// Merge hover/focus colors into palette
|
|
451
|
-
hoverFocusColors.forEach(({ color }) => {
|
|
451
|
+
hoverFocusColors.forEach(({ color, property }) => {
|
|
452
452
|
const isDuplicate = colors.palette.some((c) => c.color === color);
|
|
453
453
|
if (!isDuplicate && color) {
|
|
454
454
|
// Normalize and add to palette
|
|
@@ -461,6 +461,18 @@ export async function extractBranding(
|
|
|
461
461
|
normalized = `#${r}${g}${b}`;
|
|
462
462
|
}
|
|
463
463
|
|
|
464
|
+
// Skip chromatic colors that only appear in border/outline on hover — likely focus rings, not brand colors
|
|
465
|
+
if (property !== 'background-color') {
|
|
466
|
+
const hex = normalized.replace('#', '');
|
|
467
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
468
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
469
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
470
|
+
const max = Math.max(r, g, b);
|
|
471
|
+
const min = Math.min(r, g, b);
|
|
472
|
+
const saturation = max === 0 ? 0 : (max - min) / max;
|
|
473
|
+
if (saturation > 0.3) return;
|
|
474
|
+
}
|
|
475
|
+
|
|
464
476
|
colors.palette.push({
|
|
465
477
|
color,
|
|
466
478
|
normalized,
|
|
@@ -619,16 +631,14 @@ export async function extractBranding(
|
|
|
619
631
|
result.isCanvasOnly = true;
|
|
620
632
|
}
|
|
621
633
|
|
|
622
|
-
//
|
|
634
|
+
// Take screenshot if requested
|
|
623
635
|
if (options.screenshotPath) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
console.error(chalk.dim(` ↳ Screenshot failed: ${err.message}`));
|
|
631
|
-
}
|
|
636
|
+
await page.screenshot({ path: options.screenshotPath, fullPage: false });
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Include raw colors before filtering if requested
|
|
640
|
+
if (options.includeRawColors) {
|
|
641
|
+
result.colors.rawColors = colors._raw || [];
|
|
632
642
|
}
|
|
633
643
|
|
|
634
644
|
return result;
|
|
@@ -987,6 +997,21 @@ async function extractColors(page) {
|
|
|
987
997
|
continue;
|
|
988
998
|
}
|
|
989
999
|
|
|
1000
|
+
// Skip UI framework default theme variables
|
|
1001
|
+
if (
|
|
1002
|
+
prop.startsWith("--el-") || // Element Plus/UI
|
|
1003
|
+
prop.startsWith("--p-") || // PrimeVue/PrimeReact
|
|
1004
|
+
prop.startsWith("--chakra-") || // Chakra UI
|
|
1005
|
+
prop.startsWith("--mantine-") || // Mantine
|
|
1006
|
+
prop.startsWith("--ant-") || // Ant Design
|
|
1007
|
+
prop.startsWith("--bs-") || // Bootstrap
|
|
1008
|
+
prop.startsWith("--swiper-") || // Swiper
|
|
1009
|
+
prop.startsWith("--rsbs-") || // React Spring Bottom Sheet
|
|
1010
|
+
prop.startsWith("--toastify-") // React Toastify
|
|
1011
|
+
) {
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
990
1015
|
// Skip obvious system/default variables
|
|
991
1016
|
if (prop.includes("--system-") || prop.includes("--default-")) {
|
|
992
1017
|
continue;
|
|
@@ -1078,6 +1103,10 @@ async function extractColors(page) {
|
|
|
1078
1103
|
) {
|
|
1079
1104
|
return;
|
|
1080
1105
|
}
|
|
1106
|
+
const rect = el.getBoundingClientRect();
|
|
1107
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1081
1110
|
|
|
1082
1111
|
const bgColor = computed.backgroundColor;
|
|
1083
1112
|
const textColor = computed.color;
|
|
@@ -1128,12 +1157,11 @@ async function extractColors(page) {
|
|
|
1128
1157
|
);
|
|
1129
1158
|
}
|
|
1130
1159
|
|
|
1131
|
-
// Collect
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
];
|
|
1160
|
+
// Collect colors by property type
|
|
1161
|
+
const bgColors = extractColorsFromValue(bgColor);
|
|
1162
|
+
const textColors = extractColorsFromValue(textColor);
|
|
1163
|
+
const borderColors = extractColorsFromValue(borderColor);
|
|
1164
|
+
const allColors = [...bgColors, ...textColors, ...borderColors];
|
|
1137
1165
|
|
|
1138
1166
|
allColors.forEach((color) => {
|
|
1139
1167
|
if (color && color !== "rgba(0, 0, 0, 0)" && color !== "transparent") {
|
|
@@ -1141,10 +1169,12 @@ async function extractColors(page) {
|
|
|
1141
1169
|
const existing = colorMap.get(normalized) || {
|
|
1142
1170
|
original: color, // Keep first seen format
|
|
1143
1171
|
count: 0,
|
|
1172
|
+
bgCount: 0,
|
|
1144
1173
|
score: 0,
|
|
1145
1174
|
sources: new Set(),
|
|
1146
1175
|
};
|
|
1147
1176
|
existing.count++;
|
|
1177
|
+
if (bgColors.includes(color)) existing.bgCount++;
|
|
1148
1178
|
existing.score += score;
|
|
1149
1179
|
if (score > 1) {
|
|
1150
1180
|
const source = context.split(" ")[0].substring(0, 30); // Limit source length
|
|
@@ -1191,6 +1221,22 @@ async function extractColors(page) {
|
|
|
1191
1221
|
return true;
|
|
1192
1222
|
}
|
|
1193
1223
|
|
|
1224
|
+
// Chromatic colors (saturated) that never appear as backgrounds are likely browser defaults
|
|
1225
|
+
// or framework internals, not brand colors. Real brand colors appear on backgrounds somewhere.
|
|
1226
|
+
if (data.bgCount === 0) {
|
|
1227
|
+
const hex = normalized.replace('#', '');
|
|
1228
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
1229
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
1230
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
1231
|
+
const max = Math.max(r, g, b);
|
|
1232
|
+
const min = Math.min(r, g, b);
|
|
1233
|
+
const saturation = max === 0 ? 0 : (max - min) / max;
|
|
1234
|
+
// If saturation > 0.3, this is a chromatic color with no background usage
|
|
1235
|
+
if (saturation > 0.3) {
|
|
1236
|
+
return true;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1194
1240
|
return false;
|
|
1195
1241
|
}
|
|
1196
1242
|
|
|
@@ -1272,16 +1318,9 @@ async function extractColors(page) {
|
|
|
1272
1318
|
return Math.sqrt(deltaL * deltaL + deltaA * deltaA + deltaB * deltaB);
|
|
1273
1319
|
}
|
|
1274
1320
|
|
|
1275
|
-
// Capture all colors before filtering (for QA/debugging)
|
|
1276
1321
|
const rawColors = Array.from(colorMap.entries())
|
|
1277
|
-
.
|
|
1278
|
-
|
|
1279
|
-
normalized: normalizedColor,
|
|
1280
|
-
count: data.count,
|
|
1281
|
-
score: data.score,
|
|
1282
|
-
sources: Array.from(data.sources).slice(0, 3),
|
|
1283
|
-
}))
|
|
1284
|
-
.sort((a, b) => b.count - a.count);
|
|
1322
|
+
.filter(([, data]) => data.count >= threshold)
|
|
1323
|
+
.map(([normalized, data]) => ({ color: data.original, normalized, count: data.count }));
|
|
1285
1324
|
|
|
1286
1325
|
const palette = Array.from(colorMap.entries())
|
|
1287
1326
|
.filter(([normalizedColor, data]) => {
|
|
@@ -1371,8 +1410,8 @@ async function extractColors(page) {
|
|
|
1371
1410
|
return {
|
|
1372
1411
|
semantic: semanticColors,
|
|
1373
1412
|
palette: perceptuallyDeduped,
|
|
1374
|
-
rawColors,
|
|
1375
1413
|
cssVariables: filteredCssVariables,
|
|
1414
|
+
_raw: rawColors,
|
|
1376
1415
|
};
|
|
1377
1416
|
});
|
|
1378
1417
|
|