vibe-design-system 2.8.61 → 2.8.63

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.61",
3
+ "version": "2.8.63",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "homepage": "https://vibedesign.tech",
6
6
  "repository": {
@@ -83,6 +83,72 @@ const SKIP_LIST = VDS_CONFIG.skipList
83
83
  ? [...VDS_CONFIG.skipList]
84
84
  : [...DEFAULT_SKIP_LIST, ...(VDS_CONFIG.extraSkipList ?? [])];
85
85
 
86
+ /**
87
+ * Semantic component classifier.
88
+ * Maps component name + tokens + props to an Atlassian-style functional category.
89
+ * Used for sidebar grouping (shadcn/UI components) and Component Inventory story.
90
+ * Returns the category string, or null if no confident match.
91
+ */
92
+ function classifyComponent(name, tokens = [], props = []) {
93
+ const n = name.toLowerCase();
94
+
95
+ // Status Indicators — short labels, counts, status metadata
96
+ if (
97
+ /^(badge|tag|lozenge|chip|pill|statusbadge|statuschip|statustag|statuslabel|statusdot|statusicon|countertag|counterbadge|tagging)$/.test(n) ||
98
+ /badge$|lozenge$|statuschip$|statusbadge$|statustag$|statuspill$|countertag$/.test(n)
99
+ ) return "Status Indicators";
100
+
101
+ // Forms and Input — interactive user input elements
102
+ if (
103
+ /^(button|input|checkbox|radiogroup|radio|select|form|field|toggle|textarea|switch|slider|range|datepicker|timepicker|datetimepicker|combobox|autocomplete|fileupload|fileinput|otpinput|pinfield|searchinput|searchbar|colorpicker|numberinput|currencyinput|passwordinput|formfield|forminput|searchfield)$/.test(n) ||
104
+ /button$|input$|field$|form$|picker$|^select$|toggle$|checkbox$|^radio$|switch$|slider$|upload$|combobox$/.test(n)
105
+ ) return "Forms and Input";
106
+
107
+ // Navigation — page/section navigation patterns
108
+ if (
109
+ /^(navbar|nav|navigation|navmenu|menu|breadcrumb|breadcrumbs|tabs|tabbar|tablist|tabpanel|pagination|sidebar|topbar|link|navlink|menuitem|menubar|stepper|stepbar|stepindicator|progresstracker|sidenav)$/.test(n) ||
110
+ /navbar$|navmenu$|breadcrumb$|^tabs$|pagination$|sidebar$|topbar$|stepper$|stepbar$|sidenav$/.test(n)
111
+ ) return "Navigation";
112
+
113
+ // Overlays and Layering — content floating above the page
114
+ if (
115
+ /^(modal|dialog|drawer|popover|tooltip|dropdown|overlay|lightbox|sheet|bottomsheet|contextmenu|actionsheet|flyout|slideover|offcanvas|alertdialog)$/.test(n) ||
116
+ /modal$|dialog$|drawer$|tooltip$|popover$|dropdown$|overlay$|sheet$|lightbox$/.test(n)
117
+ ) return "Overlays and Layering";
118
+
119
+ // Loading — loading and skeleton states
120
+ if (
121
+ /^(spinner|skeleton|loader|progressbar|loadingbar|loadingindicator|skeletonloader|skeletoncard|loadingspinner|skeletonlist|skeletonrow|loadingoverlay)$/.test(n) ||
122
+ /spinner$|skeleton$|loader$|loading$|^progressbar$/.test(n)
123
+ ) return "Loading";
124
+
125
+ // Messaging — system notifications and inline feedback
126
+ if (
127
+ /^(alert|banner|toast|notification|message|flag|snackbar|callout|announcement|inlinemessage|sectionmessage|toastnotification|errormessage|warningbanner|infobanner|successbanner)$/.test(n) ||
128
+ /alert$|banner$|toast$|notification$|^message$|snackbar$|callout$|^flag$/.test(n)
129
+ ) return "Messaging";
130
+
131
+ // Images and Icons — visual assets
132
+ if (
133
+ /^(avatar|avatargroup|icon|image|logo|tile|thumbnail|photo|picture|iconbutton|useravatar|profileimage|brandlogo|appicon|mediaplayer|imagegallery)$/.test(n) ||
134
+ /^avatar$|avatargroup$|^icon$|^image$|^logo$|^tile$|thumbnail$|^photo$/.test(n)
135
+ ) return "Images and Icons";
136
+
137
+ // Layout and Structure — structural page / content organisation
138
+ if (
139
+ /^(card|panel|container|grid|layout|page|section|divider|separator|accordion|collapsible|expandable|pagelayout|pageheader|applayout|applayoutshell|layoutgrid|contentblock|featureblock|herosection|featurelist|pricingcard|testimonial)$/.test(n) ||
140
+ /card$|panel$|container$|layout$|section$|divider$|separator$|accordion$|grid$|header$|^pagelayout$/.test(n)
141
+ ) return "Layout and Structure";
142
+
143
+ // Text and Data Display — showing information and data
144
+ if (
145
+ /^(table|list|datatable|datagrid|chart|graph|stat|stats|metric|heading|text|label|code|display|summary|detail|statblock|metricscard|kpicard|statscard|datatablerow|tablerow|tablecell|listitem|listrow|leaderboard|scorecard)$/.test(n) ||
146
+ /table$|list$|chart$|graph$|stats$|metric$|^heading$|^code$|display$|detail$|summary$|kpicard$|scorecard$|leaderboard$/.test(n)
147
+ ) return "Text and Data Display";
148
+
149
+ return null; // No category matched — caller falls back to original group
150
+ }
151
+
86
152
  /** shadcn/ui composite component recipes: component name → imports + render. */
87
153
  const RECIPES = {
88
154
  Accordion: {
@@ -932,6 +998,105 @@ function scanComponentUsages(componentName, projectRoot) {
932
998
  .map(([className, count]) => ({ className, count }));
933
999
  }
934
1000
 
1001
+ /**
1002
+ * Comprehensive component usage scanner for the "Usage" story.
1003
+ * Walks all src/ .tsx/.jsx files (excluding ui/, stories/, the component file itself)
1004
+ * and returns: which files use the component, total usage count, most-used prop names,
1005
+ * and co-occurring components (other PascalCase components in the same files).
1006
+ */
1007
+ function scanComponentFileUsages(componentName, projectRoot) {
1008
+ const uiPathFrag = path.join("components", "ui") + path.sep;
1009
+ const storiesPathFrag = path.join("stories") + path.sep;
1010
+
1011
+ const fileUsages = []; // { relPath, basename, count }
1012
+ const propCounts = {}; // propName → count across all usages
1013
+ const coCompCounts = {}; // compName → co-occurrence count
1014
+
1015
+ const escapedName = componentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1016
+ const usageRe = new RegExp(`<${escapedName}(?:\\s+([\\s\\S]*?))?(?:\\s*/>|\\s*>)`, "g");
1017
+ const coCompRe = /<([A-Z][a-zA-Z0-9]+)(?:\s|\/|>)/g;
1018
+ const SKIP_CO = new Set(["React", "Fragment", "Suspense", "StrictMode", "Component", "ErrorBoundary"]);
1019
+
1020
+ function walk(dir) {
1021
+ let entries;
1022
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
1023
+ for (const e of entries) {
1024
+ if (_SCAN_IGNORE_DIRS.has(e.name)) continue;
1025
+ const full = path.join(dir, e.name);
1026
+ if (e.isDirectory()) { walk(full); continue; }
1027
+ if (!/\.(tsx|jsx)$/.test(e.name)) continue;
1028
+ if (full.includes(uiPathFrag)) continue;
1029
+ if (full.includes(storiesPathFrag)) continue;
1030
+ // Skip the component's own definition file
1031
+ const baseNoExt = path.basename(full, path.extname(full));
1032
+ if (baseNoExt === componentName) continue;
1033
+
1034
+ let src;
1035
+ try { src = fs.readFileSync(full, "utf-8"); } catch { continue; }
1036
+ if (!src.includes(`<${componentName}`)) continue;
1037
+
1038
+ // Count usages and collect prop names
1039
+ let count = 0;
1040
+ let m;
1041
+ usageRe.lastIndex = 0;
1042
+ while ((m = usageRe.exec(src)) !== null) {
1043
+ count++;
1044
+ const propsStr = m[1] || "";
1045
+ // Extract prop names (e.g. variant="..." or isLoading or onClick={...})
1046
+ const propNameRe = /\b([a-z][a-zA-Z0-9]+)(?==|\s*>|\s*\/>|\s+[a-z])/g;
1047
+ let pm;
1048
+ while ((pm = propNameRe.exec(propsStr)) !== null) {
1049
+ const pn = pm[1];
1050
+ if (!["className", "style", "key", "id", "ref"].includes(pn)) {
1051
+ propCounts[pn] = (propCounts[pn] || 0) + 1;
1052
+ }
1053
+ }
1054
+ }
1055
+
1056
+ if (count > 0) {
1057
+ const relPath = path.relative(projectRoot, full);
1058
+ fileUsages.push({ relPath, basename: path.basename(full), count });
1059
+
1060
+ // Co-occurring components
1061
+ coCompRe.lastIndex = 0;
1062
+ while ((m = coCompRe.exec(src)) !== null) {
1063
+ const cn = m[1];
1064
+ if (cn !== componentName && !SKIP_CO.has(cn)) {
1065
+ coCompCounts[cn] = (coCompCounts[cn] || 0) + 1;
1066
+ }
1067
+ }
1068
+ }
1069
+ }
1070
+ }
1071
+
1072
+ // Walk standard src directories
1073
+ const srcDirs = [
1074
+ path.join(projectRoot, "src"),
1075
+ path.join(projectRoot, "client", "src"),
1076
+ path.join(projectRoot, "app"),
1077
+ ].filter(d => fs.existsSync(d));
1078
+ for (const d of srcDirs) walk(d);
1079
+
1080
+ const totalCount = fileUsages.reduce((s, f) => s + f.count, 0);
1081
+
1082
+ const topProps = Object.entries(propCounts)
1083
+ .sort((a, b) => b[1] - a[1])
1084
+ .slice(0, 6)
1085
+ .map(([name, count]) => ({ name, count }));
1086
+
1087
+ const coComponents = Object.entries(coCompCounts)
1088
+ .sort((a, b) => b[1] - a[1])
1089
+ .slice(0, 6)
1090
+ .map(([name, count]) => ({ name, count }));
1091
+
1092
+ return {
1093
+ files: fileUsages.sort((a, b) => b.count - a.count).slice(0, 10),
1094
+ totalCount,
1095
+ topProps,
1096
+ coComponents,
1097
+ };
1098
+ }
1099
+
935
1100
  /** Render'da args'a uygulanacak fallback (useState(estimate.toString()) gibi kullanımlar için; args/Controls undefined yapsa bile). */
936
1101
  const RENDER_ARGS_FALLBACKS = {
937
1102
  TaskEstimateInput: ", estimate: (args && args.estimate) ?? 0, value: (args && args.value) ?? 0, task: (args && args.task) ?? { id: \"1\", title: \"Example\", estimate: 0 }",
@@ -1770,12 +1935,17 @@ function buildStoryFileContent(comp) {
1770
1935
  }
1771
1936
  if (!importPath.startsWith(".")) importPath = "./" + importPath;
1772
1937
 
1773
- // Normalize legacy "shadcn" group "UI"; filename groups (e.g. "NavLink.tsx") → "Components"
1938
+ // Determine semantic category and story title
1774
1939
  const rawGroup = comp.group || "Components";
1775
- const group = rawGroup === "shadcn" ? "UI"
1940
+ const baseGroup = rawGroup === "shadcn" ? "UI"
1776
1941
  : rawGroup.includes(".") ? "Components" // root-level file without subdirectory
1777
1942
  : rawGroup;
1778
- // Title: "Module/ComponentName" (category intentionally dropped folder is the context)
1943
+ // Apply Atlassian-style semantic categorization to ALL components (shadcn and project-specific alike).
1944
+ // If no semantic category is detected, fall back to the original folder-based group.
1945
+ const compTokensForClassify = Array.isArray(comp.tokens) ? comp.tokens : [];
1946
+ const compPropsForClassify = Array.isArray(comp.props) ? comp.props : [];
1947
+ const semanticCat = classifyComponent(componentName, compTokensForClassify, compPropsForClassify);
1948
+ const group = semanticCat || baseGroup;
1779
1949
  const title = `${group}/${componentName}`;
1780
1950
 
1781
1951
  // Read component source once (used for export style, props, CVA parsing)
@@ -1940,6 +2110,170 @@ function buildStoryFileContent(comp) {
1940
2110
  }
1941
2111
  }
1942
2112
 
2113
+ // --- Design Tokens story ---
2114
+ // Generate a "Tokens" export that shows which Tailwind tokens this component uses,
2115
+ // resolved to project-specific color/spacing values from foundations.
2116
+ {
2117
+ const compTokens = Array.isArray(comp.tokens) ? comp.tokens : [];
2118
+ const foundColors = FOUNDATIONS_DATA?.colors || {};
2119
+ if (compTokens.length >= 3) {
2120
+ // Keep only base tokens (no state/responsive prefixes like hover:, focus:, sm:, dark:)
2121
+ const cleanTokens = compTokens.filter(t => !/:/.test(t));
2122
+
2123
+ // Exclude font-size tokens (text-xs, text-sm, text-base…) from color category
2124
+ const colorRaw = cleanTokens.filter(t =>
2125
+ /^(bg|text|border|ring|from|to|fill|stroke)-/.test(t) &&
2126
+ !/^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl|\d)/.test(t)
2127
+ );
2128
+ const spacingRaw = cleanTokens.filter(t => /^(p[xylrbt]?|m[xylrbt]?|gap|space-[xy]|w-|h-|min-[wh]|max-[wh]|size-)/.test(t));
2129
+ const typographyRaw = cleanTokens.filter(t => /^(text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl)|font-)/.test(t));
2130
+ const radiusRaw = cleanTokens.filter(t => /^rounded/.test(t));
2131
+ const animRaw = cleanTokens.filter(t => /^(transition|duration|animate|ease|delay)-/.test(t));
2132
+
2133
+ // Resolve semantic color tokens → hex swatch from foundations.colors
2134
+ const resolvedColors = colorRaw.map(token => {
2135
+ const m = token.match(/^(?:bg|text|border|ring|from|to|fill|stroke)-(.+)$/);
2136
+ const key = m ? m[1] : null;
2137
+ const entry = key ? foundColors[key] : null;
2138
+ const hex = entry?.hex && /^#[0-9a-fA-F]{3,8}$/.test(entry.hex) ? entry.hex : null;
2139
+ return { token, hex, label: key };
2140
+ });
2141
+
2142
+ const hasContent = resolvedColors.length > 0 || spacingRaw.length > 0 || typographyRaw.length > 0 || radiusRaw.length > 0 || animRaw.length > 0;
2143
+ if (hasContent) {
2144
+ lines.push("");
2145
+ lines.push(`export const Tokens: Story = {`);
2146
+ lines.push(` name: "Design Tokens",`);
2147
+ lines.push(` parameters: { layout: "fullscreen" },`);
2148
+ lines.push(` render: () => {`);
2149
+ lines.push(` const colorTokens = ${JSON.stringify(resolvedColors)};`);
2150
+ lines.push(` const spacingTokens = ${JSON.stringify(spacingRaw)};`);
2151
+ lines.push(` const typographyTokens = ${JSON.stringify(typographyRaw)};`);
2152
+ lines.push(` const radiusTokens = ${JSON.stringify(radiusRaw)};`);
2153
+ lines.push(` const animationTokens = ${JSON.stringify(animRaw)};`);
2154
+ lines.push(` const chip = (label: string, bg: string, color: string) => (`);
2155
+ lines.push(` <span key={label} style={{ fontFamily: "monospace", fontSize: 11, background: bg, color, padding: "3px 9px", borderRadius: 5, border: \`1px solid \${bg === "#f9fafb" ? "#e5e7eb" : bg}\`, whiteSpace: "nowrap" as any }}>{label}</span>`);
2156
+ lines.push(` );`);
2157
+ lines.push(` const section = (title: string, children: any) => (`);
2158
+ lines.push(` <section style={{ marginBottom: 28 }}>`);
2159
+ lines.push(` <p style={{ margin: "0 0 10px", fontSize: 11, fontWeight: 700, color: "#6b7280", textTransform: "uppercase", letterSpacing: "0.08em" }}>{title}</p>`);
2160
+ lines.push(` {children}`);
2161
+ lines.push(` </section>`);
2162
+ lines.push(` );`);
2163
+ lines.push(` return (`);
2164
+ lines.push(` <div style={{ padding: 40, background: "#fff", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh" }}>`);
2165
+ lines.push(` <h2 style={{ fontSize: 22, fontWeight: 700, margin: "0 0 6px" }}>Design Tokens</h2>`);
2166
+ lines.push(` <p style={{ fontSize: 13, color: "#6b7280", margin: "0 0 32px" }}>Tailwind utilities used in <code style={{ background: "#f3f4f6", padding: "1px 6px", borderRadius: 4, fontSize: 12 }}>${componentName}</code> — resolved to project values.</p>`);
2167
+ lines.push(` {colorTokens.length > 0 && section("Color", (`);
2168
+ lines.push(` <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>`);
2169
+ lines.push(` {colorTokens.map(({ token, hex, label }) => (`);
2170
+ lines.push(` <div key={token} style={{ display: "flex", alignItems: "center", gap: 7, padding: "7px 12px", border: "1px solid #e5e7eb", borderRadius: 8, background: "#f9fafb" }}>`);
2171
+ lines.push(` {hex && <span style={{ display: "inline-block", width: 16, height: 16, borderRadius: 4, background: hex, border: "1px solid rgba(0,0,0,0.1)", flexShrink: 0 }} />}`);
2172
+ lines.push(` <code style={{ fontSize: 12, color: "#374151", fontWeight: 600 }}>{token}</code>`);
2173
+ lines.push(` {hex && <span style={{ fontSize: 11, color: "#9ca3af" }}>{hex}</span>}`);
2174
+ lines.push(` </div>`);
2175
+ lines.push(` ))}`);
2176
+ lines.push(` </div>`);
2177
+ lines.push(` ))}`);
2178
+ lines.push(` {spacingTokens.length > 0 && section("Spacing", (`);
2179
+ lines.push(` <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>{spacingTokens.map(t => chip(t, "#faf5ff", "#6d28d9"))}</div>`);
2180
+ lines.push(` ))}`);
2181
+ lines.push(` {typographyTokens.length > 0 && section("Typography", (`);
2182
+ lines.push(` <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>{typographyTokens.map(t => chip(t, "#fffbeb", "#92400e"))}</div>`);
2183
+ lines.push(` ))}`);
2184
+ lines.push(` {radiusTokens.length > 0 && section("Border Radius", (`);
2185
+ lines.push(` <div style={{ display: "flex", gap: 16, flexWrap: "wrap", alignItems: "flex-end" }}>`);
2186
+ lines.push(` {radiusTokens.map(t => {`);
2187
+ lines.push(` const px = t === "rounded-none" ? 0 : t === "rounded-sm" ? 2 : t === "rounded" ? 4 : t === "rounded-md" ? 6 : t === "rounded-lg" ? 8 : t === "rounded-xl" ? 12 : t === "rounded-2xl" ? 16 : t === "rounded-3xl" ? 24 : t === "rounded-full" ? 9999 : 4;`);
2188
+ lines.push(` return (`);
2189
+ lines.push(` <div key={t} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 6 }}>`);
2190
+ lines.push(` <div style={{ width: 44, height: 44, background: "#6366f1", borderRadius: px }} />`);
2191
+ lines.push(` <code style={{ fontSize: 10, color: "#6b7280" }}>{t}</code>`);
2192
+ lines.push(` </div>`);
2193
+ lines.push(` );`);
2194
+ lines.push(` })}`);
2195
+ lines.push(` </div>`);
2196
+ lines.push(` ))}`);
2197
+ lines.push(` {animationTokens.length > 0 && section("Motion", (`);
2198
+ lines.push(` <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>{animationTokens.map(t => chip(t, "#f0fdf4", "#166534"))}</div>`);
2199
+ lines.push(` ))}`);
2200
+ lines.push(` </div>`);
2201
+ lines.push(` );`);
2202
+ lines.push(` },`);
2203
+ lines.push(`};`);
2204
+ }
2205
+ }
2206
+ }
2207
+
2208
+ // --- Usage story ---
2209
+ // Shows where this component is actually used in the project:
2210
+ // files, usage count, most-used props, and co-occurring components.
2211
+ // All data is derived from the project source — no placeholder content.
2212
+ {
2213
+ const usageData = scanComponentFileUsages(componentName, PROJECT_ROOT);
2214
+ if (usageData.totalCount > 0) {
2215
+ lines.push(``);
2216
+ lines.push(`export const Usage: Story = {`);
2217
+ lines.push(` name: "Usage",`);
2218
+ lines.push(` parameters: { layout: "fullscreen" },`);
2219
+ lines.push(` render: () => {`);
2220
+ lines.push(` const usageFiles = ${JSON.stringify(usageData.files)};`);
2221
+ lines.push(` const totalCount = ${usageData.totalCount};`);
2222
+ lines.push(` const topProps = ${JSON.stringify(usageData.topProps)};`);
2223
+ lines.push(` const coComponents = ${JSON.stringify(usageData.coComponents)};`);
2224
+ lines.push(` const chip = (label: string, sub?: string) => (`);
2225
+ lines.push(` <span key={label} style={{ display: "inline-flex", alignItems: "center", gap: 4, background: "#f3f4f6", border: "1px solid #e5e7eb", borderRadius: 6, padding: "3px 10px", fontSize: 12, color: "#374151", fontFamily: "monospace", whiteSpace: "nowrap" as any }}>`);
2226
+ lines.push(` {label}{sub && <span style={{ color: "#9ca3af", fontSize: 11, marginLeft: 3 }}>×{sub}</span>}`);
2227
+ lines.push(` </span>`);
2228
+ lines.push(` );`);
2229
+ lines.push(` const sectionHead = (label: string) => (`);
2230
+ lines.push(` <p style={{ margin: "0 0 12px", fontSize: 11, fontWeight: 700, color: "#6b7280", textTransform: "uppercase" as any, letterSpacing: "0.08em" }}>{label}</p>`);
2231
+ lines.push(` );`);
2232
+ lines.push(` return (`);
2233
+ lines.push(` <div style={{ padding: 40, background: "#fff", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh" }}>`);
2234
+ lines.push(` <div style={{ marginBottom: 32 }}>`);
2235
+ lines.push(` <h2 style={{ fontSize: 22, fontWeight: 700, margin: "0 0 6px" }}>Usage</h2>`);
2236
+ lines.push(` <p style={{ fontSize: 13, color: "#6b7280", margin: 0 }}>`);
2237
+ lines.push(` <code style={{ background: "#f3f4f6", padding: "1px 6px", borderRadius: 4, fontSize: 12 }}>${componentName}</code> appears <strong>{totalCount}</strong> time{totalCount !== 1 ? "s" : ""} across <strong>{usageFiles.length}</strong> file{usageFiles.length !== 1 ? "s" : ""} in this project.`);
2238
+ lines.push(` </p>`);
2239
+ lines.push(` </div>`);
2240
+ // Found In section
2241
+ lines.push(` <section style={{ marginBottom: 32 }}>`);
2242
+ lines.push(` {sectionHead("Found In")}`);
2243
+ lines.push(` <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>`);
2244
+ lines.push(` {usageFiles.map((f: any) => (`);
2245
+ lines.push(` <div key={f.relPath} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "9px 14px", border: "1px solid #e5e7eb", borderRadius: 8, background: "#f9fafb" }}>`);
2246
+ lines.push(` <code style={{ fontSize: 12, color: "#374151" }}>{f.relPath}</code>`);
2247
+ lines.push(` <span style={{ flexShrink: 0, marginLeft: 12, fontSize: 11, background: "#e0e7ff", color: "#3730a3", padding: "2px 8px", borderRadius: 12, fontWeight: 600 }}>{f.count}×</span>`);
2248
+ lines.push(` </div>`);
2249
+ lines.push(` ))}`);
2250
+ lines.push(` </div>`);
2251
+ lines.push(` </section>`);
2252
+ // Most Used Props section
2253
+ lines.push(` {topProps.length > 0 && (`);
2254
+ lines.push(` <section style={{ marginBottom: 32 }}>`);
2255
+ lines.push(` {sectionHead("Most Used Props")}`);
2256
+ lines.push(` <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>`);
2257
+ lines.push(` {topProps.map((p: any) => chip(p.name, p.count.toString()))}`);
2258
+ lines.push(` </div>`);
2259
+ lines.push(` </section>`);
2260
+ lines.push(` )}`);
2261
+ // Often Used With section
2262
+ lines.push(` {coComponents.length > 0 && (`);
2263
+ lines.push(` <section style={{ marginBottom: 32 }}>`);
2264
+ lines.push(` {sectionHead("Often Used With")}`);
2265
+ lines.push(` <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>`);
2266
+ lines.push(` {coComponents.map((c: any) => chip(c.name))}`);
2267
+ lines.push(` </div>`);
2268
+ lines.push(` </section>`);
2269
+ lines.push(` )}`);
2270
+ lines.push(` </div>`);
2271
+ lines.push(` );`);
2272
+ lines.push(` },`);
2273
+ lines.push(`};`);
2274
+ }
2275
+ }
2276
+
1943
2277
  return lines.join("\n");
1944
2278
  }
1945
2279
 
@@ -2125,7 +2459,7 @@ function writeFoundationsStories(foundations) {
2125
2459
  const colorsContent = [
2126
2460
  "import type { Meta, StoryObj } from \"@storybook/react\";",
2127
2461
  "",
2128
- "const meta = { title: \"Foundations/Colors\" } satisfies Meta;",
2462
+ "const meta = { title: \"Foundations/Colors\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
2129
2463
  "export default meta;",
2130
2464
  "type Story = StoryObj;",
2131
2465
  "",
@@ -2250,7 +2584,7 @@ function writeFoundationsStories(foundations) {
2250
2584
  const typoContent = [
2251
2585
  "import type { Meta, StoryObj } from \"@storybook/react\";",
2252
2586
  "",
2253
- "const meta = { title: \"Foundations/Typography\" } satisfies Meta;",
2587
+ "const meta = { title: \"Foundations/Typography\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
2254
2588
  "export default meta;",
2255
2589
  "type Story = StoryObj;",
2256
2590
  "",
@@ -2410,7 +2744,7 @@ function writeFoundationsStories(foundations) {
2410
2744
  [
2411
2745
  "import type { Meta, StoryObj } from \"@storybook/react\";",
2412
2746
  "",
2413
- "const meta = { title: \"Foundations/Brand\" } satisfies Meta;",
2747
+ "const meta = { title: \"Foundations/Brand\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
2414
2748
  "export default meta;",
2415
2749
  "type Story = StoryObj;",
2416
2750
  "",
@@ -2444,7 +2778,7 @@ function writeFoundationsStories(foundations) {
2444
2778
  "import type { Meta, StoryObj } from \"@storybook/react\";",
2445
2779
  "import * as Lucide from \"lucide-react\";",
2446
2780
  "",
2447
- "const meta = { title: \"Foundations/Icons\" } satisfies Meta;",
2781
+ "const meta = { title: \"Foundations/Icons\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
2448
2782
  "export default meta;",
2449
2783
  "type Story = StoryObj;",
2450
2784
  "type IconEntry = { name: string; total: number; topFiles: string[] };",
@@ -2508,10 +2842,16 @@ function writeFoundationsStories(foundations) {
2508
2842
  " </div>",
2509
2843
  " );",
2510
2844
  " };",
2845
+ " const TILE_BKGS = [",
2846
+ " { bg: \"#f3f4f6\", iconColor: \"#374151\", border: \"#e5e7eb\", label: \"neutral\" },",
2847
+ " { bg: \"#fff\", iconColor: \"#374151\", border: \"#e5e7eb\", label: \"white\" },",
2848
+ " { bg: \"#4f46e5\", iconColor: \"#ffffff\", border: \"#4f46e5\", label: \"brand\" },",
2849
+ " ];",
2850
+ " const TILE_SIZES = [16, 20, 24];",
2511
2851
  " return (",
2512
2852
  " <div style={{ padding: 32, fontFamily: \"system-ui,sans-serif\", background: \"#fff\", minHeight: \"100vh\", color: \"#111\" }}>",
2513
2853
  " <h2 style={{ fontSize: 20, fontWeight: 700, margin: \"0 0 4px\" }}>Icons</h2>",
2514
- " <div style={{ marginBottom: 24 }}>",
2854
+ " <div style={{ marginBottom: 28 }}>",
2515
2855
  " <p style={{ margin: 0, marginBottom: 4, fontSize: 13, color: \"#6b7280\" }}>",
2516
2856
  " {iconData.length} icons imported from <code style={{ fontSize: 12, background: \"#f3f4f6\", padding: \"1px 5px\", borderRadius: 4 }}>lucide-react</code> in app code.",
2517
2857
  " </p>",
@@ -2519,6 +2859,50 @@ function writeFoundationsStories(foundations) {
2519
2859
  " Sorted by usage frequency · Badge = total JSX usages · Click any card to copy the icon name.",
2520
2860
  " </p>",
2521
2861
  " </div>",
2862
+ "",
2863
+ " {/* ── Icon Tiles ── */}",
2864
+ " {usedIcons.slice(0, 16).length > 0 && (",
2865
+ " <section style={{ marginBottom: 40 }}>",
2866
+ " <p style={{ margin: \"0 0 4px\", fontSize: 11, fontWeight: 700, color: \"#6b7280\", textTransform: \"uppercase\", letterSpacing: \"0.06em\" }}>",
2867
+ " Icon Tiles",
2868
+ " </p>",
2869
+ " <p style={{ margin: \"0 0 16px\", fontSize: 12, color: \"#9ca3af\" }}>",
2870
+ " Three sizes (16 · 20 · 24 px) × three backgrounds. Click to copy icon name.",
2871
+ " </p>",
2872
+ " <div style={{ display: \"grid\", gridTemplateColumns: \"repeat(auto-fill, minmax(110px, 1fr))\", gap: 12 }}>",
2873
+ " {usedIcons.slice(0, 16).map((d) => {",
2874
+ " const Icon = (Lucide as Record<string, any>)[d.name];",
2875
+ " if (!Icon) return null;",
2876
+ " const isCopied = copied === d.name;",
2877
+ " return (",
2878
+ " <div key={d.name} onClick={() => handleCopy(d.name)}",
2879
+ " style={{ cursor: \"pointer\", border: `1px solid ${isCopied ? \"#16a34a\" : \"#e5e7eb\"}`, borderRadius: 10,",
2880
+ " padding: 10, background: isCopied ? \"#f0fdf4\" : \"#fff\", transition: \"border-color 0.15s\" }}>",
2881
+ " {TILE_BKGS.map((bkg) => (",
2882
+ " <div key={bkg.label} style={{ display: \"flex\", gap: 3, marginBottom: 3 }}>",
2883
+ " {TILE_SIZES.map((sz) => (",
2884
+ " <div key={sz} style={{",
2885
+ " width: 34, height: 34, borderRadius: 6,",
2886
+ " background: bkg.bg,",
2887
+ " border: `1px solid ${bkg.border}`,",
2888
+ " display: \"flex\", alignItems: \"center\", justifyContent: \"center\", flexShrink: 0,",
2889
+ " }}>",
2890
+ " <Icon size={sz} strokeWidth={1.75} color={bkg.iconColor} />",
2891
+ " </div>",
2892
+ " ))}",
2893
+ " </div>",
2894
+ " ))}",
2895
+ " <div style={{ fontSize: 10, color: isCopied ? \"#16a34a\" : \"#6b7280\", marginTop: 6,",
2896
+ " textAlign: \"center\", overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\" as any }}>",
2897
+ " {isCopied ? \"Copied!\" : d.name}",
2898
+ " </div>",
2899
+ " </div>",
2900
+ " );",
2901
+ " })}",
2902
+ " </div>",
2903
+ " </section>",
2904
+ " )}",
2905
+ "",
2522
2906
  " {usedIcons.length > 0 && (",
2523
2907
  " <>",
2524
2908
  " <p style={{ margin: \"0 0 12px\", fontSize: 12, fontWeight: 600, color: \"#374151\",",
@@ -2567,7 +2951,7 @@ function writeFoundationsStories(foundations) {
2567
2951
  const gridContent = [
2568
2952
  "import type { Meta, StoryObj } from \"@storybook/react\";",
2569
2953
  "",
2570
- "const meta = { title: \"Foundations/Grid\" } satisfies Meta;",
2954
+ "const meta = { title: \"Foundations/Grid\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
2571
2955
  "export default meta;",
2572
2956
  "type Story = StoryObj;",
2573
2957
  "",
@@ -2602,7 +2986,7 @@ function writeFoundationsStories(foundations) {
2602
2986
  " );",
2603
2987
  " }",
2604
2988
  " return (",
2605
- " <div style={{ fontFamily: \"monospace\", fontSize: 10, color: \"#475569\", marginBottom: 8, background: \"#0a0f1a\", padding: \"4px 6px\", borderRadius: 4 }}>",
2989
+ " <div style={{ fontFamily: \"monospace\", fontSize: 10, color: \"#475569\", marginBottom: 8, background: \"#f1f5f9\", padding: \"4px 6px\", borderRadius: 4, border: \"1px solid #e5e7eb\" }}>",
2606
2990
  " grid-cols-{colVal}",
2607
2991
  " </div>",
2608
2992
  " );",
@@ -2618,29 +3002,29 @@ function writeFoundationsStories(foundations) {
2618
3002
  " <p style={{ margin: \"0 0 14px\", fontSize: 12, color: \"#475569\" }}>",
2619
3003
  " Tailwind CSS breakpoint prefixes detected in source.",
2620
3004
  " </p>",
2621
- " <div style={{ position: \"relative\", height: 28, marginBottom: 14, background: \"#0a0f1a\", borderRadius: 8, border: \"1px solid #1e293b\", overflow: \"hidden\" }}>",
3005
+ " <div style={{ position: \"relative\", height: 28, marginBottom: 14, background: \"#f1f5f9\", borderRadius: 8, border: \"1px solid #e2e8f0\", overflow: \"hidden\" }}>",
2622
3006
  " {ALL_BPS.map((bp) => {",
2623
3007
  " const cfg = BP_CONFIG[bp];",
2624
3008
  " const used = !!(gridSystem.breakpoints as any)?.[bp];",
2625
3009
  " const leftPct = (cfg.px / 1600) * 100;",
2626
3010
  " return (",
2627
3011
  " <div key={bp} style={{ position: \"absolute\", left: `${leftPct}%`, top: 0, bottom: 0,",
2628
- " borderLeft: `2px solid ${used ? cfg.color : \"#1e293b\"}`,",
3012
+ " borderLeft: `2px solid ${used ? cfg.color : \"#cbd5e1\"}`,",
2629
3013
  " display: \"flex\", alignItems: \"center\", paddingLeft: 4 }}>",
2630
- " <span style={{ fontSize: 9, color: used ? cfg.color : \"#334155\",",
3014
+ " <span style={{ fontSize: 9, color: used ? cfg.color : \"#94a3b8\",",
2631
3015
  " whiteSpace: \"nowrap\", fontWeight: 600 }}>{bp}</span>",
2632
3016
  " </div>",
2633
3017
  " );",
2634
3018
  " })}",
2635
3019
  " </div>",
2636
- " <div style={{ borderRadius: 8, border: \"1px solid #1e293b\", overflow: \"hidden\" }}>",
3020
+ " <div style={{ borderRadius: 8, border: \"1px solid #e5e7eb\", overflow: \"hidden\" }}>",
2637
3021
  " <table style={{ width: \"100%\", borderCollapse: \"collapse\", fontSize: 12 }}>",
2638
3022
  " <thead>",
2639
- " <tr style={{ background: \"#0a0f1a\", borderBottom: \"1px solid #1e293b\" }}>",
2640
- " <th style={{ textAlign: \"left\", padding: \"8px 12px\", color: \"#64748b\", fontWeight: 600 }}>Prefix</th>",
2641
- " <th style={{ textAlign: \"left\", padding: \"8px 12px\", color: \"#64748b\", fontWeight: 600 }}>Min-width</th>",
2642
- " <th style={{ textAlign: \"left\", padding: \"8px 12px\", color: \"#64748b\", fontWeight: 600 }}>Usages</th>",
2643
- " <th style={{ textAlign: \"left\", padding: \"8px 12px\", color: \"#64748b\", fontWeight: 600 }}>Top components</th>",
3023
+ " <tr style={{ background: \"#f8fafc\", borderBottom: \"1px solid #e5e7eb\" }}>",
3024
+ " <th style={{ textAlign: \"left\", padding: \"8px 12px\", color: \"#6b7280\", fontWeight: 600 }}>Prefix</th>",
3025
+ " <th style={{ textAlign: \"left\", padding: \"8px 12px\", color: \"#6b7280\", fontWeight: 600 }}>Min-width</th>",
3026
+ " <th style={{ textAlign: \"left\", padding: \"8px 12px\", color: \"#6b7280\", fontWeight: 600 }}>Usages</th>",
3027
+ " <th style={{ textAlign: \"left\", padding: \"8px 12px\", color: \"#6b7280\", fontWeight: 600 }}>Top components</th>",
2644
3028
  " </tr>",
2645
3029
  " </thead>",
2646
3030
  " <tbody>",
@@ -2648,12 +3032,12 @@ function writeFoundationsStories(foundations) {
2648
3032
  " const cfg = BP_CONFIG[bp];",
2649
3033
  " const data = (gridSystem.breakpoints as any)?.[bp];",
2650
3034
  " return (",
2651
- " <tr key={bp} style={{ borderBottom: i < 4 ? \"1px solid #1e293b\" : \"none\", opacity: data ? 1 : 0.3 }}>",
3035
+ " <tr key={bp} style={{ borderBottom: i < 4 ? \"1px solid #f0f4f8\" : \"none\", opacity: data ? 1 : 0.4 }}>",
2652
3036
  " <td style={{ padding: \"8px 12px\" }}>",
2653
- " <code style={{ fontSize: 12, fontWeight: 700, color: data ? cfg.color : \"#475569\" }}>{bp}:</code>",
3037
+ " <code style={{ fontSize: 12, fontWeight: 700, color: data ? cfg.color : \"#94a3b8\" }}>{bp}:</code>",
2654
3038
  " </td>",
2655
- " <td style={{ padding: \"8px 12px\", color: \"#94a3b8\", fontFamily: \"monospace\" }}>{cfg.px}px</td>",
2656
- " <td style={{ padding: \"8px 12px\", color: data ? \"#e2e8f0\" : \"#334155\" }}>",
3039
+ " <td style={{ padding: \"8px 12px\", color: \"#475569\", fontFamily: \"monospace\" }}>{cfg.px}px</td>",
3040
+ " <td style={{ padding: \"8px 12px\", color: data ? \"#111827\" : \"#94a3b8\" }}>",
2657
3041
  " {data ? `×${data.count}` : \"—\"}",
2658
3042
  " </td>",
2659
3043
  " <td style={{ padding: \"8px 12px\" }}>",
@@ -2680,12 +3064,12 @@ function writeFoundationsStories(foundations) {
2680
3064
  " </p>",
2681
3065
  " <div style={{ display: \"grid\", gridTemplateColumns: \"repeat(auto-fill, minmax(180px, 1fr))\", gap: 12 }}>",
2682
3066
  " {(colEntries as [string, { count: number; topFiles: string[] }][]).map(([val, data]) => (",
2683
- " <div key={val} style={{ background: \"#0a0f1a\", border: \"1px solid #1e293b\", borderRadius: 8, padding: 12 }}>",
3067
+ " <div key={val} style={{ background: \"#f8fafc\", border: \"1px solid #e5e7eb\", borderRadius: 8, padding: 12 }}>",
2684
3068
  " {renderColPreview(val)}",
2685
3069
  " <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", marginBottom: 6 }}>",
2686
- " <code style={{ fontSize: 11, color: \"#a78bfa\" }}>grid-cols-{val}</code>",
2687
- " <span style={{ fontSize: 11, fontWeight: 600, background: \"#1e293b\",",
2688
- " color: \"#94a3b8\", padding: \"1px 6px\", borderRadius: 999 }}>×{data.count}</span>",
3070
+ " <code style={{ fontSize: 11, color: \"#4f46e5\" }}>grid-cols-{val}</code>",
3071
+ " <span style={{ fontSize: 11, fontWeight: 600, background: \"#e8f0fe\",",
3072
+ " color: \"#3730a3\", padding: \"1px 6px\", borderRadius: 999 }}>×{data.count}</span>",
2689
3073
  " </div>",
2690
3074
  " <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 4 }}>",
2691
3075
  " {data.topFiles.map(chip)}",
@@ -2706,8 +3090,8 @@ function writeFoundationsStories(foundations) {
2706
3090
  " <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 8 }}>",
2707
3091
  " {(gapEntries as [string, { count: number; topFiles: string[] }][]).map(([val, data]) => (",
2708
3092
  " <span key={val} style={{ display: \"flex\", alignItems: \"center\", gap: 6,",
2709
- " background: \"#0a0f1a\", border: \"1px solid #1e293b\", borderRadius: 6, padding: \"5px 10px\", fontSize: 12 }}>",
2710
- " <code style={{ color: \"#67e8f9\" }}>gap-{val}</code>",
3093
+ " background: \"#f8fafc\", border: \"1px solid #e5e7eb\", borderRadius: 6, padding: \"5px 10px\", fontSize: 12 }}>",
3094
+ " <code style={{ color: \"#0891b2\" }}>gap-{val}</code>",
2711
3095
  " <span style={{ color: \"#374151\", fontSize: 11 }}>×{data.count}</span>",
2712
3096
  " </span>",
2713
3097
  " ))}",
@@ -2725,8 +3109,8 @@ function writeFoundationsStories(foundations) {
2725
3109
  " <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 8 }}>",
2726
3110
  " {(maxWEntries as [string, number][]).map(([val, count]) => (",
2727
3111
  " <span key={val} style={{ display: \"flex\", alignItems: \"center\", gap: 6,",
2728
- " background: \"#0a0f1a\", border: \"1px solid #1e293b\", borderRadius: 6, padding: \"5px 10px\", fontSize: 12 }}>",
2729
- " <code style={{ color: \"#4ade80\" }}>max-w-{val}</code>",
3112
+ " background: \"#f8fafc\", border: \"1px solid #e5e7eb\", borderRadius: 6, padding: \"5px 10px\", fontSize: 12 }}>",
3113
+ " <code style={{ color: \"#16a34a\" }}>max-w-{val}</code>",
2730
3114
  " <span style={{ color: \"#475569\", fontSize: 11 }}>×{count}</span>",
2731
3115
  " </span>",
2732
3116
  " ))}",
@@ -2756,7 +3140,7 @@ function writeFoundationsStories(foundations) {
2756
3140
  [
2757
3141
  "import type { Meta, StoryObj } from \"@storybook/react\";",
2758
3142
  "",
2759
- "const meta = { title: \"Foundations/Button Usage\" } satisfies Meta;",
3143
+ "const meta = { title: \"Foundations/Button Usage\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
2760
3144
  "export default meta;",
2761
3145
  "type Story = StoryObj;",
2762
3146
  "",
@@ -2770,19 +3154,19 @@ function writeFoundationsStories(foundations) {
2770
3154
  " <table style={{ borderCollapse: \"collapse\", width: \"100%\", fontSize: 13 }}>",
2771
3155
  " <thead>",
2772
3156
  " <tr>",
2773
- " <th style={{ textAlign: \"left\", padding: \"4px 8px\", borderBottom: \"1px solid #333\" }}>Variant</th>",
2774
- " <th style={{ textAlign: \"left\", padding: \"4px 8px\", borderBottom: \"1px solid #333\" }}>Size</th>",
2775
- " <th style={{ textAlign: \"left\", padding: \"4px 8px\", borderBottom: \"1px solid #333\" }}>asChild</th>",
2776
- " <th style={{ textAlign: \"right\", padding: \"4px 8px\", borderBottom: \"1px solid #333\" }}>Count</th>",
3157
+ " <th style={{ textAlign: \"left\", padding: \"4px 8px\", borderBottom: \"1px solid #e5e7eb\" }}>Variant</th>",
3158
+ " <th style={{ textAlign: \"left\", padding: \"4px 8px\", borderBottom: \"1px solid #e5e7eb\" }}>Size</th>",
3159
+ " <th style={{ textAlign: \"left\", padding: \"4px 8px\", borderBottom: \"1px solid #e5e7eb\" }}>asChild</th>",
3160
+ " <th style={{ textAlign: \"right\", padding: \"4px 8px\", borderBottom: \"1px solid #e5e7eb\" }}>Count</th>",
2777
3161
  " </tr>",
2778
3162
  " </thead>",
2779
3163
  " <tbody>",
2780
3164
  " {combos.map((c) => (",
2781
3165
  " <tr key={c.key}>",
2782
- " <td style={{ padding: \"4px 8px\", borderBottom: \"1px solid #222\" }}>{c.variant}</td>",
2783
- " <td style={{ padding: \"4px 8px\", borderBottom: \"1px solid #222\" }}>{c.size}</td>",
2784
- " <td style={{ padding: \"4px 8px\", borderBottom: \"1px solid #222\" }}>{c.asChild ? \"yes\" : \"no\"}</td>",
2785
- " <td style={{ padding: \"4px 8px\", borderBottom: \"1px solid #222\", textAlign: \"right\" }}>{c.count}</td>",
3166
+ " <td style={{ padding: \"4px 8px\", borderBottom: \"1px solid #f0f0f0\" }}>{c.variant}</td>",
3167
+ " <td style={{ padding: \"4px 8px\", borderBottom: \"1px solid #f0f0f0\" }}>{c.size}</td>",
3168
+ " <td style={{ padding: \"4px 8px\", borderBottom: \"1px solid #f0f0f0\" }}>{c.asChild ? \"yes\" : \"no\"}</td>",
3169
+ " <td style={{ padding: \"4px 8px\", borderBottom: \"1px solid #f0f0f0\", textAlign: \"right\" }}>{c.count}</td>",
2786
3170
  " </tr>",
2787
3171
  " ))}",
2788
3172
  " </tbody>",
@@ -2831,7 +3215,7 @@ function writeFoundationsStories(foundations) {
2831
3215
  const spacingContent = [
2832
3216
  "import type { Meta, StoryObj } from \"@storybook/react\";",
2833
3217
  "",
2834
- "const meta = { title: \"Foundations/Spacing & Layout\" } satisfies Meta;",
3218
+ "const meta = { title: \"Foundations/Spacing & Layout\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
2835
3219
  "export default meta;",
2836
3220
  "type Story = StoryObj;",
2837
3221
  "",
@@ -2938,7 +3322,7 @@ function writeFoundationsStories(foundations) {
2938
3322
  const elevationContent = [
2939
3323
  "import type { Meta, StoryObj } from \"@storybook/react\";",
2940
3324
  "",
2941
- "const meta = { title: \"Foundations/Elevation & Shadows\" } satisfies Meta;",
3325
+ "const meta = { title: \"Foundations/Elevation & Shadows\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
2942
3326
  "export default meta;",
2943
3327
  "type Story = StoryObj;",
2944
3328
  "",
@@ -3060,7 +3444,7 @@ function writeFoundationsStories(foundations) {
3060
3444
  const borderContent = [
3061
3445
  "import type { Meta, StoryObj } from \"@storybook/react\";",
3062
3446
  "",
3063
- "const meta = { title: \"Foundations/Border & Radius\" } satisfies Meta;",
3447
+ "const meta = { title: \"Foundations/Border & Radius\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
3064
3448
  "export default meta;",
3065
3449
  "type Story = StoryObj;",
3066
3450
  "",
@@ -3118,7 +3502,6 @@ function writeFoundationsStories(foundations) {
3118
3502
  const motion = foundations?.motion || {};
3119
3503
  const durations = motion.transitionDuration || {};
3120
3504
  const easings = motion.transitionTimingFunction || {};
3121
- const animations = motion.animation || {};
3122
3505
 
3123
3506
  const durationRows = Object.entries(durations)
3124
3507
  .filter(([k]) => k !== "0" && k !== "DEFAULT")
@@ -3129,103 +3512,168 @@ function writeFoundationsStories(foundations) {
3129
3512
  .filter(([k]) => k !== "DEFAULT")
3130
3513
  .map(([token, value]) => ({ token, value }));
3131
3514
 
3132
- const animationRows = Object.entries(animations)
3133
- .filter(([k]) => k !== "none")
3134
- .map(([token, value]) => ({ token, value }));
3515
+ // Tailwind default scales (used when project has no custom tokens)
3516
+ const TAILWIND_DEFAULT_DURATIONS = [
3517
+ { token: "75", value: "75ms" },
3518
+ { token: "100", value: "100ms" },
3519
+ { token: "150", value: "150ms" },
3520
+ { token: "200", value: "200ms" },
3521
+ { token: "300", value: "300ms" },
3522
+ { token: "500", value: "500ms" },
3523
+ { token: "700", value: "700ms" },
3524
+ { token: "1000", value: "1000ms" },
3525
+ ];
3526
+ const TAILWIND_DEFAULT_EASINGS = [
3527
+ { token: "linear", value: "linear" },
3528
+ { token: "ease-in", value: "cubic-bezier(0.4, 0, 1, 1)" },
3529
+ { token: "ease-out", value: "cubic-bezier(0, 0, 0.2, 1)" },
3530
+ { token: "ease-in-out", value: "cubic-bezier(0.4, 0, 0.2, 1)" },
3531
+ ];
3532
+
3533
+ const effectiveDurations = durationRows.length > 0 ? durationRows : TAILWIND_DEFAULT_DURATIONS;
3534
+ const effectiveEasings = easingRows.length > 0 ? easingRows.slice(0, 4) : TAILWIND_DEFAULT_EASINGS;
3535
+ const hasCustomDurations = durationRows.length > 0;
3135
3536
 
3136
- const usedAnimations = (foundations?.tokenUsage?.animations || []).slice(0, 8);
3537
+ // Which duration tokens are actually used in the project?
3538
+ const usedDurationKeys = new Set(
3539
+ (foundations?.tokenUsage?.animations || [])
3540
+ .filter(a => a.token.startsWith("duration-"))
3541
+ .map(a => a.token.replace("duration-", ""))
3542
+ );
3543
+
3544
+ // All motion-related token chips from tokenUsage
3545
+ const motionChips = (foundations?.tokenUsage?.animations || []).slice(0, 24);
3137
3546
 
3138
3547
  const motionContent = [
3139
3548
  "import type { Meta, StoryObj } from \"@storybook/react\";",
3140
- "import { useState } from \"react\";",
3549
+ "import { useState, useEffect } from \"react\";",
3141
3550
  "",
3142
- "const meta = { title: \"Foundations/Motion & Interaction\" } satisfies Meta;",
3551
+ "const meta = { title: \"Foundations/Motion & Interaction\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
3143
3552
  "export default meta;",
3144
3553
  "type Story = StoryObj;",
3145
3554
  "",
3146
- `const durationTokens: { token: string; value: string }[] = ${JSON.stringify(durationRows)};`,
3147
- `const easingTokens: { token: string; value: string }[] = ${JSON.stringify(easingRows)};`,
3148
- `const animationTokens: { token: string; value: string }[] = ${JSON.stringify(animationRows)};`,
3149
- `const usedAnimations: { token: string; count: number }[] = ${JSON.stringify(usedAnimations)};`,
3555
+ `const effectiveDurations: { token: string; value: string }[] = ${JSON.stringify(effectiveDurations)};`,
3556
+ `const effectiveEasings: { token: string; value: string }[] = ${JSON.stringify(effectiveEasings)};`,
3557
+ `const usedDurationKeys: Set<string> = new Set(${JSON.stringify([...usedDurationKeys])});`,
3558
+ `const motionChips: { token: string; count: number }[] = ${JSON.stringify(motionChips)};`,
3559
+ `const hasCustomDurations = ${hasCustomDurations};`,
3150
3560
  "",
3561
+ "export const Default: Story = {",
3562
+ " render: () => {",
3563
+ " const [replayKey, setReplayKey] = useState(0);",
3564
+ " const [easingKey, setEasingKey] = useState(0);",
3151
3565
  "",
3152
- "function DurationDemo({ token, value }: { token: string; value: string }) {",
3153
- " const [active, setActive] = useState(false);",
3154
- " return (",
3155
- " <div style={{ display: \"flex\", alignItems: \"center\", gap: 16 }}>",
3156
- " <span style={{ width: 40, fontSize: 12, color: \"#9ca3af\", textAlign: \"right\", flexShrink: 0, fontVariantNumeric: \"tabular-nums\" }}>{token}</span>",
3157
- " <code style={{ width: 68, fontSize: 12, color: \"#6b7280\", flexShrink: 0 }}>{value}</code>",
3158
- " <div",
3159
- " style={{ position: \"relative\", width: 220, height: 32, background: \"#f1f5f9\", borderRadius: 8, cursor: \"pointer\", overflow: \"hidden\", flexShrink: 0 }}",
3160
- " onClick={() => setActive((a) => !a)}",
3161
- " >",
3162
- " <div style={{",
3163
- " position: \"absolute\",",
3164
- " top: 4, left: active ? 172 : 4,",
3165
- " width: 44, height: 24,",
3166
- " background: \"var(--color-primary, #6366f1)\",",
3167
- " borderRadius: 6,",
3168
- " transition: `left ${value} cubic-bezier(0.4,0,0.2,1)`,",
3169
- " }} />",
3170
- " </div>",
3171
- " <span style={{ fontSize: 11, color: \"#d1d5db\" }}>click</span>",
3172
- " </div>",
3173
- " );",
3174
- "}",
3566
+ " // Inject @keyframes once into the document head",
3567
+ " useEffect(() => {",
3568
+ " const id = \"vds-motion-styles\";",
3569
+ " if (document.getElementById(id)) return;",
3570
+ " const el = document.createElement(\"style\");",
3571
+ " el.id = id;",
3572
+ " el.textContent = [",
3573
+ " \"@keyframes vdsEnter { from { opacity: 0; transform: translateY(14px) scale(0.96); } to { opacity: 1; transform: translateY(0) scale(1); } }\",",
3574
+ " \"@keyframes vdsSlide { from { opacity: 0.15; transform: translateX(-56px); } to { opacity: 1; transform: translateX(0); } }\",",
3575
+ " ].join(\" \");",
3576
+ " document.head.appendChild(el);",
3577
+ " }, []);",
3175
3578
  "",
3176
- "export const Default: Story = {",
3177
- " render: () => (",
3178
- " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, maxWidth: 700, color: \"#111\", background: \"#fff\", minHeight: \"100vh\" }}>",
3179
- " <h2 style={{ fontSize: 20, fontWeight: 700, margin: \"0 0 4px\" }}>Motion & Interaction</h2>",
3180
- " <p style={{ fontSize: 13, color: \"#888\", margin: \"0 0 32px\" }}>Consistent timing and easing for smooth, intentional motion</p>",
3181
- " {usedAnimations.length > 0 && (",
3182
- " <div style={{ marginBottom: 28 }}>",
3183
- " <p style={{ fontSize: 12, color: \"#6b7280\", margin: \"0 0 8px\", fontWeight: 500 }}>Used animations in this project</p>",
3184
- " <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 6 }}>",
3185
- " {usedAnimations.map(({ token, count }) => (",
3186
- " <span key={token} style={{ padding: \"3px 10px\", background: \"#d1fae5\", color: \"#065f46\", borderRadius: 20, fontSize: 12, fontWeight: 500 }}>",
3187
- " {token} <span style={{ opacity: 0.6 }}>×{count}</span>",
3188
- " </span>",
3189
- " ))}",
3579
+ " const replayBtn = (onClick: () => void) => (",
3580
+ " <button onClick={onClick} style={{ padding: \"6px 16px\", borderRadius: 8, border: \"1px solid #e5e7eb\",",
3581
+ " background: \"#f9fafb\", fontSize: 12, fontWeight: 600, cursor: \"pointer\", color: \"#374151\", flexShrink: 0 }}>",
3582
+ " Replay",
3583
+ " </button>",
3584
+ " );",
3585
+ "",
3586
+ " return (",
3587
+ " <div style={{ padding: 40, fontFamily: \"system-ui,sans-serif\", background: \"#fff\", minHeight: \"100vh\", color: \"#111\" }}>",
3588
+ " <h2 style={{ fontSize: 22, fontWeight: 700, margin: \"0 0 4px\" }}>Motion & Interaction</h2>",
3589
+ " <p style={{ fontSize: 13, color: \"#6b7280\", margin: \"0 0 40px\" }}>Consistent timing and easing for smooth, intentional motion.</p>",
3590
+ "",
3591
+ " {/* ── Entering Motion ── */}",
3592
+ " <section style={{ marginBottom: 48 }}>",
3593
+ " <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"flex-start\", marginBottom: 18 }}>",
3594
+ " <div>",
3595
+ " <p style={{ margin: \"0 0 2px\", fontSize: 11, fontWeight: 700, color: \"#6b7280\", textTransform: \"uppercase\", letterSpacing: \"0.08em\" }}>",
3596
+ " {hasCustomDurations ? \"Duration Scale\" : \"Duration Scale — Tailwind defaults\"}",
3597
+ " </p>",
3598
+ " <p style={{ margin: 0, fontSize: 12, color: \"#9ca3af\" }}>",
3599
+ " Each box enters with a different duration. {usedDurationKeys.size > 0 ? \"Solid fill = used in this project.\" : \"\"}",
3600
+ " </p>",
3601
+ " </div>",
3602
+ " {replayBtn(() => setReplayKey(k => k + 1))}",
3190
3603
  " </div>",
3191
- " </div>",
3192
- " )}",
3193
- " <h3 style={{ fontSize: 15, fontWeight: 600, margin: \"0 0 14px\", color: \"#374151\" }}>Duration</h3>",
3194
- " {durationTokens.length === 0 ? (",
3195
- " <p style={{ color: \"#999\", fontSize: 13, marginBottom: 32 }}>No duration tokens detected.</p>",
3196
- " ) : (",
3197
- " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 10, marginBottom: 40 }}>",
3198
- " {durationTokens.map((d) => <DurationDemo key={d.token} token={d.token} value={d.value} />)}",
3199
- " </div>",
3200
- " )}",
3201
- " <h3 style={{ fontSize: 15, fontWeight: 600, margin: \"0 0 14px\", color: \"#374151\" }}>Easing</h3>",
3202
- " {easingTokens.length === 0 ? (",
3203
- " <p style={{ color: \"#999\", fontSize: 13, marginBottom: 32 }}>No easing tokens detected.</p>",
3204
- " ) : (",
3205
- " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 6, marginBottom: 40, maxWidth: 500 }}>",
3206
- " {easingTokens.map(({ token, value }) => (",
3207
- " <div key={token} style={{ display: \"flex\", alignItems: \"center\", gap: 12, padding: \"8px 12px\", background: \"#f8fafc\", borderRadius: 8, border: \"1px solid #e5e7eb\" }}>",
3208
- " <span style={{ width: 64, fontSize: 12, fontWeight: 600, flexShrink: 0 }}>{token}</span>",
3209
- " <code style={{ fontSize: 11, color: \"#6b7280\" }}>{value}</code>",
3604
+ " <div style={{ display: \"flex\", gap: 12, flexWrap: \"wrap\" }}>",
3605
+ " {effectiveDurations.map(({ token, value }) => {",
3606
+ " const isActive = usedDurationKeys.has(token);",
3607
+ " return (",
3608
+ " <div key={`${token}-${replayKey}`} style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", gap: 8 }}>",
3609
+ " <div style={{",
3610
+ " width: 72, height: 72, borderRadius: 14,",
3611
+ " background: isActive ? \"#4f46e5\" : \"#e0e7ff\",",
3612
+ " border: isActive ? \"2px solid #4f46e5\" : \"2px solid #c7d2fe\",",
3613
+ " animation: `vdsEnter ${value} cubic-bezier(0.2, 0, 0, 1) both`,",
3614
+ " }} />",
3615
+ " <div style={{ textAlign: \"center\" }}>",
3616
+ " <div style={{ fontSize: 12, fontWeight: isActive ? 700 : 500, color: isActive ? \"#4f46e5\" : \"#374151\" }}>{value}</div>",
3617
+ " <div style={{ fontSize: 10, color: \"#9ca3af\", fontFamily: \"monospace\" }}>duration-{token}</div>",
3618
+ " </div>",
3619
+ " </div>",
3620
+ " );",
3621
+ " })}",
3622
+ " </div>",
3623
+ " </section>",
3624
+ "",
3625
+ " {/* ── Easing Comparison ── */}",
3626
+ " <section style={{ marginBottom: 48 }}>",
3627
+ " <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"flex-start\", marginBottom: 18 }}>",
3628
+ " <div>",
3629
+ " <p style={{ margin: \"0 0 2px\", fontSize: 11, fontWeight: 700, color: \"#6b7280\", textTransform: \"uppercase\", letterSpacing: \"0.08em\" }}>Easing</p>",
3630
+ " <p style={{ margin: 0, fontSize: 12, color: \"#9ca3af\" }}>Same duration (400ms), different easing curves — notice how each feels.</p>",
3210
3631
  " </div>",
3211
- " ))}",
3212
- " </div>",
3213
- " )}",
3214
- " {animationTokens.length > 0 && (",
3215
- " <>",
3216
- " <h3 style={{ fontSize: 15, fontWeight: 600, margin: \"0 0 14px\", color: \"#374151\" }}>Animations</h3>",
3217
- " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 4 }}>",
3218
- " {animationTokens.map(({ token, value }) => (",
3219
- " <div key={token} style={{ display: \"flex\", gap: 12, padding: \"7px 0\", borderBottom: \"1px solid #f0f0f0\", alignItems: \"flex-start\" }}>",
3220
- " <span style={{ width: 128, fontSize: 12, fontWeight: 600, flexShrink: 0, paddingTop: 1 }}>{token}</span>",
3221
- " <code style={{ fontSize: 11, color: \"#9ca3af\", wordBreak: \"break-all\" as any }}>{value}</code>",
3632
+ " {replayBtn(() => setEasingKey(k => k + 1))}",
3633
+ " </div>",
3634
+ " <div style={{ display: \"flex\", flexDirection: \"column\", gap: 12 }}>",
3635
+ " {effectiveEasings.map(({ token, value }) => (",
3636
+ " <div key={`${token}-${easingKey}`} style={{ display: \"flex\", alignItems: \"center\", gap: 20 }}>",
3637
+ " <div style={{ width: 130, flexShrink: 0 }}>",
3638
+ " <div style={{ fontSize: 13, fontWeight: 600, color: \"#374151\", marginBottom: 2 }}>{token}</div>",
3639
+ " <code style={{ fontSize: 10, color: \"#9ca3af\", wordBreak: \"break-all\" as any }}>{value}</code>",
3640
+ " </div>",
3641
+ " <div style={{ flex: 1, position: \"relative\", height: 52, background: \"#f8fafc\",",
3642
+ " borderRadius: 12, border: \"1px solid #e5e7eb\", overflow: \"hidden\" }}>",
3643
+ " <div style={{",
3644
+ " position: \"absolute\", left: 10, top: \"50%\", transform: \"translateY(-50%)\",",
3645
+ " width: 40, height: 40, background: \"#4f46e5\", borderRadius: 10,",
3646
+ " animation: `vdsSlide 400ms ${value} both`,",
3647
+ " }} />",
3648
+ " </div>",
3222
3649
  " </div>",
3223
3650
  " ))}",
3224
3651
  " </div>",
3225
- " </>",
3226
- " )}",
3227
- " </div>",
3228
- " ),",
3652
+ " </section>",
3653
+ "",
3654
+ " {/* ── Motion Token Usage ── */}",
3655
+ " <section>",
3656
+ " <p style={{ margin: \"0 0 2px\", fontSize: 11, fontWeight: 700, color: \"#6b7280\", textTransform: \"uppercase\", letterSpacing: \"0.08em\" }}>",
3657
+ " Motion Tokens Used in Project",
3658
+ " </p>",
3659
+ " <p style={{ margin: \"0 0 12px\", fontSize: 12, color: \"#9ca3af\" }}>transition-*, duration-*, animate-* utilities detected in source files.</p>",
3660
+ " {motionChips.length === 0 ? (",
3661
+ " <p style={{ fontSize: 13, color: \"#9ca3af\" }}>No transition or animation utilities detected in source files.</p>",
3662
+ " ) : (",
3663
+ " <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: 6 }}>",
3664
+ " {motionChips.map(({ token, count }) => (",
3665
+ " <span key={token} style={{ padding: \"4px 10px\", background: \"#f0fdf4\", color: \"#166534\",",
3666
+ " border: \"1px solid #bbf7d0\", borderRadius: 20, fontSize: 12, fontFamily: \"monospace\" }}>",
3667
+ " {token} <span style={{ opacity: 0.7 }}>×{count}</span>",
3668
+ " </span>",
3669
+ " ))}",
3670
+ " </div>",
3671
+ " )}",
3672
+ " </section>",
3673
+ "",
3674
+ " </div>",
3675
+ " );",
3676
+ " },",
3229
3677
  "};",
3230
3678
  ].join("\n");
3231
3679
  fs.writeFileSync(path.join(foundationsDir, "Motion.stories.tsx"), motionContent, "utf-8");
@@ -3250,7 +3698,7 @@ function writeComponentSuggestionsStory(componentSuggestions) {
3250
3698
  "import type { Meta, StoryObj } from \"@storybook/react\";",
3251
3699
  "import { useState } from \"react\";",
3252
3700
  "",
3253
- "const meta = { title: \"Foundations/Component Suggestions\" } satisfies Meta;",
3701
+ "const meta = { title: \"Foundations/Component Suggestions\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
3254
3702
  "export default meta;",
3255
3703
  "type Story = StoryObj;",
3256
3704
  "",
@@ -3319,6 +3767,133 @@ function writeComponentSuggestionsStory(componentSuggestions) {
3319
3767
  console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "ComponentSuggestions.stories.tsx")));
3320
3768
  }
3321
3769
 
3770
+ /**
3771
+ * Generates Foundations/Component Inventory story.
3772
+ * Shows all project components classified by Atlassian-style functional category.
3773
+ * Every value is derived from vds-output.json — no placeholder content.
3774
+ */
3775
+ function writeComponentInventoryStory(components, foundations) {
3776
+ if (!Array.isArray(components) || components.length === 0) return;
3777
+ const foundationsDir = path.join(STORIES_DIR, "foundations");
3778
+ ensureDir(foundationsDir);
3779
+
3780
+ // Build per-component data
3781
+ const CATEGORY_ORDER = [
3782
+ "Forms and Input", "Status Indicators", "Navigation", "Overlays and Layering",
3783
+ "Loading", "Messaging", "Images and Icons", "Layout and Structure", "Text and Data Display",
3784
+ ];
3785
+ const categorized = {}; // category → [{ name, group, tokenCount, propCount }]
3786
+
3787
+ for (const comp of components) {
3788
+ const g = comp.group || "Components";
3789
+ const gLower = g.toLowerCase();
3790
+ const isShadcnGroup = gLower === "shadcn" || gLower === "ui";
3791
+ const tokens = Array.isArray(comp.tokens) ? comp.tokens : [];
3792
+ const props = Array.isArray(comp.props) ? comp.props : [];
3793
+ const semantic = classifyComponent(comp.name, tokens, props);
3794
+ const category = semantic || g;
3795
+
3796
+ if (!categorized[category]) categorized[category] = [];
3797
+ categorized[category].push({
3798
+ name: comp.name,
3799
+ group: g,
3800
+ tokenCount: tokens.filter(t => !/:/.test(t)).length,
3801
+ propCount: props.length,
3802
+ });
3803
+ }
3804
+
3805
+ const sortedCategories = Object.keys(categorized).sort((a, b) => {
3806
+ const ai = CATEGORY_ORDER.indexOf(a);
3807
+ const bi = CATEGORY_ORDER.indexOf(b);
3808
+ if (ai >= 0 && bi >= 0) return ai - bi;
3809
+ if (ai >= 0) return -1;
3810
+ if (bi >= 0) return 1;
3811
+ return a.localeCompare(b);
3812
+ });
3813
+
3814
+ const inventoryData = sortedCategories.map(cat => ({
3815
+ category: cat,
3816
+ components: categorized[cat].sort((a, b) => a.name.localeCompare(b.name)),
3817
+ }));
3818
+
3819
+ const totalComponents = components.length;
3820
+ const uniqueTokens = [...new Set(
3821
+ components.flatMap(c => Array.isArray(c.tokens) ? c.tokens.filter(t => !/:/.test(t)) : [])
3822
+ )].length;
3823
+
3824
+ const content = [
3825
+ `import type { Meta, StoryObj } from "@storybook/react";`,
3826
+ ``,
3827
+ `const meta = { title: "Foundations/Component Inventory", parameters: { layout: "fullscreen" } } satisfies Meta;`,
3828
+ `export default meta;`,
3829
+ `type Story = StoryObj;`,
3830
+ ``,
3831
+ `const inventoryData: { category: string; components: { name: string; group: string; tokenCount: number; propCount: number }[] }[] = ${JSON.stringify(inventoryData)};`,
3832
+ `const totalComponents = ${totalComponents};`,
3833
+ `const uniqueTokens = ${uniqueTokens};`,
3834
+ ``,
3835
+ `export const Default: Story = {`,
3836
+ ` render: () => (`,
3837
+ ` <div style={{ padding: 32, background: "#fff", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh" }}>`,
3838
+ ` {/* Header */}`,
3839
+ ` <div style={{ marginBottom: 40 }}>`,
3840
+ ` <h2 style={{ fontSize: 28, fontWeight: 700, margin: "0 0 8px" }}>Component Inventory</h2>`,
3841
+ ` <p style={{ fontSize: 14, color: "#6b7280", margin: "0 0 20px" }}>All components detected in this project, classified by function.</p>`,
3842
+ ` <div style={{ display: "flex", gap: 12, flexWrap: "wrap" as any }}>`,
3843
+ ` {[`,
3844
+ ` { value: totalComponents.toString(), label: "Components" },`,
3845
+ ` { value: inventoryData.length.toString(), label: "Categories" },`,
3846
+ ` { value: uniqueTokens.toString(), label: "Unique Tokens" },`,
3847
+ ` ].map(stat => (`,
3848
+ ` <div key={stat.label} style={{ padding: "12px 20px", background: "#f3f4f6", borderRadius: 10, textAlign: "center" as any, minWidth: 90 }}>`,
3849
+ ` <div style={{ fontSize: 26, fontWeight: 800, color: "#111", lineHeight: 1 }}>{stat.value}</div>`,
3850
+ ` <div style={{ fontSize: 12, color: "#6b7280", marginTop: 4 }}>{stat.label}</div>`,
3851
+ ` </div>`,
3852
+ ` ))}`,
3853
+ ` </div>`,
3854
+ ` </div>`,
3855
+ ` {/* Category groups */}`,
3856
+ ` {inventoryData.map(group => (`,
3857
+ ` <div key={group.category} style={{ marginBottom: 40 }}>`,
3858
+ ` <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 16, paddingBottom: 10, borderBottom: "2px solid #f3f4f6" }}>`,
3859
+ ` <h3 style={{ margin: 0, fontSize: 15, fontWeight: 700, color: "#111" }}>{group.category}</h3>`,
3860
+ ` <span style={{ fontSize: 12, background: "#e0e7ff", color: "#3730a3", padding: "2px 9px", borderRadius: 12, fontWeight: 600 }}>{group.components.length}</span>`,
3861
+ ` </div>`,
3862
+ ` <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(190px, 1fr))", gap: 10 }}>`,
3863
+ ` {group.components.map(comp => (`,
3864
+ ` <div key={comp.name} style={{ border: "1px solid #e5e7eb", borderRadius: 10, padding: "14px 16px", background: "#fafafa" }}>`,
3865
+ ` <div style={{ fontWeight: 700, fontSize: 14, color: "#111", marginBottom: 8 }}>{comp.name}</div>`,
3866
+ ` <div style={{ display: "flex", gap: 5, flexWrap: "wrap" as any }}>`,
3867
+ ` {comp.propCount > 0 && (`,
3868
+ ` <span style={{ fontSize: 10, background: "#f3f4f6", color: "#6b7280", padding: "2px 7px", borderRadius: 10, border: "1px solid #e5e7eb" }}>`,
3869
+ ` {comp.propCount} props`,
3870
+ ` </span>`,
3871
+ ` )}`,
3872
+ ` {comp.tokenCount > 0 && (`,
3873
+ ` <span style={{ fontSize: 10, background: "#eff6ff", color: "#1d4ed8", padding: "2px 7px", borderRadius: 10, border: "1px solid #dbeafe" }}>`,
3874
+ ` {comp.tokenCount} tokens`,
3875
+ ` </span>`,
3876
+ ` )}`,
3877
+ ` {comp.group && comp.group !== "shadcn" && comp.group !== "UI" && comp.group !== "Components" && (`,
3878
+ ` <span style={{ fontSize: 10, background: "#f0fdf4", color: "#166534", padding: "2px 7px", borderRadius: 10, border: "1px solid #bbf7d0" }}>`,
3879
+ ` {comp.group}`,
3880
+ ` </span>`,
3881
+ ` )}`,
3882
+ ` </div>`,
3883
+ ` </div>`,
3884
+ ` ))}`,
3885
+ ` </div>`,
3886
+ ` </div>`,
3887
+ ` ))}`,
3888
+ ` </div>`,
3889
+ ` ),`,
3890
+ `};`,
3891
+ ].join("\n");
3892
+
3893
+ fs.writeFileSync(path.join(foundationsDir, "ComponentInventory.stories.tsx"), content, "utf-8");
3894
+ console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "ComponentInventory.stories.tsx")));
3895
+ }
3896
+
3322
3897
  function writeChangelogStory(changelog) {
3323
3898
  const foundationsDir = path.join(STORIES_DIR, "foundations");
3324
3899
  ensureDir(foundationsDir);
@@ -3327,7 +3902,7 @@ function writeChangelogStory(changelog) {
3327
3902
  [
3328
3903
  "import type { Meta, StoryObj } from \"@storybook/react\";",
3329
3904
  "",
3330
- "const meta = { title: \"Foundations/Changelog\", tags: [\"autodocs\"] } satisfies Meta;",
3905
+ "const meta = { title: \"Foundations/Changelog\", tags: [\"autodocs\"], parameters: { layout: \"fullscreen\" } } satisfies Meta;",
3331
3906
  "export default meta;",
3332
3907
  "type Story = StoryObj;",
3333
3908
  "",
@@ -3591,6 +4166,7 @@ function main() {
3591
4166
  if (componentSuggestions?.length) {
3592
4167
  writeComponentSuggestionsStory(componentSuggestions);
3593
4168
  }
4169
+ writeComponentInventoryStory(components, foundations);
3594
4170
  writeChangelogStory(data.changelog);
3595
4171
  try {
3596
4172
  const fd = path.join(STORIES_DIR, "foundations");