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 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.1")
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
- // Save page screenshot if requested
634
+ // Take screenshot if requested
623
635
  if (options.screenshotPath) {
624
- try {
625
- const { mkdirSync } = await import("fs");
626
- const { dirname } = await import("path");
627
- mkdirSync(dirname(options.screenshotPath), { recursive: true });
628
- await page.screenshot({ path: options.screenshotPath, fullPage: false });
629
- } catch (err) {
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 all colors from this element
1132
- const allColors = [
1133
- ...extractColorsFromValue(bgColor),
1134
- ...extractColorsFromValue(textColor),
1135
- ...extractColorsFromValue(borderColor)
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
- .map(([normalizedColor, data]) => ({
1278
- color: data.original,
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dembrandt",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Extract design tokens and brand assets from any website",
5
5
  "main": "index.js",
6
6
  "type": "module",