vibe-design-system 2.8.64 → 2.8.65

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.64",
3
+ "version": "2.8.65",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "homepage": "https://vibedesign.tech",
6
6
  "repository": {
@@ -2525,7 +2525,7 @@ function writeFoundationsStories(foundations) {
2525
2525
  "",
2526
2526
  "export const Default: Story = {",
2527
2527
  " render: () => (",
2528
- " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, background: \"#fff\", minHeight: 500, color: \"#111\" }}>",
2528
+ " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, background: \"#fff\", minHeight: \"100vh\", width: \"100%\", color: \"#111\" }}>",
2529
2529
  " <h2 style={{ fontSize: 20, fontWeight: 700, margin: \"0 0 4px\", color: \"#111\" }}>Colors</h2>",
2530
2530
  " <p style={{ fontSize: 13, color: \"#888\", margin: \"0 0 40px\" }}>Design tokens defined in CSS variables — grouped by role</p>",
2531
2531
  "",
@@ -3815,6 +3815,136 @@ function writeComponentSuggestionsStory(componentSuggestions) {
3815
3815
  console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "ComponentSuggestions.stories.tsx")));
3816
3816
  }
3817
3817
 
3818
+ /**
3819
+ * Determines the visual preview shape and colors for a component in the inventory.
3820
+ * Returns an HTML string rendered at generation time using the project's resolved colors.
3821
+ */
3822
+ function buildInventoryPreviewHtml(compName, category, colorSwatches, tokens) {
3823
+ const n = compName.toLowerCase().replace(/[\s-]+/g, "");
3824
+
3825
+ // Extract colors from resolved swatches
3826
+ const bgSwatch = colorSwatches.find(s => /^bg-(?!gradient)/.test(s.token));
3827
+ const textSwatch = colorSwatches.find(s => /^text-/.test(s.token));
3828
+ const brdSwatch = colorSwatches.find(s => /^border-/.test(s.token));
3829
+ const accSwatch = colorSwatches.find(s => s.token === "bg-primary" || s.token === "border-primary" || s.token === "ring-primary");
3830
+
3831
+ // Fallback neutral palette (works on white canvas)
3832
+ const bg = bgSwatch?.hex || "#f3f4f6";
3833
+ const txt = textSwatch?.hex || "#374151";
3834
+ const brd = brdSwatch?.hex || "#e5e7eb";
3835
+ const acc = accSwatch?.hex || "#6366f1";
3836
+ // Lighten/darken helpers (inline, no deps)
3837
+ const hex2 = (h) => { // slightly lighter bg for header strips
3838
+ const n = parseInt(h.slice(1), 16);
3839
+ const r = Math.min(255, (n >> 16) + 30), g = Math.min(255, ((n >> 8) & 0xff) + 30), b = Math.min(255, (n & 0xff) + 30);
3840
+ return "#" + [r,g,b].map(x => x.toString(16).padStart(2,"0")).join("");
3841
+ };
3842
+
3843
+ // Border-radius from tokens
3844
+ const radTok = tokens.find(t => /^rounded/.test(t));
3845
+ const rad = radTok === "rounded-none" ? 0 : radTok === "rounded-sm" ? 2 : radTok === "rounded" ? 4
3846
+ : radTok === "rounded-md" ? 6 : radTok === "rounded-lg" ? 8 : radTok === "rounded-xl" ? 12
3847
+ : radTok === "rounded-2xl" ? 16 : radTok === "rounded-3xl" ? 24 : radTok === "rounded-full" ? 9999 : 4;
3848
+
3849
+ const wrap = (inner) => `<div style="width:100%;height:56px;display:flex;align-items:center;justify-content:center;overflow:hidden;padding:0 10px;box-sizing:border-box">${inner}</div>`;
3850
+
3851
+ // ── Specific component shapes ────────────────────────────────────────────
3852
+ if (/^input$/.test(n) || (category === "Forms and Input" && /input/.test(n))) {
3853
+ return wrap(`<div style="width:100%;max-width:200px;background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:6px 10px;display:flex;align-items:center;gap:6px;box-sizing:border-box"><div style="flex:1;height:8px;background:${txt};opacity:0.3;border-radius:2px"></div></div>`);
3854
+ }
3855
+ if (/^textarea$/.test(n)) {
3856
+ return wrap(`<div style="width:100%;max-width:200px;background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:6px 10px;height:38px;box-sizing:border-box;display:flex;flex-direction:column;gap:4px"><div style="height:6px;background:${txt};opacity:0.3;border-radius:2px;width:80%"></div><div style="height:6px;background:${txt};opacity:0.2;border-radius:2px;width:50%"></div></div>`);
3857
+ }
3858
+ if (/^(button|submitbutton)$/.test(n) || (category === "Forms and Input" && /^button$/.test(n))) {
3859
+ return wrap(`<div style="display:inline-flex;align-items:center;background:${acc};color:#fff;border:1px solid ${acc};border-radius:${rad}px;padding:6px 18px;font-size:11px;font-weight:600;white-space:nowrap">${compName}</div>`);
3860
+ }
3861
+ if (/^toggle$/.test(n)) {
3862
+ return wrap(`<div style="display:flex;align-items:center;gap:6px"><div style="display:inline-flex;align-items:center;background:${acc};color:#fff;border-radius:${rad}px;padding:5px 12px;font-size:11px;font-weight:600">ON</div><div style="display:inline-flex;align-items:center;background:${bg};color:${txt};border:1px solid ${brd};border-radius:${rad}px;padding:5px 12px;font-size:11px">OFF</div></div>`);
3863
+ }
3864
+ if (/^(badge|chip|tag|lozenge|pill)$/.test(n) || category === "Status Indicators") {
3865
+ const bBg = acc + "20"; const bTxt = acc;
3866
+ return wrap(`<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:center"><span style="background:${bBg};color:${bTxt};border:1px solid ${brd};border-radius:${Math.max(rad,20)}px;padding:3px 10px;font-size:11px;font-weight:600">${compName}</span><span style="background:${brd}40;color:${txt};border:1px solid ${brd};border-radius:${Math.max(rad,20)}px;padding:3px 10px;font-size:11px">Default</span></div>`);
3867
+ }
3868
+ if (/^(checkbox|radio|radiogroup)$/.test(n)) {
3869
+ return wrap(`<div style="display:flex;flex-direction:column;gap:5px"><div style="display:flex;align-items:center;gap:6px"><div style="width:14px;height:14px;border-radius:3px;background:${acc};flex-shrink:0"></div><div style="height:6px;background:${txt};opacity:0.35;border-radius:2px;width:50px"></div></div><div style="display:flex;align-items:center;gap:6px"><div style="width:14px;height:14px;border-radius:3px;background:${bg};border:2px solid ${brd};flex-shrink:0"></div><div style="height:6px;background:${txt};opacity:0.25;border-radius:2px;width:40px"></div></div></div>`);
3870
+ }
3871
+ if (/^switch$/.test(n)) {
3872
+ return wrap(`<div style="display:flex;align-items:center;gap:12px"><div style="width:36px;height:20px;background:${acc};border-radius:9999px;position:relative;flex-shrink:0"><div style="position:absolute;right:2px;top:2px;width:16px;height:16px;background:#fff;border-radius:9999px"></div></div><div style="width:36px;height:20px;background:${brd};border-radius:9999px;position:relative;flex-shrink:0"><div style="position:absolute;left:2px;top:2px;width:16px;height:16px;background:#fff;border-radius:9999px"></div></div></div>`);
3873
+ }
3874
+ if (/^(select|combobox)$/.test(n)) {
3875
+ return wrap(`<div style="width:100%;max-width:190px;background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:6px 10px;display:flex;align-items:center;gap:4px;box-sizing:border-box"><div style="flex:1;height:8px;background:${txt};opacity:0.3;border-radius:2px"></div><div style="font-size:8px;color:${txt};opacity:0.5;flex-shrink:0">▾</div></div>`);
3876
+ }
3877
+ if (/^slider$/.test(n)) {
3878
+ return wrap(`<div style="width:80%;position:relative;padding:8px 0"><div style="height:4px;background:${brd};border-radius:9999px;position:relative"><div style="position:absolute;left:0;top:0;height:100%;width:60%;background:${acc};border-radius:9999px"></div><div style="position:absolute;left:calc(60% - 7px);top:50%;transform:translateY(-50%);width:14px;height:14px;background:${bg};border:2px solid ${acc};border-radius:9999px"></div></div></div>`);
3879
+ }
3880
+ if (/^skeleton$/.test(n) || category === "Loading") {
3881
+ return wrap(`<div style="display:flex;flex-direction:column;gap:5px;width:80%"><div style="height:10px;background:${bg === "#f3f4f6" ? "#e5e7eb" : hex2(bg)};border-radius:${rad}px;width:100%"></div><div style="height:8px;background:${bg === "#f3f4f6" ? "#e5e7eb" : hex2(bg)};border-radius:${rad}px;width:80%;opacity:0.7"></div><div style="height:8px;background:${bg === "#f3f4f6" ? "#e5e7eb" : hex2(bg)};border-radius:${rad}px;width:60%;opacity:0.5"></div></div>`);
3882
+ }
3883
+ if (/^(spinner|loader)$/.test(n)) {
3884
+ return wrap(`<div style="width:24px;height:24px;border-radius:9999px;border:3px solid ${brd};border-top-color:${acc};"></div>`);
3885
+ }
3886
+ if (/^(alert|banner|notification)$/.test(n) || category === "Messaging") {
3887
+ return wrap(`<div style="display:flex;align-items:center;gap:8px;background:${acc}15;border:1px solid ${acc}40;border-left:3px solid ${acc};border-radius:${rad}px;padding:6px 10px;width:100%;max-width:220px;box-sizing:border-box"><div style="flex:1;height:7px;background:${txt};opacity:0.3;border-radius:2px"></div></div>`);
3888
+ }
3889
+ if (/^toast$/.test(n)) {
3890
+ return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:8px 12px;width:100%;max-width:210px;box-sizing:border-box;display:flex;justify-content:space-between;align-items:center"><div style="height:7px;background:${txt};opacity:0.4;border-radius:2px;width:60%"></div><div style="font-size:9px;color:${txt};opacity:0.4">✕</div></div>`);
3891
+ }
3892
+ if (/^avatar$/.test(n) || /^(images and icons)$/i.test(category)) {
3893
+ return wrap(`<div style="display:flex;gap:8px;align-items:center"><div style="width:36px;height:36px;border-radius:9999px;background:${acc};display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;color:#fff;flex-shrink:0">${compName.slice(0,1).toUpperCase()}</div><div style="width:28px;height:28px;border-radius:9999px;background:${bg};border:1px solid ${brd};flex-shrink:0"></div></div>`);
3894
+ }
3895
+ if (/^(card|tile|panel)$/.test(n) || (category === "Layout and Structure" && /^card$/.test(n))) {
3896
+ return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:10px 12px;width:85%;box-sizing:border-box"><div style="height:7px;background:${txt};opacity:0.4;border-radius:2px;width:50%;margin-bottom:5px"></div><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:80%;margin-bottom:4px"></div><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:65%"></div></div>`);
3897
+ }
3898
+ if (/^(accordion|collapsible)$/.test(n)) {
3899
+ return wrap(`<div style="width:85%;box-sizing:border-box"><div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:7px 12px;display:flex;justify-content:space-between;align-items:center"><div style="height:7px;background:${txt};opacity:0.4;border-radius:2px;width:50%"></div><div style="font-size:10px;color:${txt};opacity:0.5">▾</div></div></div>`);
3900
+ }
3901
+ if (/^(dialog|modal|sheet|alertdialog)$/.test(n) || category === "Overlays and Layering") {
3902
+ return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;overflow:hidden;width:85%;box-sizing:border-box"><div style="background:${hex2(bg)};border-bottom:1px solid ${brd};padding:5px 10px;display:flex;justify-content:space-between;align-items:center"><div style="height:6px;background:${txt};opacity:0.5;border-radius:2px;width:50px"></div><div style="font-size:9px;color:${txt};opacity:0.3">✕</div></div><div style="padding:8px 10px;display:flex;flex-direction:column;gap:3px"><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:80%"></div><div style="height:5px;background:${txt};opacity:0.15;border-radius:2px;width:55%"></div></div></div>`);
3903
+ }
3904
+ if (/^drawer$/.test(n)) {
3905
+ return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px ${rad}px 0 0;width:85%;box-sizing:border-box"><div style="padding:8px 12px;border-bottom:1px solid ${brd};display:flex;align-items:center;gap:6px"><div style="height:6px;background:${txt};opacity:0.4;border-radius:2px;width:60%"></div></div><div style="padding:8px 12px;display:flex;flex-direction:column;gap:3px"><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:100%"></div></div></div>`);
3906
+ }
3907
+ if (/^(tabs|tab)$/.test(n)) {
3908
+ return wrap(`<div style="width:85%;box-sizing:border-box"><div style="display:flex;background:${bg};border-radius:${rad}px;padding:3px;gap:2px"><div style="flex:1;background:${bg === "#f3f4f6" ? "#fff" : hex2(bg)};border-radius:${Math.max(rad-2,2)}px;padding:4px 0;font-size:10px;font-weight:600;color:${txt};text-align:center">Tab 1</div><div style="flex:1;padding:4px 0;font-size:10px;color:${txt};opacity:0.5;text-align:center">Tab 2</div><div style="flex:1;padding:4px 0;font-size:10px;color:${txt};opacity:0.5;text-align:center">Tab 3</div></div></div>`);
3909
+ }
3910
+ if (/^(table|datatable)$/.test(n) || category === "Text and Data Display") {
3911
+ return wrap(`<div style="border:1px solid ${brd};border-radius:${rad}px;overflow:hidden;width:85%;box-sizing:border-box"><div style="display:flex;background:${hex2(bg)};border-bottom:1px solid ${brd};padding:4px 8px;gap:8px">${[1,1,1].map(()=>`<div style="flex:1;height:5px;background:${txt};opacity:0.3;border-radius:1px"></div>`).join("")}</div><div style="display:flex;background:${bg};padding:4px 8px;gap:8px">${[1,1,1].map(()=>`<div style="flex:1;height:5px;background:${txt};opacity:0.15;border-radius:1px"></div>`).join("")}</div><div style="display:flex;background:${bg};padding:4px 8px;gap:8px;opacity:0.7">${[1,1,1].map(()=>`<div style="flex:1;height:5px;background:${txt};opacity:0.15;border-radius:1px"></div>`).join("")}</div></div>`);
3912
+ }
3913
+ if (/^(sidebar|appsidebar)$/.test(n)) {
3914
+ return wrap(`<div style="display:flex;gap:6px;align-items:flex-start"><div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;width:50px;display:flex;flex-direction:column;gap:4px;padding:6px 4px"><div style="height:4px;background:${acc};border-radius:2px;width:80%;margin:0 auto"></div><div style="height:4px;background:${txt};opacity:0.25;border-radius:2px;width:70%;margin:0 auto"></div><div style="height:4px;background:${txt};opacity:0.25;border-radius:2px;width:60%;margin:0 auto"></div></div><div style="flex:1;background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:6px"><div style="height:5px;background:${txt};opacity:0.15;border-radius:2px;width:90%"></div></div></div>`);
3915
+ }
3916
+ if (/^(navbar|nav|navigation|breadcrumb|pagination|navigationmenu)$/.test(n) || /^Navigation$/.test(category)) {
3917
+ return wrap(`<div style="display:flex;align-items:center;gap:8px;background:${bg};border-bottom:1px solid ${brd};padding:6px 10px;width:100%;max-width:220px;box-sizing:border-box"><div style="height:6px;background:${acc};border-radius:2px;width:35px"></div><div style="height:6px;background:${txt};opacity:0.25;border-radius:2px;width:28px"></div><div style="height:6px;background:${txt};opacity:0.25;border-radius:2px;width:32px"></div></div>`);
3918
+ }
3919
+ if (/^(progress)$/.test(n)) {
3920
+ return wrap(`<div style="width:80%;box-sizing:border-box"><div style="height:6px;background:${brd};border-radius:9999px;overflow:hidden"><div style="height:100%;width:65%;background:${acc};border-radius:9999px"></div></div></div>`);
3921
+ }
3922
+ if (/^(dropdownmenu|contextmenu|menubar|menu)$/.test(n)) {
3923
+ return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;overflow:hidden;width:70%;box-sizing:border-box">${[["60%","#a0a"],["40%",""],["55%",""]].map(([w,a])=>`<div style="padding:4px 10px;display:flex;align-items:center;gap:6px;${a?`background:${acc}20`:``}"><div style="flex:0 0 ${w};height:5px;background:${txt};opacity:${a?0.5:0.25};border-radius:1px"></div></div>`).join("")}</div>`);
3924
+ }
3925
+ if (/^(popover|tooltip|hovercard)$/.test(n)) {
3926
+ return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:8px 12px;width:75%;box-sizing:border-box;box-shadow:0 4px 12px rgba(0,0,0,0.1)"><div style="height:6px;background:${txt};opacity:0.3;border-radius:2px;width:60%;margin-bottom:4px"></div><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:80%"></div></div>`);
3927
+ }
3928
+ if (/^(resizable|splitpane)$/.test(n)) {
3929
+ return wrap(`<div style="display:flex;align-items:stretch;gap:0;width:85%;height:40px"><div style="flex:1;background:${bg};border:1px solid ${brd};border-radius:${rad}px 0 0 ${rad}px"></div><div style="width:5px;background:${brd};cursor:col-resize"></div><div style="flex:1;background:${bg};border:1px solid ${brd};border-left:none;border-radius:0 ${rad}px ${rad}px 0"></div></div>`);
3930
+ }
3931
+ if (/^(chart|pricechart|sparkline)$/.test(n)) {
3932
+ const pts = [22,18,28,15,24,12,20,16,26,10,22,18].map((y,x)=>`${x*14},${y}`).join(" ");
3933
+ return wrap(`<div style="width:85%;height:40px;position:relative"><svg viewBox="0 0 168 32" style="width:100%;height:100%"><polyline points="${pts}" fill="none" stroke="${acc}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div>`);
3934
+ }
3935
+ if (/^(separator)$/.test(n)) {
3936
+ return wrap(`<div style="width:85%;height:1px;background:${brd}"></div>`);
3937
+ }
3938
+ // Generic page/section components → show token color stripe
3939
+ if (colorSwatches.length > 0) {
3940
+ const stripeW = Math.floor(100 / colorSwatches.length);
3941
+ const stripes = colorSwatches.map(s => `<div style="flex:1;background:${s.hex};height:20px"></div>`).join("");
3942
+ return wrap(`<div style="width:85%;display:flex;flex-direction:column;gap:5px"><div style="display:flex;border-radius:4px;overflow:hidden;height:12px">${stripes}</div><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:60%"></div><div style="height:5px;background:${txt};opacity:0.15;border-radius:2px;width:40%"></div></div>`);
3943
+ }
3944
+ // Absolute fallback: neutral box
3945
+ return wrap(`<div style="width:85%;height:28px;background:${bg};border:1px solid ${brd};border-radius:${rad}px"></div>`);
3946
+ }
3947
+
3818
3948
  /**
3819
3949
  * Generates Foundations/Component Inventory story.
3820
3950
  * Shows all project components classified by Atlassian-style functional category.
@@ -3860,19 +3990,17 @@ function writeComponentInventoryStory(components, foundations) {
3860
3990
  })
3861
3991
  .filter(s => s.hex); // only swatches with resolved hex
3862
3992
 
3863
- // Representative token fingerprint (spacing + typography, max 4, no color)
3864
- const tokenFingerprint = tokens
3865
- .filter(t => !/:/.test(t) && !/^(bg|text|border|ring|from|to|fill|stroke)-/.test(t))
3866
- .slice(0, 4);
3993
+ // Visual preview HTML (computed at generation time using project's resolved colors)
3994
+ const previewHtml = buildInventoryPreviewHtml(comp.name, category, colorSwatches, tokens.filter(t => !/:/.test(t)));
3867
3995
 
3868
3996
  if (!categorized[category]) categorized[category] = [];
3869
3997
  categorized[category].push({
3870
- name: comp.name,
3871
- group: g,
3872
- tokenCount: tokens.filter(t => !/:/.test(t)).length,
3873
- propCount: props.length,
3998
+ name: comp.name,
3999
+ group: g,
4000
+ tokenCount: tokens.filter(t => !/:/.test(t)).length,
4001
+ propCount: props.length,
3874
4002
  colorSwatches,
3875
- tokenFingerprint,
4003
+ previewHtml,
3876
4004
  });
3877
4005
  }
3878
4006
 
@@ -3902,24 +4030,24 @@ function writeComponentInventoryStory(components, foundations) {
3902
4030
  `export default meta;`,
3903
4031
  `type Story = StoryObj;`,
3904
4032
  ``,
3905
- `const inventoryData: { category: string; components: { name: string; group: string; tokenCount: number; propCount: number; colorSwatches: { token: string; hex: string }[]; tokenFingerprint: string[] }[] }[] = ${JSON.stringify(inventoryData)};`,
4033
+ `const inventoryData: { category: string; components: { name: string; group: string; tokenCount: number; propCount: number; colorSwatches: { token: string; hex: string }[]; previewHtml: string }[] }[] = ${JSON.stringify(inventoryData)};`,
3906
4034
  `const totalComponents = ${totalComponents};`,
3907
4035
  `const uniqueTokens = ${uniqueTokens};`,
3908
4036
  ``,
3909
4037
  `export const Default: Story = {`,
3910
4038
  ` render: () => (`,
3911
- ` <div style={{ padding: 32, background: "#fff", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh", width: "100%" }}>`,
4039
+ ` <div style={{ padding: 32, background: "#f8f9fa", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh", width: "100%" }}>`,
3912
4040
  ` {/* Header */}`,
3913
4041
  ` <div style={{ marginBottom: 40 }}>`,
3914
4042
  ` <h2 style={{ fontSize: 28, fontWeight: 700, margin: "0 0 8px" }}>Component Inventory</h2>`,
3915
- ` <p style={{ fontSize: 14, color: "#6b7280", margin: "0 0 20px" }}>All components detected in this project, classified by function.</p>`,
4043
+ ` <p style={{ fontSize: 14, color: "#6b7280", margin: "0 0 20px" }}>All components detected in this project visual style fingerprints derived from project tokens.</p>`,
3916
4044
  ` <div style={{ display: "flex", gap: 12, flexWrap: "wrap" as any }}>`,
3917
4045
  ` {[`,
3918
4046
  ` { value: totalComponents.toString(), label: "Components" },`,
3919
4047
  ` { value: inventoryData.length.toString(), label: "Categories" },`,
3920
4048
  ` { value: uniqueTokens.toString(), label: "Unique Tokens" },`,
3921
4049
  ` ].map(stat => (`,
3922
- ` <div key={stat.label} style={{ padding: "12px 20px", background: "#f3f4f6", borderRadius: 10, textAlign: "center" as any, minWidth: 90 }}>`,
4050
+ ` <div key={stat.label} style={{ padding: "12px 20px", background: "#fff", borderRadius: 10, textAlign: "center" as any, minWidth: 90, border: "1px solid #e5e7eb" }}>`,
3923
4051
  ` <div style={{ fontSize: 26, fontWeight: 800, color: "#111", lineHeight: 1 }}>{stat.value}</div>`,
3924
4052
  ` <div style={{ fontSize: 12, color: "#6b7280", marginTop: 4 }}>{stat.label}</div>`,
3925
4053
  ` </div>`,
@@ -3928,40 +4056,22 @@ function writeComponentInventoryStory(components, foundations) {
3928
4056
  ` </div>`,
3929
4057
  ` {/* Category groups */}`,
3930
4058
  ` {inventoryData.map(group => (`,
3931
- ` <div key={group.category} style={{ marginBottom: 40 }}>`,
3932
- ` <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 16, paddingBottom: 10, borderBottom: "2px solid #f3f4f6" }}>`,
4059
+ ` <div key={group.category} style={{ marginBottom: 44 }}>`,
4060
+ ` <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 16, paddingBottom: 10, borderBottom: "2px solid #e5e7eb" }}>`,
3933
4061
  ` <h3 style={{ margin: 0, fontSize: 15, fontWeight: 700, color: "#111" }}>{group.category}</h3>`,
3934
4062
  ` <span style={{ fontSize: 12, background: "#e0e7ff", color: "#3730a3", padding: "2px 9px", borderRadius: 12, fontWeight: 600 }}>{group.components.length}</span>`,
3935
4063
  ` </div>`,
3936
- ` <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(210px, 1fr))", gap: 12 }}>`,
4064
+ ` <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))", gap: 12 }}>`,
3937
4065
  ` {group.components.map(comp => (`,
3938
- ` <div key={comp.name} style={{ border: "1px solid #e5e7eb", borderRadius: 10, overflow: "hidden", background: "#fff", display: "flex", flexDirection: "column" as any }}>`,
3939
- ` {/* Color swatch strip — project-resolved colors from foundations */}`,
3940
- ` {comp.colorSwatches.length > 0 ? (`,
3941
- ` <div style={{ display: "flex", height: 8 }}>`,
3942
- ` {comp.colorSwatches.map((s: any) => (`,
3943
- ` <div key={s.token} title={s.token} style={{ flex: 1, background: s.hex }} />`,
3944
- ` ))}`,
3945
- ` </div>`,
3946
- ` ) : (`,
3947
- ` <div style={{ height: 4, background: "#f3f4f6" }} />`,
3948
- ` )}`,
3949
- ` <div style={{ padding: "12px 14px", flexGrow: 1 }}>`,
4066
+ ` <div key={comp.name} style={{ border: "1px solid #e5e7eb", borderRadius: 10, overflow: "hidden", background: "#fff", display: "flex", flexDirection: "column" as any, boxShadow: "0 1px 3px rgba(0,0,0,0.04)" }}>`,
4067
+ ` {/* Visual previewrendered from project tokens at generation time */}`,
4068
+ ` <div style={{ background: "#f8f9fa", borderBottom: "1px solid #f0f0f0", minHeight: 64, display: "flex", alignItems: "center", justifyContent: "center" }}`,
4069
+ ` dangerouslySetInnerHTML={{ __html: comp.previewHtml }}`,
4070
+ ` />`,
4071
+ ` {/* Info strip */}`,
4072
+ ` <div style={{ padding: "10px 12px" }}>`,
3950
4073
  ` <div style={{ fontWeight: 700, fontSize: 13, color: "#111", marginBottom: 6 }}>{comp.name}</div>`,
3951
- ` {/* Token fingerprint pills */}`,
3952
- ` {comp.tokenFingerprint.length > 0 && (`,
3953
- ` <div style={{ display: "flex", gap: 4, flexWrap: "wrap" as any, marginBottom: 8 }}>`,
3954
- ` {comp.tokenFingerprint.map((t: string) => (`,
3955
- ` <code key={t} style={{ fontSize: 9, background: "#f3f4f6", color: "#6b7280", padding: "1px 5px", borderRadius: 4, border: "1px solid #e5e7eb" }}>{t}</code>`,
3956
- ` ))}`,
3957
- ` </div>`,
3958
- ` )}`,
3959
- ` <div style={{ display: "flex", gap: 5, flexWrap: "wrap" as any }}>`,
3960
- ` {comp.propCount > 0 && (`,
3961
- ` <span style={{ fontSize: 10, background: "#f3f4f6", color: "#6b7280", padding: "2px 7px", borderRadius: 10, border: "1px solid #e5e7eb" }}>`,
3962
- ` {comp.propCount} props`,
3963
- ` </span>`,
3964
- ` )}`,
4074
+ ` <div style={{ display: "flex", gap: 4, flexWrap: "wrap" as any }}>`,
3965
4075
  ` {comp.tokenCount > 0 && (`,
3966
4076
  ` <span style={{ fontSize: 10, background: "#eff6ff", color: "#1d4ed8", padding: "2px 7px", borderRadius: 10, border: "1px solid #dbeafe" }}>`,
3967
4077
  ` {comp.tokenCount} tokens`,
@@ -3972,6 +4082,11 @@ function writeComponentInventoryStory(components, foundations) {
3972
4082
  ` {comp.colorSwatches.length} colors`,
3973
4083
  ` </span>`,
3974
4084
  ` )}`,
4085
+ ` {comp.propCount > 0 && (`,
4086
+ ` <span style={{ fontSize: 10, background: "#f3f4f6", color: "#6b7280", padding: "2px 7px", borderRadius: 10, border: "1px solid #e5e7eb" }}>`,
4087
+ ` {comp.propCount} props`,
4088
+ ` </span>`,
4089
+ ` )}`,
3975
4090
  ` {comp.group && comp.group !== "shadcn" && comp.group !== "UI" && comp.group !== "Components" && (`,
3976
4091
  ` <span style={{ fontSize: 10, background: "#f0fdf4", color: "#166534", padding: "2px 7px", borderRadius: 10, border: "1px solid #bbf7d0" }}>`,
3977
4092
  ` {comp.group}`,