sonance-brand-mcp 1.1.4 → 1.1.7
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/assets/components/accordion.stories.tsx +310 -0
- package/dist/assets/components/accordion.tsx +56 -30
- package/dist/assets/components/alert.stories.tsx +199 -0
- package/dist/assets/components/autocomplete.stories.tsx +307 -0
- package/dist/assets/components/autocomplete.tsx +28 -4
- package/dist/assets/components/avatar.stories.tsx +175 -0
- package/dist/assets/components/badge.stories.tsx +258 -0
- package/dist/assets/components/breadcrumbs.stories.tsx +175 -0
- package/dist/assets/components/button.stories.tsx +362 -0
- package/dist/assets/components/button.tsx +48 -3
- package/dist/assets/components/calendar.stories.tsx +247 -0
- package/dist/assets/components/card.stories.tsx +275 -0
- package/dist/assets/components/card.tsx +26 -1
- package/dist/assets/components/checkbox-group.stories.tsx +281 -0
- package/dist/assets/components/checkbox.stories.tsx +160 -0
- package/dist/assets/components/checkbox.tsx +32 -4
- package/dist/assets/components/code.stories.tsx +265 -0
- package/dist/assets/components/date-input.stories.tsx +278 -0
- package/dist/assets/components/date-input.tsx +24 -2
- package/dist/assets/components/date-picker.stories.tsx +337 -0
- package/dist/assets/components/date-picker.tsx +28 -4
- package/dist/assets/components/date-range-picker.stories.tsx +340 -0
- package/dist/assets/components/dialog.stories.tsx +285 -0
- package/dist/assets/components/divider.stories.tsx +176 -0
- package/dist/assets/components/drawer.stories.tsx +216 -0
- package/dist/assets/components/dropdown.stories.tsx +342 -0
- package/dist/assets/components/dropdown.tsx +55 -10
- package/dist/assets/components/form.stories.tsx +372 -0
- package/dist/assets/components/image.stories.tsx +348 -0
- package/dist/assets/components/input-otp.stories.tsx +336 -0
- package/dist/assets/components/input-otp.tsx +24 -2
- package/dist/assets/components/input.stories.tsx +223 -0
- package/dist/assets/components/input.tsx +27 -2
- package/dist/assets/components/kbd.stories.tsx +272 -0
- package/dist/assets/components/link.stories.tsx +199 -0
- package/dist/assets/components/link.tsx +50 -1
- package/dist/assets/components/listbox.stories.tsx +287 -0
- package/dist/assets/components/listbox.tsx +30 -7
- package/dist/assets/components/navbar.stories.tsx +218 -0
- package/dist/assets/components/number-input.stories.tsx +295 -0
- package/dist/assets/components/number-input.tsx +30 -8
- package/dist/assets/components/pagination.stories.tsx +280 -0
- package/dist/assets/components/pagination.tsx +45 -21
- package/dist/assets/components/popover.stories.tsx +219 -0
- package/dist/assets/components/progress.stories.tsx +153 -0
- package/dist/assets/components/radio-group.stories.tsx +187 -0
- package/dist/assets/components/radio-group.tsx +30 -6
- package/dist/assets/components/range-calendar.stories.tsx +334 -0
- package/dist/assets/components/scroll-shadow.stories.tsx +335 -0
- package/dist/assets/components/select.stories.tsx +192 -0
- package/dist/assets/components/select.tsx +54 -7
- package/dist/assets/components/skeleton.stories.tsx +166 -0
- package/dist/assets/components/slider.stories.tsx +145 -0
- package/dist/assets/components/slider.tsx +43 -8
- package/dist/assets/components/spacer.stories.tsx +216 -0
- package/dist/assets/components/spinner.stories.tsx +149 -0
- package/dist/assets/components/switch.stories.tsx +170 -0
- package/dist/assets/components/switch.tsx +29 -4
- package/dist/assets/components/table.stories.tsx +322 -0
- package/dist/assets/components/tabs.stories.tsx +306 -0
- package/dist/assets/components/tabs.tsx +25 -4
- package/dist/assets/components/textarea.stories.tsx +103 -0
- package/dist/assets/components/textarea.tsx +27 -3
- package/dist/assets/components/theme-toggle.stories.tsx +248 -0
- package/dist/assets/components/time-input.stories.tsx +365 -0
- package/dist/assets/components/time-input.tsx +25 -3
- package/dist/assets/components/toast.stories.tsx +195 -0
- package/dist/assets/components/tooltip.stories.tsx +226 -0
- package/dist/assets/components/user.stories.tsx +274 -0
- package/dist/index.js +1649 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -306,6 +306,189 @@ function getAssetPath(assetType) {
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Detect output type from user's request/prompt
|
|
311
|
+
*/
|
|
312
|
+
function detectFromRequest(prompt) {
|
|
313
|
+
const lowerPrompt = prompt.toLowerCase();
|
|
314
|
+
// Document indicators
|
|
315
|
+
if (/word|docx|pdf|proposal|spec sheet|report|spreadsheet|excel|xlsx/.test(lowerPrompt))
|
|
316
|
+
return "doc";
|
|
317
|
+
// Marketing indicators
|
|
318
|
+
if (/email|newsletter|banner|landing page|campaign|advertisement|promo/.test(lowerPrompt))
|
|
319
|
+
return "marketing";
|
|
320
|
+
// Technical doc indicators
|
|
321
|
+
if (/readme|documentation|api doc|guide|changelog|\.md\b/.test(lowerPrompt))
|
|
322
|
+
return "tech";
|
|
323
|
+
// UI indicators
|
|
324
|
+
if (/component|button|card|form|modal|dashboard|app|page|ui|react|next\.?js/.test(lowerPrompt))
|
|
325
|
+
return "ui";
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Detect output type from code patterns
|
|
330
|
+
*/
|
|
331
|
+
function detectFromCode(code) {
|
|
332
|
+
// Document generation libraries
|
|
333
|
+
if (/from docx|python-docx|Document\(|reportlab|openpyxl|Workbook\(|docx\.|\.docx|PDFDocument/.test(code))
|
|
334
|
+
return "doc";
|
|
335
|
+
// Marketing patterns (email-safe HTML)
|
|
336
|
+
if (/email-safe|mso-|outlook|table.*cellpadding|inline.*style.*color|<!--\[if mso\]/i.test(code))
|
|
337
|
+
return "marketing";
|
|
338
|
+
// Technical documentation (Markdown)
|
|
339
|
+
if (/^#{1,3}\s|```[\w]*\n|README|\.md"|frontmatter|---\n.*title:/m.test(code))
|
|
340
|
+
return "tech";
|
|
341
|
+
// UI/React patterns (default)
|
|
342
|
+
return "ui";
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Smart detection: tries request first, then code
|
|
346
|
+
*/
|
|
347
|
+
function smartDetectOutputType(prompt, code) {
|
|
348
|
+
const fromPrompt = detectFromRequest(prompt);
|
|
349
|
+
if (fromPrompt)
|
|
350
|
+
return fromPrompt;
|
|
351
|
+
return detectFromCode(code);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Extract brand colors from live CSS file
|
|
355
|
+
*/
|
|
356
|
+
function extractBrandColors() {
|
|
357
|
+
try {
|
|
358
|
+
const cssContent = fs.readFileSync(getAssetPath("css"), "utf-8");
|
|
359
|
+
const extractColor = (varName) => {
|
|
360
|
+
const match = cssContent.match(new RegExp(`${varName}:\\s*([#\\w]+)`));
|
|
361
|
+
return match ? match[1] : "";
|
|
362
|
+
};
|
|
363
|
+
return {
|
|
364
|
+
charcoal: extractColor("--sonance-charcoal") || "#343d46",
|
|
365
|
+
silver: extractColor("--sonance-silver") || "#e2e2e2",
|
|
366
|
+
white: extractColor("--sonance-white") || "#FFFFFF",
|
|
367
|
+
black: extractColor("--sonance-black") || "#000000",
|
|
368
|
+
teal: "#00D3C8", // Sonance accent
|
|
369
|
+
gray50: extractColor("--sonance-gray-50") || "#f8f9f9",
|
|
370
|
+
gray100: extractColor("--sonance-gray-100") || "#f0f1f2",
|
|
371
|
+
gray500: extractColor("--sonance-gray-500") || "#7a8389",
|
|
372
|
+
gray900: extractColor("--sonance-gray-900") || "#343d46",
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
// Return defaults if CSS can't be read
|
|
377
|
+
return {
|
|
378
|
+
charcoal: "#343d46",
|
|
379
|
+
silver: "#e2e2e2",
|
|
380
|
+
white: "#FFFFFF",
|
|
381
|
+
black: "#000000",
|
|
382
|
+
teal: "#00D3C8",
|
|
383
|
+
gray50: "#f8f9f9",
|
|
384
|
+
gray100: "#f0f1f2",
|
|
385
|
+
gray500: "#7a8389",
|
|
386
|
+
gray900: "#343d46",
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Convert hex color to different formats for various document libraries
|
|
392
|
+
*/
|
|
393
|
+
function hexToFormats(hex) {
|
|
394
|
+
const cleanHex = hex.replace("#", "");
|
|
395
|
+
const r = parseInt(cleanHex.substring(0, 2), 16);
|
|
396
|
+
const g = parseInt(cleanHex.substring(2, 4), 16);
|
|
397
|
+
const b = parseInt(cleanHex.substring(4, 6), 16);
|
|
398
|
+
return {
|
|
399
|
+
hex: `#${cleanHex}`,
|
|
400
|
+
hexNoHash: cleanHex.toUpperCase(),
|
|
401
|
+
rgb: { r, g, b },
|
|
402
|
+
rgbColor: `RGBColor(0x${cleanHex.substring(0, 2).toUpperCase()}, 0x${cleanHex.substring(2, 4).toUpperCase()}, 0x${cleanHex.substring(4, 6).toUpperCase()})`,
|
|
403
|
+
reportlab: `HexColor('#${cleanHex}')`,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Get brand colors in document-ready formats
|
|
408
|
+
*/
|
|
409
|
+
function getBrandColorsForDocs() {
|
|
410
|
+
const colors = extractBrandColors();
|
|
411
|
+
return {
|
|
412
|
+
charcoal: hexToFormats(colors.charcoal),
|
|
413
|
+
silver: hexToFormats(colors.silver),
|
|
414
|
+
white: hexToFormats(colors.white),
|
|
415
|
+
black: hexToFormats(colors.black),
|
|
416
|
+
teal: hexToFormats(colors.teal),
|
|
417
|
+
gray50: hexToFormats(colors.gray50),
|
|
418
|
+
gray100: hexToFormats(colors.gray100),
|
|
419
|
+
gray500: hexToFormats(colors.gray500),
|
|
420
|
+
gray900: hexToFormats(colors.gray900),
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
const BRAND_PALETTES = {
|
|
424
|
+
sonance: {
|
|
425
|
+
primary: "#343d46", // Sonance Charcoal
|
|
426
|
+
secondary: "#e2e2e2", // Sonance Silver
|
|
427
|
+
accent: "#00D3C8", // Sonance Teal
|
|
428
|
+
background: "#FFFFFF",
|
|
429
|
+
foreground: "#343d46",
|
|
430
|
+
name: "Sonance",
|
|
431
|
+
preferredTheme: "light",
|
|
432
|
+
},
|
|
433
|
+
iport: {
|
|
434
|
+
primary: "#0F161D", // IPORT Dark
|
|
435
|
+
secondary: "#FC4C02", // IPORT Orange
|
|
436
|
+
accent: "#FC4C02", // IPORT Orange (accent)
|
|
437
|
+
background: "#0F161D",
|
|
438
|
+
foreground: "#FFFFFF",
|
|
439
|
+
name: "IPORT",
|
|
440
|
+
preferredTheme: "dark",
|
|
441
|
+
},
|
|
442
|
+
blaze: {
|
|
443
|
+
primary: "#28282B", // Blaze Dark
|
|
444
|
+
secondary: "#00A3E1", // Blaze Blue
|
|
445
|
+
accent: "#00A3E1", // Blaze Blue (primary accent)
|
|
446
|
+
accentSecondary: "#C02B0A", // Blaze Red (secondary accent)
|
|
447
|
+
background: "#28282B",
|
|
448
|
+
foreground: "#FFFFFF",
|
|
449
|
+
name: "Blaze Audio",
|
|
450
|
+
preferredTheme: "dark",
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
/**
|
|
454
|
+
* Get brand colors in document-ready formats for a specific brand
|
|
455
|
+
*/
|
|
456
|
+
function getBrandColorsForDocsMulti(brand = "sonance") {
|
|
457
|
+
const palette = BRAND_PALETTES[brand];
|
|
458
|
+
return {
|
|
459
|
+
primary: hexToFormats(palette.primary),
|
|
460
|
+
secondary: hexToFormats(palette.secondary),
|
|
461
|
+
accent: hexToFormats(palette.accent),
|
|
462
|
+
accentSecondary: palette.accentSecondary ? hexToFormats(palette.accentSecondary) : hexToFormats(palette.accent),
|
|
463
|
+
background: hexToFormats(palette.background),
|
|
464
|
+
foreground: hexToFormats(palette.foreground),
|
|
465
|
+
white: hexToFormats("#FFFFFF"),
|
|
466
|
+
black: hexToFormats("#000000"),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Get regex patterns to detect brand colors in code
|
|
471
|
+
*/
|
|
472
|
+
function getBrandColorPatterns(brand) {
|
|
473
|
+
const patterns = {
|
|
474
|
+
sonance: {
|
|
475
|
+
primaryPattern: /343d46|343D46|#343d46|0x34.*0x3D.*0x46|rgb\(52.*61.*70\)|charcoal|CHARCOAL|sonance-charcoal/i,
|
|
476
|
+
secondaryPattern: /e2e2e2|E2E2E2|#e2e2e2|0xE2.*0xE2.*0xE2|rgb\(226.*226.*226\)|silver|SILVER|sonance-silver/i,
|
|
477
|
+
accentPattern: /00D3C8|00d3c8|#00D3C8|0x00.*0xD3.*0xC8|rgb\(0.*211.*200\)|teal|TEAL/i,
|
|
478
|
+
},
|
|
479
|
+
iport: {
|
|
480
|
+
primaryPattern: /0F161D|0f161d|#0F161D|0x0F.*0x16.*0x1D|rgb\(15.*22.*29\)|iport.*dark/i,
|
|
481
|
+
secondaryPattern: /FC4C02|fc4c02|#FC4C02|0xFC.*0x4C.*0x02|rgb\(252.*76.*2\)|iport.*orange/i,
|
|
482
|
+
accentPattern: /FC4C02|fc4c02|#FC4C02|0xFC.*0x4C.*0x02|rgb\(252.*76.*2\)|iport.*orange/i,
|
|
483
|
+
},
|
|
484
|
+
blaze: {
|
|
485
|
+
primaryPattern: /28282B|28282b|#28282B|0x28.*0x28.*0x2B|rgb\(40.*40.*43\)|blaze.*dark/i,
|
|
486
|
+
secondaryPattern: /00A3E1|00a3e1|#00A3E1|0x00.*0xA3.*0xE1|rgb\(0.*163.*225\)|blaze.*blue/i,
|
|
487
|
+
accentPattern: /00A3E1|00a3e1|#00A3E1|C02B0A|c02b0a|#C02B0A|blaze.*(blue|red)/i,
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
return patterns[brand];
|
|
491
|
+
}
|
|
309
492
|
/**
|
|
310
493
|
* Sonance MCP Server
|
|
311
494
|
*
|
|
@@ -467,7 +650,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
467
650
|
},
|
|
468
651
|
{
|
|
469
652
|
name: "design_component",
|
|
470
|
-
description: "Returns brand-specific design tokens, colors, implementation rules, and logo assets for designing a component. Smart defaults: Sonance/Light/Default logo lockup if not specified.",
|
|
653
|
+
description: "Returns brand-specific design tokens, colors, implementation rules, and logo assets for designing a component. Smart defaults: Sonance/Light/Default logo lockup if not specified. Auto-detects output type (ui/doc/marketing/tech) if not specified.",
|
|
471
654
|
inputSchema: {
|
|
472
655
|
type: "object",
|
|
473
656
|
properties: {
|
|
@@ -486,14 +669,114 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
486
669
|
enum: ["default", "sonance", "iport", "blaze"],
|
|
487
670
|
description: "Which logo to use. 'default' = Sonance+James+IPORT lockup. Or specify 'sonance', 'iport', or 'blaze' for individual brand logos. Defaults to 'default' if not specified.",
|
|
488
671
|
},
|
|
672
|
+
output_type: {
|
|
673
|
+
type: "string",
|
|
674
|
+
enum: ["ui", "doc", "marketing", "tech"],
|
|
675
|
+
description: "Type of output being designed. 'ui' = React/Next.js components, 'doc' = Word/PDF documents, 'marketing' = email/landing pages, 'tech' = documentation. Auto-detected if not specified.",
|
|
676
|
+
},
|
|
489
677
|
component_description: {
|
|
490
678
|
type: "string",
|
|
491
|
-
description: "What the user wants to design (e.g., 'hero section', 'pricing card', 'navigation bar')",
|
|
679
|
+
description: "What the user wants to design (e.g., 'hero section', 'pricing card', 'navigation bar', 'proposal PDF', 'email template')",
|
|
492
680
|
},
|
|
493
681
|
},
|
|
494
682
|
required: ["component_description"],
|
|
495
683
|
},
|
|
496
684
|
},
|
|
685
|
+
{
|
|
686
|
+
name: "evaluate_design",
|
|
687
|
+
description: "Evaluates a design or component against the Design Excellence Framework for Sonance, IPORT, or Blaze. Returns a score (0-100), tier (1-5), and specific feedback for improvement. Auto-detects output type and applies appropriate rubrics.",
|
|
688
|
+
inputSchema: {
|
|
689
|
+
type: "object",
|
|
690
|
+
properties: {
|
|
691
|
+
code: {
|
|
692
|
+
type: "string",
|
|
693
|
+
description: "The code/component to evaluate against brand guidelines",
|
|
694
|
+
},
|
|
695
|
+
description: {
|
|
696
|
+
type: "string",
|
|
697
|
+
description: "Brief description of what the component does",
|
|
698
|
+
},
|
|
699
|
+
brand: {
|
|
700
|
+
type: "string",
|
|
701
|
+
enum: ["sonance", "iport", "blaze"],
|
|
702
|
+
description: "Target brand for evaluation. Defaults to 'sonance' if not specified.",
|
|
703
|
+
},
|
|
704
|
+
output_type: {
|
|
705
|
+
type: "string",
|
|
706
|
+
enum: ["ui", "doc", "marketing", "tech"],
|
|
707
|
+
description: "Type of output being evaluated. 'ui' = React/Next.js, 'doc' = Word/PDF, 'marketing' = email/landing, 'tech' = documentation. Auto-detected from code if not specified.",
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
required: ["code"],
|
|
711
|
+
},
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
name: "get_document_template",
|
|
715
|
+
description: "Returns code templates for generating branded documents (Word, PDF, Excel) for Sonance, IPORT, or Blaze. Includes proper brand colors, fonts, and layout patterns for python-docx, ReportLab, openpyxl.",
|
|
716
|
+
inputSchema: {
|
|
717
|
+
type: "object",
|
|
718
|
+
properties: {
|
|
719
|
+
format: {
|
|
720
|
+
type: "string",
|
|
721
|
+
enum: ["word-python", "pdf-python", "excel-python"],
|
|
722
|
+
description: "The document format and language. word-python = python-docx, pdf-python = ReportLab, excel-python = openpyxl",
|
|
723
|
+
},
|
|
724
|
+
brand: {
|
|
725
|
+
type: "string",
|
|
726
|
+
enum: ["sonance", "iport", "blaze"],
|
|
727
|
+
description: "Target brand for the document. Defaults to 'sonance' if not specified.",
|
|
728
|
+
},
|
|
729
|
+
template_type: {
|
|
730
|
+
type: "string",
|
|
731
|
+
enum: ["proposal", "spec-sheet", "report", "invoice", "presentation"],
|
|
732
|
+
description: "Type of document being created",
|
|
733
|
+
},
|
|
734
|
+
},
|
|
735
|
+
required: ["format"],
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
name: "get_excellence_checklist",
|
|
740
|
+
description: "Returns the pre-delivery excellence checklist for Sonance UI work. Use this before delivering any component to ensure it meets Tier 4+ standards.",
|
|
741
|
+
inputSchema: {
|
|
742
|
+
type: "object",
|
|
743
|
+
properties: {},
|
|
744
|
+
required: [],
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
name: "get_anti_patterns",
|
|
749
|
+
description: "Returns common anti-patterns and mistakes to avoid when building Sonance-branded UI. Includes critical failures that result in Tier 1 scores.",
|
|
750
|
+
inputSchema: {
|
|
751
|
+
type: "object",
|
|
752
|
+
properties: {},
|
|
753
|
+
required: [],
|
|
754
|
+
},
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
name: "rebrand_document",
|
|
758
|
+
description: "Returns universal rebranding instructions for converting ANY existing document to a specific brand (Sonance, IPORT, or Blaze). Works with any document format - provides color replacements, font substitutions, and logo placement rules that apply regardless of document structure.",
|
|
759
|
+
inputSchema: {
|
|
760
|
+
type: "object",
|
|
761
|
+
properties: {
|
|
762
|
+
source_format: {
|
|
763
|
+
type: "string",
|
|
764
|
+
enum: ["word", "pdf", "excel", "powerpoint", "html", "generic"],
|
|
765
|
+
description: "What type of document you're rebranding. Use 'generic' if unsure or for other formats.",
|
|
766
|
+
},
|
|
767
|
+
brand: {
|
|
768
|
+
type: "string",
|
|
769
|
+
enum: ["sonance", "iport", "blaze"],
|
|
770
|
+
description: "Target brand for rebranding. Defaults to 'sonance' if not specified.",
|
|
771
|
+
},
|
|
772
|
+
current_colors: {
|
|
773
|
+
type: "string",
|
|
774
|
+
description: "Optional: describe or list colors currently in the document that need replacing (e.g., 'blue headers, gray backgrounds')",
|
|
775
|
+
},
|
|
776
|
+
},
|
|
777
|
+
required: ["source_format"],
|
|
778
|
+
},
|
|
779
|
+
},
|
|
497
780
|
],
|
|
498
781
|
};
|
|
499
782
|
});
|
|
@@ -729,6 +1012,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
729
1012
|
const theme = rawArgs.theme || (() => { defaultsUsed.push("theme → Light"); return "light"; })();
|
|
730
1013
|
const logoPreference = rawArgs.logo_preference || (() => { defaultsUsed.push("logo → Default lockup"); return "default"; })();
|
|
731
1014
|
const component_description = rawArgs.component_description;
|
|
1015
|
+
// Smart output type detection
|
|
1016
|
+
const detectedOutputType = rawArgs.output_type || detectFromRequest(component_description) || "ui";
|
|
1017
|
+
if (!rawArgs.output_type) {
|
|
1018
|
+
defaultsUsed.push(`output type → ${detectedOutputType} (auto-detected)`);
|
|
1019
|
+
}
|
|
732
1020
|
// Logo path mapping based on preference and theme
|
|
733
1021
|
// Light theme = dark logo (for light backgrounds), Dark theme = light logo (for dark backgrounds)
|
|
734
1022
|
const logoMap = {
|
|
@@ -926,31 +1214,176 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
926
1214
|
}
|
|
927
1215
|
// Build defaults notice if any were applied
|
|
928
1216
|
const defaultsNotice = defaultsUsed.length > 0
|
|
929
|
-
? `\n> **Note**: Defaults applied: ${defaultsUsed.join(", ")}. Specify \`brand\`, \`theme\`, or \`
|
|
1217
|
+
? `\n> **Note**: Defaults applied: ${defaultsUsed.join(", ")}. Specify \`brand\`, \`theme\`, \`logo_preference\`, or \`output_type\` explicitly for different styling.\n`
|
|
930
1218
|
: "";
|
|
931
|
-
|
|
1219
|
+
// Get live brand colors for document formats
|
|
1220
|
+
const docColors = getBrandColorsForDocs();
|
|
1221
|
+
// Output-type-specific implementation guidance
|
|
1222
|
+
const outputTypeGuidance = {
|
|
1223
|
+
ui: `## Implementation: React/Next.js UI Component
|
|
932
1224
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1225
|
+
### Tailwind CSS Classes
|
|
1226
|
+
\`\`\`jsx
|
|
1227
|
+
// Primary Button
|
|
1228
|
+
<button className="bg-primary text-primary-foreground px-6 py-3 text-sm font-medium uppercase tracking-wide hover:bg-primary-hover transition-colors">
|
|
1229
|
+
|
|
1230
|
+
// Card
|
|
1231
|
+
<div className="bg-card border border-border rounded-sm p-6 shadow-sm">
|
|
938
1232
|
|
|
939
|
-
|
|
1233
|
+
// Section
|
|
1234
|
+
<section className="py-20 px-6 lg:py-24 lg:px-8 bg-background text-foreground">
|
|
1235
|
+
\`\`\`
|
|
1236
|
+
|
|
1237
|
+
### Logo Asset
|
|
940
1238
|
- **Selected**: ${logoPreferenceLabel}
|
|
941
1239
|
- **Path**: \`${logoPath}\`
|
|
942
|
-
- **Usage**:
|
|
1240
|
+
- **Usage**:
|
|
943
1241
|
\`\`\`jsx
|
|
944
1242
|
<img src="${logoPath}" alt="${logoPreferenceLabel}" className="h-8 w-auto" />
|
|
945
1243
|
// or with Next.js Image:
|
|
946
1244
|
<Image src="${logoPath}" alt="${logoPreferenceLabel}" width={200} height={40} />
|
|
947
1245
|
\`\`\`
|
|
948
1246
|
|
|
949
|
-
|
|
1247
|
+
### Key Requirements
|
|
1248
|
+
- Use semantic Tailwind classes (bg-primary, not bg-[#343d46])
|
|
1249
|
+
- Support light/dark mode with \`dark:\` variants
|
|
1250
|
+
- Add hover states (hover:) and focus states (focus-visible:)
|
|
1251
|
+
- Use sharp corners (rounded-sm or rounded-none)
|
|
1252
|
+
- Generous spacing (py-20/py-24 for sections)`,
|
|
1253
|
+
doc: `## Implementation: Document Generation (Word/PDF/Excel)
|
|
1254
|
+
|
|
1255
|
+
### Sonance Brand Constants for Documents
|
|
1256
|
+
\`\`\`python
|
|
1257
|
+
# python-docx / openpyxl colors
|
|
1258
|
+
CHARCOAL = '${docColors.charcoal.hexNoHash}' # For headers, text
|
|
1259
|
+
SILVER = '${docColors.silver.hexNoHash}' # For backgrounds, borders
|
|
1260
|
+
WHITE = '${docColors.white.hexNoHash}'
|
|
1261
|
+
BLACK = '${docColors.black.hexNoHash}'
|
|
1262
|
+
TEAL = '${docColors.teal.hexNoHash}' # Accent color
|
|
1263
|
+
|
|
1264
|
+
# As RGBColor (python-docx)
|
|
1265
|
+
from docx.shared import RGBColor
|
|
1266
|
+
CHARCOAL_RGB = ${docColors.charcoal.rgbColor}
|
|
1267
|
+
SILVER_RGB = ${docColors.silver.rgbColor}
|
|
1268
|
+
TEAL_RGB = ${docColors.teal.rgbColor}
|
|
1269
|
+
\`\`\`
|
|
1270
|
+
|
|
1271
|
+
### Typography for Documents
|
|
1272
|
+
- **Font**: Montserrat (embed) or Arial (fallback)
|
|
1273
|
+
- **Title**: 28pt, Charcoal, Light weight
|
|
1274
|
+
- **Heading 1**: 18pt, Charcoal, Medium weight
|
|
1275
|
+
- **Heading 2**: 14pt, Charcoal, Medium weight
|
|
1276
|
+
- **Body**: 11pt, Charcoal, Regular weight
|
|
1277
|
+
- **Line spacing**: 1.15
|
|
1278
|
+
|
|
1279
|
+
### Page Layout
|
|
1280
|
+
- **Margins**: 1.25" top, 1" sides, 1" bottom
|
|
1281
|
+
- **Header**: Logo + document title
|
|
1282
|
+
- **Footer**: Page numbers, confidentiality notice
|
|
1283
|
+
|
|
1284
|
+
### Logo for Documents
|
|
1285
|
+
- **Path**: \`${logoPath}\`
|
|
1286
|
+
- **Size**: 2" wide in header
|
|
1287
|
+
- **Position**: Top-left or centered
|
|
1288
|
+
|
|
1289
|
+
> **Tip**: Use \`get_document_template\` tool for complete code templates.`,
|
|
1290
|
+
marketing: `## Implementation: Marketing Asset (Email/Landing Page)
|
|
1291
|
+
|
|
1292
|
+
### Email-Safe CSS
|
|
1293
|
+
\`\`\`html
|
|
1294
|
+
<!-- Inline styles required for email clients -->
|
|
1295
|
+
<table style="background-color: ${docColors.charcoal.hex}; font-family: 'Montserrat', Arial, sans-serif;">
|
|
1296
|
+
<tr>
|
|
1297
|
+
<td style="color: ${docColors.white.hex}; font-size: 14px; line-height: 1.6;">
|
|
1298
|
+
Your content here
|
|
1299
|
+
</td>
|
|
1300
|
+
</tr>
|
|
1301
|
+
</table>
|
|
1302
|
+
|
|
1303
|
+
<!-- CTA Button (Outlook-safe) -->
|
|
1304
|
+
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse;">
|
|
1305
|
+
<tr>
|
|
1306
|
+
<td style="background-color: ${docColors.teal.hex}; border-radius: 2px; padding: 12px 24px;">
|
|
1307
|
+
<a href="#" style="color: ${docColors.white.hex}; text-decoration: none; font-weight: 500; text-transform: uppercase; letter-spacing: 0.08em;">
|
|
1308
|
+
Learn More
|
|
1309
|
+
</a>
|
|
1310
|
+
</td>
|
|
1311
|
+
</tr>
|
|
1312
|
+
</table>
|
|
1313
|
+
\`\`\`
|
|
1314
|
+
|
|
1315
|
+
### Brand Colors (Inline)
|
|
1316
|
+
- Charcoal: \`${docColors.charcoal.hex}\`
|
|
1317
|
+
- Silver: \`${docColors.silver.hex}\`
|
|
1318
|
+
- Teal (CTA): \`${docColors.teal.hex}\`
|
|
1319
|
+
- White: \`${docColors.white.hex}\`
|
|
1320
|
+
|
|
1321
|
+
### Logo for Email
|
|
1322
|
+
- **Path**: Use absolute URL to hosted logo
|
|
1323
|
+
- **Size**: 150-200px wide
|
|
1324
|
+
- **Alt text**: "Sonance" (required)
|
|
1325
|
+
|
|
1326
|
+
### Key Requirements
|
|
1327
|
+
- All styles must be inline (no external CSS)
|
|
1328
|
+
- Use tables for layout (email compatibility)
|
|
1329
|
+
- Max width: 600px for email, 1200px for landing
|
|
1330
|
+
- Include fallback fonts: Arial, sans-serif`,
|
|
1331
|
+
tech: `## Implementation: Technical Documentation
|
|
1332
|
+
|
|
1333
|
+
### Markdown Structure
|
|
1334
|
+
\`\`\`markdown
|
|
1335
|
+
# Document Title
|
|
1336
|
+
|
|
1337
|
+
Brief introduction paragraph.
|
|
1338
|
+
|
|
1339
|
+
## Section Heading
|
|
1340
|
+
|
|
1341
|
+
Content with proper formatting.
|
|
1342
|
+
|
|
1343
|
+
### Subsection
|
|
1344
|
+
|
|
1345
|
+
- Bullet points for lists
|
|
1346
|
+
- Keep items concise
|
|
1347
|
+
|
|
1348
|
+
\\\`\\\`\\\`typescript
|
|
1349
|
+
// Code blocks with syntax highlighting
|
|
1350
|
+
const example = "Sonance";
|
|
1351
|
+
\\\`\\\`\\\`
|
|
1352
|
+
|
|
1353
|
+
> **Note**: Use blockquotes for callouts
|
|
1354
|
+
\`\`\`
|
|
1355
|
+
|
|
1356
|
+
### Documentation Style
|
|
1357
|
+
- **Headings**: Title case, no periods
|
|
1358
|
+
- **Code**: Syntax-highlighted blocks
|
|
1359
|
+
- **Lists**: Use bullets for unordered, numbers for steps
|
|
1360
|
+
- **Links**: Descriptive text, not "click here"
|
|
1361
|
+
|
|
1362
|
+
### Brand Application
|
|
1363
|
+
- Use "Sonance" not "sonance" or "SONANCE" in prose
|
|
1364
|
+
- Maintain professional, premium tone
|
|
1365
|
+
- Generous spacing between sections
|
|
1366
|
+
- Clear hierarchy with heading levels
|
|
1367
|
+
|
|
1368
|
+
### Logo for Documentation
|
|
1369
|
+
- Include logo in README header
|
|
1370
|
+
- Use dark variant for GitHub (light mode default)
|
|
1371
|
+
- Path: \`${logoPath}\``,
|
|
1372
|
+
};
|
|
1373
|
+
const response = `# Design Context for: ${component_description}
|
|
1374
|
+
|
|
1375
|
+
**Brand**: ${brand.toUpperCase()}
|
|
1376
|
+
**Theme**: ${theme.charAt(0).toUpperCase() + theme.slice(1)}
|
|
1377
|
+
**Output Type**: ${detectedOutputType.toUpperCase()}
|
|
1378
|
+
**Logo**: ${logoPreferenceLabel}
|
|
1379
|
+
${defaultsNotice}
|
|
1380
|
+
${tokens}
|
|
1381
|
+
|
|
1382
|
+
${outputTypeGuidance[detectedOutputType]}
|
|
950
1383
|
|
|
951
1384
|
## Typography (All Brands)
|
|
952
1385
|
- **Font Family**: Montserrat
|
|
953
|
-
- **Headlines**: font-weight 300 (Light) or 500 (Medium)
|
|
1386
|
+
- **Headlines**: font-weight 300 (Light) or 500 (Medium)
|
|
954
1387
|
- **Body**: font-weight 400 (Regular), line-height 1.6
|
|
955
1388
|
- **Buttons/CTAs**: font-weight 500, uppercase, letter-spacing 0.08em
|
|
956
1389
|
|
|
@@ -967,6 +1400,1150 @@ Now design the **${component_description}** following these tokens and principle
|
|
|
967
1400
|
content: [{ type: "text", text: response }],
|
|
968
1401
|
};
|
|
969
1402
|
}
|
|
1403
|
+
case "evaluate_design": {
|
|
1404
|
+
const evalArgs = args;
|
|
1405
|
+
if (!evalArgs.code) {
|
|
1406
|
+
return {
|
|
1407
|
+
content: [{ type: "text", text: "Error: code is required for evaluation" }],
|
|
1408
|
+
isError: true,
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
const code = evalArgs.code;
|
|
1412
|
+
const description = evalArgs.description || "Component";
|
|
1413
|
+
const evalBrand = evalArgs.brand || "sonance";
|
|
1414
|
+
const evalPalette = BRAND_PALETTES[evalBrand];
|
|
1415
|
+
// Smart output type detection
|
|
1416
|
+
const outputType = evalArgs.output_type || smartDetectOutputType(description, code);
|
|
1417
|
+
// Get brand-specific patterns for evaluation
|
|
1418
|
+
const brandPatterns = getBrandColorPatterns(evalBrand);
|
|
1419
|
+
// Output-type-specific checks
|
|
1420
|
+
let checks;
|
|
1421
|
+
let checkDescriptions;
|
|
1422
|
+
if (outputType === "doc") {
|
|
1423
|
+
// Document-specific checks (Word/PDF/Excel) - FORMAT-AGNOSTIC
|
|
1424
|
+
// These patterns work across python-docx, ReportLab, openpyxl, and manual edits
|
|
1425
|
+
// Brand-specific color patterns
|
|
1426
|
+
const primaryPattern = brandPatterns.primaryPattern;
|
|
1427
|
+
const secondaryPattern = brandPatterns.secondaryPattern;
|
|
1428
|
+
const accentPattern = brandPatterns.accentPattern;
|
|
1429
|
+
// Common non-brand colors that indicate incomplete rebranding (exclude target brand colors)
|
|
1430
|
+
const nonBrandColors = evalBrand === "sonance"
|
|
1431
|
+
? /0066CC|0078D4|1E90FF|007BFF|2196F3|FF5722|FF9800|4CAF50|8BC34A|E91E63|9C27B0|673AB7|3F51B5|FC4C02|0F161D|28282B|00A3E1|C02B0A/i
|
|
1432
|
+
: evalBrand === "iport"
|
|
1433
|
+
? /0066CC|0078D4|1E90FF|007BFF|2196F3|FF5722|FF9800|4CAF50|8BC34A|E91E63|9C27B0|673AB7|3F51B5|343d46|00D3C8|28282B|00A3E1|C02B0A/i
|
|
1434
|
+
: /0066CC|0078D4|1E90FF|007BFF|2196F3|FF5722|FF9800|4CAF50|8BC34A|E91E63|9C27B0|673AB7|3F51B5|343d46|00D3C8|FC4C02|0F161D/i;
|
|
1435
|
+
// Brand-specific logo patterns
|
|
1436
|
+
const logoPatterns = {
|
|
1437
|
+
sonance: /logo|sonance.*\.(png|jpg|svg)|Sonance_logo|add_picture|drawImage|Image\(/i,
|
|
1438
|
+
iport: /logo|iport.*\.(png|jpg|svg)|IPORT_Lockup|add_picture|drawImage|Image\(/i,
|
|
1439
|
+
blaze: /logo|blaze.*\.(png|jpg|svg)|Blaze_Lockup|add_picture|drawImage|Image\(/i,
|
|
1440
|
+
};
|
|
1441
|
+
checks = {
|
|
1442
|
+
// Brand Compliance (25%) - REBRANDING FOCUSED
|
|
1443
|
+
usesPrimaryColor: primaryPattern.test(code),
|
|
1444
|
+
usesSecondaryColor: secondaryPattern.test(code) || primaryPattern.test(code), // Secondary optional if primary present
|
|
1445
|
+
noOldBrandColors: !nonBrandColors.test(code),
|
|
1446
|
+
hasFontConfig: /Montserrat|Arial|font.*name|font_name|fontName|font-family/i.test(code),
|
|
1447
|
+
hasLogoReference: logoPatterns[evalBrand].test(code),
|
|
1448
|
+
// Typography (15%)
|
|
1449
|
+
hasFontSizes: /pt|Pt\(|font.*size|fontSize|font-size/i.test(code),
|
|
1450
|
+
hasHeadingStyles: /heading|title|Heading|style.*=.*'Heading|h1|h2|H1|H2/i.test(code),
|
|
1451
|
+
hasLineSpacing: /line.*spacing|lineSpacing|space_after|space_before|leading|line-height/i.test(code),
|
|
1452
|
+
// Layout (15%)
|
|
1453
|
+
hasMargins: /margin|Inches|Cm|Pt.*margin|padding/i.test(code),
|
|
1454
|
+
hasPageSetup: /page.*setup|section|orientation|paper.*size|pagesize/i.test(code),
|
|
1455
|
+
hasStructure: /add_paragraph|add_heading|add_table|Paragraph|Table|<table|<div|<section/i.test(code),
|
|
1456
|
+
// Visual Hierarchy (15%)
|
|
1457
|
+
hasMultipleStyles: /Heading 1|Heading 2|Title|Subtitle|style\[|h1.*h2|H1.*H2/i.test(code),
|
|
1458
|
+
usesProperWeights: !/font-weight.*700|fontWeight.*700|bold.*True|\.bold.*=.*True/i.test(code) || /font-weight.*(300|400|500)|fontWeight.*(300|400|500)|Medium|Light/i.test(code),
|
|
1459
|
+
// Medium Excellence (15%)
|
|
1460
|
+
hasErrorHandling: /try|except|catch|error|Error/i.test(code),
|
|
1461
|
+
hasComments: /^#|\/\/|\/\*|\"\"\"|<!--/m.test(code),
|
|
1462
|
+
isModular: /def |function |class |const.*=.*\(|export/i.test(code),
|
|
1463
|
+
// Polish (15%)
|
|
1464
|
+
hasHeaderFooter: /header|footer|page.*number|Header|Footer/i.test(code),
|
|
1465
|
+
hasWhitespace: /space_after|space_before|margin|padding|gap/i.test(code),
|
|
1466
|
+
};
|
|
1467
|
+
checkDescriptions = {
|
|
1468
|
+
usesPrimaryColor: { category: "Brand Compliance", description: `Uses ${evalPalette.name} primary color (${evalPalette.primary})`, weight: 8 },
|
|
1469
|
+
usesSecondaryColor: { category: "Brand Compliance", description: `Uses ${evalPalette.name} secondary color (${evalPalette.secondary}) or neutral colors`, weight: 4 },
|
|
1470
|
+
noOldBrandColors: { category: "Brand Compliance", description: `No old/non-${evalPalette.name} brand colors present`, weight: 6 },
|
|
1471
|
+
hasFontConfig: { category: "Brand Compliance", description: "Configures Montserrat or Arial font", weight: 5 },
|
|
1472
|
+
hasLogoReference: { category: "Brand Compliance", description: `References ${evalPalette.name} logo`, weight: 2 },
|
|
1473
|
+
hasFontSizes: { category: "Typography", description: "Defines font sizes", weight: 5 },
|
|
1474
|
+
hasHeadingStyles: { category: "Typography", description: "Uses heading styles", weight: 5 },
|
|
1475
|
+
hasLineSpacing: { category: "Typography", description: "Sets line spacing", weight: 5 },
|
|
1476
|
+
hasMargins: { category: "Layout", description: "Configures margins/padding", weight: 5 },
|
|
1477
|
+
hasPageSetup: { category: "Layout", description: "Has page setup", weight: 5 },
|
|
1478
|
+
hasStructure: { category: "Layout", description: "Uses structured document elements", weight: 5 },
|
|
1479
|
+
hasMultipleStyles: { category: "Visual Hierarchy", description: "Multiple heading levels", weight: 8 },
|
|
1480
|
+
usesProperWeights: { category: "Visual Hierarchy", description: "Uses proper font weights (not bold 700)", weight: 7 },
|
|
1481
|
+
hasErrorHandling: { category: "Medium Excellence", description: "Has error handling", weight: 5 },
|
|
1482
|
+
hasComments: { category: "Medium Excellence", description: "Has code comments", weight: 5 },
|
|
1483
|
+
isModular: { category: "Medium Excellence", description: "Uses modular code structure", weight: 5 },
|
|
1484
|
+
hasHeaderFooter: { category: "Polish", description: "Has header/footer", weight: 7 },
|
|
1485
|
+
hasWhitespace: { category: "Polish", description: "Has whitespace/spacing configuration", weight: 8 },
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
else if (outputType === "marketing") {
|
|
1489
|
+
// Marketing-specific checks (Email/Landing)
|
|
1490
|
+
const primaryHex = evalPalette.primary.replace('#', '');
|
|
1491
|
+
checks = {
|
|
1492
|
+
// Brand Compliance (20%)
|
|
1493
|
+
usesInlineColors: new RegExp(`color.*:.*#?${primaryHex}|background.*:.*#?${primaryHex}`, 'i').test(code) || brandPatterns.primaryPattern.test(code),
|
|
1494
|
+
hasInlineStyles: /style\s*=/i.test(code),
|
|
1495
|
+
hasFallbackFonts: /Arial|sans-serif|font-family.*,/i.test(code),
|
|
1496
|
+
// Typography (15%)
|
|
1497
|
+
hasFontSizes: /font-size|fontSize/i.test(code),
|
|
1498
|
+
hasLineHeight: /line-height|lineHeight/i.test(code),
|
|
1499
|
+
hasTextFormatting: /text-transform|letter-spacing|font-weight/i.test(code),
|
|
1500
|
+
// Layout (15%)
|
|
1501
|
+
usesTableLayout: /<table|table.*border/i.test(code),
|
|
1502
|
+
hasMaxWidth: /max-width|maxWidth|width.*600|width.*100%/i.test(code),
|
|
1503
|
+
hasCellPadding: /cellpadding|padding/i.test(code),
|
|
1504
|
+
// Visual Hierarchy (15%)
|
|
1505
|
+
hasCTA: /button|btn|cta|call.*to.*action|href/i.test(code),
|
|
1506
|
+
hasHeadings: /<h[1-6]|font-size.*[2-4][0-9]px/i.test(code),
|
|
1507
|
+
// Medium Excellence (15%)
|
|
1508
|
+
isOutlookSafe: /mso-|<!--\[if|VML/i.test(code),
|
|
1509
|
+
hasAltText: /alt\s*=/i.test(code),
|
|
1510
|
+
hasResponsive: /media.*query|@media|max-width.*100%/i.test(code),
|
|
1511
|
+
// Polish (10%)
|
|
1512
|
+
hasPreheader: /preheader|preview.*text/i.test(code),
|
|
1513
|
+
hasUnsubscribe: /unsubscribe|opt.*out/i.test(code),
|
|
1514
|
+
};
|
|
1515
|
+
checkDescriptions = {
|
|
1516
|
+
usesInlineColors: { category: "Brand Compliance", description: `Uses ${evalPalette.name} colors inline`, weight: 7 },
|
|
1517
|
+
hasInlineStyles: { category: "Brand Compliance", description: "Uses inline styles (required)", weight: 7 },
|
|
1518
|
+
hasFallbackFonts: { category: "Brand Compliance", description: "Has font fallbacks", weight: 6 },
|
|
1519
|
+
hasFontSizes: { category: "Typography", description: "Defines font sizes", weight: 5 },
|
|
1520
|
+
hasLineHeight: { category: "Typography", description: "Sets line height", weight: 5 },
|
|
1521
|
+
hasTextFormatting: { category: "Typography", description: "Has text formatting", weight: 5 },
|
|
1522
|
+
usesTableLayout: { category: "Layout", description: "Uses table layout", weight: 5 },
|
|
1523
|
+
hasMaxWidth: { category: "Layout", description: "Has max-width constraint", weight: 5 },
|
|
1524
|
+
hasCellPadding: { category: "Layout", description: "Has proper spacing", weight: 5 },
|
|
1525
|
+
hasCTA: { category: "Visual Hierarchy", description: "Has call-to-action", weight: 8 },
|
|
1526
|
+
hasHeadings: { category: "Visual Hierarchy", description: "Has heading hierarchy", weight: 7 },
|
|
1527
|
+
isOutlookSafe: { category: "Medium Excellence", description: "Outlook compatible", weight: 5 },
|
|
1528
|
+
hasAltText: { category: "Medium Excellence", description: "Images have alt text", weight: 5 },
|
|
1529
|
+
hasResponsive: { category: "Medium Excellence", description: "Has responsive design", weight: 5 },
|
|
1530
|
+
hasPreheader: { category: "Polish", description: "Has preheader text", weight: 5 },
|
|
1531
|
+
hasUnsubscribe: { category: "Polish", description: "Has unsubscribe link", weight: 5 },
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
else if (outputType === "tech") {
|
|
1535
|
+
// Technical documentation checks
|
|
1536
|
+
const brandNamePattern = new RegExp(evalPalette.name, 'i');
|
|
1537
|
+
checks = {
|
|
1538
|
+
// Brand Compliance (20%)
|
|
1539
|
+
usesBrandName: brandNamePattern.test(code),
|
|
1540
|
+
hasConsistentFormatting: /^#{1,3}\s/m.test(code),
|
|
1541
|
+
hasProfessionalTone: !/lol|haha|!!!|\?\?/i.test(code),
|
|
1542
|
+
// Typography (15%)
|
|
1543
|
+
hasHeadingHierarchy: /^#\s[\s\S]*^##\s/m.test(code),
|
|
1544
|
+
hasCodeBlocks: /```[\w]*\n/m.test(code),
|
|
1545
|
+
hasLists: /^[\-\*]\s|^\d+\.\s/m.test(code),
|
|
1546
|
+
// Layout (15%)
|
|
1547
|
+
hasTableOfContents: /table.*contents|\[.*\]\(#/i.test(code),
|
|
1548
|
+
hasSections: /^##\s/m.test(code),
|
|
1549
|
+
hasSpacing: /\n\n/.test(code),
|
|
1550
|
+
// Visual Hierarchy (15%)
|
|
1551
|
+
hasMultipleHeadingLevels: /^#\s[\s\S]*^##\s[\s\S]*^###\s/m.test(code),
|
|
1552
|
+
hasEmphasis: /\*\*.*\*\*|__.*__/m.test(code),
|
|
1553
|
+
// Medium Excellence (15%)
|
|
1554
|
+
hasLinks: /\[.*\]\(http/m.test(code),
|
|
1555
|
+
hasImageAlt: /!\[.+\]/m.test(code),
|
|
1556
|
+
hasCallouts: />\s\*\*|>\s\[!|:::/.test(code),
|
|
1557
|
+
// Polish (10%)
|
|
1558
|
+
hasIntro: /^#\s.*\n\n\w/m.test(code),
|
|
1559
|
+
hasConclusion: /conclusion|summary|next.*steps/i.test(code),
|
|
1560
|
+
};
|
|
1561
|
+
checkDescriptions = {
|
|
1562
|
+
usesBrandName: { category: "Brand Compliance", description: `Uses '${evalPalette.name}' correctly`, weight: 7 },
|
|
1563
|
+
hasConsistentFormatting: { category: "Brand Compliance", description: "Consistent markdown formatting", weight: 7 },
|
|
1564
|
+
hasProfessionalTone: { category: "Brand Compliance", description: "Professional tone", weight: 6 },
|
|
1565
|
+
hasHeadingHierarchy: { category: "Typography", description: "Proper heading hierarchy", weight: 5 },
|
|
1566
|
+
hasCodeBlocks: { category: "Typography", description: "Uses code blocks", weight: 5 },
|
|
1567
|
+
hasLists: { category: "Typography", description: "Uses lists appropriately", weight: 5 },
|
|
1568
|
+
hasTableOfContents: { category: "Layout", description: "Has navigation/TOC", weight: 5 },
|
|
1569
|
+
hasSections: { category: "Layout", description: "Has clear sections", weight: 5 },
|
|
1570
|
+
hasSpacing: { category: "Layout", description: "Has proper spacing", weight: 5 },
|
|
1571
|
+
hasMultipleHeadingLevels: { category: "Visual Hierarchy", description: "Multiple heading levels", weight: 8 },
|
|
1572
|
+
hasEmphasis: { category: "Visual Hierarchy", description: "Uses emphasis (bold)", weight: 7 },
|
|
1573
|
+
hasLinks: { category: "Medium Excellence", description: "Has external links", weight: 5 },
|
|
1574
|
+
hasImageAlt: { category: "Medium Excellence", description: "Images have alt text", weight: 5 },
|
|
1575
|
+
hasCallouts: { category: "Medium Excellence", description: "Has callouts/notes", weight: 5 },
|
|
1576
|
+
hasIntro: { category: "Polish", description: "Has introduction", weight: 5 },
|
|
1577
|
+
hasConclusion: { category: "Polish", description: "Has conclusion/next steps", weight: 5 },
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
else {
|
|
1581
|
+
// UI Component checks (React/Next.js) - default
|
|
1582
|
+
checks = {
|
|
1583
|
+
// Brand Compliance (20%)
|
|
1584
|
+
usesSemantic: /bg-(primary|secondary|background|foreground|card|input)|text-(primary|secondary|foreground|muted)/.test(code),
|
|
1585
|
+
noHardcodedColors: !/#[0-9a-fA-F]{6}/.test(code) || /--sonance-|var\(--/.test(code),
|
|
1586
|
+
usesCorrectRadius: !/rounded-(xl|lg|2xl|3xl|full)/.test(code) || /rounded-(sm|none|md)/.test(code),
|
|
1587
|
+
// Typography (15%)
|
|
1588
|
+
usesMontserrat: /font-sans|Montserrat|--font-primary/.test(code),
|
|
1589
|
+
noFontBold: !/font-bold/.test(code) || /font-(light|medium|normal)/.test(code),
|
|
1590
|
+
hasTypographyHierarchy: /text-(xs|sm|base|lg|xl|2xl|3xl|4xl)/.test(code),
|
|
1591
|
+
// Color Application (10%)
|
|
1592
|
+
hasDarkMode: /dark:|data-theme|resolvedTheme/.test(code),
|
|
1593
|
+
usesVariables: /var\(--|--[a-z]/.test(code),
|
|
1594
|
+
// Layout & Spacing (15%)
|
|
1595
|
+
hasGenerousSpacing: /p[xy]?-(12|16|20|24|32)|space-(12|16|20|24)/.test(code),
|
|
1596
|
+
noTightSpacing: !/p[xy]?-[12](?!\d)/.test(code),
|
|
1597
|
+
// Visual Hierarchy (15%)
|
|
1598
|
+
hasMultipleSizes: (/text-sm/.test(code) && /text-(lg|xl|2xl)/.test(code)),
|
|
1599
|
+
hasWeightVariation: /font-(light|medium|normal)/.test(code),
|
|
1600
|
+
// Medium Excellence (15%)
|
|
1601
|
+
isTypeScript: /interface |type |: string|: number|: boolean|React\./.test(code),
|
|
1602
|
+
hasAccessibility: /aria-|role=|alt=/.test(code),
|
|
1603
|
+
hasTransitions: /transition|duration-/.test(code),
|
|
1604
|
+
// Polish (10%)
|
|
1605
|
+
hasHoverStates: /hover:/.test(code),
|
|
1606
|
+
hasFocusStates: /focus:|focus-visible:/.test(code),
|
|
1607
|
+
};
|
|
1608
|
+
checkDescriptions = {
|
|
1609
|
+
usesSemantic: { category: "Brand Compliance", description: "Uses semantic color classes", weight: 4 },
|
|
1610
|
+
noHardcodedColors: { category: "Brand Compliance", description: "No hardcoded hex colors", weight: 3 },
|
|
1611
|
+
usesCorrectRadius: { category: "Brand Compliance", description: "Uses sharp corners (rounded-sm)", weight: 3 },
|
|
1612
|
+
usesMontserrat: { category: "Typography", description: "Uses Montserrat font", weight: 4 },
|
|
1613
|
+
noFontBold: { category: "Typography", description: "No font-bold on headlines", weight: 4 },
|
|
1614
|
+
hasTypographyHierarchy: { category: "Typography", description: "Has typography hierarchy", weight: 2 },
|
|
1615
|
+
hasDarkMode: { category: "Color Application", description: "Supports dark mode", weight: 5 },
|
|
1616
|
+
usesVariables: { category: "Color Application", description: "Uses CSS variables", weight: 5 },
|
|
1617
|
+
hasGenerousSpacing: { category: "Layout", description: "Has generous spacing", weight: 5 },
|
|
1618
|
+
noTightSpacing: { category: "Layout", description: "No cramped layouts", weight: 5 },
|
|
1619
|
+
hasMultipleSizes: { category: "Visual Hierarchy", description: "Multiple font sizes", weight: 5 },
|
|
1620
|
+
hasWeightVariation: { category: "Visual Hierarchy", description: "Font weight variation", weight: 5 },
|
|
1621
|
+
isTypeScript: { category: "Medium Excellence", description: "Uses TypeScript", weight: 4 },
|
|
1622
|
+
hasAccessibility: { category: "Medium Excellence", description: "Has accessibility attrs", weight: 3 },
|
|
1623
|
+
hasTransitions: { category: "Medium Excellence", description: "Has transitions", weight: 3 },
|
|
1624
|
+
hasHoverStates: { category: "Polish", description: "Has hover states", weight: 5 },
|
|
1625
|
+
hasFocusStates: { category: "Polish", description: "Has focus states", weight: 5 },
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
// Calculate score based on weights
|
|
1629
|
+
let totalScore = 0;
|
|
1630
|
+
const checkResults = [];
|
|
1631
|
+
for (const [key, passed] of Object.entries(checks)) {
|
|
1632
|
+
const desc = checkDescriptions[key];
|
|
1633
|
+
if (desc) {
|
|
1634
|
+
checkResults.push({
|
|
1635
|
+
check: key,
|
|
1636
|
+
passed,
|
|
1637
|
+
category: desc.category,
|
|
1638
|
+
description: desc.description,
|
|
1639
|
+
weight: desc.weight,
|
|
1640
|
+
});
|
|
1641
|
+
if (passed) {
|
|
1642
|
+
totalScore += desc.weight;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
// Determine tier
|
|
1647
|
+
let tier;
|
|
1648
|
+
let tierLabel;
|
|
1649
|
+
let deliverable;
|
|
1650
|
+
if (totalScore >= 95) {
|
|
1651
|
+
tier = 5;
|
|
1652
|
+
tierLabel = "Portfolio-Worthy";
|
|
1653
|
+
deliverable = true;
|
|
1654
|
+
}
|
|
1655
|
+
else if (totalScore >= 85) {
|
|
1656
|
+
tier = 4;
|
|
1657
|
+
tierLabel = "Professional";
|
|
1658
|
+
deliverable = true;
|
|
1659
|
+
}
|
|
1660
|
+
else if (totalScore >= 70) {
|
|
1661
|
+
tier = 3;
|
|
1662
|
+
tierLabel = "Needs Iteration";
|
|
1663
|
+
deliverable = false;
|
|
1664
|
+
}
|
|
1665
|
+
else if (totalScore >= 50) {
|
|
1666
|
+
tier = 2;
|
|
1667
|
+
tierLabel = "Major Iteration Required";
|
|
1668
|
+
deliverable = false;
|
|
1669
|
+
}
|
|
1670
|
+
else {
|
|
1671
|
+
tier = 1;
|
|
1672
|
+
tierLabel = "Rebuild Required";
|
|
1673
|
+
deliverable = false;
|
|
1674
|
+
}
|
|
1675
|
+
// Build gaps list from failed checks
|
|
1676
|
+
const gaps = checkResults
|
|
1677
|
+
.filter(c => !c.passed)
|
|
1678
|
+
.map(c => c.description);
|
|
1679
|
+
// Group results by category
|
|
1680
|
+
const byCategory = {};
|
|
1681
|
+
for (const result of checkResults) {
|
|
1682
|
+
if (!byCategory[result.category]) {
|
|
1683
|
+
byCategory[result.category] = [];
|
|
1684
|
+
}
|
|
1685
|
+
byCategory[result.category].push(result);
|
|
1686
|
+
}
|
|
1687
|
+
// Build category scores table
|
|
1688
|
+
const categoryScores = Object.entries(byCategory).map(([category, results]) => {
|
|
1689
|
+
const earned = results.filter(r => r.passed).reduce((sum, r) => sum + r.weight, 0);
|
|
1690
|
+
const max = results.reduce((sum, r) => sum + r.weight, 0);
|
|
1691
|
+
const checkMarks = results.map(r => `${r.passed ? "✓" : "✗"} ${r.description}`).join(", ");
|
|
1692
|
+
return { category, earned, max, checkMarks };
|
|
1693
|
+
});
|
|
1694
|
+
// Output type labels
|
|
1695
|
+
const outputTypeLabels = {
|
|
1696
|
+
ui: "React/Next.js UI Component",
|
|
1697
|
+
doc: "Document (Word/PDF/Excel)",
|
|
1698
|
+
marketing: "Marketing Asset (Email/Landing)",
|
|
1699
|
+
tech: "Technical Documentation",
|
|
1700
|
+
};
|
|
1701
|
+
const evalResponse = `# Design Excellence Evaluation: ${description}
|
|
1702
|
+
|
|
1703
|
+
**Output Type**: ${outputTypeLabels[outputType]} (${outputType === evalArgs.output_type ? "specified" : "auto-detected"})
|
|
1704
|
+
|
|
1705
|
+
## Score: ${totalScore}/100 — Tier ${tier} (${tierLabel})
|
|
1706
|
+
|
|
1707
|
+
${deliverable ? "✅ **Ready for delivery**" : "⚠️ **Iterate before delivery** — minimum threshold is 85 (Tier 4)"}
|
|
1708
|
+
|
|
1709
|
+
---
|
|
1710
|
+
|
|
1711
|
+
## Dimension Scores
|
|
1712
|
+
|
|
1713
|
+
| Category | Score | Max | Details |
|
|
1714
|
+
|----------|-------|-----|---------|
|
|
1715
|
+
${categoryScores.map(c => `| ${c.category} | ${c.earned} | ${c.max} | ${c.checkMarks} |`).join('\n')}
|
|
1716
|
+
|
|
1717
|
+
---
|
|
1718
|
+
|
|
1719
|
+
${gaps.length > 0 ? `## Gaps to Address
|
|
1720
|
+
|
|
1721
|
+
${gaps.map((gap, i) => `${i + 1}. ${gap}`).join('\n')}
|
|
1722
|
+
|
|
1723
|
+
---` : "## No major gaps identified — excellent work!"}
|
|
1724
|
+
|
|
1725
|
+
## Next Steps
|
|
1726
|
+
|
|
1727
|
+
${deliverable
|
|
1728
|
+
? "This output meets Sonance brand standards. Consider polish improvements for Tier 5."
|
|
1729
|
+
: `Iterate on the gaps above to reach Tier 4 (85+). Current gap: ${85 - totalScore} points.`}
|
|
1730
|
+
`;
|
|
1731
|
+
return {
|
|
1732
|
+
content: [{ type: "text", text: evalResponse }],
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
case "get_excellence_checklist": {
|
|
1736
|
+
const checklist = `# Sonance Pre-Delivery Excellence Checklist
|
|
1737
|
+
|
|
1738
|
+
Before delivering any Sonance-branded UI work, verify each item:
|
|
1739
|
+
|
|
1740
|
+
## Typography
|
|
1741
|
+
- [ ] Montserrat font is loaded and applied
|
|
1742
|
+
- [ ] Headlines use weight 300 (light) or 500 (medium) — **never 700 (bold)**
|
|
1743
|
+
- [ ] Clear 4-level typography hierarchy
|
|
1744
|
+
- [ ] Appropriate line-height (1.5-1.75 for body)
|
|
1745
|
+
|
|
1746
|
+
## Colors
|
|
1747
|
+
- [ ] All colors use semantic CSS variables (--background, --foreground, etc.)
|
|
1748
|
+
- [ ] No hardcoded hex colors in classes
|
|
1749
|
+
- [ ] Light mode works correctly
|
|
1750
|
+
- [ ] Dark mode works correctly
|
|
1751
|
+
- [ ] Contrast ratio ≥4.5:1 for text
|
|
1752
|
+
|
|
1753
|
+
## Layout & Spacing
|
|
1754
|
+
- [ ] Generous section padding (80-120px vertical on desktop)
|
|
1755
|
+
- [ ] "Designed to Disappear" aesthetic — breathing room
|
|
1756
|
+
- [ ] Sharp corners (0-4px radius max)
|
|
1757
|
+
- [ ] No cramped layouts
|
|
1758
|
+
|
|
1759
|
+
## Interactivity
|
|
1760
|
+
- [ ] Hover states on all clickable elements
|
|
1761
|
+
- [ ] Focus-visible states for keyboard navigation
|
|
1762
|
+
- [ ] Smooth transitions (200-400ms, ease-out)
|
|
1763
|
+
- [ ] No bouncy or dramatic animations
|
|
1764
|
+
|
|
1765
|
+
## Accessibility
|
|
1766
|
+
- [ ] Semantic HTML elements
|
|
1767
|
+
- [ ] ARIA labels where needed
|
|
1768
|
+
- [ ] Alt text on images
|
|
1769
|
+
- [ ] Keyboard navigable
|
|
1770
|
+
|
|
1771
|
+
## Code Quality
|
|
1772
|
+
- [ ] TypeScript types/interfaces
|
|
1773
|
+
- [ ] Responsive design (mobile-first)
|
|
1774
|
+
- [ ] No console errors
|
|
1775
|
+
- [ ] Clean, readable code
|
|
1776
|
+
|
|
1777
|
+
---
|
|
1778
|
+
|
|
1779
|
+
## Quality Gate
|
|
1780
|
+
|
|
1781
|
+
**Minimum for delivery:** Score 85+ on self-evaluation (Tier 4)
|
|
1782
|
+
**Target:** Score 95+ (Tier 5 — Portfolio-Worthy)
|
|
1783
|
+
|
|
1784
|
+
Use \`evaluate_design\` tool to score your work before delivery.
|
|
1785
|
+
`;
|
|
1786
|
+
return {
|
|
1787
|
+
content: [{ type: "text", text: checklist }],
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
case "get_anti_patterns": {
|
|
1791
|
+
const antiPatterns = `# Sonance Anti-Patterns: What NOT to Do
|
|
1792
|
+
|
|
1793
|
+
## Critical Failures (Immediate Tier 1)
|
|
1794
|
+
|
|
1795
|
+
These mistakes immediately drop your score to Tier 1 and require a rebuild:
|
|
1796
|
+
|
|
1797
|
+
### 1. Bold Headlines
|
|
1798
|
+
\`\`\`jsx
|
|
1799
|
+
// ❌ WRONG — never use font-bold for headlines
|
|
1800
|
+
<h1 className="font-bold text-4xl">Welcome to Sonance</h1>
|
|
1801
|
+
|
|
1802
|
+
// ✅ CORRECT — use light (300) or medium (500)
|
|
1803
|
+
<h1 className="font-light text-4xl">Welcome to Sonance</h1>
|
|
1804
|
+
<h1 className="font-medium text-4xl">Welcome to Sonance</h1>
|
|
1805
|
+
\`\`\`
|
|
1806
|
+
|
|
1807
|
+
### 2. Hardcoded Colors
|
|
1808
|
+
\`\`\`jsx
|
|
1809
|
+
// ❌ WRONG — hardcoded hex values
|
|
1810
|
+
<div className="bg-[#343d46] text-[#ffffff]">
|
|
1811
|
+
|
|
1812
|
+
// ✅ CORRECT — semantic tokens
|
|
1813
|
+
<div className="bg-primary text-primary-foreground">
|
|
1814
|
+
<div className="bg-sonance-charcoal text-white">
|
|
1815
|
+
\`\`\`
|
|
1816
|
+
|
|
1817
|
+
### 3. Rounded Corners
|
|
1818
|
+
\`\`\`jsx
|
|
1819
|
+
// ❌ WRONG — too playful for Sonance
|
|
1820
|
+
<div className="rounded-xl">
|
|
1821
|
+
<div className="rounded-lg">
|
|
1822
|
+
<div className="rounded-2xl">
|
|
1823
|
+
|
|
1824
|
+
// ✅ CORRECT — sharp, premium aesthetic
|
|
1825
|
+
<div className="rounded-sm"> // 2px
|
|
1826
|
+
<div className="rounded-none"> // 0px
|
|
1827
|
+
<div className="rounded-md"> // 4px max
|
|
1828
|
+
\`\`\`
|
|
1829
|
+
|
|
1830
|
+
### 4. Cramped Layouts
|
|
1831
|
+
\`\`\`jsx
|
|
1832
|
+
// ❌ WRONG — no breathing room
|
|
1833
|
+
<section className="py-4 px-2">
|
|
1834
|
+
<section className="py-8 px-4">
|
|
1835
|
+
|
|
1836
|
+
// ✅ CORRECT — "Designed to Disappear" generous spacing
|
|
1837
|
+
<section className="py-20 px-6 lg:py-24 lg:px-8">
|
|
1838
|
+
<section className="py-16 px-6 md:py-24">
|
|
1839
|
+
\`\`\`
|
|
1840
|
+
|
|
1841
|
+
---
|
|
1842
|
+
|
|
1843
|
+
## Common Mistakes (Tier 3 Traps)
|
|
1844
|
+
|
|
1845
|
+
These keep you stuck at Tier 3 (70-84), below the delivery threshold:
|
|
1846
|
+
|
|
1847
|
+
### Wrong Font Family
|
|
1848
|
+
\`\`\`jsx
|
|
1849
|
+
// ❌ WRONG — generic fonts
|
|
1850
|
+
font-family: 'Inter', sans-serif;
|
|
1851
|
+
font-family: 'Roboto', sans-serif;
|
|
1852
|
+
className="font-inter"
|
|
1853
|
+
|
|
1854
|
+
// ✅ CORRECT — Montserrat only
|
|
1855
|
+
font-family: 'Montserrat', sans-serif;
|
|
1856
|
+
className="font-sans" // with proper Tailwind config
|
|
1857
|
+
\`\`\`
|
|
1858
|
+
|
|
1859
|
+
### Light Mode Only
|
|
1860
|
+
\`\`\`jsx
|
|
1861
|
+
// ❌ WRONG — no dark mode support
|
|
1862
|
+
<div className="bg-white text-gray-900">
|
|
1863
|
+
|
|
1864
|
+
// ✅ CORRECT — theme-aware
|
|
1865
|
+
<div className="bg-background text-foreground">
|
|
1866
|
+
<div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
|
|
1867
|
+
\`\`\`
|
|
1868
|
+
|
|
1869
|
+
### Missing Hover States
|
|
1870
|
+
\`\`\`jsx
|
|
1871
|
+
// ❌ WRONG — static button
|
|
1872
|
+
<button className="bg-primary text-white px-6 py-3">
|
|
1873
|
+
|
|
1874
|
+
// ✅ CORRECT — interactive feedback
|
|
1875
|
+
<button className="bg-primary text-white px-6 py-3 hover:bg-primary-hover transition-colors">
|
|
1876
|
+
\`\`\`
|
|
1877
|
+
|
|
1878
|
+
### Missing Focus States
|
|
1879
|
+
\`\`\`jsx
|
|
1880
|
+
// ❌ WRONG — accessibility issue
|
|
1881
|
+
<button className="bg-primary">Click</button>
|
|
1882
|
+
|
|
1883
|
+
// ✅ CORRECT — keyboard accessible
|
|
1884
|
+
<button className="bg-primary focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary">
|
|
1885
|
+
\`\`\`
|
|
1886
|
+
|
|
1887
|
+
### First-Draft Delivery
|
|
1888
|
+
\`\`\`
|
|
1889
|
+
// ❌ WRONG workflow
|
|
1890
|
+
Create → Deliver
|
|
1891
|
+
|
|
1892
|
+
// ✅ CORRECT workflow
|
|
1893
|
+
Create → Evaluate → Iterate → Evaluate → Deliver (when score ≥85)
|
|
1894
|
+
\`\`\`
|
|
1895
|
+
|
|
1896
|
+
---
|
|
1897
|
+
|
|
1898
|
+
## Quick Reference
|
|
1899
|
+
|
|
1900
|
+
| Anti-Pattern | Fix |
|
|
1901
|
+
|--------------|-----|
|
|
1902
|
+
| \`font-bold\` on headlines | \`font-light\` or \`font-medium\` |
|
|
1903
|
+
| \`#343d46\` hardcoded | \`bg-primary\` or \`var(--primary)\` |
|
|
1904
|
+
| \`rounded-lg\` | \`rounded-sm\` or \`rounded-none\` |
|
|
1905
|
+
| \`py-4\` sections | \`py-20\` or \`py-24\` |
|
|
1906
|
+
| Inter/Roboto font | Montserrat |
|
|
1907
|
+
| Light mode only | Add \`dark:\` variants |
|
|
1908
|
+
| No hover states | Add \`hover:\` classes |
|
|
1909
|
+
| No focus states | Add \`focus-visible:\` classes |
|
|
1910
|
+
|
|
1911
|
+
---
|
|
1912
|
+
|
|
1913
|
+
**Remember:** The minimum delivery threshold is **85 points (Tier 4)**. Use \`evaluate_design\` to check your score.
|
|
1914
|
+
`;
|
|
1915
|
+
return {
|
|
1916
|
+
content: [{ type: "text", text: antiPatterns }],
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
case "get_document_template": {
|
|
1920
|
+
const templateArgs = args;
|
|
1921
|
+
if (!templateArgs.format) {
|
|
1922
|
+
return {
|
|
1923
|
+
content: [{ type: "text", text: "Error: format is required (word-python, pdf-python, or excel-python)" }],
|
|
1924
|
+
isError: true,
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
// Get brand-specific colors
|
|
1928
|
+
const targetBrand = templateArgs.brand || "sonance";
|
|
1929
|
+
const colors = getBrandColorsForDocsMulti(targetBrand);
|
|
1930
|
+
const palette = BRAND_PALETTES[targetBrand];
|
|
1931
|
+
const templateType = templateArgs.template_type || "report";
|
|
1932
|
+
// Brand-specific color names for comments
|
|
1933
|
+
const colorNames = {
|
|
1934
|
+
sonance: { primary: "CHARCOAL", secondary: "SILVER", accent: "TEAL" },
|
|
1935
|
+
iport: { primary: "IPORT_DARK", secondary: "IPORT_ORANGE", accent: "IPORT_ORANGE" },
|
|
1936
|
+
blaze: { primary: "BLAZE_DARK", secondary: "BLAZE_BLUE", accent: "BLAZE_BLUE" },
|
|
1937
|
+
};
|
|
1938
|
+
const brandNames = colorNames[targetBrand];
|
|
1939
|
+
const templates = {
|
|
1940
|
+
"word-python": `# ${palette.name} Word Document Template (python-docx)
|
|
1941
|
+
|
|
1942
|
+
\`\`\`python
|
|
1943
|
+
from docx import Document
|
|
1944
|
+
from docx.shared import Inches, Pt, RGBColor
|
|
1945
|
+
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
1946
|
+
from docx.enum.style import WD_STYLE_TYPE
|
|
1947
|
+
|
|
1948
|
+
# ============================================
|
|
1949
|
+
# ${palette.name.toUpperCase()} BRAND CONSTANTS (Live from Component Library)
|
|
1950
|
+
# ============================================
|
|
1951
|
+
${brandNames.primary} = RGBColor(0x${colors.primary.hexNoHash.slice(0, 2)}, 0x${colors.primary.hexNoHash.slice(2, 4)}, 0x${colors.primary.hexNoHash.slice(4, 6)})
|
|
1952
|
+
${brandNames.secondary} = RGBColor(0x${colors.secondary.hexNoHash.slice(0, 2)}, 0x${colors.secondary.hexNoHash.slice(2, 4)}, 0x${colors.secondary.hexNoHash.slice(4, 6)})
|
|
1953
|
+
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
|
1954
|
+
${brandNames.accent} = RGBColor(0x${colors.accent.hexNoHash.slice(0, 2)}, 0x${colors.accent.hexNoHash.slice(2, 4)}, 0x${colors.accent.hexNoHash.slice(4, 6)})
|
|
1955
|
+
|
|
1956
|
+
FONT_PRIMARY = 'Montserrat'
|
|
1957
|
+
FONT_FALLBACK = 'Arial'
|
|
1958
|
+
|
|
1959
|
+
# ============================================
|
|
1960
|
+
# CREATE DOCUMENT
|
|
1961
|
+
# ============================================
|
|
1962
|
+
doc = Document()
|
|
1963
|
+
|
|
1964
|
+
# Set default font (will use fallback if Montserrat not installed)
|
|
1965
|
+
style = doc.styles['Normal']
|
|
1966
|
+
font = style.font
|
|
1967
|
+
font.name = FONT_FALLBACK # Use fallback; Montserrat if embedded
|
|
1968
|
+
font.size = Pt(11)
|
|
1969
|
+
font.color.rgb = ${brandNames.primary}
|
|
1970
|
+
|
|
1971
|
+
# Configure margins (1.25" top, 1" sides)
|
|
1972
|
+
sections = doc.sections
|
|
1973
|
+
for section in sections:
|
|
1974
|
+
section.top_margin = Inches(1.25)
|
|
1975
|
+
section.bottom_margin = Inches(1)
|
|
1976
|
+
section.left_margin = Inches(1)
|
|
1977
|
+
section.right_margin = Inches(1)
|
|
1978
|
+
|
|
1979
|
+
# ============================================
|
|
1980
|
+
# TITLE STYLE
|
|
1981
|
+
# ============================================
|
|
1982
|
+
title_style = doc.styles.add_style('${palette.name}Title', WD_STYLE_TYPE.PARAGRAPH)
|
|
1983
|
+
title_style.font.size = Pt(28)
|
|
1984
|
+
title_style.font.color.rgb = ${brandNames.primary}
|
|
1985
|
+
title_style.font.name = FONT_FALLBACK
|
|
1986
|
+
title_style.paragraph_format.space_after = Pt(24)
|
|
1987
|
+
title_style.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
1988
|
+
|
|
1989
|
+
# ============================================
|
|
1990
|
+
# HEADING STYLES
|
|
1991
|
+
# ============================================
|
|
1992
|
+
h1_style = doc.styles.add_style('${palette.name}H1', WD_STYLE_TYPE.PARAGRAPH)
|
|
1993
|
+
h1_style.font.size = Pt(18)
|
|
1994
|
+
h1_style.font.color.rgb = ${brandNames.primary}
|
|
1995
|
+
h1_style.font.name = FONT_FALLBACK
|
|
1996
|
+
h1_style.paragraph_format.space_before = Pt(18)
|
|
1997
|
+
h1_style.paragraph_format.space_after = Pt(12)
|
|
1998
|
+
|
|
1999
|
+
h2_style = doc.styles.add_style('${palette.name}H2', WD_STYLE_TYPE.PARAGRAPH)
|
|
2000
|
+
h2_style.font.size = Pt(14)
|
|
2001
|
+
h2_style.font.color.rgb = ${brandNames.primary}
|
|
2002
|
+
h2_style.font.name = FONT_FALLBACK
|
|
2003
|
+
h2_style.paragraph_format.space_before = Pt(14)
|
|
2004
|
+
h2_style.paragraph_format.space_after = Pt(8)
|
|
2005
|
+
|
|
2006
|
+
# ============================================
|
|
2007
|
+
# ADD CONTENT
|
|
2008
|
+
# ============================================
|
|
2009
|
+
# Add logo (if available)
|
|
2010
|
+
# doc.add_picture('path/to/logo.png', width=Inches(2))
|
|
2011
|
+
|
|
2012
|
+
# Title
|
|
2013
|
+
doc.add_paragraph('${templateType === 'proposal' ? 'Project Proposal' : templateType === 'spec-sheet' ? 'Product Specification' : templateType === 'invoice' ? 'Invoice' : 'Report Title'}', style='${palette.name}Title')
|
|
2014
|
+
|
|
2015
|
+
# Introduction
|
|
2016
|
+
doc.add_paragraph('Section Heading', style='${palette.name}H1')
|
|
2017
|
+
doc.add_paragraph('Your content here. Use the ${palette.name} brand voice: professional, premium, pioneering.')
|
|
2018
|
+
|
|
2019
|
+
# Subsection
|
|
2020
|
+
doc.add_paragraph('Subsection', style='${palette.name}H2')
|
|
2021
|
+
doc.add_paragraph('Additional details with proper formatting.')
|
|
2022
|
+
|
|
2023
|
+
# ============================================
|
|
2024
|
+
# HEADER/FOOTER
|
|
2025
|
+
# ============================================
|
|
2026
|
+
header = doc.sections[0].header
|
|
2027
|
+
header_para = header.paragraphs[0]
|
|
2028
|
+
header_para.text = "${palette.name.toUpperCase()}"
|
|
2029
|
+
header_para.style.font.size = Pt(9)
|
|
2030
|
+
header_para.style.font.color.rgb = ${brandNames.primary}
|
|
2031
|
+
|
|
2032
|
+
footer = doc.sections[0].footer
|
|
2033
|
+
footer_para = footer.paragraphs[0]
|
|
2034
|
+
footer_para.text = "Confidential | Page "
|
|
2035
|
+
footer_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
2036
|
+
|
|
2037
|
+
# ============================================
|
|
2038
|
+
# SAVE
|
|
2039
|
+
# ============================================
|
|
2040
|
+
doc.save('${targetBrand}_${templateType}.docx')
|
|
2041
|
+
\`\`\`
|
|
2042
|
+
|
|
2043
|
+
## Key Points
|
|
2044
|
+
- **Colors**: Use ${brandNames.primary} for text, ${brandNames.secondary} for backgrounds/borders
|
|
2045
|
+
- **Fonts**: Montserrat preferred, Arial as fallback
|
|
2046
|
+
- **Margins**: 1.25" top, 1" sides and bottom
|
|
2047
|
+
- **Hierarchy**: Title (28pt) > H1 (18pt) > H2 (14pt) > Body (11pt)
|
|
2048
|
+
${palette.preferredTheme === "dark" ? `- **Note**: ${palette.name} prefers dark backgrounds - consider inverting` : ""}
|
|
2049
|
+
`,
|
|
2050
|
+
"pdf-python": `# ${palette.name} PDF Template (ReportLab)
|
|
2051
|
+
|
|
2052
|
+
\`\`\`python
|
|
2053
|
+
from reportlab.lib.pagesizes import letter
|
|
2054
|
+
from reportlab.lib.units import inch
|
|
2055
|
+
from reportlab.lib.colors import HexColor
|
|
2056
|
+
from reportlab.pdfgen import canvas
|
|
2057
|
+
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
2058
|
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image
|
|
2059
|
+
|
|
2060
|
+
# ============================================
|
|
2061
|
+
# ${palette.name.toUpperCase()} BRAND CONSTANTS (Live from Component Library)
|
|
2062
|
+
# ============================================
|
|
2063
|
+
${brandNames.primary} = HexColor('${colors.primary.hex}')
|
|
2064
|
+
${brandNames.secondary} = HexColor('${colors.secondary.hex}')
|
|
2065
|
+
WHITE = HexColor('${colors.white.hex}')
|
|
2066
|
+
${brandNames.accent} = HexColor('${colors.accent.hex}')
|
|
2067
|
+
|
|
2068
|
+
FONT_PRIMARY = 'Helvetica' # Use Helvetica (built-in), or register Montserrat
|
|
2069
|
+
|
|
2070
|
+
# ============================================
|
|
2071
|
+
# STYLES
|
|
2072
|
+
# ============================================
|
|
2073
|
+
styles = getSampleStyleSheet()
|
|
2074
|
+
|
|
2075
|
+
title_style = ParagraphStyle(
|
|
2076
|
+
'${palette.name}Title',
|
|
2077
|
+
parent=styles['Heading1'],
|
|
2078
|
+
fontName=FONT_PRIMARY,
|
|
2079
|
+
fontSize=28,
|
|
2080
|
+
textColor=${brandNames.primary},
|
|
2081
|
+
spaceAfter=24,
|
|
2082
|
+
)
|
|
2083
|
+
|
|
2084
|
+
h1_style = ParagraphStyle(
|
|
2085
|
+
'${palette.name}H1',
|
|
2086
|
+
parent=styles['Heading2'],
|
|
2087
|
+
fontName=FONT_PRIMARY,
|
|
2088
|
+
fontSize=18,
|
|
2089
|
+
textColor=${brandNames.primary},
|
|
2090
|
+
spaceBefore=18,
|
|
2091
|
+
spaceAfter=12,
|
|
2092
|
+
)
|
|
2093
|
+
|
|
2094
|
+
h2_style = ParagraphStyle(
|
|
2095
|
+
'${palette.name}H2',
|
|
2096
|
+
parent=styles['Heading3'],
|
|
2097
|
+
fontName=FONT_PRIMARY,
|
|
2098
|
+
fontSize=14,
|
|
2099
|
+
textColor=${brandNames.primary},
|
|
2100
|
+
spaceBefore=14,
|
|
2101
|
+
spaceAfter=8,
|
|
2102
|
+
)
|
|
2103
|
+
|
|
2104
|
+
body_style = ParagraphStyle(
|
|
2105
|
+
'${palette.name}Body',
|
|
2106
|
+
parent=styles['Normal'],
|
|
2107
|
+
fontName=FONT_PRIMARY,
|
|
2108
|
+
fontSize=11,
|
|
2109
|
+
textColor=${brandNames.primary},
|
|
2110
|
+
leading=15, # Line height
|
|
2111
|
+
)
|
|
2112
|
+
|
|
2113
|
+
# ============================================
|
|
2114
|
+
# CREATE PDF
|
|
2115
|
+
# ============================================
|
|
2116
|
+
doc = SimpleDocTemplate(
|
|
2117
|
+
'${targetBrand}_${templateType}.pdf',
|
|
2118
|
+
pagesize=letter,
|
|
2119
|
+
topMargin=1.25*inch,
|
|
2120
|
+
bottomMargin=1*inch,
|
|
2121
|
+
leftMargin=1*inch,
|
|
2122
|
+
rightMargin=1*inch,
|
|
2123
|
+
)
|
|
2124
|
+
|
|
2125
|
+
# Content
|
|
2126
|
+
content = []
|
|
2127
|
+
|
|
2128
|
+
# Logo (optional)
|
|
2129
|
+
# content.append(Image('logo.png', width=2*inch, height=0.5*inch))
|
|
2130
|
+
|
|
2131
|
+
# Title
|
|
2132
|
+
content.append(Paragraph('${templateType === 'proposal' ? 'Project Proposal' : templateType === 'spec-sheet' ? 'Product Specification' : templateType === 'invoice' ? 'Invoice' : 'Report Title'}', title_style))
|
|
2133
|
+
|
|
2134
|
+
# Section
|
|
2135
|
+
content.append(Paragraph('Section Heading', h1_style))
|
|
2136
|
+
content.append(Paragraph('Your content here. Use the ${palette.name} brand voice: professional, premium, pioneering.', body_style))
|
|
2137
|
+
|
|
2138
|
+
# Subsection
|
|
2139
|
+
content.append(Spacer(1, 12))
|
|
2140
|
+
content.append(Paragraph('Subsection', h2_style))
|
|
2141
|
+
content.append(Paragraph('Additional details with proper formatting.', body_style))
|
|
2142
|
+
|
|
2143
|
+
# Build PDF
|
|
2144
|
+
doc.build(content)
|
|
2145
|
+
\`\`\`
|
|
2146
|
+
|
|
2147
|
+
## Key Points
|
|
2148
|
+
- **Colors**: Use HexColor('${colors.primary.hex}') for text
|
|
2149
|
+
- **Fonts**: Helvetica (built-in) or register Montserrat TTF
|
|
2150
|
+
- **Margins**: 1.25" top, 1" sides and bottom
|
|
2151
|
+
- **Leading**: 15pt for body text (1.4x line height)
|
|
2152
|
+
${palette.preferredTheme === "dark" ? `- **Note**: ${palette.name} prefers dark backgrounds - consider inverting` : ""}
|
|
2153
|
+
`,
|
|
2154
|
+
"excel-python": `# ${palette.name} Excel Template (openpyxl)
|
|
2155
|
+
|
|
2156
|
+
\`\`\`python
|
|
2157
|
+
from openpyxl import Workbook
|
|
2158
|
+
from openpyxl.styles import Font, Fill, PatternFill, Alignment, Border, Side
|
|
2159
|
+
from openpyxl.utils import get_column_letter
|
|
2160
|
+
|
|
2161
|
+
# ============================================
|
|
2162
|
+
# ${palette.name.toUpperCase()} BRAND CONSTANTS (Live from Component Library)
|
|
2163
|
+
# ============================================
|
|
2164
|
+
${brandNames.primary} = '${colors.primary.hexNoHash}'
|
|
2165
|
+
${brandNames.secondary} = '${colors.secondary.hexNoHash}'
|
|
2166
|
+
WHITE = '${colors.white.hexNoHash}'
|
|
2167
|
+
${brandNames.accent} = '${colors.accent.hexNoHash}'
|
|
2168
|
+
|
|
2169
|
+
FONT_PRIMARY = 'Arial' # Excel-safe fallback for Montserrat
|
|
2170
|
+
|
|
2171
|
+
# ============================================
|
|
2172
|
+
# STYLES
|
|
2173
|
+
# ============================================
|
|
2174
|
+
# Header style
|
|
2175
|
+
header_font = Font(name=FONT_PRIMARY, size=12, bold=True, color=WHITE)
|
|
2176
|
+
header_fill = PatternFill(start_color=${brandNames.primary}, end_color=${brandNames.primary}, fill_type='solid')
|
|
2177
|
+
header_alignment = Alignment(horizontal='center', vertical='center')
|
|
2178
|
+
|
|
2179
|
+
# Body style
|
|
2180
|
+
body_font = Font(name=FONT_PRIMARY, size=11, color=${brandNames.primary})
|
|
2181
|
+
body_alignment = Alignment(horizontal='left', vertical='center')
|
|
2182
|
+
|
|
2183
|
+
# Accent style (for totals, highlights)
|
|
2184
|
+
accent_font = Font(name=FONT_PRIMARY, size=11, bold=True, color=WHITE)
|
|
2185
|
+
accent_fill = PatternFill(start_color=${brandNames.accent}, end_color=${brandNames.accent}, fill_type='solid')
|
|
2186
|
+
|
|
2187
|
+
# Border style
|
|
2188
|
+
thin_border = Border(
|
|
2189
|
+
left=Side(style='thin', color=${brandNames.secondary}),
|
|
2190
|
+
right=Side(style='thin', color=${brandNames.secondary}),
|
|
2191
|
+
top=Side(style='thin', color=${brandNames.secondary}),
|
|
2192
|
+
bottom=Side(style='thin', color=${brandNames.secondary}),
|
|
2193
|
+
)
|
|
2194
|
+
|
|
2195
|
+
# ============================================
|
|
2196
|
+
# CREATE WORKBOOK
|
|
2197
|
+
# ============================================
|
|
2198
|
+
wb = Workbook()
|
|
2199
|
+
ws = wb.active
|
|
2200
|
+
ws.title = '${templateType === 'spec-sheet' ? 'Specifications' : templateType === 'invoice' ? 'Invoice' : 'Data'}'
|
|
2201
|
+
|
|
2202
|
+
# ============================================
|
|
2203
|
+
# HEADER ROW
|
|
2204
|
+
# ============================================
|
|
2205
|
+
headers = ['Item', 'Description', 'Quantity', 'Price', 'Total']
|
|
2206
|
+
for col, header in enumerate(headers, 1):
|
|
2207
|
+
cell = ws.cell(row=1, column=col, value=header)
|
|
2208
|
+
cell.font = header_font
|
|
2209
|
+
cell.fill = header_fill
|
|
2210
|
+
cell.alignment = header_alignment
|
|
2211
|
+
cell.border = thin_border
|
|
2212
|
+
|
|
2213
|
+
# ============================================
|
|
2214
|
+
# DATA ROWS
|
|
2215
|
+
# ============================================
|
|
2216
|
+
data = [
|
|
2217
|
+
['Product A', 'High-quality audio solution', 2, 1500.00, 3000.00],
|
|
2218
|
+
['Product B', 'Premium installation kit', 1, 250.00, 250.00],
|
|
2219
|
+
]
|
|
2220
|
+
|
|
2221
|
+
for row_num, row_data in enumerate(data, 2):
|
|
2222
|
+
for col_num, value in enumerate(row_data, 1):
|
|
2223
|
+
cell = ws.cell(row=row_num, column=col_num, value=value)
|
|
2224
|
+
cell.font = body_font
|
|
2225
|
+
cell.alignment = body_alignment
|
|
2226
|
+
cell.border = thin_border
|
|
2227
|
+
|
|
2228
|
+
# Format currency columns
|
|
2229
|
+
if col_num in [4, 5]:
|
|
2230
|
+
cell.number_format = '$#,##0.00'
|
|
2231
|
+
|
|
2232
|
+
# ============================================
|
|
2233
|
+
# TOTAL ROW
|
|
2234
|
+
# ============================================
|
|
2235
|
+
total_row = len(data) + 2
|
|
2236
|
+
ws.cell(row=total_row, column=4, value='Total:').font = accent_font
|
|
2237
|
+
total_cell = ws.cell(row=total_row, column=5, value='=SUM(E2:E{})'.format(total_row - 1))
|
|
2238
|
+
total_cell.font = accent_font
|
|
2239
|
+
total_cell.fill = accent_fill
|
|
2240
|
+
total_cell.number_format = '$#,##0.00'
|
|
2241
|
+
|
|
2242
|
+
# ============================================
|
|
2243
|
+
# COLUMN WIDTHS
|
|
2244
|
+
# ============================================
|
|
2245
|
+
column_widths = [15, 35, 12, 12, 15]
|
|
2246
|
+
for i, width in enumerate(column_widths, 1):
|
|
2247
|
+
ws.column_dimensions[get_column_letter(i)].width = width
|
|
2248
|
+
|
|
2249
|
+
# ============================================
|
|
2250
|
+
# SAVE
|
|
2251
|
+
# ============================================
|
|
2252
|
+
wb.save('${targetBrand}_${templateType}.xlsx')
|
|
2253
|
+
\`\`\`
|
|
2254
|
+
|
|
2255
|
+
## Key Points
|
|
2256
|
+
- **Colors**: Use '${colors.primary.hexNoHash}' (no #) for openpyxl
|
|
2257
|
+
- **Header**: ${brandNames.primary} background, white text
|
|
2258
|
+
- **Accent**: ${brandNames.accent} for totals and highlights
|
|
2259
|
+
- **Border**: ${brandNames.secondary} thin borders
|
|
2260
|
+
- **Font**: Arial (Excel-safe Montserrat alternative)
|
|
2261
|
+
${palette.preferredTheme === "dark" ? `- **Note**: ${palette.name} prefers dark backgrounds` : ""}
|
|
2262
|
+
`,
|
|
2263
|
+
};
|
|
2264
|
+
const template = templates[templateArgs.format];
|
|
2265
|
+
if (!template) {
|
|
2266
|
+
return {
|
|
2267
|
+
content: [{ type: "text", text: `Unknown format: ${templateArgs.format}` }],
|
|
2268
|
+
isError: true,
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
return {
|
|
2272
|
+
content: [{ type: "text", text: template }],
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
case "rebrand_document": {
|
|
2276
|
+
const rebrandArgs = args;
|
|
2277
|
+
if (!rebrandArgs.source_format) {
|
|
2278
|
+
return {
|
|
2279
|
+
content: [{ type: "text", text: "Error: source_format is required (word, pdf, excel, powerpoint, html, or generic)" }],
|
|
2280
|
+
isError: true,
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
// Get brand-specific colors
|
|
2284
|
+
const targetBrand = rebrandArgs.brand || "sonance";
|
|
2285
|
+
const brandColors = getBrandColorsForDocsMulti(targetBrand);
|
|
2286
|
+
const palette = BRAND_PALETTES[targetBrand];
|
|
2287
|
+
// Build format-specific color syntax dynamically based on brand
|
|
2288
|
+
const buildColorSyntax = (brand) => {
|
|
2289
|
+
const c = getBrandColorsForDocsMulti(brand);
|
|
2290
|
+
return {
|
|
2291
|
+
word: {
|
|
2292
|
+
primary: `${c.primary.rgbColor} or '${c.primary.hexNoHash}'`,
|
|
2293
|
+
secondary: `${c.secondary.rgbColor} or '${c.secondary.hexNoHash}'`,
|
|
2294
|
+
accent: `${c.accent.rgbColor} or '${c.accent.hexNoHash}'`,
|
|
2295
|
+
white: `RGBColor(0xFF, 0xFF, 0xFF) or 'FFFFFF'`,
|
|
2296
|
+
},
|
|
2297
|
+
pdf: {
|
|
2298
|
+
primary: `HexColor('${c.primary.hex}')`,
|
|
2299
|
+
secondary: `HexColor('${c.secondary.hex}')`,
|
|
2300
|
+
accent: `HexColor('${c.accent.hex}')`,
|
|
2301
|
+
white: `HexColor('#FFFFFF')`,
|
|
2302
|
+
},
|
|
2303
|
+
excel: {
|
|
2304
|
+
primary: `'${c.primary.hexNoHash}' (no # prefix for openpyxl)`,
|
|
2305
|
+
secondary: `'${c.secondary.hexNoHash}'`,
|
|
2306
|
+
accent: `'${c.accent.hexNoHash}'`,
|
|
2307
|
+
white: `'FFFFFF'`,
|
|
2308
|
+
},
|
|
2309
|
+
powerpoint: {
|
|
2310
|
+
primary: `RGBColor(${c.primary.rgb.r}, ${c.primary.rgb.g}, ${c.primary.rgb.b}) or ${c.primary.hex}`,
|
|
2311
|
+
secondary: `RGBColor(${c.secondary.rgb.r}, ${c.secondary.rgb.g}, ${c.secondary.rgb.b}) or ${c.secondary.hex}`,
|
|
2312
|
+
accent: `RGBColor(${c.accent.rgb.r}, ${c.accent.rgb.g}, ${c.accent.rgb.b}) or ${c.accent.hex}`,
|
|
2313
|
+
white: `RGBColor(255, 255, 255) or #FFFFFF`,
|
|
2314
|
+
},
|
|
2315
|
+
html: {
|
|
2316
|
+
primary: `${c.primary.hex} or rgb(${c.primary.rgb.r}, ${c.primary.rgb.g}, ${c.primary.rgb.b})`,
|
|
2317
|
+
secondary: `${c.secondary.hex} or rgb(${c.secondary.rgb.r}, ${c.secondary.rgb.g}, ${c.secondary.rgb.b})`,
|
|
2318
|
+
accent: `${c.accent.hex} or rgb(${c.accent.rgb.r}, ${c.accent.rgb.g}, ${c.accent.rgb.b})`,
|
|
2319
|
+
white: `#FFFFFF or rgb(255, 255, 255)`,
|
|
2320
|
+
},
|
|
2321
|
+
generic: {
|
|
2322
|
+
primary: `${c.primary.hex} (${palette.name} Primary)`,
|
|
2323
|
+
secondary: `${c.secondary.hex} (${palette.name} Secondary)`,
|
|
2324
|
+
accent: `${c.accent.hex} (${palette.name} Accent)`,
|
|
2325
|
+
white: `#FFFFFF (White)`,
|
|
2326
|
+
},
|
|
2327
|
+
};
|
|
2328
|
+
};
|
|
2329
|
+
const colorSyntax = buildColorSyntax(targetBrand);
|
|
2330
|
+
const syntax = colorSyntax[rebrandArgs.source_format] || colorSyntax.generic;
|
|
2331
|
+
// Brand-specific color names
|
|
2332
|
+
const colorNames = {
|
|
2333
|
+
sonance: { primary: "Charcoal", secondary: "Silver", accent: "Teal" },
|
|
2334
|
+
iport: { primary: "IPORT Dark", secondary: "Orange", accent: "Orange" },
|
|
2335
|
+
blaze: { primary: "Blaze Dark", secondary: "Blue", accent: "Blue/Red" },
|
|
2336
|
+
};
|
|
2337
|
+
const brandColorNames = colorNames[targetBrand];
|
|
2338
|
+
// Brand-specific logo guidance
|
|
2339
|
+
const logoGuidance = {
|
|
2340
|
+
sonance: `- **Light backgrounds**: Use \`Sonance_logo_dark_mode.png\`
|
|
2341
|
+
- **Dark backgrounds**: Use \`Sonance_logo_light_mode.png\`
|
|
2342
|
+
- **Path**: \`assets/logos/Sonance_logo_[light/dark]_mode.png\``,
|
|
2343
|
+
iport: `- **Light backgrounds**: Use \`IPORT_Lockup_2C_Dark.png\`
|
|
2344
|
+
- **Dark backgrounds**: Use \`IPORT_Lockup_2C_Light.png\`
|
|
2345
|
+
- **Note**: IPORT prefers dark backgrounds
|
|
2346
|
+
- **Path**: \`assets/logos/IPORT_Lockup_*.png\``,
|
|
2347
|
+
blaze: `- **Light backgrounds**: Use \`Blaze_Lockup_2C_Light.png\`
|
|
2348
|
+
- **Dark backgrounds**: Use \`Blaze_Lockup_3C_Dark.png\`
|
|
2349
|
+
- **Note**: Blaze prefers dark backgrounds
|
|
2350
|
+
- **Path**: \`assets/logos/Blaze_Lockup_*.png\``,
|
|
2351
|
+
};
|
|
2352
|
+
// Build custom color replacement guidance if user provided current colors
|
|
2353
|
+
let customColorSection = "";
|
|
2354
|
+
if (rebrandArgs.current_colors) {
|
|
2355
|
+
customColorSection = `
|
|
2356
|
+
### Your Specific Replacements
|
|
2357
|
+
|
|
2358
|
+
Based on your description ("${rebrandArgs.current_colors}"):
|
|
2359
|
+
|
|
2360
|
+
| What You Have | Replace With | ${palette.name} Color |
|
|
2361
|
+
|---------------|--------------|---------------|
|
|
2362
|
+
| Blue elements | ${syntax.primary} | ${brandColorNames.primary} (primary) |
|
|
2363
|
+
| Gray backgrounds | ${syntax.secondary} | ${brandColorNames.secondary} (secondary) |
|
|
2364
|
+
| Accent/highlight colors | ${syntax.accent} | ${brandColorNames.accent} (accent) |
|
|
2365
|
+
| Black text | ${syntax.primary} | ${brandColorNames.primary} (softer than pure black) |
|
|
2366
|
+
| White backgrounds | Keep as-is or use ${palette.preferredTheme === "dark" ? syntax.primary : "white"} | ${palette.preferredTheme === "dark" ? "Consider dark background" : "White is approved"} |
|
|
2367
|
+
|
|
2368
|
+
`;
|
|
2369
|
+
}
|
|
2370
|
+
const rebrandGuide = `# Universal Rebranding Guide: ${rebrandArgs.source_format.toUpperCase()} → ${palette.name}
|
|
2371
|
+
|
|
2372
|
+
This guide helps you rebrand **any ${rebrandArgs.source_format} document** to ${palette.name} standards, regardless of its current design or structure.
|
|
2373
|
+
|
|
2374
|
+
${palette.preferredTheme === "dark" ? `> **Note:** ${palette.name} prefers **dark backgrounds**. Consider inverting light documents.` : ""}
|
|
2375
|
+
|
|
2376
|
+
---
|
|
2377
|
+
|
|
2378
|
+
## Core Principle
|
|
2379
|
+
|
|
2380
|
+
**Don't redesign the layout—just replace the brand elements:**
|
|
2381
|
+
1. Colors → ${palette.name} palette
|
|
2382
|
+
2. Fonts → Montserrat (or Arial fallback)
|
|
2383
|
+
3. Logos → ${palette.name} logo
|
|
2384
|
+
|
|
2385
|
+
The document's structure stays the same; only the brand identity changes.
|
|
2386
|
+
|
|
2387
|
+
---
|
|
2388
|
+
|
|
2389
|
+
## Color Replacements
|
|
2390
|
+
|
|
2391
|
+
### ${palette.name} Brand Palette
|
|
2392
|
+
|
|
2393
|
+
| Color | Hex | ${rebrandArgs.source_format.toUpperCase()} Format | Usage |
|
|
2394
|
+
|-------|-----|--------|-------|
|
|
2395
|
+
| **${brandColorNames.primary}** | ${brandColors.primary.hex} | ${syntax.primary} | Headers, body text, primary elements |
|
|
2396
|
+
| **${brandColorNames.secondary}** | ${brandColors.secondary.hex} | ${syntax.secondary} | ${palette.preferredTheme === "dark" ? "Accents, CTAs, highlights" : "Backgrounds, borders, dividers"} |
|
|
2397
|
+
| **${brandColorNames.accent}** | ${brandColors.accent.hex} | ${syntax.accent} | Accents, CTAs, highlights |
|
|
2398
|
+
| **White** | #FFFFFF | ${syntax.white} | ${palette.preferredTheme === "dark" ? "Text on dark backgrounds" : "Backgrounds, text on dark"} |
|
|
2399
|
+
| **Black** | #000000 | Use sparingly | Only for maximum contrast |
|
|
2400
|
+
|
|
2401
|
+
### Universal Replacement Rules
|
|
2402
|
+
|
|
2403
|
+
| Find (Any Of These) | Replace With |
|
|
2404
|
+
|---------------------|--------------|
|
|
2405
|
+
| Blues (#0066CC, #0078D4, #1E90FF, navy, etc.) | ${brandColorNames.primary} ${brandColors.primary.hex} |
|
|
2406
|
+
| Reds (#FF0000, #CC0000, crimson, etc.) | ${brandColorNames.primary} ${brandColors.primary.hex} or ${brandColorNames.accent} ${brandColors.accent.hex} for CTAs |
|
|
2407
|
+
| Greens (#00FF00, #228B22, etc.) | ${brandColorNames.accent} ${brandColors.accent.hex} |
|
|
2408
|
+
| Oranges/Yellows | ${brandColorNames.accent} ${brandColors.accent.hex} |
|
|
2409
|
+
| Light grays (#F0F0F0, #EEEEEE, #D3D3D3) | ${brandColorNames.secondary} ${brandColors.secondary.hex} |
|
|
2410
|
+
| Dark grays (#333333, #666666, #555555) | ${brandColorNames.primary} ${brandColors.primary.hex} |
|
|
2411
|
+
| Pure black text (#000000) | ${brandColorNames.primary} ${brandColors.primary.hex} (softer, more premium) |
|
|
2412
|
+
${customColorSection}
|
|
2413
|
+
---
|
|
2414
|
+
|
|
2415
|
+
## Typography Replacements
|
|
2416
|
+
|
|
2417
|
+
### Font Substitution Map
|
|
2418
|
+
|
|
2419
|
+
| Current Font | Replace With | Notes |
|
|
2420
|
+
|--------------|--------------|-------|
|
|
2421
|
+
| Arial | Montserrat (or keep Arial as fallback) | Sans-serif ✓ |
|
|
2422
|
+
| Helvetica | Montserrat | Very similar feel |
|
|
2423
|
+
| Calibri | Montserrat | Microsoft default → ${palette.name} |
|
|
2424
|
+
| Times New Roman | Montserrat | Serif → Sans-serif |
|
|
2425
|
+
| Georgia | Montserrat | Serif → Sans-serif |
|
|
2426
|
+
| Verdana | Montserrat | |
|
|
2427
|
+
| Any other font | Montserrat | Default to brand font |
|
|
2428
|
+
|
|
2429
|
+
### Weight Mapping
|
|
2430
|
+
|
|
2431
|
+
| Current Weight | ${palette.name} Weight | Usage |
|
|
2432
|
+
|----------------|----------------|-------|
|
|
2433
|
+
| Bold (700) for headers | **Medium (500)** or **Light (300)** | Headlines should NOT be bold |
|
|
2434
|
+
| Bold (700) for emphasis | Medium (500) | In-line emphasis |
|
|
2435
|
+
| Regular (400) | Regular (400) | Body text |
|
|
2436
|
+
| Light (300) | Light (300) | Large display text |
|
|
2437
|
+
|
|
2438
|
+
### Size Guidelines (Documents)
|
|
2439
|
+
|
|
2440
|
+
| Element | Size |
|
|
2441
|
+
|---------|------|
|
|
2442
|
+
| Document Title | 28pt |
|
|
2443
|
+
| Heading 1 | 18pt |
|
|
2444
|
+
| Heading 2 | 14pt |
|
|
2445
|
+
| Body Text | 11pt |
|
|
2446
|
+
| Captions/Footnotes | 9pt |
|
|
2447
|
+
|
|
2448
|
+
---
|
|
2449
|
+
|
|
2450
|
+
## Logo Replacement
|
|
2451
|
+
|
|
2452
|
+
### Logo Assets
|
|
2453
|
+
${logoGuidance[targetBrand]}
|
|
2454
|
+
|
|
2455
|
+
### Placement Rules
|
|
2456
|
+
|
|
2457
|
+
| Placement | Size | Position |
|
|
2458
|
+
|-----------|------|----------|
|
|
2459
|
+
| Document Header | 2" wide (150-200px) | Top-left or centered |
|
|
2460
|
+
| Cover Page | 3" wide (250-300px) | Centered, upper third |
|
|
2461
|
+
| Footer | 1" wide (75-100px) | Bottom-left or centered |
|
|
2462
|
+
| Email Signature | 150px wide | Left-aligned |
|
|
2463
|
+
|
|
2464
|
+
### Clear Space
|
|
2465
|
+
Maintain clear space around the logo equal to the height of the first letter on all sides.
|
|
2466
|
+
|
|
2467
|
+
---
|
|
2468
|
+
|
|
2469
|
+
## Whitespace & Spacing
|
|
2470
|
+
|
|
2471
|
+
### The "Designed to Disappear" Principle
|
|
2472
|
+
${palette.name} aesthetic requires **generous whitespace**. When rebranding:
|
|
2473
|
+
|
|
2474
|
+
- **Don't compress** — if the original has tight spacing, expand it
|
|
2475
|
+
- **Section padding**: Minimum 40-60pt between major sections
|
|
2476
|
+
- **Paragraph spacing**: 1.15 line height, 6-12pt after paragraphs
|
|
2477
|
+
- **Margins**: 1.25" top, 1" sides (for print documents)
|
|
2478
|
+
|
|
2479
|
+
---
|
|
2480
|
+
|
|
2481
|
+
## Quick Checklist
|
|
2482
|
+
|
|
2483
|
+
Before considering your rebrand complete:
|
|
2484
|
+
|
|
2485
|
+
- [ ] All brand colors replaced with ${brandColorNames.primary}/${brandColorNames.secondary}/${brandColorNames.accent} palette
|
|
2486
|
+
- [ ] No original brand colors remain
|
|
2487
|
+
- [ ] Font changed to Montserrat (or Arial fallback)
|
|
2488
|
+
- [ ] Headlines use weight 300-500 (not bold 700)
|
|
2489
|
+
- [ ] Logo replaced with ${palette.name} logo
|
|
2490
|
+
- [ ] Logo has proper clear space
|
|
2491
|
+
- [ ] Adequate whitespace maintained
|
|
2492
|
+
- [ ] Document feels premium and "breathable"
|
|
2493
|
+
${palette.preferredTheme === "dark" ? `- [ ] Dark background applied (${palette.name} preference)` : ""}
|
|
2494
|
+
|
|
2495
|
+
---
|
|
2496
|
+
|
|
2497
|
+
## Format-Specific Tips: ${rebrandArgs.source_format.toUpperCase()}
|
|
2498
|
+
|
|
2499
|
+
${rebrandArgs.source_format === "word" ? `
|
|
2500
|
+
### Word/DOCX
|
|
2501
|
+
- Use **Find & Replace** (Ctrl+H) for text colors
|
|
2502
|
+
- Update **Styles** (Heading 1, Heading 2, Normal) for global changes
|
|
2503
|
+
- Set default font in **Design > Fonts**
|
|
2504
|
+
- Header/Footer: Insert > Header > Edit, add logo
|
|
2505
|
+
` : ""}${rebrandArgs.source_format === "pdf" ? `
|
|
2506
|
+
### PDF
|
|
2507
|
+
- Use **Adobe Acrobat Pro** Edit PDF feature, or
|
|
2508
|
+
- Regenerate from source (Word, InDesign, etc.)
|
|
2509
|
+
- For ReportLab/python: Use \`HexColor('${brandColors.primary.hex}')\`
|
|
2510
|
+
- Embed Montserrat font or use Helvetica (built-in)
|
|
2511
|
+
` : ""}${rebrandArgs.source_format === "excel" ? `
|
|
2512
|
+
### Excel
|
|
2513
|
+
- Use **Cell Styles** for consistent formatting
|
|
2514
|
+
- Header row: Fill color \`${brandColors.primary.hexNoHash}\`, font color \`FFFFFF\`
|
|
2515
|
+
- Data rows: Font color \`${brandColors.primary.hexNoHash}\`
|
|
2516
|
+
- Borders: Color \`${brandColors.secondary.hexNoHash}\`
|
|
2517
|
+
- Accent cells: Fill \`${brandColors.accent.hexNoHash}\`
|
|
2518
|
+
` : ""}${rebrandArgs.source_format === "powerpoint" ? `
|
|
2519
|
+
### PowerPoint
|
|
2520
|
+
- Update **Slide Master** for global changes
|
|
2521
|
+
- Theme colors: Edit in Design > Variants > Colors
|
|
2522
|
+
- Replace logos on Slide Master for all slides
|
|
2523
|
+
- Title slides: Use Light (300) weight Montserrat
|
|
2524
|
+
` : ""}${rebrandArgs.source_format === "html" ? `
|
|
2525
|
+
### HTML/Web
|
|
2526
|
+
- Update CSS variables or inline styles
|
|
2527
|
+
- Use semantic classes if available (bg-primary, text-foreground)
|
|
2528
|
+
- For emails: All styles must be inline
|
|
2529
|
+
- Include font fallback: \`'Montserrat', Arial, sans-serif\`
|
|
2530
|
+
` : ""}${rebrandArgs.source_format === "generic" ? `
|
|
2531
|
+
### General Approach
|
|
2532
|
+
1. Identify all color values in the document
|
|
2533
|
+
2. Replace using the color mapping above
|
|
2534
|
+
3. Update font family and weights
|
|
2535
|
+
4. Replace any logos with ${palette.name} logo
|
|
2536
|
+
5. Review spacing and add whitespace where needed
|
|
2537
|
+
` : ""}
|
|
2538
|
+
|
|
2539
|
+
---
|
|
2540
|
+
|
|
2541
|
+
**Remember:** The goal is to make the document unmistakably ${palette.name} while preserving its original structure and content.
|
|
2542
|
+
`;
|
|
2543
|
+
return {
|
|
2544
|
+
content: [{ type: "text", text: rebrandGuide }],
|
|
2545
|
+
};
|
|
2546
|
+
}
|
|
970
2547
|
default:
|
|
971
2548
|
return {
|
|
972
2549
|
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
@@ -1011,6 +2588,22 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
|
1011
2588
|
},
|
|
1012
2589
|
],
|
|
1013
2590
|
},
|
|
2591
|
+
{
|
|
2592
|
+
name: "quality-check",
|
|
2593
|
+
description: "Evaluate UI work against the Sonance Design Excellence Framework and get an improvement plan",
|
|
2594
|
+
arguments: [
|
|
2595
|
+
{
|
|
2596
|
+
name: "code",
|
|
2597
|
+
description: "The code/component to evaluate",
|
|
2598
|
+
required: true,
|
|
2599
|
+
},
|
|
2600
|
+
{
|
|
2601
|
+
name: "description",
|
|
2602
|
+
description: "Brief description of what the component does",
|
|
2603
|
+
required: false,
|
|
2604
|
+
},
|
|
2605
|
+
],
|
|
2606
|
+
},
|
|
1014
2607
|
],
|
|
1015
2608
|
}));
|
|
1016
2609
|
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
@@ -1071,6 +2664,49 @@ Provide specific feedback on what matches and what needs to change.`,
|
|
|
1071
2664
|
],
|
|
1072
2665
|
};
|
|
1073
2666
|
}
|
|
2667
|
+
if (name === "quality-check") {
|
|
2668
|
+
const code = promptArgs?.code || "";
|
|
2669
|
+
const description = promptArgs?.description || "Component";
|
|
2670
|
+
return {
|
|
2671
|
+
messages: [
|
|
2672
|
+
{
|
|
2673
|
+
role: "user",
|
|
2674
|
+
content: {
|
|
2675
|
+
type: "text",
|
|
2676
|
+
text: `Evaluate this ${description} against the Sonance Design Excellence Framework:
|
|
2677
|
+
|
|
2678
|
+
\`\`\`
|
|
2679
|
+
${code}
|
|
2680
|
+
\`\`\`
|
|
2681
|
+
|
|
2682
|
+
Follow these steps:
|
|
2683
|
+
1. Call evaluate_design with the code to get an automated score
|
|
2684
|
+
2. Call get_anti_patterns to check for critical failures
|
|
2685
|
+
3. If score < 85 (Tier 4), provide specific fixes for each gap
|
|
2686
|
+
4. If score >= 85, suggest polish improvements for Tier 5
|
|
2687
|
+
|
|
2688
|
+
Framework reminder:
|
|
2689
|
+
- Tier 5 (95-100): Portfolio-worthy
|
|
2690
|
+
- Tier 4 (85-94): Professional - minimum for delivery
|
|
2691
|
+
- Tier 3 (70-84): Iterate before delivery
|
|
2692
|
+
- Tier 2 (50-69): Major iteration needed
|
|
2693
|
+
- Tier 1 (0-49): Rebuild from scratch
|
|
2694
|
+
|
|
2695
|
+
Scoring dimensions:
|
|
2696
|
+
- Brand Compliance (20%): Semantic colors, no hardcoded hex, sharp corners
|
|
2697
|
+
- Typography (15%): Montserrat, light/medium weights only, hierarchy
|
|
2698
|
+
- Color Application (10%): Dark mode support, CSS variables
|
|
2699
|
+
- Layout & Spacing (15%): Generous whitespace, py-20/24 sections
|
|
2700
|
+
- Visual Hierarchy (15%): Size and weight variation
|
|
2701
|
+
- Medium Excellence (15%): TypeScript, accessibility, transitions
|
|
2702
|
+
- Polish & Finishing (10%): Hover states, focus states
|
|
2703
|
+
|
|
2704
|
+
Provide the score, tier, and a clear improvement plan to reach Tier 4+ if needed.`,
|
|
2705
|
+
},
|
|
2706
|
+
},
|
|
2707
|
+
],
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
1074
2710
|
throw new Error(`Unknown prompt: ${name}`);
|
|
1075
2711
|
});
|
|
1076
2712
|
// ============================================
|