raven-mcp 1.0.0 → 1.1.0
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/dist/index.js +972 -0
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
- package/scripts/postinstall.cjs +34 -0
- package/src/data/principles/component-architecture.json +103 -0
- package/src/data/principles/mobile-ux.json +180 -0
- package/src/data/principles/responsive-layout.json +95 -0
- package/src/data/tokens/registry.json +57 -12
- package/src/data/tokens/systems/airbnb.json +187 -0
- package/src/data/tokens/systems/apple-hig.json +152 -0
- package/src/data/tokens/systems/github-primer.json +154 -0
- package/src/data/tokens/systems/material-design.json +235 -0
- package/src/data/tokens/systems/notion.json +160 -0
- package/src/data/tokens/systems/shadcn.json +162 -0
- package/src/data/tokens/systems/spotify.json +155 -0
- package/src/data/tokens/systems/supabase.json +179 -0
- package/src/data/tokens/systems/tailwind.json +279 -0
- package/src/data/tokens/systems/vercel.json +145 -0
package/dist/index.js
CHANGED
|
@@ -125,6 +125,271 @@ function tokensToCSSByGroup(tokens, prefix) {
|
|
|
125
125
|
}
|
|
126
126
|
return ":root {\n" + sections.join("\n") + "}";
|
|
127
127
|
}
|
|
128
|
+
// ── Color math (zero deps) ─────────────────────────────────────────
|
|
129
|
+
function hexToRGB(hex) {
|
|
130
|
+
var h = hex.replace("#", "");
|
|
131
|
+
if (h.length === 3)
|
|
132
|
+
h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
133
|
+
return {
|
|
134
|
+
r: parseInt(h.substring(0, 2), 16),
|
|
135
|
+
g: parseInt(h.substring(2, 4), 16),
|
|
136
|
+
b: parseInt(h.substring(4, 6), 16)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function rgbToHex(r, g, b) {
|
|
140
|
+
var clamp = (v) => Math.max(0, Math.min(255, Math.round(v)));
|
|
141
|
+
return "#" + [clamp(r), clamp(g), clamp(b)].map(v => v.toString(16).padStart(2, "0")).join("");
|
|
142
|
+
}
|
|
143
|
+
function hexToHSL(hex) {
|
|
144
|
+
var { r, g, b } = hexToRGB(hex);
|
|
145
|
+
var rn = r / 255, gn = g / 255, bn = b / 255;
|
|
146
|
+
var max = Math.max(rn, gn, bn), min = Math.min(rn, gn, bn);
|
|
147
|
+
var h = 0, s = 0, l = (max + min) / 2;
|
|
148
|
+
if (max !== min) {
|
|
149
|
+
var d = max - min;
|
|
150
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
151
|
+
if (max === rn)
|
|
152
|
+
h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6;
|
|
153
|
+
else if (max === gn)
|
|
154
|
+
h = ((bn - rn) / d + 2) / 6;
|
|
155
|
+
else
|
|
156
|
+
h = ((rn - gn) / d + 4) / 6;
|
|
157
|
+
}
|
|
158
|
+
return { h: h * 360, s: s * 100, l: l * 100 };
|
|
159
|
+
}
|
|
160
|
+
function hslToHex(h, s, l) {
|
|
161
|
+
var sn = s / 100, ln = l / 100;
|
|
162
|
+
var c = (1 - Math.abs(2 * ln - 1)) * sn;
|
|
163
|
+
var x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
164
|
+
var m = ln - c / 2;
|
|
165
|
+
var r = 0, g = 0, b = 0;
|
|
166
|
+
if (h < 60) {
|
|
167
|
+
r = c;
|
|
168
|
+
g = x;
|
|
169
|
+
}
|
|
170
|
+
else if (h < 120) {
|
|
171
|
+
r = x;
|
|
172
|
+
g = c;
|
|
173
|
+
}
|
|
174
|
+
else if (h < 180) {
|
|
175
|
+
g = c;
|
|
176
|
+
b = x;
|
|
177
|
+
}
|
|
178
|
+
else if (h < 240) {
|
|
179
|
+
g = x;
|
|
180
|
+
b = c;
|
|
181
|
+
}
|
|
182
|
+
else if (h < 300) {
|
|
183
|
+
r = x;
|
|
184
|
+
b = c;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
r = c;
|
|
188
|
+
b = x;
|
|
189
|
+
}
|
|
190
|
+
return rgbToHex((r + m) * 255, (g + m) * 255, (b + m) * 255);
|
|
191
|
+
}
|
|
192
|
+
function hexToRGBNormalized(hex) {
|
|
193
|
+
var { r, g, b } = hexToRGB(hex);
|
|
194
|
+
return { r: +(r / 255).toFixed(4), g: +(g / 255).toFixed(4), b: +(b / 255).toFixed(4), a: 1 };
|
|
195
|
+
}
|
|
196
|
+
// ── WCAG contrast ──────────────────────────────────────────────────
|
|
197
|
+
function getRelativeLuminance(hex) {
|
|
198
|
+
var { r, g, b } = hexToRGB(hex);
|
|
199
|
+
var sRGB = [r, g, b].map(v => {
|
|
200
|
+
var c = v / 255;
|
|
201
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
202
|
+
});
|
|
203
|
+
return 0.2126 * sRGB[0] + 0.7152 * sRGB[1] + 0.0722 * sRGB[2];
|
|
204
|
+
}
|
|
205
|
+
function getContrastRatio(hex1, hex2) {
|
|
206
|
+
var l1 = getRelativeLuminance(hex1);
|
|
207
|
+
var l2 = getRelativeLuminance(hex2);
|
|
208
|
+
var lighter = Math.max(l1, l2), darker = Math.min(l1, l2);
|
|
209
|
+
return +((lighter + 0.05) / (darker + 0.05)).toFixed(2);
|
|
210
|
+
}
|
|
211
|
+
function contrastGrade(ratio) {
|
|
212
|
+
if (ratio >= 7)
|
|
213
|
+
return "AAA";
|
|
214
|
+
if (ratio >= 4.5)
|
|
215
|
+
return "AA";
|
|
216
|
+
if (ratio >= 3)
|
|
217
|
+
return "AA-lg";
|
|
218
|
+
return "Fail";
|
|
219
|
+
}
|
|
220
|
+
// ── Palette generation ─────────────────────────────────────────────
|
|
221
|
+
function generatePalette(brandHex, includeDark) {
|
|
222
|
+
var hsl = hexToHSL(brandHex);
|
|
223
|
+
var h = hsl.h, s = hsl.s, l = hsl.l;
|
|
224
|
+
var light = {
|
|
225
|
+
"$type": "color",
|
|
226
|
+
"primary": { "$value": brandHex, "$description": "Primary brand color" },
|
|
227
|
+
"primary-light": { "$value": hslToHex(h, Math.max(s - 10, 0), Math.min(l + 15, 95)), "$description": "Lighter primary" },
|
|
228
|
+
"primary-dark": { "$value": hslToHex(h, Math.min(s + 5, 100), Math.max(l - 15, 10)), "$description": "Darker primary" },
|
|
229
|
+
"secondary": { "$value": hslToHex((h + 180) % 360, Math.max(s - 25, 10), 50), "$description": "Complementary secondary" },
|
|
230
|
+
"accent": { "$value": hslToHex((h + 30) % 360, Math.min(s, 90), 55), "$description": "Analogous accent" },
|
|
231
|
+
"background": { "$value": hslToHex(h, Math.max(s * 0.08, 2), 98), "$description": "Page background" },
|
|
232
|
+
"background-offset": { "$value": hslToHex(h, Math.max(s * 0.1, 3), 96), "$description": "Offset background" },
|
|
233
|
+
"surface": { "$value": hslToHex(h, Math.max(s * 0.06, 2), 100), "$description": "Card/surface background" },
|
|
234
|
+
"surface-elevated": { "$value": "#FFFFFF", "$description": "Elevated surface" },
|
|
235
|
+
"border": { "$value": hslToHex(h, Math.max(s * 0.1, 5), 88), "$description": "Default border" },
|
|
236
|
+
"border-light": { "$value": hslToHex(h, Math.max(s * 0.08, 3), 93), "$description": "Subtle border" },
|
|
237
|
+
"text-primary": { "$value": hslToHex(h, Math.min(s * 0.3, 15), 12), "$description": "Primary text" },
|
|
238
|
+
"text-secondary": { "$value": hslToHex(h, Math.min(s * 0.15, 10), 40), "$description": "Secondary text" },
|
|
239
|
+
"text-tertiary": { "$value": hslToHex(h, Math.min(s * 0.1, 8), 60), "$description": "Tertiary text" },
|
|
240
|
+
"text-inverse": { "$value": "#FFFFFF", "$description": "Inverse (on-primary) text" },
|
|
241
|
+
"success": { "$value": "#22C55E", "$description": "Success state" },
|
|
242
|
+
"error": { "$value": "#EF4444", "$description": "Error state" },
|
|
243
|
+
"warning": { "$value": "#F59E0B", "$description": "Warning state" },
|
|
244
|
+
"info": { "$value": brandHex, "$description": "Info state (matches primary)" }
|
|
245
|
+
};
|
|
246
|
+
var dark = null;
|
|
247
|
+
if (includeDark) {
|
|
248
|
+
dark = {
|
|
249
|
+
"$type": "color",
|
|
250
|
+
"primary": { "$value": hslToHex(h, Math.min(s + 5, 100), Math.min(l + 10, 70)), "$description": "Primary brand color (dark)" },
|
|
251
|
+
"primary-light": { "$value": hslToHex(h, Math.max(s - 5, 0), Math.min(l + 20, 80)), "$description": "Lighter primary (dark)" },
|
|
252
|
+
"primary-dark": { "$value": hslToHex(h, Math.min(s + 10, 100), Math.max(l - 5, 20)), "$description": "Darker primary (dark)" },
|
|
253
|
+
"secondary": { "$value": hslToHex((h + 180) % 360, Math.max(s - 20, 15), 60), "$description": "Complementary secondary (dark)" },
|
|
254
|
+
"accent": { "$value": hslToHex((h + 30) % 360, Math.min(s, 85), 65), "$description": "Analogous accent (dark)" },
|
|
255
|
+
"background": { "$value": hslToHex(h, Math.min(s * 0.2, 12), 8), "$description": "Page background (dark)" },
|
|
256
|
+
"background-offset": { "$value": hslToHex(h, Math.min(s * 0.2, 12), 11), "$description": "Offset background (dark)" },
|
|
257
|
+
"surface": { "$value": hslToHex(h, Math.min(s * 0.15, 10), 14), "$description": "Card/surface (dark)" },
|
|
258
|
+
"surface-elevated": { "$value": hslToHex(h, Math.min(s * 0.15, 10), 18), "$description": "Elevated surface (dark)" },
|
|
259
|
+
"border": { "$value": hslToHex(h, Math.min(s * 0.12, 8), 22), "$description": "Default border (dark)" },
|
|
260
|
+
"border-light": { "$value": hslToHex(h, Math.min(s * 0.1, 6), 18), "$description": "Subtle border (dark)" },
|
|
261
|
+
"text-primary": { "$value": hslToHex(h, Math.max(s * 0.08, 3), 93), "$description": "Primary text (dark)" },
|
|
262
|
+
"text-secondary": { "$value": hslToHex(h, Math.max(s * 0.06, 3), 65), "$description": "Secondary text (dark)" },
|
|
263
|
+
"text-tertiary": { "$value": hslToHex(h, Math.max(s * 0.05, 2), 45), "$description": "Tertiary text (dark)" },
|
|
264
|
+
"text-inverse": { "$value": hslToHex(h, Math.min(s * 0.3, 15), 12), "$description": "Inverse text (dark)" },
|
|
265
|
+
"success": { "$value": "#34D399", "$description": "Success (dark)" },
|
|
266
|
+
"error": { "$value": "#F87171", "$description": "Error (dark)" },
|
|
267
|
+
"warning": { "$value": "#FBBF24", "$description": "Warning (dark)" },
|
|
268
|
+
"info": { "$value": hslToHex(h, Math.min(s + 5, 100), Math.min(l + 10, 70)), "$description": "Info (dark)" }
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return { light, dark };
|
|
272
|
+
}
|
|
273
|
+
// ── Style presets ──────────────────────────────────────────────────
|
|
274
|
+
var STYLE_PRESETS = {
|
|
275
|
+
minimal: {
|
|
276
|
+
defaultColor: "#3B82F6",
|
|
277
|
+
typography: {
|
|
278
|
+
"font-family": { "$type": "fontFamily", "display": { "$value": "\"Inter\", system-ui, sans-serif", "$description": "Display font" }, "body": { "$value": "\"Inter\", system-ui, sans-serif", "$description": "Body font" }, "mono": { "$value": "ui-monospace, \"SF Mono\", monospace", "$description": "Monospace font" } },
|
|
279
|
+
"font-size": { "$type": "dimension", "2xs": { "$value": "11px" }, "xs": { "$value": "12px" }, "sm": { "$value": "14px" }, "base": { "$value": "16px" }, "lg": { "$value": "18px" }, "xl": { "$value": "20px" }, "2xl": { "$value": "24px" }, "3xl": { "$value": "30px" }, "4xl": { "$value": "36px" }, "5xl": { "$value": "48px" } },
|
|
280
|
+
"font-weight": { "$type": "fontWeight", "regular": { "$value": "400" }, "medium": { "$value": "500" }, "semibold": { "$value": "600" }, "bold": { "$value": "700" } },
|
|
281
|
+
"line-height": { "$type": "number", "tight": { "$value": "1.25" }, "normal": { "$value": "1.5" }, "relaxed": { "$value": "1.75" } }
|
|
282
|
+
},
|
|
283
|
+
spacing: { "$type": "dimension", "0": { "$value": "0px" }, "1": { "$value": "4px" }, "2": { "$value": "8px" }, "3": { "$value": "12px" }, "4": { "$value": "16px" }, "5": { "$value": "20px" }, "6": { "$value": "24px" }, "8": { "$value": "32px" }, "10": { "$value": "40px" }, "12": { "$value": "48px" }, "16": { "$value": "64px" }, "20": { "$value": "80px" } },
|
|
284
|
+
radius: { "$type": "dimension", "none": { "$value": "0px" }, "sm": { "$value": "4px" }, "md": { "$value": "6px" }, "lg": { "$value": "8px" }, "xl": { "$value": "12px" }, "2xl": { "$value": "16px" }, "full": { "$value": "9999px" } },
|
|
285
|
+
elevation: { "sm": { "$type": "shadow", "$value": "0 1px 2px rgba(0,0,0,0.05)", "$description": "Subtle" }, "md": { "$type": "shadow", "$value": "0 4px 6px -1px rgba(0,0,0,0.1)", "$description": "Medium" }, "lg": { "$type": "shadow", "$value": "0 10px 15px -3px rgba(0,0,0,0.1)", "$description": "Large" }, "xl": { "$type": "shadow", "$value": "0 20px 25px -5px rgba(0,0,0,0.1)", "$description": "Extra large" } },
|
|
286
|
+
motion: { "duration": { "$type": "duration", "fast": { "$value": "100ms" }, "normal": { "$value": "200ms" }, "slow": { "$value": "400ms" } }, "easing": { "$type": "cubicBezier", "default": { "$value": "cubic-bezier(0.4, 0, 0.2, 1)" }, "in": { "$value": "cubic-bezier(0.4, 0, 1, 1)" }, "out": { "$value": "cubic-bezier(0, 0, 0.2, 1)" }, "bounce": { "$value": "cubic-bezier(0.34, 1.56, 0.64, 1)" } } }
|
|
287
|
+
},
|
|
288
|
+
bold: {
|
|
289
|
+
defaultColor: "#8B5CF6",
|
|
290
|
+
typography: {
|
|
291
|
+
"font-family": { "$type": "fontFamily", "display": { "$value": "\"Plus Jakarta Sans\", system-ui, sans-serif", "$description": "Display font" }, "body": { "$value": "\"Inter\", system-ui, sans-serif", "$description": "Body font" }, "mono": { "$value": "\"JetBrains Mono\", monospace", "$description": "Monospace font" } },
|
|
292
|
+
"font-size": { "$type": "dimension", "2xs": { "$value": "12px" }, "xs": { "$value": "13px" }, "sm": { "$value": "15px" }, "base": { "$value": "17px" }, "lg": { "$value": "20px" }, "xl": { "$value": "24px" }, "2xl": { "$value": "30px" }, "3xl": { "$value": "36px" }, "4xl": { "$value": "48px" }, "5xl": { "$value": "60px" } },
|
|
293
|
+
"font-weight": { "$type": "fontWeight", "regular": { "$value": "400" }, "medium": { "$value": "500" }, "semibold": { "$value": "600" }, "bold": { "$value": "700" }, "extrabold": { "$value": "800" } },
|
|
294
|
+
"line-height": { "$type": "number", "tight": { "$value": "1.2" }, "normal": { "$value": "1.5" }, "relaxed": { "$value": "1.7" } }
|
|
295
|
+
},
|
|
296
|
+
spacing: { "$type": "dimension", "0": { "$value": "0px" }, "1": { "$value": "4px" }, "2": { "$value": "8px" }, "3": { "$value": "12px" }, "4": { "$value": "16px" }, "6": { "$value": "24px" }, "8": { "$value": "32px" }, "10": { "$value": "40px" }, "12": { "$value": "48px" }, "16": { "$value": "64px" }, "20": { "$value": "80px" }, "24": { "$value": "96px" } },
|
|
297
|
+
radius: { "$type": "dimension", "none": { "$value": "0px" }, "sm": { "$value": "8px" }, "md": { "$value": "12px" }, "lg": { "$value": "16px" }, "xl": { "$value": "20px" }, "2xl": { "$value": "24px" }, "full": { "$value": "9999px" } },
|
|
298
|
+
elevation: { "sm": { "$type": "shadow", "$value": "0 2px 4px rgba(0,0,0,0.08)", "$description": "Subtle" }, "md": { "$type": "shadow", "$value": "0 8px 16px -2px rgba(0,0,0,0.12)", "$description": "Medium" }, "lg": { "$type": "shadow", "$value": "0 16px 32px -4px rgba(0,0,0,0.15)", "$description": "Large" }, "xl": { "$type": "shadow", "$value": "0 24px 48px -8px rgba(0,0,0,0.2)", "$description": "Extra large" } },
|
|
299
|
+
motion: { "duration": { "$type": "duration", "fast": { "$value": "150ms" }, "normal": { "$value": "300ms" }, "slow": { "$value": "500ms" } }, "easing": { "$type": "cubicBezier", "default": { "$value": "cubic-bezier(0.16, 1, 0.3, 1)" }, "in": { "$value": "cubic-bezier(0.55, 0, 1, 0.45)" }, "out": { "$value": "cubic-bezier(0, 0.55, 0.45, 1)" }, "bounce": { "$value": "cubic-bezier(0.34, 1.56, 0.64, 1)" } } }
|
|
300
|
+
},
|
|
301
|
+
warm: {
|
|
302
|
+
defaultColor: "#D97706",
|
|
303
|
+
typography: {
|
|
304
|
+
"font-family": { "$type": "fontFamily", "display": { "$value": "\"DM Serif Display\", Georgia, serif", "$description": "Display font" }, "body": { "$value": "\"DM Sans\", system-ui, sans-serif", "$description": "Body font" }, "mono": { "$value": "\"Fira Code\", monospace", "$description": "Monospace font" } },
|
|
305
|
+
"font-size": { "$type": "dimension", "2xs": { "$value": "11px" }, "xs": { "$value": "13px" }, "sm": { "$value": "15px" }, "base": { "$value": "17px" }, "lg": { "$value": "19px" }, "xl": { "$value": "22px" }, "2xl": { "$value": "28px" }, "3xl": { "$value": "34px" }, "4xl": { "$value": "42px" }, "5xl": { "$value": "54px" } },
|
|
306
|
+
"font-weight": { "$type": "fontWeight", "regular": { "$value": "400" }, "medium": { "$value": "500" }, "semibold": { "$value": "600" }, "bold": { "$value": "700" } },
|
|
307
|
+
"line-height": { "$type": "number", "tight": { "$value": "1.3" }, "normal": { "$value": "1.6" }, "relaxed": { "$value": "1.8" } }
|
|
308
|
+
},
|
|
309
|
+
spacing: { "$type": "dimension", "0": { "$value": "0px" }, "1": { "$value": "4px" }, "2": { "$value": "8px" }, "3": { "$value": "12px" }, "4": { "$value": "16px" }, "5": { "$value": "20px" }, "6": { "$value": "24px" }, "8": { "$value": "32px" }, "10": { "$value": "40px" }, "12": { "$value": "48px" }, "16": { "$value": "64px" }, "20": { "$value": "80px" } },
|
|
310
|
+
radius: { "$type": "dimension", "none": { "$value": "0px" }, "sm": { "$value": "6px" }, "md": { "$value": "10px" }, "lg": { "$value": "14px" }, "xl": { "$value": "18px" }, "2xl": { "$value": "24px" }, "full": { "$value": "9999px" } },
|
|
311
|
+
elevation: { "sm": { "$type": "shadow", "$value": "0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)", "$description": "Soft subtle" }, "md": { "$type": "shadow", "$value": "0 4px 12px rgba(0,0,0,0.08)", "$description": "Soft medium" }, "lg": { "$type": "shadow", "$value": "0 12px 24px rgba(0,0,0,0.1)", "$description": "Soft large" }, "xl": { "$type": "shadow", "$value": "0 20px 40px rgba(0,0,0,0.12)", "$description": "Soft extra large" } },
|
|
312
|
+
motion: { "duration": { "$type": "duration", "fast": { "$value": "120ms" }, "normal": { "$value": "250ms" }, "slow": { "$value": "450ms" } }, "easing": { "$type": "cubicBezier", "default": { "$value": "cubic-bezier(0.25, 0.1, 0.25, 1)" }, "in": { "$value": "cubic-bezier(0.42, 0, 1, 1)" }, "out": { "$value": "cubic-bezier(0, 0, 0.58, 1)" }, "bounce": { "$value": "cubic-bezier(0.34, 1.4, 0.64, 1)" } } }
|
|
313
|
+
},
|
|
314
|
+
corporate: {
|
|
315
|
+
defaultColor: "#1E40AF",
|
|
316
|
+
typography: {
|
|
317
|
+
"font-family": { "$type": "fontFamily", "display": { "$value": "\"Inter\", system-ui, sans-serif", "$description": "Display font" }, "body": { "$value": "\"Inter\", system-ui, sans-serif", "$description": "Body font" }, "mono": { "$value": "\"SF Mono\", monospace", "$description": "Monospace font" } },
|
|
318
|
+
"font-size": { "$type": "dimension", "2xs": { "$value": "11px" }, "xs": { "$value": "12px" }, "sm": { "$value": "14px" }, "base": { "$value": "16px" }, "lg": { "$value": "18px" }, "xl": { "$value": "20px" }, "2xl": { "$value": "24px" }, "3xl": { "$value": "28px" }, "4xl": { "$value": "32px" }, "5xl": { "$value": "40px" } },
|
|
319
|
+
"font-weight": { "$type": "fontWeight", "regular": { "$value": "400" }, "medium": { "$value": "500" }, "semibold": { "$value": "600" }, "bold": { "$value": "700" } },
|
|
320
|
+
"line-height": { "$type": "number", "tight": { "$value": "1.25" }, "normal": { "$value": "1.5" }, "relaxed": { "$value": "1.65" } }
|
|
321
|
+
},
|
|
322
|
+
spacing: { "$type": "dimension", "0": { "$value": "0px" }, "1": { "$value": "4px" }, "2": { "$value": "8px" }, "3": { "$value": "12px" }, "4": { "$value": "16px" }, "5": { "$value": "20px" }, "6": { "$value": "24px" }, "8": { "$value": "32px" }, "10": { "$value": "40px" }, "12": { "$value": "48px" }, "16": { "$value": "64px" } },
|
|
323
|
+
radius: { "$type": "dimension", "none": { "$value": "0px" }, "sm": { "$value": "2px" }, "md": { "$value": "4px" }, "lg": { "$value": "6px" }, "xl": { "$value": "8px" }, "2xl": { "$value": "12px" }, "full": { "$value": "9999px" } },
|
|
324
|
+
elevation: { "sm": { "$type": "shadow", "$value": "0 1px 2px rgba(0,0,0,0.06)", "$description": "Crisp subtle" }, "md": { "$type": "shadow", "$value": "0 2px 8px rgba(0,0,0,0.1)", "$description": "Crisp medium" }, "lg": { "$type": "shadow", "$value": "0 4px 16px rgba(0,0,0,0.12)", "$description": "Crisp large" }, "xl": { "$type": "shadow", "$value": "0 8px 24px rgba(0,0,0,0.15)", "$description": "Crisp extra large" } },
|
|
325
|
+
motion: { "duration": { "$type": "duration", "fast": { "$value": "100ms" }, "normal": { "$value": "180ms" }, "slow": { "$value": "350ms" } }, "easing": { "$type": "cubicBezier", "default": { "$value": "cubic-bezier(0.4, 0, 0.2, 1)" }, "in": { "$value": "cubic-bezier(0.4, 0, 1, 1)" }, "out": { "$value": "cubic-bezier(0, 0, 0.2, 1)" }, "bounce": { "$value": "cubic-bezier(0.25, 1.5, 0.5, 1)" } } }
|
|
326
|
+
},
|
|
327
|
+
playful: {
|
|
328
|
+
defaultColor: "#EC4899",
|
|
329
|
+
typography: {
|
|
330
|
+
"font-family": { "$type": "fontFamily", "display": { "$value": "\"Nunito\", system-ui, sans-serif", "$description": "Display font" }, "body": { "$value": "\"Nunito Sans\", system-ui, sans-serif", "$description": "Body font" }, "mono": { "$value": "\"Fira Code\", monospace", "$description": "Monospace font" } },
|
|
331
|
+
"font-size": { "$type": "dimension", "2xs": { "$value": "12px" }, "xs": { "$value": "13px" }, "sm": { "$value": "15px" }, "base": { "$value": "17px" }, "lg": { "$value": "20px" }, "xl": { "$value": "24px" }, "2xl": { "$value": "32px" }, "3xl": { "$value": "40px" }, "4xl": { "$value": "52px" }, "5xl": { "$value": "64px" } },
|
|
332
|
+
"font-weight": { "$type": "fontWeight", "regular": { "$value": "400" }, "medium": { "$value": "500" }, "semibold": { "$value": "600" }, "bold": { "$value": "700" }, "extrabold": { "$value": "800" } },
|
|
333
|
+
"line-height": { "$type": "number", "tight": { "$value": "1.2" }, "normal": { "$value": "1.55" }, "relaxed": { "$value": "1.75" } }
|
|
334
|
+
},
|
|
335
|
+
spacing: { "$type": "dimension", "0": { "$value": "0px" }, "1": { "$value": "4px" }, "2": { "$value": "8px" }, "3": { "$value": "12px" }, "4": { "$value": "16px" }, "6": { "$value": "24px" }, "8": { "$value": "32px" }, "10": { "$value": "40px" }, "12": { "$value": "48px" }, "16": { "$value": "64px" }, "20": { "$value": "80px" }, "24": { "$value": "96px" } },
|
|
336
|
+
radius: { "$type": "dimension", "none": { "$value": "0px" }, "sm": { "$value": "10px" }, "md": { "$value": "16px" }, "lg": { "$value": "20px" }, "xl": { "$value": "24px" }, "2xl": { "$value": "32px" }, "full": { "$value": "9999px" } },
|
|
337
|
+
elevation: { "sm": { "$type": "shadow", "$value": "0 2px 8px rgba(0,0,0,0.06)", "$description": "Playful subtle" }, "md": { "$type": "shadow", "$value": "0 8px 24px rgba(0,0,0,0.1)", "$description": "Playful medium" }, "lg": { "$type": "shadow", "$value": "0 16px 40px rgba(0,0,0,0.12)", "$description": "Playful large" }, "xl": { "$type": "shadow", "$value": "0 24px 48px rgba(0,0,0,0.16)", "$description": "Playful extra large" } },
|
|
338
|
+
motion: { "duration": { "$type": "duration", "fast": { "$value": "150ms" }, "normal": { "$value": "350ms" }, "slow": { "$value": "600ms" } }, "easing": { "$type": "cubicBezier", "default": { "$value": "cubic-bezier(0.34, 1.56, 0.64, 1)" }, "in": { "$value": "cubic-bezier(0.55, 0, 1, 0.45)" }, "out": { "$value": "cubic-bezier(0, 0.55, 0.45, 1)" }, "bounce": { "$value": "cubic-bezier(0.175, 0.885, 0.32, 1.275)" } } }
|
|
339
|
+
},
|
|
340
|
+
dark: {
|
|
341
|
+
defaultColor: "#00BFFF",
|
|
342
|
+
typography: {
|
|
343
|
+
"font-family": { "$type": "fontFamily", "display": { "$value": "\"Inter\", system-ui, sans-serif", "$description": "Display font" }, "body": { "$value": "\"Inter\", system-ui, sans-serif", "$description": "Body font" }, "mono": { "$value": "\"Cascadia Code\", ui-monospace, monospace", "$description": "Monospace font" } },
|
|
344
|
+
"font-size": { "$type": "dimension", "2xs": { "$value": "11px" }, "xs": { "$value": "12px" }, "sm": { "$value": "14px" }, "base": { "$value": "16px" }, "lg": { "$value": "18px" }, "xl": { "$value": "20px" }, "2xl": { "$value": "24px" }, "3xl": { "$value": "30px" }, "4xl": { "$value": "36px" }, "5xl": { "$value": "48px" } },
|
|
345
|
+
"font-weight": { "$type": "fontWeight", "regular": { "$value": "400" }, "medium": { "$value": "500" }, "semibold": { "$value": "600" }, "bold": { "$value": "700" } },
|
|
346
|
+
"line-height": { "$type": "number", "tight": { "$value": "1.25" }, "normal": { "$value": "1.5" }, "relaxed": { "$value": "1.75" } }
|
|
347
|
+
},
|
|
348
|
+
spacing: { "$type": "dimension", "0": { "$value": "0px" }, "1": { "$value": "4px" }, "2": { "$value": "8px" }, "3": { "$value": "12px" }, "4": { "$value": "16px" }, "5": { "$value": "20px" }, "6": { "$value": "24px" }, "8": { "$value": "32px" }, "10": { "$value": "40px" }, "12": { "$value": "48px" }, "16": { "$value": "64px" }, "20": { "$value": "80px" } },
|
|
349
|
+
radius: { "$type": "dimension", "none": { "$value": "0px" }, "sm": { "$value": "6px" }, "md": { "$value": "8px" }, "lg": { "$value": "12px" }, "xl": { "$value": "16px" }, "2xl": { "$value": "20px" }, "full": { "$value": "9999px" } },
|
|
350
|
+
elevation: { "sm": { "$type": "shadow", "$value": "0 0 8px rgba(0,191,255,0.06), 0 2px 4px rgba(0,0,0,0.2)", "$description": "Glow subtle" }, "md": { "$type": "shadow", "$value": "0 0 16px rgba(0,191,255,0.08), 0 4px 12px rgba(0,0,0,0.25)", "$description": "Glow medium" }, "lg": { "$type": "shadow", "$value": "0 0 24px rgba(0,191,255,0.1), 0 8px 24px rgba(0,0,0,0.3)", "$description": "Glow large" }, "xl": { "$type": "shadow", "$value": "0 0 40px rgba(0,191,255,0.12), 0 16px 40px rgba(0,0,0,0.35)", "$description": "Glow extra large" } },
|
|
351
|
+
motion: { "duration": { "$type": "duration", "fast": { "$value": "100ms" }, "normal": { "$value": "250ms" }, "slow": { "$value": "450ms" } }, "easing": { "$type": "cubicBezier", "default": { "$value": "cubic-bezier(0.16, 1, 0.3, 1)" }, "in": { "$value": "cubic-bezier(0.4, 0, 1, 1)" }, "out": { "$value": "cubic-bezier(0, 0, 0.2, 1)" }, "bounce": { "$value": "cubic-bezier(0.34, 1.56, 0.64, 1)" } } }
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
function generateTokenSet(opts) {
|
|
355
|
+
var styleName = opts.style || "minimal";
|
|
356
|
+
var preset = STYLE_PRESETS[styleName] || STYLE_PRESETS.minimal;
|
|
357
|
+
var includeDark = opts.dark_mode !== false;
|
|
358
|
+
var brandColor = opts.brand_color || preset.defaultColor;
|
|
359
|
+
// Start from base system or preset
|
|
360
|
+
var tokens;
|
|
361
|
+
if (opts.base_system) {
|
|
362
|
+
var base = loadSystem(opts.base_system);
|
|
363
|
+
if (!base) {
|
|
364
|
+
tokens = {};
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
tokens = JSON.parse(JSON.stringify(base)); // deep clone
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
tokens = {};
|
|
372
|
+
}
|
|
373
|
+
tokens["$name"] = opts.name;
|
|
374
|
+
tokens["$description"] = "Design system for " + opts.name + " — generated by Raven MCP";
|
|
375
|
+
// Generate color palette from brand color
|
|
376
|
+
var palette = generatePalette(brandColor, includeDark);
|
|
377
|
+
tokens["color"] = palette.light;
|
|
378
|
+
if (palette.dark)
|
|
379
|
+
tokens["color-dark"] = palette.dark;
|
|
380
|
+
// Apply preset for missing groups
|
|
381
|
+
if (!tokens["typography"])
|
|
382
|
+
tokens["typography"] = preset.typography;
|
|
383
|
+
if (!tokens["spacing"])
|
|
384
|
+
tokens["spacing"] = preset.spacing;
|
|
385
|
+
if (!tokens["radius"])
|
|
386
|
+
tokens["radius"] = preset.radius;
|
|
387
|
+
if (!tokens["elevation"])
|
|
388
|
+
tokens["elevation"] = preset.elevation;
|
|
389
|
+
if (!tokens["motion"])
|
|
390
|
+
tokens["motion"] = preset.motion;
|
|
391
|
+
return tokens;
|
|
392
|
+
}
|
|
128
393
|
// ── Search and matching helpers ─────────────────────────────────────
|
|
129
394
|
function matchesTags(tags, query) {
|
|
130
395
|
var terms = query.toLowerCase().split(/[\s,]+/).filter(Boolean);
|
|
@@ -616,6 +881,713 @@ server.tool("compose_system", "Mix tokens from different design systems to creat
|
|
|
616
881
|
}]
|
|
617
882
|
};
|
|
618
883
|
});
|
|
884
|
+
// ── Tool 11: audit_page ────────────────────────────────────────────
|
|
885
|
+
server.tool("audit_page", "Audit HTML/CSS against Raven's design quality standards. Checks typography (min 13px, weight 400+), accessibility (WCAG touch targets, alt text, contrast), responsive patterns (flexbox over grid, clamp sizing, max-width containers), and style guide compliance (CSS custom properties, no bare hex). Returns pass/fail per check with specific fix instructions.", {
|
|
886
|
+
html: z.string().describe("The full HTML content of the page to audit"),
|
|
887
|
+
strict: z.boolean().optional().describe("Strict mode — also flags warnings as failures. Default: false")
|
|
888
|
+
}, async ({ html, strict }) => {
|
|
889
|
+
var issues = [];
|
|
890
|
+
var passes = [];
|
|
891
|
+
var isStrict = strict || false;
|
|
892
|
+
// ── Structure checks
|
|
893
|
+
if (/<html[^>]*lang=/.test(html))
|
|
894
|
+
passes.push("html[lang] attribute present");
|
|
895
|
+
else
|
|
896
|
+
issues.push({ severity: "error", rule: "structure/lang", message: "Missing lang attribute on <html>", fix: "Add lang=\"en\" to the <html> tag" });
|
|
897
|
+
if (/<meta[^>]*viewport/.test(html))
|
|
898
|
+
passes.push("viewport meta tag present");
|
|
899
|
+
else
|
|
900
|
+
issues.push({ severity: "error", rule: "structure/viewport", message: "Missing viewport meta tag", fix: "Add <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">" });
|
|
901
|
+
if (/<title>[^<]+<\/title>/.test(html))
|
|
902
|
+
passes.push("title tag present with content");
|
|
903
|
+
else
|
|
904
|
+
issues.push({ severity: "error", rule: "structure/title", message: "Missing or empty <title> tag", fix: "Add a descriptive <title> element in <head>" });
|
|
905
|
+
// ── Typography checks
|
|
906
|
+
var fontSizeMatches = html.match(/font-size\s*:\s*(\d+(?:\.\d+)?)\s*px/g) || [];
|
|
907
|
+
var tooSmall = fontSizeMatches.filter(function (m) {
|
|
908
|
+
var num = parseFloat(m.replace(/font-size\s*:\s*/, "").replace(/\s*px/, ""));
|
|
909
|
+
return num < 13 && num > 0;
|
|
910
|
+
});
|
|
911
|
+
if (tooSmall.length === 0)
|
|
912
|
+
passes.push("All font sizes >= 13px");
|
|
913
|
+
else
|
|
914
|
+
issues.push({ severity: "error", rule: "typography/min-size", message: "Found " + tooSmall.length + " font-size declarations below 13px: " + tooSmall.join(", "), fix: "Increase all font sizes to minimum 13px per Nielsen Norman standards" });
|
|
915
|
+
var fontWeightMatches = html.match(/font-weight\s*:\s*(\d+)/g) || [];
|
|
916
|
+
var tooThin = fontWeightMatches.filter(function (m) {
|
|
917
|
+
var num = parseInt(m.replace(/font-weight\s*:\s*/, ""));
|
|
918
|
+
return num < 400 && num > 0;
|
|
919
|
+
});
|
|
920
|
+
if (tooThin.length === 0)
|
|
921
|
+
passes.push("All font weights >= 400");
|
|
922
|
+
else
|
|
923
|
+
issues.push({ severity: "error", rule: "typography/min-weight", message: "Found " + tooThin.length + " font-weight declarations below 400: " + tooThin.join(", "), fix: "Use font-weight 400+ for all text. 300 is too thin for screen readability" });
|
|
924
|
+
// ── Accessibility checks
|
|
925
|
+
var imgTags = html.match(/<img\b[^>]*>/g) || [];
|
|
926
|
+
var missingAlt = imgTags.filter(function (t) { return !/alt\s*=/.test(t); });
|
|
927
|
+
if (missingAlt.length === 0)
|
|
928
|
+
passes.push("All images have alt attributes");
|
|
929
|
+
else
|
|
930
|
+
issues.push({ severity: "error", rule: "a11y/img-alt", message: missingAlt.length + " <img> tags missing alt attribute", fix: "Add descriptive alt text to all images. Use alt=\"\" for decorative images" });
|
|
931
|
+
// ── Responsive checks
|
|
932
|
+
var hasFlexWrap = /flex-wrap\s*:\s*wrap/.test(html);
|
|
933
|
+
if (hasFlexWrap)
|
|
934
|
+
passes.push("Uses flex-wrap for fluid layout");
|
|
935
|
+
else
|
|
936
|
+
issues.push({ severity: "warning", rule: "responsive/flex-wrap", message: "No flex-wrap detected. Cards and grids should use display:flex; flex-wrap:wrap with min-width on children", fix: "Replace grid-template-columns with display:flex; flex-wrap:wrap and flex:1 1 280px; min-width:280px on children" });
|
|
937
|
+
var gridInMedia = html.match(/@media[\s\S]*?grid-template-columns/g) || [];
|
|
938
|
+
if (gridInMedia.length === 0)
|
|
939
|
+
passes.push("No grid-template-columns in media queries");
|
|
940
|
+
else
|
|
941
|
+
issues.push({ severity: "warning", rule: "responsive/no-grid-breakpoints", message: gridInMedia.length + " grid-template-columns overrides found in media queries", fix: "Remove grid-template-columns from media queries. Use flexbox with min-width instead — it wraps naturally" });
|
|
942
|
+
var hasClamp = /clamp\s*\(/.test(html);
|
|
943
|
+
if (hasClamp)
|
|
944
|
+
passes.push("Uses clamp() for fluid sizing");
|
|
945
|
+
else
|
|
946
|
+
issues.push({ severity: "warning", rule: "responsive/clamp", message: "No clamp() detected for fluid sizing", fix: "Use clamp(48px, 8vw, 128px) for section padding and clamp(16px, 4vw, 24px) for container padding" });
|
|
947
|
+
var hasMaxWidth = /max-width\s*:\s*(1[12]\d{2}|1200)\s*px/.test(html);
|
|
948
|
+
if (hasMaxWidth)
|
|
949
|
+
passes.push("Content has max-width constraint");
|
|
950
|
+
else
|
|
951
|
+
issues.push({ severity: "warning", rule: "responsive/max-width", message: "No 1200px max-width constraint detected on content containers", fix: "Add max-width: 1200px; margin: 0 auto to content containers" });
|
|
952
|
+
// ── Style guide checks
|
|
953
|
+
var hasCustomProps = /var\s*\(\s*--/.test(html);
|
|
954
|
+
if (hasCustomProps)
|
|
955
|
+
passes.push("Uses CSS custom properties");
|
|
956
|
+
else
|
|
957
|
+
issues.push({ severity: "warning", rule: "tokens/custom-properties", message: "No CSS custom properties (var(--xxx)) detected", fix: "Use CSS custom properties for all colors, spacing, and typography values" });
|
|
958
|
+
var styleBlocks = html.match(/<style[^>]*>([\s\S]*?)<\/style>/g) || [];
|
|
959
|
+
var bareHexCount = 0;
|
|
960
|
+
for (var block of styleBlocks) {
|
|
961
|
+
var cssLines = block.split("\n");
|
|
962
|
+
for (var cssLine of cssLines) {
|
|
963
|
+
if (/^\s*--/.test(cssLine) || /^\s*\/[/*]/.test(cssLine))
|
|
964
|
+
continue;
|
|
965
|
+
if (/var\s*\(/.test(cssLine))
|
|
966
|
+
continue;
|
|
967
|
+
if (/stroke|fill/.test(cssLine))
|
|
968
|
+
continue;
|
|
969
|
+
var hexMatches = cssLine.match(/#[0-9a-fA-F]{3,8}(?![-\w])/g) || [];
|
|
970
|
+
bareHexCount += hexMatches.length;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (bareHexCount <= 5)
|
|
974
|
+
passes.push("Minimal bare hex colors (" + bareHexCount + ")");
|
|
975
|
+
else
|
|
976
|
+
issues.push({ severity: "warning", rule: "tokens/no-bare-hex", message: bareHexCount + " bare hex color values found outside custom property definitions", fix: "Define colors as --color-name: #hex in :root, then use var(--color-name) throughout" });
|
|
977
|
+
// ── Touch target check
|
|
978
|
+
var btnPadding = html.match(/\.btn[^{]*\{[^}]*padding\s*:\s*(\d+)px/g) || [];
|
|
979
|
+
var smallButtons = btnPadding.filter(function (m) {
|
|
980
|
+
var match = m.match(/padding\s*:\s*(\d+)px/);
|
|
981
|
+
return match && parseInt(match[1]) < 10;
|
|
982
|
+
});
|
|
983
|
+
if (smallButtons.length === 0)
|
|
984
|
+
passes.push("Button padding adequate for touch targets");
|
|
985
|
+
else
|
|
986
|
+
issues.push({ severity: "error", rule: "a11y/touch-target", message: "Button padding too small for 44px WCAG touch targets", fix: "Use minimum padding: 12px 24px on all buttons" });
|
|
987
|
+
var errors = issues.filter(function (i) { return i.severity === "error"; });
|
|
988
|
+
var warnings = issues.filter(function (i) { return i.severity === "warning"; });
|
|
989
|
+
var totalChecks = passes.length + issues.length;
|
|
990
|
+
var failCount = isStrict ? issues.length : errors.length;
|
|
991
|
+
var result = {
|
|
992
|
+
score: Math.round(((totalChecks - failCount) / totalChecks) * 100),
|
|
993
|
+
grade: failCount === 0 ? "A" : failCount <= 2 ? "B" : failCount <= 4 ? "C" : "D",
|
|
994
|
+
summary: passes.length + "/" + totalChecks + " checks passed" + (failCount > 0 ? " — " + failCount + " issues to fix" : " — all clear"),
|
|
995
|
+
passes: passes,
|
|
996
|
+
errors: errors,
|
|
997
|
+
warnings: isStrict ? warnings.map(function (w) { return Object.assign({}, w, { severity: "error" }); }) : warnings,
|
|
998
|
+
fix_priority: errors.concat(warnings).map(function (i) { return i.rule + ": " + i.fix; })
|
|
999
|
+
};
|
|
1000
|
+
return {
|
|
1001
|
+
content: [{
|
|
1002
|
+
type: "text",
|
|
1003
|
+
text: JSON.stringify(result, null, 2)
|
|
1004
|
+
}]
|
|
1005
|
+
};
|
|
1006
|
+
});
|
|
1007
|
+
// ── Tool 12: get_brand_system ──────────────────────────────────────
|
|
1008
|
+
server.tool("get_brand_system", "Get a complete design system for building an app with branding like a specific company. Say 'Make me an app with branding like Spotify' and get the full token set, style guide, and implementation instructions. Matches against 12 known design systems and provides closest match with ready-to-use CSS.", {
|
|
1009
|
+
company: z.string().describe("The company whose branding to use (e.g. 'Spotify', 'Stripe', 'Apple', 'Linear', 'Airbnb')"),
|
|
1010
|
+
format: z.enum(["css", "dtcg", "guide"]).optional().describe("Output format: 'css' for CSS variables, 'dtcg' for W3C tokens, 'guide' for full implementation guide. Default: guide"),
|
|
1011
|
+
mode: z.enum(["light", "dark"]).optional().describe("Color mode preference. Default: based on the system's primary mode")
|
|
1012
|
+
}, async ({ company, format, mode }) => {
|
|
1013
|
+
var registry = loadRegistry();
|
|
1014
|
+
var fmt = format || "guide";
|
|
1015
|
+
var searchTerm = company.toLowerCase().trim();
|
|
1016
|
+
// Direct match first
|
|
1017
|
+
var matchedSystem = null;
|
|
1018
|
+
for (var sys of registry.systems) {
|
|
1019
|
+
if (sys.id === searchTerm ||
|
|
1020
|
+
sys.name.toLowerCase() === searchTerm ||
|
|
1021
|
+
sys.name.toLowerCase().replace(/[^a-z0-9]/g, "") === searchTerm.replace(/[^a-z0-9]/g, "")) {
|
|
1022
|
+
matchedSystem = sys;
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
// Fuzzy match by tags and description
|
|
1027
|
+
if (!matchedSystem) {
|
|
1028
|
+
var bestScore = 0;
|
|
1029
|
+
for (var s of registry.systems) {
|
|
1030
|
+
var score = 0;
|
|
1031
|
+
var haystack = (s.name + " " + s.description + " " + (s.tags || []).join(" ")).toLowerCase();
|
|
1032
|
+
var terms = searchTerm.split(/\s+/);
|
|
1033
|
+
for (var term of terms) {
|
|
1034
|
+
if (haystack.includes(term))
|
|
1035
|
+
score += 2;
|
|
1036
|
+
}
|
|
1037
|
+
if (s.category && s.category.toLowerCase().includes(searchTerm))
|
|
1038
|
+
score += 3;
|
|
1039
|
+
for (var tag of (s.tags || [])) {
|
|
1040
|
+
if (tag.includes(searchTerm) || searchTerm.includes(tag))
|
|
1041
|
+
score += 1;
|
|
1042
|
+
}
|
|
1043
|
+
if (score > bestScore) {
|
|
1044
|
+
bestScore = score;
|
|
1045
|
+
matchedSystem = s;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
if (bestScore === 0)
|
|
1049
|
+
matchedSystem = null;
|
|
1050
|
+
}
|
|
1051
|
+
if (!matchedSystem) {
|
|
1052
|
+
var available = registry.systems.map(function (s) { return s.name; });
|
|
1053
|
+
return {
|
|
1054
|
+
content: [{
|
|
1055
|
+
type: "text",
|
|
1056
|
+
text: JSON.stringify({
|
|
1057
|
+
error: "No matching design system found for '" + company + "'",
|
|
1058
|
+
suggestion: "Try one of the available systems, or describe the aesthetic you want (e.g. 'dark minimal developer tool', 'warm consumer marketplace')",
|
|
1059
|
+
available_systems: available,
|
|
1060
|
+
tip: "You can also use compose_system to mix tokens from multiple systems — e.g. Linear's colors + Stripe's typography"
|
|
1061
|
+
}, null, 2)
|
|
1062
|
+
}]
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
var tokens = loadSystem(matchedSystem.id);
|
|
1066
|
+
if (!tokens) {
|
|
1067
|
+
return {
|
|
1068
|
+
content: [{
|
|
1069
|
+
type: "text",
|
|
1070
|
+
text: "System matched (" + matchedSystem.name + ") but token file not found."
|
|
1071
|
+
}]
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
// If dark mode requested and dark tokens exist, merge them
|
|
1075
|
+
if (mode === "dark" && tokens["color-dark"]) {
|
|
1076
|
+
var darkColors = tokens["color-dark"];
|
|
1077
|
+
for (var dk of Object.keys(darkColors)) {
|
|
1078
|
+
if (!dk.startsWith("$")) {
|
|
1079
|
+
tokens.color[dk] = darkColors[dk];
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
if (fmt === "css") {
|
|
1084
|
+
var css = tokensToCSSByGroup(tokens, matchedSystem.id);
|
|
1085
|
+
return {
|
|
1086
|
+
content: [{
|
|
1087
|
+
type: "text",
|
|
1088
|
+
text: css
|
|
1089
|
+
}]
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
if (fmt === "dtcg") {
|
|
1093
|
+
return {
|
|
1094
|
+
content: [{
|
|
1095
|
+
type: "text",
|
|
1096
|
+
text: JSON.stringify(tokens, null, 2)
|
|
1097
|
+
}]
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
// Full implementation guide
|
|
1101
|
+
var flat = flattenTokens(tokens, "");
|
|
1102
|
+
var primaryColor = flat.find(function (t) { return t.path === "color.primary"; });
|
|
1103
|
+
var bgColor = flat.find(function (t) { return t.path === "color.background"; });
|
|
1104
|
+
var fontDisplay = flat.find(function (t) { return t.path === "typography.font-family.display"; });
|
|
1105
|
+
var fontBody = flat.find(function (t) { return t.path === "typography.font-family.body"; });
|
|
1106
|
+
var radiusBase = flat.find(function (t) { return t.path.includes("radius") && t.path.includes("base"); });
|
|
1107
|
+
var isDark = false;
|
|
1108
|
+
if (bgColor) {
|
|
1109
|
+
var hex = String(bgColor.value).replace("#", "");
|
|
1110
|
+
if (hex.length >= 6) {
|
|
1111
|
+
var r = parseInt(hex.substring(0, 2), 16);
|
|
1112
|
+
var g = parseInt(hex.substring(2, 4), 16);
|
|
1113
|
+
var b = parseInt(hex.substring(4, 6), 16);
|
|
1114
|
+
isDark = (r + g + b) / 3 < 128;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
var guide = {
|
|
1118
|
+
brand: matchedSystem.name,
|
|
1119
|
+
description: matchedSystem.description,
|
|
1120
|
+
category: matchedSystem.category,
|
|
1121
|
+
tags: matchedSystem.tags,
|
|
1122
|
+
aesthetic_summary: "Build with " + matchedSystem.name + "'s design language: " + (matchedSystem.tags || []).join(", ") + ".",
|
|
1123
|
+
quick_start: {
|
|
1124
|
+
primary_color: primaryColor ? primaryColor.value : "See tokens",
|
|
1125
|
+
background: bgColor ? bgColor.value : "See tokens",
|
|
1126
|
+
font_display: fontDisplay ? fontDisplay.value : "system-ui",
|
|
1127
|
+
font_body: fontBody ? fontBody.value : "system-ui",
|
|
1128
|
+
border_radius: radiusBase ? radiusBase.value : "See tokens",
|
|
1129
|
+
mode: isDark ? "dark-first" : "light-first"
|
|
1130
|
+
},
|
|
1131
|
+
css_variables: tokensToCSSByGroup(tokens, matchedSystem.id),
|
|
1132
|
+
implementation_rules: [
|
|
1133
|
+
"Use CSS custom properties (var(--" + matchedSystem.id + "-xxx)) for every visual value — no bare hex, px, or font names",
|
|
1134
|
+
"All font sizes minimum 13px, all font weights minimum 400",
|
|
1135
|
+
"Use flexbox with flex-wrap for card/grid layouts — no hard breakpoints",
|
|
1136
|
+
"Use clamp() for fluid padding: clamp(48px, 8vw, 128px) vertical, clamp(16px, 4vw, 24px) horizontal",
|
|
1137
|
+
"Content max-width: 1200px with margin: 0 auto",
|
|
1138
|
+
"All interactive elements: minimum 44px touch target (padding: 12px 24px on buttons)",
|
|
1139
|
+
"All images need alt attributes",
|
|
1140
|
+
"viewport meta tag required",
|
|
1141
|
+
isDark ? "Dark background — ensure text contrast ratio >= 4.5:1 (WCAG AA)" : "Light background — ensure text contrast ratio >= 4.5:1 (WCAG AA)"
|
|
1142
|
+
],
|
|
1143
|
+
responsive_pattern: {
|
|
1144
|
+
layout: "display: flex; flex-wrap: wrap — cards get flex: 1 1 280px; min-width: 280px",
|
|
1145
|
+
padding: "clamp(48px, 8vw, 128px) clamp(16px, 4vw, 24px)",
|
|
1146
|
+
container: "max-width: 1200px; margin: 0 auto",
|
|
1147
|
+
media_queries: "Only for: nav collapse, font scaling, element hiding. Never for grid-template-columns."
|
|
1148
|
+
},
|
|
1149
|
+
token_count: flat.length + " tokens available",
|
|
1150
|
+
groups: Object.keys(tokens).filter(function (k) { return !k.startsWith("$"); })
|
|
1151
|
+
};
|
|
1152
|
+
return {
|
|
1153
|
+
content: [{
|
|
1154
|
+
type: "text",
|
|
1155
|
+
text: JSON.stringify(guide, null, 2)
|
|
1156
|
+
}]
|
|
1157
|
+
};
|
|
1158
|
+
});
|
|
1159
|
+
// ── HTML visual export ─────────────────────────────────────────────
|
|
1160
|
+
function tokensToHTML(tokens, systemName) {
|
|
1161
|
+
var colorTokens = tokens["color"] || {};
|
|
1162
|
+
var darkTokens = tokens["color-dark"] || null;
|
|
1163
|
+
var typo = tokens["typography"] || {};
|
|
1164
|
+
var spacing = tokens["spacing"] || {};
|
|
1165
|
+
var radius = tokens["radius"] || {};
|
|
1166
|
+
var elevation = tokens["elevation"] || {};
|
|
1167
|
+
var motion = tokens["motion"] || {};
|
|
1168
|
+
var systemId = systemName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
1169
|
+
var primaryColor = colorTokens["primary"]?.["$value"] || "#3B82F6";
|
|
1170
|
+
var bgColor = colorTokens["background"]?.["$value"] || "#FAFAFA";
|
|
1171
|
+
var textColor = colorTokens["text-primary"]?.["$value"] || "#111111";
|
|
1172
|
+
var textSecondary = colorTokens["text-secondary"]?.["$value"] || "#666666";
|
|
1173
|
+
var surfaceColor = colorTokens["surface"]?.["$value"] || "#FFFFFF";
|
|
1174
|
+
var borderColor = colorTokens["border"]?.["$value"] || "#E5E5E5";
|
|
1175
|
+
var fontDisplay = typo["font-family"]?.["display"]?.["$value"] || "Inter, system-ui, sans-serif";
|
|
1176
|
+
var fontBody = typo["font-family"]?.["body"]?.["$value"] || "Inter, system-ui, sans-serif";
|
|
1177
|
+
var hasDark = darkTokens !== null;
|
|
1178
|
+
// Build color swatches HTML
|
|
1179
|
+
var colorSwatches = "";
|
|
1180
|
+
for (var ck of Object.keys(colorTokens)) {
|
|
1181
|
+
if (ck.startsWith("$"))
|
|
1182
|
+
continue;
|
|
1183
|
+
var cv = colorTokens[ck];
|
|
1184
|
+
if (!cv || !cv["$value"])
|
|
1185
|
+
continue;
|
|
1186
|
+
var hex = cv["$value"];
|
|
1187
|
+
var crWhite = getContrastRatio(hex, "#FFFFFF");
|
|
1188
|
+
var crBlack = getContrastRatio(hex, "#000000");
|
|
1189
|
+
var gradeWhite = contrastGrade(crWhite);
|
|
1190
|
+
var gradeBlack = contrastGrade(crBlack);
|
|
1191
|
+
var textOnSwatch = crWhite > crBlack ? "#FFFFFF" : "#000000";
|
|
1192
|
+
colorSwatches += '<div class="swatch"><div class="swatch-fill" style="background:' + hex + ';color:' + textOnSwatch + '"><span class="swatch-hex">' + hex + '</span></div><div class="swatch-info"><div class="swatch-name">' + ck + '</div><div class="swatch-var">--' + systemId + '-color-' + ck + '</div><div class="swatch-contrast"><span class="badge badge-' + gradeWhite.toLowerCase().replace("-", "") + '">⬜ ' + crWhite + ':1 ' + gradeWhite + '</span><span class="badge badge-' + gradeBlack.toLowerCase().replace("-", "") + '">⬛ ' + crBlack + ':1 ' + gradeBlack + '</span></div></div></div>';
|
|
1193
|
+
}
|
|
1194
|
+
// Dark color swatches
|
|
1195
|
+
var darkSwatches = "";
|
|
1196
|
+
if (hasDark) {
|
|
1197
|
+
for (var dk of Object.keys(darkTokens)) {
|
|
1198
|
+
if (dk.startsWith("$"))
|
|
1199
|
+
continue;
|
|
1200
|
+
var dv = darkTokens[dk];
|
|
1201
|
+
if (!dv || !dv["$value"])
|
|
1202
|
+
continue;
|
|
1203
|
+
var dhex = dv["$value"];
|
|
1204
|
+
var dcrW = getContrastRatio(dhex, "#FFFFFF");
|
|
1205
|
+
var dcrB = getContrastRatio(dhex, "#000000");
|
|
1206
|
+
var dgW = contrastGrade(dcrW);
|
|
1207
|
+
var dgB = contrastGrade(dcrB);
|
|
1208
|
+
var dtxt = dcrW > dcrB ? "#FFFFFF" : "#000000";
|
|
1209
|
+
darkSwatches += '<div class="swatch"><div class="swatch-fill" style="background:' + dhex + ';color:' + dtxt + '"><span class="swatch-hex">' + dhex + '</span></div><div class="swatch-info"><div class="swatch-name">' + dk + '</div><div class="swatch-var">--' + systemId + '-color-dark-' + dk + '</div><div class="swatch-contrast"><span class="badge badge-' + dgW.toLowerCase().replace("-", "") + '">⬜ ' + dcrW + ':1 ' + dgW + '</span><span class="badge badge-' + dgB.toLowerCase().replace("-", "") + '">⬛ ' + dcrB + ':1 ' + dgB + '</span></div></div></div>';
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
// Typography specimens
|
|
1213
|
+
var typoHTML = "";
|
|
1214
|
+
var fontSizes = typo["font-size"] || {};
|
|
1215
|
+
for (var tk of Object.keys(fontSizes)) {
|
|
1216
|
+
if (tk.startsWith("$"))
|
|
1217
|
+
continue;
|
|
1218
|
+
var tv = fontSizes[tk];
|
|
1219
|
+
if (!tv || !tv["$value"])
|
|
1220
|
+
continue;
|
|
1221
|
+
typoHTML += '<div class="type-specimen"><div class="type-sample" style="font-size:' + tv["$value"] + ';font-family:' + fontDisplay + '">The quick brown fox</div><div class="type-meta"><span class="type-name">' + tk + '</span><span class="type-value">' + tv["$value"] + '</span><span class="type-var">--' + systemId + '-typography-font-size-' + tk + '</span></div></div>';
|
|
1222
|
+
}
|
|
1223
|
+
// Font weight specimens
|
|
1224
|
+
var weightHTML = "";
|
|
1225
|
+
var fontWeights = typo["font-weight"] || {};
|
|
1226
|
+
for (var wk of Object.keys(fontWeights)) {
|
|
1227
|
+
if (wk.startsWith("$"))
|
|
1228
|
+
continue;
|
|
1229
|
+
var wv = fontWeights[wk];
|
|
1230
|
+
if (!wv || !wv["$value"])
|
|
1231
|
+
continue;
|
|
1232
|
+
weightHTML += '<div class="weight-specimen" style="font-weight:' + wv["$value"] + ';font-family:' + fontBody + '">' + wk + ' (' + wv["$value"] + ')</div>';
|
|
1233
|
+
}
|
|
1234
|
+
// Spacing scale
|
|
1235
|
+
var spacingHTML = "";
|
|
1236
|
+
for (var sk of Object.keys(spacing)) {
|
|
1237
|
+
if (sk.startsWith("$"))
|
|
1238
|
+
continue;
|
|
1239
|
+
var sv = spacing[sk];
|
|
1240
|
+
if (!sv || !sv["$value"])
|
|
1241
|
+
continue;
|
|
1242
|
+
var px = parseInt(sv["$value"]);
|
|
1243
|
+
spacingHTML += '<div class="space-row"><div class="space-label">' + sk + '</div><div class="space-bar" style="width:' + Math.min(px, 400) + 'px"></div><div class="space-value">' + sv["$value"] + '</div></div>';
|
|
1244
|
+
}
|
|
1245
|
+
// Radius preview
|
|
1246
|
+
var radiusHTML = "";
|
|
1247
|
+
for (var rk of Object.keys(radius)) {
|
|
1248
|
+
if (rk.startsWith("$"))
|
|
1249
|
+
continue;
|
|
1250
|
+
var rv = radius[rk];
|
|
1251
|
+
if (!rv || !rv["$value"])
|
|
1252
|
+
continue;
|
|
1253
|
+
radiusHTML += '<div class="radius-item"><div class="radius-box" style="border-radius:' + rv["$value"] + '"></div><div class="radius-label">' + rk + '<br><span>' + rv["$value"] + '</span></div></div>';
|
|
1254
|
+
}
|
|
1255
|
+
// Elevation preview
|
|
1256
|
+
var elevationHTML = "";
|
|
1257
|
+
for (var ek of Object.keys(elevation)) {
|
|
1258
|
+
if (ek.startsWith("$"))
|
|
1259
|
+
continue;
|
|
1260
|
+
var ev = elevation[ek];
|
|
1261
|
+
if (!ev)
|
|
1262
|
+
continue;
|
|
1263
|
+
var shadowVal = ev["$value"] || "";
|
|
1264
|
+
elevationHTML += '<div class="elevation-card" style="box-shadow:' + shadowVal + '"><div class="elevation-label">' + ek + '</div></div>';
|
|
1265
|
+
}
|
|
1266
|
+
// Motion preview
|
|
1267
|
+
var motionHTML = "";
|
|
1268
|
+
var durations = motion["duration"] || {};
|
|
1269
|
+
var easings = motion["easing"] || {};
|
|
1270
|
+
for (var mk of Object.keys(easings)) {
|
|
1271
|
+
if (mk.startsWith("$"))
|
|
1272
|
+
continue;
|
|
1273
|
+
var mv = easings[mk];
|
|
1274
|
+
if (!mv || !mv["$value"])
|
|
1275
|
+
continue;
|
|
1276
|
+
var dur = durations["normal"]?.["$value"] || "300ms";
|
|
1277
|
+
motionHTML += '<div class="motion-row"><div class="motion-label">' + mk + '<br><span>' + mv["$value"] + '</span></div><div class="motion-track"><div class="motion-dot" style="transition:transform ' + dur + ' ' + mv["$value"] + '"></div></div></div>';
|
|
1278
|
+
}
|
|
1279
|
+
// CSS variables block
|
|
1280
|
+
var cssBlock = tokensToCSSByGroup(tokens, systemId).replace(/</g, "<").replace(/>/g, ">");
|
|
1281
|
+
var html = '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>' + systemName + ' Design System</title><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"><style>';
|
|
1282
|
+
html += '*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}';
|
|
1283
|
+
html += 'html{scroll-behavior:smooth;overflow-x:hidden}';
|
|
1284
|
+
html += 'body{font-family:' + fontBody + ';font-size:16px;line-height:1.6;color:' + textColor + ';background:' + bgColor + '}';
|
|
1285
|
+
html += '.container{max-width:1100px;margin:0 auto;padding:0 clamp(16px,4vw,32px)}';
|
|
1286
|
+
// Header
|
|
1287
|
+
html += '.header{padding:clamp(48px,8vw,96px) 0 clamp(32px,4vw,48px);border-bottom:1px solid ' + borderColor + '}';
|
|
1288
|
+
html += '.header h1{font-family:' + fontDisplay + ';font-size:clamp(32px,5vw,56px);font-weight:800;letter-spacing:-0.03em;line-height:1.1;margin-bottom:12px}';
|
|
1289
|
+
html += '.header h1 .accent{color:' + primaryColor + '}';
|
|
1290
|
+
html += '.header .subtitle{font-size:18px;color:' + textSecondary + ';max-width:600px}';
|
|
1291
|
+
html += '.header .meta{font-size:13px;color:' + textSecondary + ';margin-top:16px;display:flex;gap:24px;flex-wrap:wrap;align-items:center}';
|
|
1292
|
+
html += '.header .meta a{color:' + primaryColor + ';text-decoration:none}';
|
|
1293
|
+
// Dark mode toggle
|
|
1294
|
+
html += '.toggle-btn{display:inline-flex;align-items:center;gap:8px;padding:8px 16px;border-radius:9999px;border:1px solid ' + borderColor + ';background:' + surfaceColor + ';font-size:13px;font-weight:600;cursor:pointer;color:' + textColor + ';transition:all 0.2s}';
|
|
1295
|
+
html += '.toggle-btn:hover{border-color:' + primaryColor + '}';
|
|
1296
|
+
// Section
|
|
1297
|
+
html += 'section{padding:clamp(40px,6vw,72px) 0}';
|
|
1298
|
+
html += 'section+section{border-top:1px solid ' + borderColor + '}';
|
|
1299
|
+
html += 'h2{font-family:' + fontDisplay + ';font-size:clamp(22px,3vw,30px);font-weight:700;letter-spacing:-0.02em;margin-bottom:clamp(20px,3vw,32px);color:' + textColor + '}';
|
|
1300
|
+
html += 'h3{font-size:18px;font-weight:600;margin-bottom:16px;margin-top:32px;color:' + textColor + '}';
|
|
1301
|
+
// Swatches
|
|
1302
|
+
html += '.swatch-grid{display:flex;flex-wrap:wrap;gap:16px}';
|
|
1303
|
+
html += '.swatch{width:160px;border-radius:12px;overflow:hidden;border:1px solid ' + borderColor + ';background:' + surfaceColor + '}';
|
|
1304
|
+
html += '.swatch-fill{height:80px;display:flex;align-items:flex-end;padding:8px 12px}';
|
|
1305
|
+
html += '.swatch-hex{font-family:ui-monospace,monospace;font-size:13px;font-weight:600}';
|
|
1306
|
+
html += '.swatch-info{padding:10px 12px}';
|
|
1307
|
+
html += '.swatch-name{font-size:14px;font-weight:600;margin-bottom:2px}';
|
|
1308
|
+
html += '.swatch-var{font-family:ui-monospace,monospace;font-size:11px;color:' + textSecondary + ';margin-bottom:6px;word-break:break-all}';
|
|
1309
|
+
html += '.swatch-contrast{display:flex;gap:6px;flex-wrap:wrap}';
|
|
1310
|
+
html += '.badge{font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;white-space:nowrap}';
|
|
1311
|
+
html += '.badge-aaa{background:#22C55E20;color:#16A34A}';
|
|
1312
|
+
html += '.badge-aa{background:#F59E0B20;color:#D97706}';
|
|
1313
|
+
html += '.badge-aalg{background:#F59E0B20;color:#D97706}';
|
|
1314
|
+
html += '.badge-fail{background:#EF444420;color:#DC2626}';
|
|
1315
|
+
// Typography
|
|
1316
|
+
html += '.type-specimen{margin-bottom:20px;padding-bottom:20px;border-bottom:1px solid ' + borderColor + '}';
|
|
1317
|
+
html += '.type-sample{margin-bottom:8px;color:' + textColor + '}';
|
|
1318
|
+
html += '.type-meta{display:flex;gap:16px;align-items:center;flex-wrap:wrap}';
|
|
1319
|
+
html += '.type-name{font-size:14px;font-weight:600;color:' + primaryColor + '}';
|
|
1320
|
+
html += '.type-value{font-family:ui-monospace,monospace;font-size:13px;color:' + textSecondary + '}';
|
|
1321
|
+
html += '.type-var{font-family:ui-monospace,monospace;font-size:11px;color:' + textSecondary + '}';
|
|
1322
|
+
html += '.weight-grid{display:flex;flex-wrap:wrap;gap:24px;margin-top:16px}';
|
|
1323
|
+
html += '.weight-specimen{font-size:20px;color:' + textColor + '}';
|
|
1324
|
+
// Spacing
|
|
1325
|
+
html += '.space-row{display:flex;align-items:center;gap:12px;margin-bottom:8px}';
|
|
1326
|
+
html += '.space-label{font-family:ui-monospace,monospace;font-size:13px;font-weight:600;width:40px;text-align:right;color:' + textSecondary + '}';
|
|
1327
|
+
html += '.space-bar{height:16px;background:' + primaryColor + '20;border-left:3px solid ' + primaryColor + ';border-radius:0 4px 4px 0;min-width:2px}';
|
|
1328
|
+
html += '.space-value{font-family:ui-monospace,monospace;font-size:12px;color:' + textSecondary + '}';
|
|
1329
|
+
// Radius
|
|
1330
|
+
html += '.radius-grid{display:flex;flex-wrap:wrap;gap:24px}';
|
|
1331
|
+
html += '.radius-item{text-align:center}';
|
|
1332
|
+
html += '.radius-box{width:72px;height:72px;background:' + primaryColor + '15;border:2px solid ' + primaryColor + '}';
|
|
1333
|
+
html += '.radius-label{font-size:13px;font-weight:600;margin-top:8px;color:' + textColor + '}';
|
|
1334
|
+
html += '.radius-label span{font-weight:400;color:' + textSecondary + ';font-family:ui-monospace,monospace;font-size:12px}';
|
|
1335
|
+
// Elevation
|
|
1336
|
+
html += '.elevation-grid{display:flex;flex-wrap:wrap;gap:32px}';
|
|
1337
|
+
html += '.elevation-card{width:120px;height:80px;background:' + surfaceColor + ';border-radius:12px;display:flex;align-items:center;justify-content:center}';
|
|
1338
|
+
html += '.elevation-label{font-size:14px;font-weight:600;color:' + textColor + '}';
|
|
1339
|
+
// Motion
|
|
1340
|
+
html += '.motion-row{display:flex;align-items:center;gap:16px;margin-bottom:16px}';
|
|
1341
|
+
html += '.motion-label{font-size:13px;font-weight:600;width:100px;color:' + textColor + '}';
|
|
1342
|
+
html += '.motion-label span{font-weight:400;color:' + textSecondary + ';font-size:11px;font-family:ui-monospace,monospace}';
|
|
1343
|
+
html += '.motion-track{flex:1;height:32px;background:' + primaryColor + '08;border-radius:16px;position:relative;overflow:hidden}';
|
|
1344
|
+
html += '.motion-dot{width:32px;height:32px;background:' + primaryColor + ';border-radius:50%;position:absolute;left:0;top:0}';
|
|
1345
|
+
html += '.motion-row:hover .motion-dot{transform:translateX(calc(100% + 200px))}';
|
|
1346
|
+
// Code block
|
|
1347
|
+
html += '.code-block{position:relative;background:#1a1a2e;border-radius:12px;padding:24px;margin-top:24px;overflow-x:auto}';
|
|
1348
|
+
html += '.code-block pre{font-family:ui-monospace,monospace;font-size:13px;line-height:1.7;color:#e0e0e0;white-space:pre;margin:0}';
|
|
1349
|
+
html += '.copy-btn{position:absolute;top:12px;right:12px;padding:6px 14px;border-radius:8px;border:1px solid rgba(255,255,255,0.15);background:rgba(255,255,255,0.06);color:#ccc;font-size:12px;font-weight:600;cursor:pointer}';
|
|
1350
|
+
html += '.copy-btn:hover{background:rgba(255,255,255,0.12)}';
|
|
1351
|
+
// Print
|
|
1352
|
+
html += '@media print{.toggle-btn,.copy-btn{display:none}.motion-dot{display:none}body{background:#fff}}';
|
|
1353
|
+
// Responsive
|
|
1354
|
+
html += '@media(max-width:768px){.swatch{width:140px}.swatch-grid{gap:12px}.elevation-grid{gap:16px}.elevation-card{width:100px;height:64px}}';
|
|
1355
|
+
html += '@media(max-width:480px){.swatch{width:100%}.radius-grid{gap:16px}}';
|
|
1356
|
+
html += '</style></head><body><div class="container">';
|
|
1357
|
+
// Header
|
|
1358
|
+
html += '<header class="header"><h1>' + systemName + ' <span class="accent">Design System</span></h1>';
|
|
1359
|
+
html += '<p class="subtitle">Complete design token set with colors, typography, spacing, radius, elevation, and motion.</p>';
|
|
1360
|
+
html += '<div class="meta"><span>Generated ' + new Date().toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" }) + '</span><a href="https://ravenmcp.ai" target="_blank">Built with Raven MCP</a>';
|
|
1361
|
+
if (hasDark)
|
|
1362
|
+
html += '<button class="toggle-btn" id="darkToggle" onclick="toggleDark()">🌙 Dark Mode</button>';
|
|
1363
|
+
html += '</div></header>';
|
|
1364
|
+
// Colors
|
|
1365
|
+
html += '<section><h2>Color Palette</h2><div class="swatch-grid">' + colorSwatches + '</div>';
|
|
1366
|
+
if (hasDark)
|
|
1367
|
+
html += '<h3>Dark Mode Colors</h3><div class="swatch-grid">' + darkSwatches + '</div>';
|
|
1368
|
+
html += '</section>';
|
|
1369
|
+
// Typography
|
|
1370
|
+
html += '<section><h2>Typography Scale</h2>' + typoHTML;
|
|
1371
|
+
if (weightHTML)
|
|
1372
|
+
html += '<h3>Font Weights</h3><div class="weight-grid">' + weightHTML + '</div>';
|
|
1373
|
+
html += '</section>';
|
|
1374
|
+
// Spacing
|
|
1375
|
+
if (spacingHTML)
|
|
1376
|
+
html += '<section><h2>Spacing Scale</h2>' + spacingHTML + '</section>';
|
|
1377
|
+
// Radius
|
|
1378
|
+
if (radiusHTML)
|
|
1379
|
+
html += '<section><h2>Border Radius</h2><div class="radius-grid">' + radiusHTML + '</div></section>';
|
|
1380
|
+
// Elevation
|
|
1381
|
+
if (elevationHTML)
|
|
1382
|
+
html += '<section><h2>Elevation</h2><div class="elevation-grid">' + elevationHTML + '</div></section>';
|
|
1383
|
+
// Motion
|
|
1384
|
+
if (motionHTML)
|
|
1385
|
+
html += '<section><h2>Motion & Easing</h2><p style="font-size:14px;color:' + textSecondary + ';margin-bottom:24px">Hover each row to preview the easing curve.</p>' + motionHTML + '</section>';
|
|
1386
|
+
// Code
|
|
1387
|
+
html += '<section><h2>CSS Variables</h2><p style="margin-bottom:8px;color:' + textSecondary + '">Copy these into your stylesheet to use the full token set.</p>';
|
|
1388
|
+
html += '<div class="code-block"><button class="copy-btn" onclick="copyCSS()">Copy</button><pre id="cssCode">' + cssBlock + '</pre></div></section>';
|
|
1389
|
+
// Footer
|
|
1390
|
+
html += '<footer style="padding:48px 0 64px;text-align:center;font-size:13px;color:' + textSecondary + '"><p>' + systemName + ' Design System · Generated by <a href="https://ravenmcp.ai" style="color:' + primaryColor + ';text-decoration:none">Raven MCP</a></p></footer>';
|
|
1391
|
+
html += '</div>';
|
|
1392
|
+
// Dark mode JS
|
|
1393
|
+
if (hasDark) {
|
|
1394
|
+
html += '<script>var isDark=false;function toggleDark(){isDark=!isDark;document.body.style.background=isDark?"' + (darkTokens["background"]?.["$value"] || "#111") + '":"' + bgColor + '";document.body.style.color=isDark?"' + (darkTokens["text-primary"]?.["$value"] || "#eee") + '":"' + textColor + '";document.getElementById("darkToggle").textContent=isDark?"☀️ Light Mode":"🌙 Dark Mode"}</script>';
|
|
1395
|
+
}
|
|
1396
|
+
// Copy JS
|
|
1397
|
+
html += '<script>function copyCSS(){var t=document.getElementById("cssCode").textContent;navigator.clipboard.writeText(t).then(function(){var b=document.querySelector(".copy-btn");b.textContent="Copied!";setTimeout(function(){b.textContent="Copy"},2000)})}</script>';
|
|
1398
|
+
html += '</body></html>';
|
|
1399
|
+
return html;
|
|
1400
|
+
}
|
|
1401
|
+
// ── Figma Variables export ─────────────────────────────────────────
|
|
1402
|
+
function tokensToFigmaVariables(tokens, systemName) {
|
|
1403
|
+
var variables = [];
|
|
1404
|
+
var hasDark = !!tokens["color-dark"];
|
|
1405
|
+
var modes = [{ name: "Light" }];
|
|
1406
|
+
if (hasDark)
|
|
1407
|
+
modes.push({ name: "Dark" });
|
|
1408
|
+
// Process color tokens
|
|
1409
|
+
var colorTokens = tokens["color"] || {};
|
|
1410
|
+
var darkTokens = tokens["color-dark"] || {};
|
|
1411
|
+
for (var ck of Object.keys(colorTokens)) {
|
|
1412
|
+
if (ck.startsWith("$"))
|
|
1413
|
+
continue;
|
|
1414
|
+
var cv = colorTokens[ck];
|
|
1415
|
+
if (!cv || !cv["$value"])
|
|
1416
|
+
continue;
|
|
1417
|
+
var v = { name: "color/" + ck, type: "COLOR", valuesByMode: { Light: hexToRGBNormalized(cv["$value"]) } };
|
|
1418
|
+
if (hasDark && darkTokens[ck]?.["$value"]) {
|
|
1419
|
+
v.valuesByMode.Dark = hexToRGBNormalized(darkTokens[ck]["$value"]);
|
|
1420
|
+
}
|
|
1421
|
+
variables.push(v);
|
|
1422
|
+
}
|
|
1423
|
+
// Process dimension tokens (spacing, radius)
|
|
1424
|
+
for (var group of ["spacing", "radius"]) {
|
|
1425
|
+
var grp = tokens[group] || {};
|
|
1426
|
+
for (var gk of Object.keys(grp)) {
|
|
1427
|
+
if (gk.startsWith("$"))
|
|
1428
|
+
continue;
|
|
1429
|
+
var gv = grp[gk];
|
|
1430
|
+
if (!gv || !gv["$value"])
|
|
1431
|
+
continue;
|
|
1432
|
+
var px = parseFloat(gv["$value"]);
|
|
1433
|
+
if (isNaN(px))
|
|
1434
|
+
continue;
|
|
1435
|
+
variables.push({ name: group + "/" + gk, type: "FLOAT", valuesByMode: { Light: px } });
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
// Process typography font-size
|
|
1439
|
+
var fontSize = tokens["typography"]?.["font-size"] || {};
|
|
1440
|
+
for (var fk of Object.keys(fontSize)) {
|
|
1441
|
+
if (fk.startsWith("$"))
|
|
1442
|
+
continue;
|
|
1443
|
+
var fv = fontSize[fk];
|
|
1444
|
+
if (!fv || !fv["$value"])
|
|
1445
|
+
continue;
|
|
1446
|
+
var fpx = parseFloat(fv["$value"]);
|
|
1447
|
+
if (isNaN(fpx))
|
|
1448
|
+
continue;
|
|
1449
|
+
variables.push({ name: "typography/font-size/" + fk, type: "FLOAT", valuesByMode: { Light: fpx } });
|
|
1450
|
+
}
|
|
1451
|
+
return {
|
|
1452
|
+
variableCollections: [{
|
|
1453
|
+
name: systemName,
|
|
1454
|
+
modes: modes,
|
|
1455
|
+
variables: variables
|
|
1456
|
+
}]
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
// ── SVG palette card ───────────────────────────────────────────────
|
|
1460
|
+
function tokensToSVGPalette(tokens, systemName) {
|
|
1461
|
+
var colorTokens = tokens["color"] || {};
|
|
1462
|
+
var colors = [];
|
|
1463
|
+
for (var ck of Object.keys(colorTokens)) {
|
|
1464
|
+
if (ck.startsWith("$"))
|
|
1465
|
+
continue;
|
|
1466
|
+
var cv = colorTokens[ck];
|
|
1467
|
+
if (!cv || !cv["$value"])
|
|
1468
|
+
continue;
|
|
1469
|
+
colors.push({ name: ck, hex: cv["$value"] });
|
|
1470
|
+
}
|
|
1471
|
+
var cols = 5;
|
|
1472
|
+
var rows = Math.ceil(colors.length / cols);
|
|
1473
|
+
var swatchW = 120;
|
|
1474
|
+
var swatchH = 80;
|
|
1475
|
+
var gap = 8;
|
|
1476
|
+
var padding = 32;
|
|
1477
|
+
var headerH = 60;
|
|
1478
|
+
var svgW = padding * 2 + cols * swatchW + (cols - 1) * gap;
|
|
1479
|
+
var svgH = padding + headerH + rows * (swatchH + 28) + (rows - 1) * gap + padding;
|
|
1480
|
+
var svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + svgW + ' ' + svgH + '" width="' + svgW + '" height="' + svgH + '">';
|
|
1481
|
+
svg += '<rect width="100%" height="100%" fill="#FFFFFF" rx="16"/>';
|
|
1482
|
+
svg += '<text x="' + padding + '" y="' + (padding + 24) + '" font-family="Inter, system-ui, sans-serif" font-size="20" font-weight="700" fill="#111">' + systemName + ' — Color Palette</text>';
|
|
1483
|
+
svg += '<text x="' + padding + '" y="' + (padding + 44) + '" font-family="Inter, system-ui, sans-serif" font-size="12" fill="#888">Generated by Raven MCP</text>';
|
|
1484
|
+
for (var i = 0; i < colors.length; i++) {
|
|
1485
|
+
var col = i % cols;
|
|
1486
|
+
var row = Math.floor(i / cols);
|
|
1487
|
+
var x = padding + col * (swatchW + gap);
|
|
1488
|
+
var y = padding + headerH + row * (swatchH + 28 + gap);
|
|
1489
|
+
var c = colors[i];
|
|
1490
|
+
var crW = getContrastRatio(c.hex, "#FFFFFF");
|
|
1491
|
+
var crB = getContrastRatio(c.hex, "#000000");
|
|
1492
|
+
var txtC = crW > crB ? "#FFFFFF" : "#000000";
|
|
1493
|
+
svg += '<rect x="' + x + '" y="' + y + '" width="' + swatchW + '" height="' + swatchH + '" rx="8" fill="' + c.hex + '"/>';
|
|
1494
|
+
svg += '<text x="' + (x + 8) + '" y="' + (y + swatchH - 10) + '" font-family="ui-monospace, monospace" font-size="11" font-weight="600" fill="' + txtC + '">' + c.hex + '</text>';
|
|
1495
|
+
svg += '<text x="' + (x + 2) + '" y="' + (y + swatchH + 16) + '" font-family="Inter, system-ui, sans-serif" font-size="11" font-weight="600" fill="#333">' + c.name + '</text>';
|
|
1496
|
+
}
|
|
1497
|
+
svg += '</svg>';
|
|
1498
|
+
return svg;
|
|
1499
|
+
}
|
|
1500
|
+
// ── Tool 13: generate_design_system ─────────────────────────────────
|
|
1501
|
+
server.tool("generate_design_system", "Generate a complete, custom design system with full token set. Provide a brand color to auto-generate a harmonious palette, pick a style preset, and export as visual HTML documentation, CSS variables, W3C DTCG JSON, Figma Variables, or SVG palette card. The HTML export is a beautiful, self-contained page suitable for sharing with stakeholders.", {
|
|
1502
|
+
name: z.string().describe("Name for the design system (e.g. 'Acme Corp', 'NightOwl')"),
|
|
1503
|
+
base_system: z.string().optional().describe("Start from an existing system as foundation (e.g. 'stripe', 'linear'). Colors will be replaced by brand_color if provided."),
|
|
1504
|
+
brand_color: z.string().optional().describe("Primary brand hex color (e.g. '#FF6B35'). Auto-generates a full harmonious palette using color theory."),
|
|
1505
|
+
style: z.enum(["minimal", "bold", "warm", "corporate", "playful", "dark"]).optional().describe("Aesthetic direction — influences spacing, radii, shadows, motion, and typography. Default: minimal"),
|
|
1506
|
+
dark_mode: z.boolean().optional().describe("Generate dark mode tokens alongside light. Default: true"),
|
|
1507
|
+
format: z.enum(["html", "css", "dtcg", "figma", "svg", "all"]).optional().describe("Export format: html (visual doc page), css (custom properties), dtcg (W3C JSON), figma (Figma Variables JSON), svg (color palette card), all. Default: html")
|
|
1508
|
+
}, async function (params) {
|
|
1509
|
+
var tokens = generateTokenSet({
|
|
1510
|
+
name: params.name,
|
|
1511
|
+
base_system: params.base_system,
|
|
1512
|
+
brand_color: params.brand_color,
|
|
1513
|
+
style: params.style,
|
|
1514
|
+
dark_mode: params.dark_mode
|
|
1515
|
+
});
|
|
1516
|
+
var fmt = params.format || "html";
|
|
1517
|
+
var systemId = params.name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
1518
|
+
var output;
|
|
1519
|
+
if (fmt === "css") {
|
|
1520
|
+
output = tokensToCSSByGroup(tokens, systemId);
|
|
1521
|
+
}
|
|
1522
|
+
else if (fmt === "dtcg") {
|
|
1523
|
+
output = JSON.stringify(tokens, null, 2);
|
|
1524
|
+
}
|
|
1525
|
+
else if (fmt === "html") {
|
|
1526
|
+
output = tokensToHTML(tokens, params.name);
|
|
1527
|
+
}
|
|
1528
|
+
else if (fmt === "figma") {
|
|
1529
|
+
output = JSON.stringify(tokensToFigmaVariables(tokens, params.name), null, 2);
|
|
1530
|
+
}
|
|
1531
|
+
else if (fmt === "svg") {
|
|
1532
|
+
output = tokensToSVGPalette(tokens, params.name);
|
|
1533
|
+
}
|
|
1534
|
+
else if (fmt === "all") {
|
|
1535
|
+
var all = {
|
|
1536
|
+
html: tokensToHTML(tokens, params.name),
|
|
1537
|
+
css: tokensToCSSByGroup(tokens, systemId),
|
|
1538
|
+
dtcg: tokens,
|
|
1539
|
+
figma: tokensToFigmaVariables(tokens, params.name),
|
|
1540
|
+
svg: tokensToSVGPalette(tokens, params.name)
|
|
1541
|
+
};
|
|
1542
|
+
output = JSON.stringify(all, null, 2);
|
|
1543
|
+
}
|
|
1544
|
+
else {
|
|
1545
|
+
output = tokensToHTML(tokens, params.name);
|
|
1546
|
+
}
|
|
1547
|
+
return {
|
|
1548
|
+
content: [{
|
|
1549
|
+
type: "text",
|
|
1550
|
+
text: output
|
|
1551
|
+
}]
|
|
1552
|
+
};
|
|
1553
|
+
});
|
|
1554
|
+
// ── Registration ───────────────────────────────────────────────────
|
|
1555
|
+
var REGISTER_API = "https://ravenmcp.ai/api/welcome";
|
|
1556
|
+
server.tool("raven_register", "Register your email to receive design updates and provide feedback to the Raven creator. Call this when a user wants to register, give feedback, or connect with the Raven team.", {
|
|
1557
|
+
email: z.string().email().describe("User's email address"),
|
|
1558
|
+
name: z.string().optional().describe("User's name (optional)")
|
|
1559
|
+
}, async function (params) {
|
|
1560
|
+
try {
|
|
1561
|
+
var response = await fetch(REGISTER_API, {
|
|
1562
|
+
method: "POST",
|
|
1563
|
+
headers: { "Content-Type": "application/json" },
|
|
1564
|
+
body: JSON.stringify({ email: params.email, name: params.name || "" })
|
|
1565
|
+
});
|
|
1566
|
+
if (!response.ok) {
|
|
1567
|
+
var err = await response.json();
|
|
1568
|
+
return {
|
|
1569
|
+
content: [{
|
|
1570
|
+
type: "text",
|
|
1571
|
+
text: "Registration failed: " + (err.error || "Unknown error")
|
|
1572
|
+
}]
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
return {
|
|
1576
|
+
content: [{
|
|
1577
|
+
type: "text",
|
|
1578
|
+
text: "Registered! A welcome email has been sent to " + params.email + " from Drew Cunliffe (Raven's creator). It includes quick-start tips and a direct line for feedback. Check your inbox."
|
|
1579
|
+
}]
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
catch (e) {
|
|
1583
|
+
return {
|
|
1584
|
+
content: [{
|
|
1585
|
+
type: "text",
|
|
1586
|
+
text: "Couldn't reach the registration server. The user can email drew@ravenmcp.ai directly for updates and feedback."
|
|
1587
|
+
}]
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
619
1591
|
// ── Start ───────────────────────────────────────────────────────────
|
|
620
1592
|
async function main() {
|
|
621
1593
|
var transport = new StdioServerTransport();
|