vibe-design-system 2.8.74 → 2.8.76

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-design-system",
3
- "version": "2.8.74",
3
+ "version": "2.8.76",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "homepage": "https://vibedesign.tech",
6
6
  "repository": {
@@ -522,26 +522,74 @@ function extractBrandAssets() {
522
522
  function extractLucideIconsUsed(srcDir) {
523
523
  const allFiles = getAllTsxJsxInDir(srcDir);
524
524
  const files = allFiles.filter((rel) => !/^stories[/\\]/.test(rel.replace(/\\/g, "/")));
525
+ // Pass 1: collect all imported icon names + track alias renames
525
526
  const names = new Set();
527
+ const aliasMap = new Map(); // localAlias → originalName (for `import { X as Y }`)
526
528
  const importRe = /import\s*\{([^}]+)\}\s*from\s*["']lucide-react["']/g;
529
+ const fileContents = new Map();
527
530
  for (const rel of files) {
528
531
  const fullPath = path.join(srcDir, rel);
529
532
  try {
530
533
  const content = fs.readFileSync(fullPath, "utf-8");
534
+ fileContents.set(rel, content);
531
535
  let m;
532
536
  while ((m = importRe.exec(content)) !== null) {
533
537
  const block = m[1];
534
538
  block.split(",").forEach((part) => {
535
539
  const trimmed = part.trim();
536
- const asMatch = trimmed.match(/^(\w+)\s+as\s+/);
537
- const name = asMatch ? asMatch[1] : trimmed.split(/\s+/)[0];
538
- if (name && /^[A-Z][a-zA-Z0-9]*$/.test(name)) names.add(name);
540
+ const asMatch = trimmed.match(/^(\w+)\s+as\s+(\w+)/);
541
+ if (asMatch) {
542
+ names.add(asMatch[1]);
543
+ aliasMap.set(asMatch[2], asMatch[1]); // Y → X
544
+ } else {
545
+ const name = trimmed.split(/\s+/)[0];
546
+ if (name && /^[A-Z][a-zA-Z0-9]*$/.test(name)) names.add(name);
547
+ }
539
548
  });
540
549
  }
541
550
  } catch (_) {}
542
551
  importRe.lastIndex = 0;
543
552
  }
544
- return [...names].sort();
553
+ if (names.size === 0) return [];
554
+
555
+ // Pass 2: count JSX usages per icon per component file
556
+ // Build lookup: all names + aliases → canonical icon name
557
+ const nameArr = [...names];
558
+ const allLocalNames = new Set([...nameArr]);
559
+ for (const [alias, orig] of aliasMap) if (names.has(orig)) allLocalNames.add(alias);
560
+
561
+ // usageCounts: iconName → Map<componentName, count>
562
+ const usageCounts = new Map();
563
+ for (const n of nameArr) usageCounts.set(n, new Map());
564
+
565
+ // JSX tag regex: matches <IconName, <IconName/ or <IconName>
566
+ const jsxTagRe = new RegExp(`<(${[...allLocalNames].join("|")})(?=[\\s/>])`, "g");
567
+
568
+ for (const [rel, content] of fileContents) {
569
+ const componentName = path.basename(rel).replace(/\.(tsx|jsx|ts|js)$/, "");
570
+ jsxTagRe.lastIndex = 0;
571
+ let m;
572
+ while ((m = jsxTagRe.exec(content)) !== null) {
573
+ const localName = m[1];
574
+ const canonName = aliasMap.has(localName) ? aliasMap.get(localName) : localName;
575
+ if (usageCounts.has(canonName)) {
576
+ const fm = usageCounts.get(canonName);
577
+ fm.set(componentName, (fm.get(componentName) || 0) + 1);
578
+ }
579
+ }
580
+ }
581
+
582
+ return nameArr
583
+ .map((name) => {
584
+ const fileMap = usageCounts.get(name) || new Map();
585
+ const total = [...fileMap.values()].reduce((a, b) => a + b, 0);
586
+ const topFiles = [...fileMap.entries()]
587
+ .sort((a, b) => b[1] - a[1])
588
+ .slice(0, 5)
589
+ .map(([f]) => f);
590
+ return { name, total, topFiles };
591
+ })
592
+ .sort((a, b) => b.total - a.total || a.name.localeCompare(b.name));
545
593
  }
546
594
 
547
595
  function extractVdsTags(content) {
@@ -1217,27 +1265,68 @@ function getTailwindExtendColors() {
1217
1265
  }
1218
1266
 
1219
1267
  function parseTailwindThemeFromText(raw) {
1220
- const result = { shadows: {}, spacing: {}, breakpoints: {}, zIndex: {}, transitionDuration: {}, transitionTimingFunction: {}, animation: {}, colors: {} };
1221
- try {
1222
- const spacingM = raw.match(/spacing\s*:\s*\{([\s\S]*?)\}/);
1223
- if (spacingM) {
1224
- const re = /["']?([\w.-]+)["']?\s*:\s*["']([^"']+)["']/g;
1225
- let m;
1226
- while ((m = re.exec(spacingM[1])) !== null) result.spacing[m[1]] = m[2];
1268
+ const result = {
1269
+ shadows: {}, spacing: {}, breakpoints: {}, zIndex: {},
1270
+ transitionDuration: {}, transitionTimingFunction: {}, animation: {}, colors: {},
1271
+ fontSize: {}, fontWeight: {},
1272
+ };
1273
+ // Helper: extract a shallow key:value block (handles one level of nesting)
1274
+ function extractBlock(key) {
1275
+ // Match "key: {" and capture until the matching closing brace (one level deep)
1276
+ const re = new RegExp(key + "\\s*:\\s*\\{");
1277
+ const start = raw.search(re);
1278
+ if (start === -1) return null;
1279
+ const open = raw.indexOf("{", start);
1280
+ if (open === -1) return null;
1281
+ let depth = 0, i = open;
1282
+ while (i < raw.length) {
1283
+ if (raw[i] === "{") depth++;
1284
+ else if (raw[i] === "}") { depth--; if (depth === 0) return raw.slice(open + 1, i); }
1285
+ i++;
1227
1286
  }
1228
- const screensM = raw.match(/screens\s*:\s*\{([\s\S]*?)\}/);
1229
- if (screensM) {
1230
- const re = /["']?([\w.-]+)["']?\s*:\s*["']([^"']+)["']/g;
1231
- let m;
1232
- while ((m = re.exec(screensM[1])) !== null) result.breakpoints[m[1]] = m[2];
1287
+ return null;
1288
+ }
1289
+ // Helper: parse simple key:"value" pairs from a block string
1290
+ function parseKV(block) {
1291
+ const out = {};
1292
+ if (!block) return out;
1293
+ const re = /["']?([\w.-]+)["']?\s*:\s*["']([^"']+)["']/g;
1294
+ let m;
1295
+ while ((m = re.exec(block)) !== null) out[m[1]] = m[2];
1296
+ return out;
1297
+ }
1298
+ // Helper: parse fontSize which can be string OR [string, {...}] (array form)
1299
+ function parseFontSize(block) {
1300
+ const out = {};
1301
+ if (!block) return out;
1302
+ // Array form: "base": ["1rem", { lineHeight: "1.5rem" }]
1303
+ const arrRe = /["']?([\w.-]+)["']?\s*:\s*\[\s*["']([^"']+)["']/g;
1304
+ let m;
1305
+ while ((m = arrRe.exec(block)) !== null) out[m[1]] = m[2];
1306
+ // Simple string form (fills in what array form missed): "xs": "0.75rem"
1307
+ const strRe = /["']?([\w.-]+)["']?\s*:\s*["']([^"']+)["']/g;
1308
+ while ((m = strRe.exec(block)) !== null) {
1309
+ if (!out[m[1]]) out[m[1]] = m[2];
1233
1310
  }
1311
+ return out;
1312
+ }
1313
+
1314
+ try {
1315
+ result.spacing = parseKV(extractBlock("spacing"));
1316
+ result.breakpoints = parseKV(extractBlock("screens"));
1317
+ result.zIndex = parseKV(extractBlock("zIndex"));
1318
+ result.transitionDuration = parseKV(extractBlock("transitionDuration"));
1319
+ result.transitionTimingFunction = parseKV(extractBlock("transitionTimingFunction"));
1320
+ result.animation = parseKV(extractBlock("animation"));
1321
+ result.fontWeight = parseKV(extractBlock("fontWeight"));
1322
+ result.fontSize = parseFontSize(extractBlock("fontSize"));
1234
1323
  } catch (_) {}
1235
1324
  return result;
1236
1325
  }
1237
1326
 
1238
1327
  /** Resolve Tailwind theme for boxShadow, spacing, screens, zIndex, motion. Uses resolveConfig for .js/.mjs, text-parse for .ts. */
1239
1328
  function getTailwindTheme() {
1240
- const empty = { shadows: {}, spacing: {}, breakpoints: {}, zIndex: {}, transitionDuration: {}, transitionTimingFunction: {}, animation: {}, colors: {} };
1329
+ const empty = { shadows: {}, spacing: {}, breakpoints: {}, zIndex: {}, transitionDuration: {}, transitionTimingFunction: {}, animation: {}, colors: {}, fontSize: {}, fontWeight: {} };
1241
1330
  const twPath = path.join(PROJECT_ROOT, "tailwind.config.js");
1242
1331
  const twPathMjs = path.join(PROJECT_ROOT, "tailwind.config.mjs");
1243
1332
  const twPathTs = path.join(PROJECT_ROOT, "tailwind.config.ts");
@@ -1260,6 +1349,8 @@ function getTailwindTheme() {
1260
1349
  transitionTimingFunction: toObj(theme.transitionTimingFunction),
1261
1350
  animation: toObj(theme.animation),
1262
1351
  colors: toObj(theme.colors),
1352
+ fontSize: toObj(theme.fontSize),
1353
+ fontWeight: toObj(theme.fontWeight),
1263
1354
  };
1264
1355
  } catch (_) {
1265
1356
  return empty;
@@ -1306,9 +1397,14 @@ function extractFoundations() {
1306
1397
  if (cssChunks.length > 0) {
1307
1398
  const css = cssChunks.join("\n");
1308
1399
 
1309
- const rootMatch = css.match(/:root\s*\{([\s\S]*?)\}/);
1310
- if (rootMatch) {
1311
- const rootVars = parseCssVarBlock(rootMatch[1]);
1400
+ // Collect ALL :root blocks (a file may have multiple, e.g. a small one early
1401
+ // and the main one inside @layer base { :root { } })
1402
+ const rootBlocks = [];
1403
+ const rootGlobalRe = /:root\s*\{([\s\S]*?)\}/g;
1404
+ let rootM;
1405
+ while ((rootM = rootGlobalRe.exec(css)) !== null) { rootBlocks.push(rootM[1]); }
1406
+ if (rootBlocks.length > 0) {
1407
+ const rootVars = parseCssVarBlock(rootBlocks.join("\n"));
1312
1408
  for (const [name, value] of Object.entries(rootVars)) {
1313
1409
  if (/\d+\s+\d+%/.test(value) || /\d+%/.test(value)) {
1314
1410
  const hsl = `hsl(${value})`;
@@ -1379,6 +1475,14 @@ function extractFoundations() {
1379
1475
  });
1380
1476
  if (typeScale.length > 0) typography.typeScale = typeScale;
1381
1477
 
1478
+ // Tailwind v4: font-weight custom props (--font-weight-*)
1479
+ const fontWeightVarRe = /--font-weight-([\w-]+)\s*:\s*([^;\n]+);/g;
1480
+ let fwvm;
1481
+ while ((fwvm = fontWeightVarRe.exec(css)) !== null) {
1482
+ if (!typography.fontWeightScale) typography.fontWeightScale = {};
1483
+ typography.fontWeightScale[fwvm[1]] = fwvm[2].trim();
1484
+ }
1485
+
1382
1486
  // Tailwind v4: extract design tokens from @layer theme { :root { ... } } or bare :root
1383
1487
  // Variables: --spacing (base), --radius-*, --shadow-*, --ease-*, --duration-*, --animate-*
1384
1488
  const v4ThemeVars = {};
@@ -1778,6 +1882,39 @@ function extractFoundations() {
1778
1882
  const mergedDurations = { ...v4Durations, ...normalizeThemeObj(twTheme.transitionDuration) };
1779
1883
  const mergedAnimations = { ...v4Animations, ...normalizeThemeObj(twTheme.animation) };
1780
1884
 
1885
+ // ── Fallback: build typeScale from twTheme.fontSize if CSS vars produced nothing ──
1886
+ if (!typography.typeScale || typography.typeScale.length === 0) {
1887
+ const fsObj = normalizeThemeObj(twTheme.fontSize);
1888
+ const fsEntries = Object.entries(fsObj);
1889
+ if (fsEntries.length > 0) {
1890
+ // Order: canonical Tailwind steps first, then any custom steps
1891
+ const ORDER = ["xs","sm","base","lg","xl","2xl","3xl","4xl","5xl","6xl","7xl","8xl","9xl"];
1892
+ const ordered = [
1893
+ ...ORDER.filter((k) => fsObj[k]).map((k) => [k, fsObj[k]]),
1894
+ ...fsEntries.filter(([k]) => !ORDER.includes(k)),
1895
+ ];
1896
+ typography.typeScale = ordered.map(([step, size]) => {
1897
+ // fontSize can be "[size, {lineHeight: ...}]" (array serialized) or plain "1rem"
1898
+ // After toObj + toString it looks like "1rem,1.5rem" or just "1rem" or "[object Object]"
1899
+ let resolvedSize = size;
1900
+ // Handle tuple-ish form from resolveConfig: e.g. "0.75rem,..." or ["0.75rem",{lineHeight:"1rem"}]
1901
+ if (resolvedSize && resolvedSize.includes(",")) resolvedSize = resolvedSize.split(",")[0].trim();
1902
+ if (!resolvedSize || resolvedSize === "[object Object]") return null;
1903
+ const pxM = resolvedSize.match(/^([\d.]+)rem$/);
1904
+ const px = pxM ? Math.round(parseFloat(pxM[1]) * 16) + "px" : null;
1905
+ return { step, size: resolvedSize, px, lineHeight: null };
1906
+ }).filter(Boolean);
1907
+ }
1908
+ }
1909
+
1910
+ // ── Fallback: build fontWeightScale from twTheme.fontWeight if CSS vars produced nothing ──
1911
+ if (!typography.fontWeightScale || Object.keys(typography.fontWeightScale).length === 0) {
1912
+ const fwObj = normalizeThemeObj(twTheme.fontWeight);
1913
+ if (Object.keys(fwObj).length > 0) {
1914
+ typography.fontWeightScale = fwObj;
1915
+ }
1916
+ }
1917
+
1781
1918
  return {
1782
1919
  colors: foundationsColors,
1783
1920
  typography,
@@ -2182,9 +2182,11 @@ function buildStoryFileContent(comp) {
2182
2182
  const resolvedColors = colorRaw.map(token => {
2183
2183
  const m = token.match(/^(?:bg|text|border|ring|from|to|fill|stroke)-(.+)$/);
2184
2184
  const key = m ? m[1] : null;
2185
- const entry = key ? foundColors[key] : null;
2185
+ // Strip opacity modifier (e.g. "muted/20" "muted") as a fallback
2186
+ const baseKey = key ? key.replace(/\/[\d.]+$/, "") : null;
2187
+ const entry = key ? (foundColors[key] || (baseKey !== key ? foundColors[baseKey] : null)) : null;
2186
2188
  const hex = entry?.hex && /^#[0-9a-fA-F]{3,8}$/.test(entry.hex) ? entry.hex : null;
2187
- return { token, hex, label: key };
2189
+ return { token, hex, label: baseKey || key };
2188
2190
  });
2189
2191
 
2190
2192
  const hasContent = resolvedColors.length > 0 || spacingRaw.length > 0 || typographyRaw.length > 0 || radiusRaw.length > 0 || animRaw.length > 0;
@@ -2605,7 +2607,32 @@ function writeFoundationsStories(foundations, components) {
2605
2607
 
2606
2608
  {
2607
2609
  const typo = foundations?.typography || {};
2608
- const typeScale = Array.isArray(typo.typeScale) ? typo.typeScale : [];
2610
+ // Tailwind v3 default type scale (canonical sizes)
2611
+ const TW_DEFAULT_TYPE_SCALE = [
2612
+ { step: "xs", size: "0.75rem", px: "12px", lineHeight: "1rem" },
2613
+ { step: "sm", size: "0.875rem", px: "14px", lineHeight: "1.25rem" },
2614
+ { step: "base", size: "1rem", px: "16px", lineHeight: "1.5rem" },
2615
+ { step: "lg", size: "1.125rem", px: "18px", lineHeight: "1.75rem" },
2616
+ { step: "xl", size: "1.25rem", px: "20px", lineHeight: "1.75rem" },
2617
+ { step: "2xl", size: "1.5rem", px: "24px", lineHeight: "2rem" },
2618
+ { step: "3xl", size: "1.875rem", px: "30px", lineHeight: "2.25rem" },
2619
+ { step: "4xl", size: "2.25rem", px: "36px", lineHeight: "2.5rem" },
2620
+ { step: "5xl", size: "3rem", px: "48px", lineHeight: "1" },
2621
+ ];
2622
+ const TW_DEFAULT_FONT_WEIGHTS = [
2623
+ { token: "thin", value: "100" },
2624
+ { token: "extralight", value: "200" },
2625
+ { token: "light", value: "300" },
2626
+ { token: "normal", value: "400" },
2627
+ { token: "medium", value: "500" },
2628
+ { token: "semibold", value: "600" },
2629
+ { token: "bold", value: "700" },
2630
+ { token: "extrabold", value: "800" },
2631
+ { token: "black", value: "900" },
2632
+ ];
2633
+ let typeScale = Array.isArray(typo.typeScale) ? typo.typeScale : [];
2634
+ const typeScaleIsDefault = typeScale.length === 0;
2635
+ if (typeScaleIsDefault) typeScale = TW_DEFAULT_TYPE_SCALE;
2609
2636
  const usedTextSizes = (foundations?.tokenUsage?.textSizes || []);
2610
2637
  const usedTextMap = Object.fromEntries(usedTextSizes.map(({ token, count }) => [token, count]));
2611
2638
 
@@ -2619,11 +2646,23 @@ function writeFoundationsStories(foundations, components) {
2619
2646
  })
2620
2647
  .filter((r) => r.value);
2621
2648
 
2622
- // Font weights
2649
+ // Font weights — check fontWeightScale (from twTheme.fontWeight), then legacy specific keys, then Tailwind defaults
2623
2650
  const weightKeys = ["fontWeightNormal", "fontWeightMedium", "fontWeightSemibold", "fontWeightBold"];
2624
- const weightRows = weightKeys
2651
+ let weightRows = weightKeys
2625
2652
  .filter((k) => typo[k])
2626
2653
  .map((k) => ({ token: k, value: String(typo[k]) }));
2654
+ if (weightRows.length === 0 && typo.fontWeightScale && typeof typo.fontWeightScale === "object") {
2655
+ const WGT_ORDER = ["thin","extralight","light","normal","medium","semibold","bold","extrabold","black"];
2656
+ const wEntries = Object.entries(typo.fontWeightScale);
2657
+ wEntries.sort(([a], [b]) => {
2658
+ const ia = WGT_ORDER.indexOf(a), ib = WGT_ORDER.indexOf(b);
2659
+ if (ia >= 0 && ib >= 0) return ia - ib;
2660
+ return parseInt(a) - parseInt(b) || a.localeCompare(b);
2661
+ });
2662
+ weightRows = wEntries.map(([token, value]) => ({ token, value: String(value) }));
2663
+ }
2664
+ const weightRowsIsDefault = weightRows.length === 0;
2665
+ if (weightRowsIsDefault) weightRows = TW_DEFAULT_FONT_WEIGHTS;
2627
2666
 
2628
2667
  // Reverse type scale for display (largest first)
2629
2668
  const scaleDesc = [...typeScale].reverse();
@@ -2639,9 +2678,11 @@ function writeFoundationsStories(foundations, components) {
2639
2678
  "type Story = StoryObj;",
2640
2679
  "",
2641
2680
  `const typeScale: { step: string; size: string; px: string | null; lineHeight: string | null }[] = ${JSON.stringify(scaleDesc)};`,
2681
+ `const typeScaleIsDefault: boolean = ${typeScaleIsDefault};`,
2642
2682
  `const usedTextSizes: { token: string; count: number }[] = ${JSON.stringify(usedTextSizes)};`,
2643
2683
  `const familyRows: { token: string; value: string }[] = ${JSON.stringify(familyRows)};`,
2644
2684
  `const weightRows: { token: string; value: string }[] = ${JSON.stringify(weightRows)};`,
2685
+ `const weightRowsIsDefault: boolean = ${weightRowsIsDefault};`,
2645
2686
  `const sansFamily = ${JSON.stringify(sansFamily)};`,
2646
2687
  "",
2647
2688
  "export const Default: Story = {",
@@ -2664,10 +2705,8 @@ function writeFoundationsStories(foundations, components) {
2664
2705
  " </div>",
2665
2706
  " </div>",
2666
2707
  " )}",
2667
- " {typeScale.length === 0 ? (",
2668
- " <p style={{ color: \"#999\", fontSize: 13, marginBottom: 32 }}>No type scale detected.</p>",
2669
- " ) : (",
2670
- " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 0, marginBottom: 48, border: \"1px solid #e5e7eb\", borderRadius: 10, overflow: \"hidden\" }}>",
2708
+ " {typeScaleIsDefault && <p style={{ fontSize: 12, color: \"#92400e\", background: \"#fef3c7\", border: \"1px solid #fde68a\", borderRadius: 6, padding: \"6px 12px\", marginBottom: 16, display: \"inline-block\" }}>ℹ️ Tailwind defaults — no custom font sizes found in this project</p>}",
2709
+ " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 0, marginBottom: 48, border: \"1px solid #e5e7eb\", borderRadius: 10, overflow: \"hidden\" }}>",
2671
2710
  " {typeScale.map(({ step, size, px, lineHeight }, i) => {",
2672
2711
  " const usage = usedTextSizes.find(u => u.token === `text-${step}`);",
2673
2712
  " const usageCount = usage?.count;",
@@ -2701,8 +2740,7 @@ function writeFoundationsStories(foundations, components) {
2701
2740
  " </div>",
2702
2741
  " );",
2703
2742
  " })}",
2704
- " </div>",
2705
- " )}",
2743
+ " </div>",
2706
2744
  "",
2707
2745
  " {/* ── FONT FAMILIES ── */}",
2708
2746
  " {familyRows.length > 0 && (",
@@ -2725,6 +2763,7 @@ function writeFoundationsStories(foundations, components) {
2725
2763
  " )}",
2726
2764
  "",
2727
2765
  " {/* ── FONT WEIGHTS ── */}",
2766
+ " {weightRowsIsDefault && <p style={{ fontSize: 12, color: \"#92400e\", background: \"#fef3c7\", border: \"1px solid #fde68a\", borderRadius: 6, padding: \"6px 12px\", marginBottom: 12, display: \"inline-block\" }}>ℹ️ Tailwind defaults — no custom font weights found in this project</p>}",
2728
2767
  " {weightRows.length > 0 && (",
2729
2768
  " <>",
2730
2769
  " <h3 style={{ fontSize: 15, fontWeight: 600, margin: \"0 0 14px\", color: \"#374151\" }}>Font Weights</h3>",
@@ -3237,6 +3276,13 @@ function writeFoundationsStories(foundations, components) {
3237
3276
  {
3238
3277
  const spacing = foundations?.spacing || {};
3239
3278
  const breakpoints = foundations?.breakpoints || {};
3279
+ // Tailwind v3 default spacing scale
3280
+ const TW_DEFAULT_SPACING = { "0": "0px", "px": "1px", "0.5": "0.125rem", "1": "0.25rem", "1.5": "0.375rem", "2": "0.5rem", "2.5": "0.625rem", "3": "0.75rem", "3.5": "0.875rem", "4": "1rem", "5": "1.25rem", "6": "1.5rem", "7": "1.75rem", "8": "2rem", "9": "2.25rem", "10": "2.5rem", "11": "2.75rem", "12": "3rem", "14": "3.5rem", "16": "4rem", "20": "5rem", "24": "6rem", "28": "7rem", "32": "8rem", "36": "9rem", "40": "10rem", "44": "11rem", "48": "12rem", "52": "13rem", "56": "14rem", "60": "15rem", "64": "16rem", "72": "18rem", "80": "20rem", "96": "24rem" };
3281
+ const TW_DEFAULT_BREAKPOINTS = { "sm": "640px", "md": "768px", "lg": "1024px", "xl": "1280px", "2xl": "1536px" };
3282
+ const spacingIsDefault = Object.keys(spacing).length === 0;
3283
+ const spacingSource = spacingIsDefault ? TW_DEFAULT_SPACING : spacing;
3284
+ const breakpointsIsDefault = Object.keys(breakpoints).length === 0;
3285
+ const breakpointsSource = breakpointsIsDefault ? TW_DEFAULT_BREAKPOINTS : breakpoints;
3240
3286
 
3241
3287
  function remToPxNum(val) {
3242
3288
  const m = String(val).match(/^([\d.]+)rem$/);
@@ -3246,7 +3292,7 @@ function writeFoundationsStories(foundations, components) {
3246
3292
  return -1;
3247
3293
  }
3248
3294
 
3249
- const spacingRows = Object.entries(spacing)
3295
+ const spacingRows = Object.entries(spacingSource)
3250
3296
  .map(([token, value]) => {
3251
3297
  const px = remToPxNum(value);
3252
3298
  const label = px >= 0 ? `${value} · ${px}px` : value;
@@ -3261,7 +3307,7 @@ function writeFoundationsStories(foundations, components) {
3261
3307
  return a.token.localeCompare(b.token);
3262
3308
  });
3263
3309
 
3264
- const breakpointRows = Object.entries(breakpoints)
3310
+ const breakpointRows = Object.entries(breakpointsSource)
3265
3311
  .map(([token, value]) => ({ token, value }))
3266
3312
  .sort((a, b) => parseInt(a.value) - parseInt(b.value));
3267
3313
 
@@ -3275,7 +3321,9 @@ function writeFoundationsStories(foundations, components) {
3275
3321
  "type Story = StoryObj;",
3276
3322
  "",
3277
3323
  `const spacingTokens: { token: string; label: string; barWidth: number }[] = ${JSON.stringify(spacingRows)};`,
3324
+ `const spacingIsDefault: boolean = ${spacingIsDefault};`,
3278
3325
  `const breakpointTokens: { token: string; value: string }[] = ${JSON.stringify(breakpointRows)};`,
3326
+ `const breakpointsIsDefault: boolean = ${breakpointsIsDefault};`,
3279
3327
  `const usedSpacing: { token: string; count: number }[] = ${JSON.stringify(usedSpacing)};`,
3280
3328
  "",
3281
3329
  "export const Default: Story = {",
@@ -3295,22 +3343,20 @@ function writeFoundationsStories(foundations, components) {
3295
3343
  " </div>",
3296
3344
  " </>",
3297
3345
  " )}",
3298
- " {spacingTokens.length === 0 ? (",
3299
- " <p style={{ color: \"#999\", fontSize: 13 }}>No spacing tokens detected. Ensure <code>tailwind.config.ts</code> is present.</p>",
3300
- " ) : (",
3301
- " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 3 }}>",
3302
- " {spacingTokens.map(({ token, label, barWidth }) => (",
3346
+ " {spacingIsDefault && <p style={{ fontSize: 12, color: \"#92400e\", background: \"#fef3c7\", border: \"1px solid #fde68a\", borderRadius: 6, padding: \"6px 12px\", marginBottom: 16, display: \"inline-block\" }}>ℹ️ Tailwind defaults — no custom spacing detected in this project</p>}",
3347
+ " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 3 }}>",
3348
+ " {spacingTokens.map(({ token, label, barWidth }) => (",
3303
3349
  " <div key={token} style={{ display: \"flex\", alignItems: \"center\", gap: 10, minHeight: 24 }}>",
3304
3350
  " <span style={{ width: 40, fontSize: 11, color: \"#9ca3af\", textAlign: \"right\", flexShrink: 0, fontVariantNumeric: \"tabular-nums\" }}>{token}</span>",
3305
3351
  " <div style={{ width: Math.max(barWidth, 2), height: 16, background: barWidth === 0 ? \"transparent\" : \"var(--color-primary, #6366f1)\", opacity: barWidth === 0 ? 1 : 0.85, borderRadius: 3, flexShrink: 0, border: barWidth === 0 ? \"1px dashed #555\" : \"none\", boxSizing: \"border-box\" as any }} />",
3306
3352
  " <code style={{ fontSize: 11, color: \"#6b7280\" }}>{label}</code>",
3307
3353
  " </div>",
3308
3354
  " ))}",
3309
- " </div>",
3310
- " )}",
3355
+ " </div>",
3311
3356
  " {breakpointTokens.length > 0 && (",
3312
3357
  " <>",
3313
3358
  " <h3 style={{ fontSize: 16, fontWeight: 600, margin: \"40px 0 12px\" }}>Breakpoints</h3>",
3359
+ " {breakpointsIsDefault && <p style={{ fontSize: 12, color: \"#92400e\", background: \"#fef3c7\", border: \"1px solid #fde68a\", borderRadius: 6, padding: \"6px 12px\", marginBottom: 10, display: \"inline-block\" }}>ℹ️ Tailwind defaults — no custom breakpoints found in this project</p>}",
3314
3360
  " <div style={{ display: \"grid\", gridTemplateColumns: \"repeat(auto-fill, minmax(130px, 1fr))\", gap: 8 }}>",
3315
3361
  " {breakpointTokens.map(({ token, value }) => (",
3316
3362
  " <div key={token} style={{ padding: \"10px 12px\", border: \"1px solid #e5e7eb\", borderRadius: 8, background: \"#fafafa\" }}>",
@@ -3346,7 +3392,11 @@ function writeFoundationsStories(foundations, components) {
3346
3392
  .map(([token, value]) => ({ token, value }));
3347
3393
 
3348
3394
  const zSemanticLabels = { "0": "Base layer", "10": "Low elevation", "20": "Dropdown / popover", "30": "Sticky header", "40": "Fixed overlay", "50": "Modal / dialog", "auto": "Auto" };
3349
- const zIndexRows = Object.entries(zIndex)
3395
+ // Tailwind v3 default z-index scale
3396
+ const TW_DEFAULT_Z_INDEX = { "0": "0", "10": "10", "20": "20", "30": "30", "40": "40", "50": "50", "auto": "auto" };
3397
+ const zIndexIsDefault = Object.keys(zIndex).length === 0;
3398
+ const zIndexSource = zIndexIsDefault ? TW_DEFAULT_Z_INDEX : zIndex;
3399
+ const zIndexRows = Object.entries(zIndexSource)
3350
3400
  .sort(([a], [b]) => {
3351
3401
  if (a === "auto") return 1;
3352
3402
  if (b === "auto") return -1;
@@ -3384,6 +3434,7 @@ function writeFoundationsStories(foundations, components) {
3384
3434
  "",
3385
3435
  `const shadowTokens: { token: string; value: string }[] = ${JSON.stringify(shadowDisplayRows)};`,
3386
3436
  `const zIndexTokens: { token: string; value: string; label: string }[] = ${JSON.stringify(zIndexRows)};`,
3437
+ `const zIndexIsDefault: boolean = ${zIndexIsDefault};`,
3387
3438
  `const usedShadows: { token: string; count: number }[] = ${JSON.stringify(usedShadows)};`,
3388
3439
  `const usedZIndex: { token: string; count: number }[] = ${JSON.stringify(usedZIndex)};`,
3389
3440
  `const zSemantics: Record<string, string> = ${JSON.stringify(zIndexSemantics)};`,
@@ -3434,9 +3485,8 @@ function writeFoundationsStories(foundations, components) {
3434
3485
  " ))}",
3435
3486
  " </div>",
3436
3487
  " )}",
3437
- " {zIndexTokens.length === 0 && usedZIndex.length === 0 ? (",
3438
- " <p style={{ color: \"#999\", fontSize: 13 }}>No z-index tokens detected.</p>",
3439
- " ) : zIndexTokens.length > 0 ? (",
3488
+ " {zIndexIsDefault && <p style={{ fontSize: 12, color: \"#92400e\", background: \"#fef3c7\", border: \"1px solid #fde68a\", borderRadius: 6, padding: \"6px 12px\", marginBottom: 14, display: \"inline-block\" }}>ℹ️ Tailwind defaults — no custom z-index found in this project</p>}",
3489
+ " {zIndexTokens.length > 0 ? (",
3440
3490
  " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 6, maxWidth: 380 }}>",
3441
3491
  " {zIndexTokens.map(({ token, value, label }) => (",
3442
3492
  " <div key={token} style={{ display: \"flex\", alignItems: \"center\", gap: 12, padding: \"8px 14px\", background: \"#fff\", borderRadius: 8, border: \"1px solid #e5e7eb\" }}>",