conductor-figma 0.2.0 → 0.3.1
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/bin/conductor.js +6 -5
- package/package.json +1 -1
- package/src/blueprints.js +775 -0
- package/src/index.js +2 -0
- package/src/orchestrator.js +100 -0
- package/src/server.js +76 -13
package/bin/conductor.js
CHANGED
|
@@ -10,17 +10,18 @@ if (args.includes('--help') || args.includes('-h')) {
|
|
|
10
10
|
⊞ CONDUCTOR — Design-intelligent MCP server for Figma
|
|
11
11
|
|
|
12
12
|
Usage:
|
|
13
|
-
conductor-figma Start MCP server +
|
|
13
|
+
conductor-figma Start MCP server + orchestrator
|
|
14
14
|
conductor-figma --port 9800 Set WebSocket port (default: 9800)
|
|
15
15
|
conductor-figma --list List all ${TOOLS.length} tools
|
|
16
16
|
conductor-figma --categories Show tool categories
|
|
17
17
|
conductor-figma --help Show this help
|
|
18
18
|
|
|
19
19
|
How it works:
|
|
20
|
-
1.
|
|
21
|
-
2.
|
|
22
|
-
3.
|
|
23
|
-
4.
|
|
20
|
+
1. You say "create a pricing page" in Cursor
|
|
21
|
+
2. CONDUCTOR generates a blueprint (30-50 commands)
|
|
22
|
+
3. Each command executes sequentially on your Figma canvas
|
|
23
|
+
4. Frames, text, cards, buttons appear in real-time
|
|
24
|
+
5. All auto-layout. All grid-aligned. All design-intelligent.
|
|
24
25
|
|
|
25
26
|
Setup:
|
|
26
27
|
~/.cursor/mcp.json:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-figma",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Design-intelligent MCP server for Figma. 61 tools across 10 categories. 8px grid, type scale ratios, auto-layout, component reuse, accessibility — real design intelligence, not shape proxying.",
|
|
5
5
|
"author": "0xDragoon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// CONDUCTOR — Blueprints v2
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
// Polished, production-grade command sequences.
|
|
5
|
+
// Every value is intentional. Every pixel is grid-aligned.
|
|
6
|
+
|
|
7
|
+
import { snapToGrid, generateTypeScale, generateSemanticColors, generatePalette } from './design/intelligence.js';
|
|
8
|
+
|
|
9
|
+
// ─── Helpers ───
|
|
10
|
+
|
|
11
|
+
function frame(name, opts) {
|
|
12
|
+
return { type: 'create_frame', data: Object.assign({ name: name, direction: 'VERTICAL', padding: 0, gap: 0, fill: '#0c0c18', cornerRadius: 0 }, opts) };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function hframe(name, opts) {
|
|
16
|
+
return frame(name, Object.assign({ direction: 'HORIZONTAL' }, opts));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function text(content, opts) {
|
|
20
|
+
return { type: 'create_text', data: Object.assign({ text: content, fontSize: 16, color: '#ffffff', fontName: { family: 'Inter', style: 'Regular' } }, opts) };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function rect(name, opts) {
|
|
24
|
+
return { type: 'create_rect', data: Object.assign({ name: name, width: 100, height: 100 }, opts) };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function ap(ref, cmd) { cmd.data.parentId = ref; return cmd; }
|
|
28
|
+
|
|
29
|
+
// ═══════════════════════════════════════════
|
|
30
|
+
// LANDING PAGE — Premium quality
|
|
31
|
+
// ═══════════════════════════════════════════
|
|
32
|
+
|
|
33
|
+
export function buildLandingPage(args) {
|
|
34
|
+
var brand = args.brandColor || '#6366f1';
|
|
35
|
+
var W = args.width || 1440;
|
|
36
|
+
var contentW = 1120;
|
|
37
|
+
var dark = args.darkMode !== false;
|
|
38
|
+
|
|
39
|
+
// Palette
|
|
40
|
+
var bg = dark ? '#09090f' : '#ffffff';
|
|
41
|
+
var bg2 = dark ? '#0f0f1c' : '#f9f9fb';
|
|
42
|
+
var bg3 = dark ? '#14142a' : '#f3f3f7';
|
|
43
|
+
var cardBg = dark ? '#12122a' : '#ffffff';
|
|
44
|
+
var cardBorder = dark ? '#1e1e3a' : '#e8e8ee';
|
|
45
|
+
var text1 = dark ? '#f0f0f8' : '#111118';
|
|
46
|
+
var text2 = dark ? '#a0a0b8' : '#55556a';
|
|
47
|
+
var text3 = dark ? '#686880' : '#88889a';
|
|
48
|
+
var divider = dark ? '#1a1a32' : '#e4e4ec';
|
|
49
|
+
|
|
50
|
+
var title = args.title || 'Ship faster with\nless overhead';
|
|
51
|
+
var subtitle = args.subtitle || 'The modern platform for teams that move fast.\nEverything you need to build, deploy, and scale.';
|
|
52
|
+
var ctaText = args.ctaText || 'Start for free';
|
|
53
|
+
var navItems = args.navItems || ['Features', 'Pricing', 'Docs', 'Blog'];
|
|
54
|
+
var features = args.features || [
|
|
55
|
+
{ icon: '⚡', title: 'Instant Deploy', desc: 'Push to deploy in seconds. Zero config. Automatic HTTPS, global CDN, and instant rollbacks.' },
|
|
56
|
+
{ icon: '📈', title: 'Auto Scale', desc: 'Scales to millions of requests automatically. Pay only for what you use. No capacity planning.' },
|
|
57
|
+
{ icon: '🔒', title: 'Enterprise Security', desc: 'SOC 2 Type II compliant. End-to-end encryption. SSO, RBAC, and audit logs built in.' },
|
|
58
|
+
];
|
|
59
|
+
var stats = args.stats || [
|
|
60
|
+
{ value: '10,000+', label: 'Teams worldwide' },
|
|
61
|
+
{ value: '99.9%', label: 'Uptime SLA' },
|
|
62
|
+
{ value: '< 50ms', label: 'Global latency' },
|
|
63
|
+
{ value: '4.9/5', label: 'Customer rating' },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
var cmds = [];
|
|
67
|
+
|
|
68
|
+
// ── 0: Page Root ──
|
|
69
|
+
cmds.push(frame('Landing Page', { width: W, fill: bg, gap: 0, primaryAxisSizingMode: 'HUG' }));
|
|
70
|
+
|
|
71
|
+
// ── 1: Navigation ──
|
|
72
|
+
var navIdx = cmds.length;
|
|
73
|
+
cmds.push(ap('$0.id', hframe('Navigation', {
|
|
74
|
+
width: W, height: 72, paddingLeft: 48, paddingRight: 48, paddingTop: 0, paddingBottom: 0, gap: 0, fill: bg,
|
|
75
|
+
counterAxisAlignItems: 'CENTER',
|
|
76
|
+
})));
|
|
77
|
+
|
|
78
|
+
// Nav logo
|
|
79
|
+
cmds.push(ap('$' + navIdx + '.id', text(args.brand || 'acme', {
|
|
80
|
+
fontSize: 20, color: text1, fontName: { family: 'Inter', style: 'Bold' },
|
|
81
|
+
})));
|
|
82
|
+
|
|
83
|
+
// Nav spacer (pushes links right)
|
|
84
|
+
var spacerIdx = cmds.length;
|
|
85
|
+
cmds.push(ap('$' + navIdx + '.id', frame('_spacer', {
|
|
86
|
+
width: 1, height: 1, fill: bg, primaryAxisSizingMode: 'FILL',
|
|
87
|
+
})));
|
|
88
|
+
|
|
89
|
+
// Nav links container
|
|
90
|
+
var navLinksIdx = cmds.length;
|
|
91
|
+
cmds.push(ap('$' + navIdx + '.id', hframe('Nav Links', {
|
|
92
|
+
gap: 32, fill: bg, counterAxisAlignItems: 'CENTER',
|
|
93
|
+
})));
|
|
94
|
+
|
|
95
|
+
for (var i = 0; i < navItems.length; i++) {
|
|
96
|
+
cmds.push(ap('$' + navLinksIdx + '.id', text(navItems[i], {
|
|
97
|
+
fontSize: 14, color: text2, fontName: { family: 'Inter', style: 'Medium' },
|
|
98
|
+
})));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Nav CTA
|
|
102
|
+
var navBtnIdx = cmds.length;
|
|
103
|
+
cmds.push(ap('$' + navIdx + '.id', hframe('Nav CTA', {
|
|
104
|
+
height: 40, paddingLeft: 20, paddingRight: 20, paddingTop: 0, paddingBottom: 0,
|
|
105
|
+
fill: brand, cornerRadius: 8,
|
|
106
|
+
primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER',
|
|
107
|
+
})));
|
|
108
|
+
cmds.push(ap('$' + navBtnIdx + '.id', text('Get started', {
|
|
109
|
+
fontSize: 14, color: '#ffffff', fontName: { family: 'Inter', style: 'Semi Bold' },
|
|
110
|
+
})));
|
|
111
|
+
|
|
112
|
+
// ── Divider ──
|
|
113
|
+
cmds.push(ap('$0.id', rect('Nav Divider', { width: W, height: 1, fill: divider })));
|
|
114
|
+
|
|
115
|
+
// ── Hero Section ──
|
|
116
|
+
var heroIdx = cmds.length;
|
|
117
|
+
cmds.push(ap('$0.id', frame('Hero Section', {
|
|
118
|
+
width: W, paddingTop: 96, paddingBottom: 96, paddingLeft: 48, paddingRight: 48,
|
|
119
|
+
gap: 32, fill: bg,
|
|
120
|
+
primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER',
|
|
121
|
+
})));
|
|
122
|
+
|
|
123
|
+
// Overline badge
|
|
124
|
+
var badgeIdx = cmds.length;
|
|
125
|
+
cmds.push(ap('$' + heroIdx + '.id', hframe('Badge', {
|
|
126
|
+
paddingLeft: 16, paddingRight: 16, paddingTop: 8, paddingBottom: 8,
|
|
127
|
+
fill: dark ? '#1a1a36' : '#f0f0ff', cornerRadius: 20,
|
|
128
|
+
primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER', gap: 8,
|
|
129
|
+
})));
|
|
130
|
+
cmds.push(ap('$' + badgeIdx + '.id', text('✦', { fontSize: 10, color: brand })));
|
|
131
|
+
cmds.push(ap('$' + badgeIdx + '.id', text('Now available — v2.0 is here', {
|
|
132
|
+
fontSize: 12, color: dark ? '#b0b0d0' : '#5555aa', fontName: { family: 'Inter', style: 'Medium' },
|
|
133
|
+
})));
|
|
134
|
+
|
|
135
|
+
// Hero heading
|
|
136
|
+
cmds.push(ap('$' + heroIdx + '.id', text(title, {
|
|
137
|
+
fontSize: 64, color: text1, fontName: { family: 'Inter', style: 'Bold' },
|
|
138
|
+
textAlignHorizontal: 'CENTER',
|
|
139
|
+
})));
|
|
140
|
+
|
|
141
|
+
// Hero subtitle
|
|
142
|
+
cmds.push(ap('$' + heroIdx + '.id', text(subtitle, {
|
|
143
|
+
fontSize: 20, color: text2, fontName: { family: 'Inter', style: 'Regular' },
|
|
144
|
+
textAlignHorizontal: 'CENTER',
|
|
145
|
+
})));
|
|
146
|
+
|
|
147
|
+
// Hero button row
|
|
148
|
+
var btnRowIdx = cmds.length;
|
|
149
|
+
cmds.push(ap('$' + heroIdx + '.id', hframe('Hero Buttons', {
|
|
150
|
+
gap: 12, fill: bg,
|
|
151
|
+
primaryAxisAlignItems: 'CENTER',
|
|
152
|
+
})));
|
|
153
|
+
|
|
154
|
+
// Primary CTA
|
|
155
|
+
var pBtnIdx = cmds.length;
|
|
156
|
+
cmds.push(ap('$' + btnRowIdx + '.id', hframe('Primary CTA', {
|
|
157
|
+
height: 52, paddingLeft: 28, paddingRight: 28, paddingTop: 0, paddingBottom: 0,
|
|
158
|
+
fill: brand, cornerRadius: 12,
|
|
159
|
+
primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER',
|
|
160
|
+
})));
|
|
161
|
+
cmds.push(ap('$' + pBtnIdx + '.id', text(ctaText, {
|
|
162
|
+
fontSize: 16, color: '#ffffff', fontName: { family: 'Inter', style: 'Semi Bold' },
|
|
163
|
+
})));
|
|
164
|
+
|
|
165
|
+
// Secondary CTA
|
|
166
|
+
var sBtnIdx = cmds.length;
|
|
167
|
+
cmds.push(ap('$' + btnRowIdx + '.id', hframe('Secondary CTA', {
|
|
168
|
+
height: 52, paddingLeft: 28, paddingRight: 28, paddingTop: 0, paddingBottom: 0,
|
|
169
|
+
fill: 'transparent', cornerRadius: 12,
|
|
170
|
+
primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER',
|
|
171
|
+
})));
|
|
172
|
+
cmds.push(ap('$' + sBtnIdx + '.id', text('See how it works →', {
|
|
173
|
+
fontSize: 16, color: text2, fontName: { family: 'Inter', style: 'Medium' },
|
|
174
|
+
})));
|
|
175
|
+
|
|
176
|
+
// ── Social proof line ──
|
|
177
|
+
var proofIdx = cmds.length;
|
|
178
|
+
cmds.push(ap('$' + heroIdx + '.id', hframe('Social Proof', {
|
|
179
|
+
gap: 8, fill: bg, counterAxisAlignItems: 'CENTER',
|
|
180
|
+
paddingTop: 16,
|
|
181
|
+
})));
|
|
182
|
+
cmds.push(ap('$' + proofIdx + '.id', text('★★★★★', {
|
|
183
|
+
fontSize: 14, color: '#fbbf24',
|
|
184
|
+
})));
|
|
185
|
+
cmds.push(ap('$' + proofIdx + '.id', text('Loved by 10,000+ teams', {
|
|
186
|
+
fontSize: 13, color: text3, fontName: { family: 'Inter', style: 'Medium' },
|
|
187
|
+
})));
|
|
188
|
+
|
|
189
|
+
// ── Stats Section ──
|
|
190
|
+
var statsIdx = cmds.length;
|
|
191
|
+
cmds.push(ap('$0.id', hframe('Stats Bar', {
|
|
192
|
+
width: W, paddingTop: 48, paddingBottom: 48, paddingLeft: 48, paddingRight: 48,
|
|
193
|
+
gap: 0, fill: bg2,
|
|
194
|
+
primaryAxisAlignItems: 'CENTER',
|
|
195
|
+
})));
|
|
196
|
+
|
|
197
|
+
// Stats inner container (centered, max-width)
|
|
198
|
+
var statsInnerIdx = cmds.length;
|
|
199
|
+
cmds.push(ap('$' + statsIdx + '.id', hframe('Stats Inner', {
|
|
200
|
+
width: contentW, gap: 0, fill: bg2,
|
|
201
|
+
})));
|
|
202
|
+
|
|
203
|
+
for (var s = 0; s < stats.length; s++) {
|
|
204
|
+
var statIdx = cmds.length;
|
|
205
|
+
var statW = Math.floor(contentW / stats.length);
|
|
206
|
+
cmds.push(ap('$' + statsInnerIdx + '.id', frame('Stat: ' + stats[s].label, {
|
|
207
|
+
width: statW, paddingTop: 24, paddingBottom: 24, gap: 4, fill: bg2,
|
|
208
|
+
primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER',
|
|
209
|
+
})));
|
|
210
|
+
cmds.push(ap('$' + statIdx + '.id', text(stats[s].value, {
|
|
211
|
+
fontSize: 36, color: text1, fontName: { family: 'Inter', style: 'Bold' },
|
|
212
|
+
})));
|
|
213
|
+
cmds.push(ap('$' + statIdx + '.id', text(stats[s].label, {
|
|
214
|
+
fontSize: 14, color: text3, fontName: { family: 'Inter', style: 'Medium' },
|
|
215
|
+
})));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ── Features Section ──
|
|
219
|
+
var featIdx = cmds.length;
|
|
220
|
+
cmds.push(ap('$0.id', frame('Features Section', {
|
|
221
|
+
width: W, paddingTop: 96, paddingBottom: 96, paddingLeft: 48, paddingRight: 48,
|
|
222
|
+
gap: 48, fill: bg,
|
|
223
|
+
counterAxisAlignItems: 'CENTER',
|
|
224
|
+
})));
|
|
225
|
+
|
|
226
|
+
// Features header
|
|
227
|
+
var featHeaderIdx = cmds.length;
|
|
228
|
+
cmds.push(ap('$' + featIdx + '.id', frame('Features Header', {
|
|
229
|
+
gap: 16, fill: bg, counterAxisAlignItems: 'CENTER',
|
|
230
|
+
})));
|
|
231
|
+
cmds.push(ap('$' + featHeaderIdx + '.id', text('Everything you need', {
|
|
232
|
+
fontSize: 40, color: text1, fontName: { family: 'Inter', style: 'Bold' },
|
|
233
|
+
textAlignHorizontal: 'CENTER',
|
|
234
|
+
})));
|
|
235
|
+
cmds.push(ap('$' + featHeaderIdx + '.id', text('Powerful features to help your team ship faster\nand with more confidence.', {
|
|
236
|
+
fontSize: 18, color: text2, fontName: { family: 'Inter', style: 'Regular' },
|
|
237
|
+
textAlignHorizontal: 'CENTER',
|
|
238
|
+
})));
|
|
239
|
+
|
|
240
|
+
// Feature card row
|
|
241
|
+
var cardRowIdx = cmds.length;
|
|
242
|
+
cmds.push(ap('$' + featIdx + '.id', hframe('Feature Cards', {
|
|
243
|
+
width: contentW, gap: 24, fill: bg,
|
|
244
|
+
})));
|
|
245
|
+
|
|
246
|
+
for (var f = 0; f < features.length; f++) {
|
|
247
|
+
var cardW = Math.floor((contentW - (features.length - 1) * 24) / features.length);
|
|
248
|
+
var cardIdx = cmds.length;
|
|
249
|
+
cmds.push(ap('$' + cardRowIdx + '.id', frame(features[f].title, {
|
|
250
|
+
width: cardW, paddingTop: 32, paddingBottom: 32, paddingLeft: 28, paddingRight: 28,
|
|
251
|
+
gap: 16, fill: cardBg, cornerRadius: 16,
|
|
252
|
+
})));
|
|
253
|
+
|
|
254
|
+
// Icon circle
|
|
255
|
+
var iconIdx = cmds.length;
|
|
256
|
+
cmds.push(ap('$' + cardIdx + '.id', hframe('Icon', {
|
|
257
|
+
width: 48, height: 48, fill: dark ? '#1c1c3a' : '#f0f0ff', cornerRadius: 12,
|
|
258
|
+
primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER',
|
|
259
|
+
})));
|
|
260
|
+
cmds.push(ap('$' + iconIdx + '.id', text(features[f].icon, { fontSize: 20 })));
|
|
261
|
+
|
|
262
|
+
// Card title
|
|
263
|
+
cmds.push(ap('$' + cardIdx + '.id', text(features[f].title, {
|
|
264
|
+
fontSize: 20, color: text1, fontName: { family: 'Inter', style: 'Semi Bold' },
|
|
265
|
+
})));
|
|
266
|
+
|
|
267
|
+
// Card description
|
|
268
|
+
cmds.push(ap('$' + cardIdx + '.id', text(features[f].desc, {
|
|
269
|
+
fontSize: 15, color: text2, fontName: { family: 'Inter', style: 'Regular' },
|
|
270
|
+
})));
|
|
271
|
+
|
|
272
|
+
// Learn more link
|
|
273
|
+
cmds.push(ap('$' + cardIdx + '.id', text('Learn more →', {
|
|
274
|
+
fontSize: 14, color: brand, fontName: { family: 'Inter', style: 'Medium' },
|
|
275
|
+
})));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── CTA Section ──
|
|
279
|
+
var ctaSectionIdx = cmds.length;
|
|
280
|
+
cmds.push(ap('$0.id', frame('CTA Section', {
|
|
281
|
+
width: W, paddingTop: 96, paddingBottom: 96, paddingLeft: 48, paddingRight: 48,
|
|
282
|
+
gap: 0, fill: bg2,
|
|
283
|
+
counterAxisAlignItems: 'CENTER',
|
|
284
|
+
})));
|
|
285
|
+
|
|
286
|
+
// CTA card
|
|
287
|
+
var ctaCardIdx = cmds.length;
|
|
288
|
+
cmds.push(ap('$' + ctaSectionIdx + '.id', frame('CTA Card', {
|
|
289
|
+
width: contentW, paddingTop: 64, paddingBottom: 64, paddingLeft: 48, paddingRight: 48,
|
|
290
|
+
gap: 28, fill: dark ? '#14142e' : '#ffffff', cornerRadius: 20,
|
|
291
|
+
primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER',
|
|
292
|
+
})));
|
|
293
|
+
|
|
294
|
+
cmds.push(ap('$' + ctaCardIdx + '.id', text('Ready to get started?', {
|
|
295
|
+
fontSize: 44, color: text1, fontName: { family: 'Inter', style: 'Bold' },
|
|
296
|
+
textAlignHorizontal: 'CENTER',
|
|
297
|
+
})));
|
|
298
|
+
|
|
299
|
+
cmds.push(ap('$' + ctaCardIdx + '.id', text('Join thousands of teams already shipping faster.\nNo credit card required.', {
|
|
300
|
+
fontSize: 18, color: text2, fontName: { family: 'Inter', style: 'Regular' },
|
|
301
|
+
textAlignHorizontal: 'CENTER',
|
|
302
|
+
})));
|
|
303
|
+
|
|
304
|
+
var ctaBtnRowIdx = cmds.length;
|
|
305
|
+
cmds.push(ap('$' + ctaCardIdx + '.id', hframe('CTA Buttons', {
|
|
306
|
+
gap: 12, fill: dark ? '#14142e' : '#ffffff',
|
|
307
|
+
})));
|
|
308
|
+
|
|
309
|
+
var ctaPrimaryIdx = cmds.length;
|
|
310
|
+
cmds.push(ap('$' + ctaBtnRowIdx + '.id', hframe('CTA Primary', {
|
|
311
|
+
height: 56, paddingLeft: 32, paddingRight: 32, paddingTop: 0, paddingBottom: 0,
|
|
312
|
+
fill: brand, cornerRadius: 12,
|
|
313
|
+
primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER',
|
|
314
|
+
})));
|
|
315
|
+
cmds.push(ap('$' + ctaPrimaryIdx + '.id', text(ctaText, {
|
|
316
|
+
fontSize: 17, color: '#ffffff', fontName: { family: 'Inter', style: 'Semi Bold' },
|
|
317
|
+
})));
|
|
318
|
+
|
|
319
|
+
var ctaSecIdx = cmds.length;
|
|
320
|
+
cmds.push(ap('$' + ctaBtnRowIdx + '.id', hframe('CTA Secondary', {
|
|
321
|
+
height: 56, paddingLeft: 32, paddingRight: 32, paddingTop: 0, paddingBottom: 0,
|
|
322
|
+
fill: 'transparent', cornerRadius: 12,
|
|
323
|
+
primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER',
|
|
324
|
+
})));
|
|
325
|
+
cmds.push(ap('$' + ctaSecIdx + '.id', text('Talk to sales →', {
|
|
326
|
+
fontSize: 17, color: text2, fontName: { family: 'Inter', style: 'Medium' },
|
|
327
|
+
})));
|
|
328
|
+
|
|
329
|
+
// ── Footer ──
|
|
330
|
+
cmds.push(ap('$0.id', rect('Footer Divider', { width: W, height: 1, fill: divider })));
|
|
331
|
+
|
|
332
|
+
var footerIdx = cmds.length;
|
|
333
|
+
cmds.push(ap('$0.id', hframe('Footer', {
|
|
334
|
+
width: W, paddingTop: 48, paddingBottom: 48, paddingLeft: 48, paddingRight: 48,
|
|
335
|
+
gap: 0, fill: bg,
|
|
336
|
+
counterAxisAlignItems: 'CENTER',
|
|
337
|
+
})));
|
|
338
|
+
|
|
339
|
+
// Footer left
|
|
340
|
+
cmds.push(ap('$' + footerIdx + '.id', text(args.brand || 'acme', {
|
|
341
|
+
fontSize: 16, color: text3, fontName: { family: 'Inter', style: 'Semi Bold' },
|
|
342
|
+
})));
|
|
343
|
+
|
|
344
|
+
// Footer spacer
|
|
345
|
+
cmds.push(ap('$' + footerIdx + '.id', frame('_spacer', { width: 1, height: 1, fill: bg, primaryAxisSizingMode: 'FILL' })));
|
|
346
|
+
|
|
347
|
+
// Footer right
|
|
348
|
+
cmds.push(ap('$' + footerIdx + '.id', text('© 2025 ' + (args.brand || 'Acme') + ' Inc. All rights reserved.', {
|
|
349
|
+
fontSize: 13, color: text3, fontName: { family: 'Inter', style: 'Regular' },
|
|
350
|
+
})));
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
commands: cmds,
|
|
354
|
+
description: 'Landing page: nav, hero with badge, ' + stats.length + ' stats, ' + features.length + ' feature cards with icons, CTA card, footer. ' + cmds.length + ' elements.',
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ═══════════════════════════════════════════
|
|
359
|
+
// PRICING PAGE
|
|
360
|
+
// ═══════════════════════════════════════════
|
|
361
|
+
|
|
362
|
+
export function buildPricingPage(args) {
|
|
363
|
+
var brand = args.brandColor || '#6366f1';
|
|
364
|
+
var W = args.width || 1440;
|
|
365
|
+
var contentW = 1080;
|
|
366
|
+
var bg = '#09090f';
|
|
367
|
+
var bg2 = '#0f0f1c';
|
|
368
|
+
var cardBg = '#12122a';
|
|
369
|
+
var text1 = '#f0f0f8';
|
|
370
|
+
var text2 = '#a0a0b8';
|
|
371
|
+
var text3 = '#686880';
|
|
372
|
+
var divider = '#1a1a32';
|
|
373
|
+
|
|
374
|
+
var tiers = args.tiers || [
|
|
375
|
+
{ name: 'Starter', price: '$0', period: '/mo', desc: 'For side projects and experiments.', features: ['1 project', '1,000 API calls/day', 'Community support', 'Basic analytics'], cta: 'Start free', highlighted: false },
|
|
376
|
+
{ name: 'Pro', price: '$29', period: '/mo', desc: 'For growing teams that need more power.', features: ['Unlimited projects', '100,000 API calls/day', 'Priority support', 'Advanced analytics', 'Team collaboration', 'Custom domains', 'Webhooks'], cta: 'Start free trial', highlighted: true },
|
|
377
|
+
{ name: 'Enterprise', price: 'Custom', period: '', desc: 'For organizations with advanced needs.', features: ['Everything in Pro', 'Unlimited API calls', 'Dedicated support', 'SSO & SAML', '99.99% SLA', 'Custom integrations', 'On-premise option', 'Audit logs'], cta: 'Contact sales', highlighted: false },
|
|
378
|
+
];
|
|
379
|
+
|
|
380
|
+
var cmds = [];
|
|
381
|
+
|
|
382
|
+
// 0: Root
|
|
383
|
+
cmds.push(frame('Pricing Page', { width: W, fill: bg, primaryAxisSizingMode: 'HUG' }));
|
|
384
|
+
|
|
385
|
+
// Header
|
|
386
|
+
var headerIdx = cmds.length;
|
|
387
|
+
cmds.push(ap('$0.id', frame('Pricing Header', {
|
|
388
|
+
width: W, paddingTop: 96, paddingBottom: 64, paddingLeft: 48, paddingRight: 48,
|
|
389
|
+
gap: 20, fill: bg,
|
|
390
|
+
primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER',
|
|
391
|
+
})));
|
|
392
|
+
|
|
393
|
+
cmds.push(ap('$' + headerIdx + '.id', text('Simple, transparent pricing', {
|
|
394
|
+
fontSize: 52, color: text1, fontName: { family: 'Inter', style: 'Bold' },
|
|
395
|
+
textAlignHorizontal: 'CENTER',
|
|
396
|
+
})));
|
|
397
|
+
|
|
398
|
+
cmds.push(ap('$' + headerIdx + '.id', text('No hidden fees. No surprises. Cancel anytime.\nStart free and scale as you grow.', {
|
|
399
|
+
fontSize: 18, color: text2, fontName: { family: 'Inter', style: 'Regular' },
|
|
400
|
+
textAlignHorizontal: 'CENTER',
|
|
401
|
+
})));
|
|
402
|
+
|
|
403
|
+
// Tier row
|
|
404
|
+
var tierRowIdx = cmds.length;
|
|
405
|
+
cmds.push(ap('$0.id', hframe('Pricing Tiers', {
|
|
406
|
+
width: W, paddingLeft: Math.floor((W - contentW) / 2), paddingRight: Math.floor((W - contentW) / 2),
|
|
407
|
+
paddingTop: 0, paddingBottom: 96,
|
|
408
|
+
gap: 20, fill: bg, counterAxisAlignItems: 'STRETCH',
|
|
409
|
+
})));
|
|
410
|
+
|
|
411
|
+
for (var t = 0; t < tiers.length; t++) {
|
|
412
|
+
var tier = tiers[t];
|
|
413
|
+
var isHl = tier.highlighted;
|
|
414
|
+
var cardW = Math.floor((contentW - (tiers.length - 1) * 20) / tiers.length);
|
|
415
|
+
|
|
416
|
+
var tierIdx = cmds.length;
|
|
417
|
+
cmds.push(ap('$' + tierRowIdx + '.id', frame(tier.name, {
|
|
418
|
+
width: cardW, paddingTop: 36, paddingBottom: 36, paddingLeft: 32, paddingRight: 32,
|
|
419
|
+
gap: 20, cornerRadius: 16,
|
|
420
|
+
fill: isHl ? '#1a1a3e' : cardBg,
|
|
421
|
+
})));
|
|
422
|
+
|
|
423
|
+
// Popular badge
|
|
424
|
+
if (isHl) {
|
|
425
|
+
var bIdx = cmds.length;
|
|
426
|
+
cmds.push(ap('$' + tierIdx + '.id', hframe('Popular', {
|
|
427
|
+
paddingLeft: 12, paddingRight: 12, paddingTop: 4, paddingBottom: 4,
|
|
428
|
+
fill: brand, cornerRadius: 6,
|
|
429
|
+
primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER',
|
|
430
|
+
})));
|
|
431
|
+
cmds.push(ap('$' + bIdx + '.id', text('MOST POPULAR', {
|
|
432
|
+
fontSize: 10, color: '#ffffff', fontName: { family: 'Inter', style: 'Bold' },
|
|
433
|
+
})));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Tier name
|
|
437
|
+
cmds.push(ap('$' + tierIdx + '.id', text(tier.name, {
|
|
438
|
+
fontSize: 22, color: text1, fontName: { family: 'Inter', style: 'Semi Bold' },
|
|
439
|
+
})));
|
|
440
|
+
|
|
441
|
+
// Price row
|
|
442
|
+
var priceIdx = cmds.length;
|
|
443
|
+
cmds.push(ap('$' + tierIdx + '.id', hframe('Price', {
|
|
444
|
+
gap: 4, fill: isHl ? '#1a1a3e' : cardBg,
|
|
445
|
+
counterAxisAlignItems: 'BASELINE',
|
|
446
|
+
})));
|
|
447
|
+
cmds.push(ap('$' + priceIdx + '.id', text(tier.price, {
|
|
448
|
+
fontSize: 48, color: text1, fontName: { family: 'Inter', style: 'Bold' },
|
|
449
|
+
})));
|
|
450
|
+
if (tier.period) {
|
|
451
|
+
cmds.push(ap('$' + priceIdx + '.id', text(tier.period, {
|
|
452
|
+
fontSize: 16, color: text3, fontName: { family: 'Inter', style: 'Regular' },
|
|
453
|
+
})));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Description
|
|
457
|
+
cmds.push(ap('$' + tierIdx + '.id', text(tier.desc, {
|
|
458
|
+
fontSize: 14, color: text2, fontName: { family: 'Inter', style: 'Regular' },
|
|
459
|
+
})));
|
|
460
|
+
|
|
461
|
+
// Divider
|
|
462
|
+
cmds.push(ap('$' + tierIdx + '.id', rect('Divider', {
|
|
463
|
+
width: cardW - 64, height: 1, fill: divider,
|
|
464
|
+
})));
|
|
465
|
+
|
|
466
|
+
// Features list
|
|
467
|
+
var featListIdx = cmds.length;
|
|
468
|
+
cmds.push(ap('$' + tierIdx + '.id', frame('Features', {
|
|
469
|
+
gap: 12, fill: isHl ? '#1a1a3e' : cardBg,
|
|
470
|
+
})));
|
|
471
|
+
|
|
472
|
+
for (var fi = 0; fi < tier.features.length; fi++) {
|
|
473
|
+
cmds.push(ap('$' + featListIdx + '.id', text('✓ ' + tier.features[fi], {
|
|
474
|
+
fontSize: 14, color: text2, fontName: { family: 'Inter', style: 'Regular' },
|
|
475
|
+
})));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// CTA button
|
|
479
|
+
var btnIdx = cmds.length;
|
|
480
|
+
cmds.push(ap('$' + tierIdx + '.id', hframe(tier.cta, {
|
|
481
|
+
height: 48, paddingLeft: 24, paddingRight: 24, paddingTop: 0, paddingBottom: 0,
|
|
482
|
+
fill: isHl ? brand : 'transparent',
|
|
483
|
+
cornerRadius: 10,
|
|
484
|
+
primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER',
|
|
485
|
+
primaryAxisSizingMode: 'FILL',
|
|
486
|
+
})));
|
|
487
|
+
cmds.push(ap('$' + btnIdx + '.id', text(tier.cta, {
|
|
488
|
+
fontSize: 15, color: isHl ? '#ffffff' : brand,
|
|
489
|
+
fontName: { family: 'Inter', style: 'Semi Bold' },
|
|
490
|
+
})));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
commands: cmds,
|
|
495
|
+
description: 'Pricing page with header and ' + tiers.length + ' tiers (' + tiers.map(function(t) { return t.name; }).join(', ') + '). ' + cmds.length + ' elements.',
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ═══════════════════════════════════════════
|
|
500
|
+
// DASHBOARD
|
|
501
|
+
// ═══════════════════════════════════════════
|
|
502
|
+
|
|
503
|
+
export function buildDashboardPage(args) {
|
|
504
|
+
var W = args.width || 1440;
|
|
505
|
+
var brand = args.brandColor || '#6366f1';
|
|
506
|
+
var bg = '#09090f';
|
|
507
|
+
var sidebarBg = '#0c0c18';
|
|
508
|
+
var cardBg = '#12122a';
|
|
509
|
+
var text1 = '#f0f0f8';
|
|
510
|
+
var text2 = '#a0a0b8';
|
|
511
|
+
var text3 = '#686880';
|
|
512
|
+
var divider = '#1a1a32';
|
|
513
|
+
var sideW = 260;
|
|
514
|
+
|
|
515
|
+
var metrics = args.metrics || [
|
|
516
|
+
{ label: 'Total Revenue', value: '$48,290', change: '+12.5%', positive: true },
|
|
517
|
+
{ label: 'Active Users', value: '2,420', change: '+8.3%', positive: true },
|
|
518
|
+
{ label: 'Conversion', value: '3.24%', change: '-0.8%', positive: false },
|
|
519
|
+
{ label: 'Avg. Session', value: '4m 32s', change: '+15.2%', positive: true },
|
|
520
|
+
];
|
|
521
|
+
|
|
522
|
+
var cmds = [];
|
|
523
|
+
|
|
524
|
+
cmds.push(hframe('Dashboard', { width: W, height: 900, fill: bg, gap: 0 }));
|
|
525
|
+
|
|
526
|
+
// Sidebar
|
|
527
|
+
var sideIdx = cmds.length;
|
|
528
|
+
cmds.push(ap('$0.id', frame('Sidebar', {
|
|
529
|
+
width: sideW, height: 900, paddingTop: 24, paddingBottom: 24, paddingLeft: 20, paddingRight: 20,
|
|
530
|
+
gap: 4, fill: sidebarBg,
|
|
531
|
+
})));
|
|
532
|
+
|
|
533
|
+
cmds.push(ap('$' + sideIdx + '.id', text('◆ Dashboard', {
|
|
534
|
+
fontSize: 16, color: text1, fontName: { family: 'Inter', style: 'Bold' },
|
|
535
|
+
})));
|
|
536
|
+
|
|
537
|
+
cmds.push(ap('$' + sideIdx + '.id', frame('_gap', { width: 1, height: 20, fill: sidebarBg })));
|
|
538
|
+
|
|
539
|
+
var sideNavItems = [
|
|
540
|
+
{ label: 'Overview', active: true },
|
|
541
|
+
{ label: 'Analytics', active: false },
|
|
542
|
+
{ label: 'Customers', active: false },
|
|
543
|
+
{ label: 'Products', active: false },
|
|
544
|
+
{ label: 'Settings', active: false },
|
|
545
|
+
];
|
|
546
|
+
|
|
547
|
+
for (var si = 0; si < sideNavItems.length; si++) {
|
|
548
|
+
var navItem = sideNavItems[si];
|
|
549
|
+
var niIdx = cmds.length;
|
|
550
|
+
cmds.push(ap('$' + sideIdx + '.id', hframe(navItem.label, {
|
|
551
|
+
height: 40, paddingLeft: 12, paddingRight: 12, paddingTop: 0, paddingBottom: 0,
|
|
552
|
+
gap: 8, cornerRadius: 8,
|
|
553
|
+
fill: navItem.active ? '#1a1a36' : sidebarBg,
|
|
554
|
+
counterAxisAlignItems: 'CENTER', primaryAxisSizingMode: 'FILL',
|
|
555
|
+
})));
|
|
556
|
+
cmds.push(ap('$' + niIdx + '.id', text(navItem.label, {
|
|
557
|
+
fontSize: 14, color: navItem.active ? text1 : text3,
|
|
558
|
+
fontName: { family: 'Inter', style: navItem.active ? 'Medium' : 'Regular' },
|
|
559
|
+
})));
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Sidebar divider
|
|
563
|
+
cmds.push(ap('$0.id', rect('Sidebar Divider', { width: 1, height: 900, fill: divider })));
|
|
564
|
+
|
|
565
|
+
// Main
|
|
566
|
+
var mainIdx = cmds.length;
|
|
567
|
+
cmds.push(ap('$0.id', frame('Main', {
|
|
568
|
+
width: W - sideW - 1, height: 900,
|
|
569
|
+
paddingTop: 32, paddingBottom: 32, paddingLeft: 40, paddingRight: 40,
|
|
570
|
+
gap: 28, fill: bg,
|
|
571
|
+
})));
|
|
572
|
+
|
|
573
|
+
// Header
|
|
574
|
+
cmds.push(ap('$' + mainIdx + '.id', text('Overview', {
|
|
575
|
+
fontSize: 28, color: text1, fontName: { family: 'Inter', style: 'Bold' },
|
|
576
|
+
})));
|
|
577
|
+
|
|
578
|
+
// Metrics row
|
|
579
|
+
var metricsRowIdx = cmds.length;
|
|
580
|
+
cmds.push(ap('$' + mainIdx + '.id', hframe('Metrics', {
|
|
581
|
+
gap: 20, fill: bg,
|
|
582
|
+
})));
|
|
583
|
+
|
|
584
|
+
var metricW = Math.floor((W - sideW - 1 - 80 - (metrics.length - 1) * 20) / metrics.length);
|
|
585
|
+
for (var mi = 0; mi < metrics.length; mi++) {
|
|
586
|
+
var mIdx = cmds.length;
|
|
587
|
+
cmds.push(ap('$' + metricsRowIdx + '.id', frame(metrics[mi].label, {
|
|
588
|
+
width: metricW, paddingTop: 24, paddingBottom: 24, paddingLeft: 24, paddingRight: 24,
|
|
589
|
+
gap: 8, fill: cardBg, cornerRadius: 12,
|
|
590
|
+
})));
|
|
591
|
+
cmds.push(ap('$' + mIdx + '.id', text(metrics[mi].label, {
|
|
592
|
+
fontSize: 13, color: text3, fontName: { family: 'Inter', style: 'Medium' },
|
|
593
|
+
})));
|
|
594
|
+
cmds.push(ap('$' + mIdx + '.id', text(metrics[mi].value, {
|
|
595
|
+
fontSize: 32, color: text1, fontName: { family: 'Inter', style: 'Bold' },
|
|
596
|
+
})));
|
|
597
|
+
cmds.push(ap('$' + mIdx + '.id', text(metrics[mi].change, {
|
|
598
|
+
fontSize: 13, color: metrics[mi].positive ? '#4ade80' : '#f87171',
|
|
599
|
+
fontName: { family: 'Inter', style: 'Semi Bold' },
|
|
600
|
+
})));
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Chart
|
|
604
|
+
var chartIdx = cmds.length;
|
|
605
|
+
cmds.push(ap('$' + mainIdx + '.id', frame('Chart Area', {
|
|
606
|
+
paddingTop: 28, paddingBottom: 28, paddingLeft: 28, paddingRight: 28,
|
|
607
|
+
gap: 16, fill: cardBg, cornerRadius: 12,
|
|
608
|
+
primaryAxisSizingMode: 'FILL',
|
|
609
|
+
})));
|
|
610
|
+
|
|
611
|
+
var chartHeaderIdx = cmds.length;
|
|
612
|
+
cmds.push(ap('$' + chartIdx + '.id', hframe('Chart Header', {
|
|
613
|
+
gap: 12, fill: cardBg, counterAxisAlignItems: 'CENTER',
|
|
614
|
+
})));
|
|
615
|
+
cmds.push(ap('$' + chartHeaderIdx + '.id', text('Revenue Over Time', {
|
|
616
|
+
fontSize: 18, color: text1, fontName: { family: 'Inter', style: 'Semi Bold' },
|
|
617
|
+
})));
|
|
618
|
+
|
|
619
|
+
cmds.push(ap('$' + chartIdx + '.id', rect('Chart Placeholder', {
|
|
620
|
+
width: W - sideW - 1 - 80 - 56, height: 240, fill: '#16163a', cornerRadius: 8,
|
|
621
|
+
})));
|
|
622
|
+
|
|
623
|
+
return {
|
|
624
|
+
commands: cmds,
|
|
625
|
+
description: 'Dashboard with sidebar (' + sideNavItems.length + ' nav items), header, ' + metrics.length + ' metric cards, and chart area. ' + cmds.length + ' elements.',
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// ═══════════════════════════════════════════
|
|
630
|
+
// SECTION BLUEPRINTS
|
|
631
|
+
// ═══════════════════════════════════════════
|
|
632
|
+
|
|
633
|
+
export function buildSection(sectionType, args) {
|
|
634
|
+
switch (sectionType) {
|
|
635
|
+
case 'hero': return buildHeroSection(args);
|
|
636
|
+
case 'features': return buildFeaturesSection(args);
|
|
637
|
+
case 'pricing': return buildPricingSection(args);
|
|
638
|
+
case 'cta': return buildCTASection(args);
|
|
639
|
+
case 'testimonials': return buildTestimonialsSection(args);
|
|
640
|
+
case 'faq': return buildFAQSection(args);
|
|
641
|
+
default: return buildHeroSection(args);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function buildHeroSection(args) {
|
|
646
|
+
var W = args.width || 1440;
|
|
647
|
+
var brand = args.brandColor || '#6366f1';
|
|
648
|
+
var bg = '#09090f';
|
|
649
|
+
var cmds = [];
|
|
650
|
+
|
|
651
|
+
cmds.push(frame('Hero', { width: W, paddingTop: 96, paddingBottom: 96, paddingLeft: 48, paddingRight: 48, gap: 32, fill: bg, primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER' }));
|
|
652
|
+
cmds.push(ap('$0.id', text(args.heading || 'Build something great', { fontSize: 64, color: '#f0f0f8', fontName: { family: 'Inter', style: 'Bold' }, textAlignHorizontal: 'CENTER' })));
|
|
653
|
+
cmds.push(ap('$0.id', text(args.subheading || 'The platform for modern teams.', { fontSize: 20, color: '#a0a0b8', textAlignHorizontal: 'CENTER' })));
|
|
654
|
+
|
|
655
|
+
var btnIdx = cmds.length;
|
|
656
|
+
cmds.push(ap('$0.id', hframe('CTA', { height: 52, paddingLeft: 28, paddingRight: 28, paddingTop: 0, paddingBottom: 0, fill: brand, cornerRadius: 12, primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER' })));
|
|
657
|
+
cmds.push(ap('$' + btnIdx + '.id', text(args.ctaText || 'Get started', { fontSize: 16, color: '#ffffff', fontName: { family: 'Inter', style: 'Semi Bold' } })));
|
|
658
|
+
|
|
659
|
+
return { commands: cmds, description: 'Hero section with heading, subtitle, and CTA.' };
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function buildFeaturesSection(args) {
|
|
663
|
+
var W = args.width || 1440;
|
|
664
|
+
var features = args.features || [
|
|
665
|
+
{ title: 'Fast', desc: 'Blazing fast performance out of the box.' },
|
|
666
|
+
{ title: 'Secure', desc: 'Enterprise-grade security by default.' },
|
|
667
|
+
{ title: 'Scalable', desc: 'Grows seamlessly with your team.' },
|
|
668
|
+
];
|
|
669
|
+
var cmds = [];
|
|
670
|
+
|
|
671
|
+
cmds.push(frame('Features', { width: W, paddingTop: 96, paddingBottom: 96, paddingLeft: 48, paddingRight: 48, gap: 48, fill: '#09090f', counterAxisAlignItems: 'CENTER' }));
|
|
672
|
+
cmds.push(ap('$0.id', text(args.heading || 'Features', { fontSize: 40, color: '#f0f0f8', fontName: { family: 'Inter', style: 'Bold' }, textAlignHorizontal: 'CENTER' })));
|
|
673
|
+
|
|
674
|
+
var rowIdx = cmds.length;
|
|
675
|
+
cmds.push(ap('$0.id', hframe('Cards', { width: 1120, gap: 24, fill: '#09090f' })));
|
|
676
|
+
|
|
677
|
+
for (var i = 0; i < features.length; i++) {
|
|
678
|
+
var cw = Math.floor((1120 - (features.length - 1) * 24) / features.length);
|
|
679
|
+
var ci = cmds.length;
|
|
680
|
+
cmds.push(ap('$' + rowIdx + '.id', frame(features[i].title, { width: cw, paddingTop: 32, paddingBottom: 32, paddingLeft: 28, paddingRight: 28, gap: 16, fill: '#12122a', cornerRadius: 16 })));
|
|
681
|
+
cmds.push(ap('$' + ci + '.id', text(features[i].title, { fontSize: 20, color: '#f0f0f8', fontName: { family: 'Inter', style: 'Semi Bold' } })));
|
|
682
|
+
cmds.push(ap('$' + ci + '.id', text(features[i].desc, { fontSize: 15, color: '#a0a0b8' })));
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return { commands: cmds, description: 'Features section with ' + features.length + ' cards.' };
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function buildPricingSection(args) { return buildPricingPage(args); }
|
|
689
|
+
|
|
690
|
+
function buildCTASection(args) {
|
|
691
|
+
var W = args.width || 1440;
|
|
692
|
+
var brand = args.brandColor || '#6366f1';
|
|
693
|
+
var cmds = [];
|
|
694
|
+
|
|
695
|
+
cmds.push(frame('CTA', { width: W, paddingTop: 96, paddingBottom: 96, paddingLeft: 48, paddingRight: 48, gap: 28, fill: '#0f0f1c', primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER' }));
|
|
696
|
+
cmds.push(ap('$0.id', text(args.heading || 'Ready to start?', { fontSize: 44, color: '#f0f0f8', fontName: { family: 'Inter', style: 'Bold' }, textAlignHorizontal: 'CENTER' })));
|
|
697
|
+
cmds.push(ap('$0.id', text(args.subheading || 'Join thousands of teams shipping faster.', { fontSize: 18, color: '#a0a0b8', textAlignHorizontal: 'CENTER' })));
|
|
698
|
+
var btnIdx = cmds.length;
|
|
699
|
+
cmds.push(ap('$0.id', hframe('CTA Button', { height: 56, paddingLeft: 32, paddingRight: 32, paddingTop: 0, paddingBottom: 0, fill: brand, cornerRadius: 12, primaryAxisAlignItems: 'CENTER', counterAxisAlignItems: 'CENTER' })));
|
|
700
|
+
cmds.push(ap('$' + btnIdx + '.id', text(args.ctaText || 'Get started free', { fontSize: 17, color: '#ffffff', fontName: { family: 'Inter', style: 'Semi Bold' } })));
|
|
701
|
+
|
|
702
|
+
return { commands: cmds, description: 'CTA section with heading, subtitle, and button.' };
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function buildTestimonialsSection(args) {
|
|
706
|
+
var W = args.width || 1440;
|
|
707
|
+
var testimonials = args.testimonials || [
|
|
708
|
+
{ quote: 'This product transformed how our team works. The speed and reliability are unmatched.', author: 'Sarah Chen', role: 'CTO, TechCorp' },
|
|
709
|
+
{ quote: 'Best tool we adopted this year. Our deployment time dropped by 80%.', author: 'Marcus Rivera', role: 'VP Engineering, ScaleUp' },
|
|
710
|
+
];
|
|
711
|
+
var cmds = [];
|
|
712
|
+
|
|
713
|
+
cmds.push(frame('Testimonials', { width: W, paddingTop: 96, paddingBottom: 96, paddingLeft: 48, paddingRight: 48, gap: 48, fill: '#09090f', counterAxisAlignItems: 'CENTER' }));
|
|
714
|
+
cmds.push(ap('$0.id', text('Trusted by the best teams', { fontSize: 40, color: '#f0f0f8', fontName: { family: 'Inter', style: 'Bold' }, textAlignHorizontal: 'CENTER' })));
|
|
715
|
+
|
|
716
|
+
var rowIdx = cmds.length;
|
|
717
|
+
cmds.push(ap('$0.id', hframe('Cards', { width: 1120, gap: 24, fill: '#09090f' })));
|
|
718
|
+
|
|
719
|
+
for (var i = 0; i < testimonials.length; i++) {
|
|
720
|
+
var tw = Math.floor((1120 - (testimonials.length - 1) * 24) / testimonials.length);
|
|
721
|
+
var ti = cmds.length;
|
|
722
|
+
cmds.push(ap('$' + rowIdx + '.id', frame('Testimonial', { width: tw, paddingTop: 32, paddingBottom: 32, paddingLeft: 28, paddingRight: 28, gap: 20, fill: '#12122a', cornerRadius: 16 })));
|
|
723
|
+
cmds.push(ap('$' + ti + '.id', text('★★★★★', { fontSize: 16, color: '#fbbf24' })));
|
|
724
|
+
cmds.push(ap('$' + ti + '.id', text('"' + testimonials[i].quote + '"', { fontSize: 16, color: '#d0d0e0', fontName: { family: 'Inter', style: 'Regular' } })));
|
|
725
|
+
cmds.push(ap('$' + ti + '.id', text(testimonials[i].author, { fontSize: 15, color: '#f0f0f8', fontName: { family: 'Inter', style: 'Semi Bold' } })));
|
|
726
|
+
cmds.push(ap('$' + ti + '.id', text(testimonials[i].role, { fontSize: 13, color: '#686880' })));
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return { commands: cmds, description: 'Testimonials with ' + testimonials.length + ' cards.' };
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function buildFAQSection(args) {
|
|
733
|
+
var W = args.width || 1440;
|
|
734
|
+
var faqs = args.faqs || [
|
|
735
|
+
{ q: 'How do I get started?', a: 'Sign up for a free account and follow our quick-start guide. You will be up and running in less than 5 minutes.' },
|
|
736
|
+
{ q: 'Can I cancel anytime?', a: 'Yes. No contracts, no cancellation fees. Cancel with one click from your dashboard.' },
|
|
737
|
+
{ q: 'Do you offer support?', a: 'All plans include email support. Pro and Enterprise plans get priority support with guaranteed response times.' },
|
|
738
|
+
];
|
|
739
|
+
var cmds = [];
|
|
740
|
+
|
|
741
|
+
cmds.push(frame('FAQ', { width: W, paddingTop: 96, paddingBottom: 96, paddingLeft: 48, paddingRight: 48, gap: 48, fill: '#09090f', counterAxisAlignItems: 'CENTER' }));
|
|
742
|
+
cmds.push(ap('$0.id', text('Frequently asked questions', { fontSize: 40, color: '#f0f0f8', fontName: { family: 'Inter', style: 'Bold' }, textAlignHorizontal: 'CENTER' })));
|
|
743
|
+
|
|
744
|
+
var listIdx = cmds.length;
|
|
745
|
+
cmds.push(ap('$0.id', frame('FAQ List', { width: 720, gap: 12, fill: '#09090f' })));
|
|
746
|
+
|
|
747
|
+
for (var i = 0; i < faqs.length; i++) {
|
|
748
|
+
var fIdx = cmds.length;
|
|
749
|
+
cmds.push(ap('$' + listIdx + '.id', frame('FAQ ' + (i + 1), { paddingTop: 24, paddingBottom: 24, paddingLeft: 24, paddingRight: 24, gap: 8, fill: '#12122a', cornerRadius: 12, primaryAxisSizingMode: 'FILL' })));
|
|
750
|
+
cmds.push(ap('$' + fIdx + '.id', text(faqs[i].q, { fontSize: 16, color: '#f0f0f8', fontName: { family: 'Inter', style: 'Semi Bold' } })));
|
|
751
|
+
cmds.push(ap('$' + fIdx + '.id', text(faqs[i].a, { fontSize: 14, color: '#a0a0b8' })));
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
return { commands: cmds, description: 'FAQ section with ' + faqs.length + ' items.' };
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// ═══════════════════════════════════════════
|
|
758
|
+
// ROUTER
|
|
759
|
+
// ═══════════════════════════════════════════
|
|
760
|
+
|
|
761
|
+
export function getBlueprint(toolName, args) {
|
|
762
|
+
switch (toolName) {
|
|
763
|
+
case 'create_page':
|
|
764
|
+
switch (args.pageType) {
|
|
765
|
+
case 'landing': return buildLandingPage(args);
|
|
766
|
+
case 'pricing': return buildPricingPage(args);
|
|
767
|
+
case 'dashboard': return buildDashboardPage(args);
|
|
768
|
+
default: return buildLandingPage(args);
|
|
769
|
+
}
|
|
770
|
+
case 'create_section':
|
|
771
|
+
return buildSection(args.sectionType, args);
|
|
772
|
+
default:
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
}
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
export { startServer } from './server.js';
|
|
6
6
|
export { Relay } from './relay.js';
|
|
7
|
+
export { executeSequence } from './orchestrator.js';
|
|
8
|
+
export { getBlueprint, buildLandingPage, buildPricingPage, buildDashboardPage, buildSection } from './blueprints.js';
|
|
7
9
|
export { TOOLS, CATEGORIES, getToolByName, getToolsByCategory, getAllToolNames } from './tools/registry.js';
|
|
8
10
|
export { handleTool } from './tools/handlers.js';
|
|
9
11
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// CONDUCTOR — Orchestrator
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
// Takes a blueprint (sequence of commands) and executes them
|
|
5
|
+
// through the relay one at a time. Each command can reference
|
|
6
|
+
// results from previous commands via $ref tokens.
|
|
7
|
+
//
|
|
8
|
+
// Example: create a frame, then create text inside it.
|
|
9
|
+
// The text command references the frame's ID via $ref.
|
|
10
|
+
//
|
|
11
|
+
// { type: 'create_frame', name: 'Hero', ... } → returns { id: '5:2' }
|
|
12
|
+
// { type: 'create_text', parentId: '$0.id', text: ... } → parentId resolved to '5:2'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Execute a sequence of Figma commands through the relay.
|
|
16
|
+
* Supports $ref tokens: '$N.field' references result N's field.
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} relay - The WebSocket relay instance
|
|
19
|
+
* @param {Array} commands - Array of { type, data } command objects
|
|
20
|
+
* @param {Function} onProgress - Optional callback(stepIndex, totalSteps, result)
|
|
21
|
+
* @returns {Promise<{ results: Array, errors: Array, success: boolean }>}
|
|
22
|
+
*/
|
|
23
|
+
export async function executeSequence(relay, commands, onProgress) {
|
|
24
|
+
var results = [];
|
|
25
|
+
var errors = [];
|
|
26
|
+
|
|
27
|
+
for (var i = 0; i < commands.length; i++) {
|
|
28
|
+
var cmd = commands[i];
|
|
29
|
+
var resolvedData = resolveRefs(cmd.data || {}, results);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
var result = await relay.sendToPlugin(cmd.type, resolvedData, 10000);
|
|
33
|
+
|
|
34
|
+
if (result && result.error) {
|
|
35
|
+
errors.push({ step: i, command: cmd.type, error: result.error });
|
|
36
|
+
results.push(result);
|
|
37
|
+
// Don't stop on error — skip and continue
|
|
38
|
+
process.stderr.write('CONDUCTOR orchestrator: step ' + i + ' (' + cmd.type + ') error: ' + result.error + '\n');
|
|
39
|
+
} else {
|
|
40
|
+
results.push(result || {});
|
|
41
|
+
if (onProgress) onProgress(i, commands.length, result);
|
|
42
|
+
process.stderr.write('CONDUCTOR orchestrator: step ' + (i + 1) + '/' + commands.length + ' ' + cmd.type + ' -> ' + (result && (result.id || result.name) || 'ok') + '\n');
|
|
43
|
+
}
|
|
44
|
+
} catch (err) {
|
|
45
|
+
errors.push({ step: i, command: cmd.type, error: String(err) });
|
|
46
|
+
results.push({ error: String(err) });
|
|
47
|
+
process.stderr.write('CONDUCTOR orchestrator: step ' + i + ' (' + cmd.type + ') threw: ' + String(err) + '\n');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Small delay between commands to let Figma process
|
|
51
|
+
await sleep(50);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
results: results,
|
|
56
|
+
errors: errors,
|
|
57
|
+
success: errors.length === 0,
|
|
58
|
+
totalSteps: commands.length,
|
|
59
|
+
completedSteps: commands.length - errors.length,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve $ref tokens in command data.
|
|
65
|
+
* '$0.id' → results[0].id
|
|
66
|
+
* '$parent' → results[results.length-1].id (last result's id)
|
|
67
|
+
* '$2.name' → results[2].name
|
|
68
|
+
*/
|
|
69
|
+
function resolveRefs(data, results) {
|
|
70
|
+
if (typeof data === 'string') {
|
|
71
|
+
// Check for $ref pattern
|
|
72
|
+
return data.replace(/\$(\d+)\.(\w+)/g, function(match, idx, field) {
|
|
73
|
+
var r = results[parseInt(idx)];
|
|
74
|
+
return (r && r[field] !== undefined) ? r[field] : match;
|
|
75
|
+
}).replace(/\$parent/g, function() {
|
|
76
|
+
var last = results[results.length - 1];
|
|
77
|
+
return (last && last.id) ? last.id : '';
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (Array.isArray(data)) {
|
|
82
|
+
return data.map(function(item) { return resolveRefs(item, results); });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (data && typeof data === 'object') {
|
|
86
|
+
var resolved = {};
|
|
87
|
+
for (var key in data) {
|
|
88
|
+
if (data.hasOwnProperty(key)) {
|
|
89
|
+
resolved[key] = resolveRefs(data[key], results);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return resolved;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return data;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function sleep(ms) {
|
|
99
|
+
return new Promise(function(resolve) { setTimeout(resolve, ms); });
|
|
100
|
+
}
|
package/src/server.js
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
// ═══════════════════════════════════════════
|
|
2
|
-
// CONDUCTOR — MCP Server +
|
|
2
|
+
// CONDUCTOR — MCP Server + Orchestrator
|
|
3
3
|
// ═══════════════════════════════════════════
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
// When create_page or create_section is called:
|
|
5
|
+
// 1. Generates a blueprint (30-50 sequential commands)
|
|
6
|
+
// 2. Executes each one through the relay to the Figma plugin
|
|
7
|
+
// 3. Each command references results from previous commands ($ref)
|
|
8
|
+
// 4. Returns a summary of everything created
|
|
9
9
|
|
|
10
10
|
import { TOOLS } from './tools/registry.js';
|
|
11
11
|
import { handleTool } from './tools/handlers.js';
|
|
12
12
|
import { Relay } from './relay.js';
|
|
13
|
+
import { getBlueprint } from './blueprints.js';
|
|
14
|
+
import { executeSequence } from './orchestrator.js';
|
|
13
15
|
|
|
14
|
-
var SERVER_INFO = { name: 'conductor-figma', version: '0.
|
|
16
|
+
var SERVER_INFO = { name: 'conductor-figma', version: '0.3.0' };
|
|
15
17
|
var CAPABILITIES = { tools: {} };
|
|
16
18
|
var relay = null;
|
|
17
19
|
|
|
20
|
+
// Tools that trigger blueprint orchestration (multi-command sequences)
|
|
21
|
+
var BLUEPRINT_TOOLS = new Set(['create_page', 'create_section']);
|
|
22
|
+
|
|
18
23
|
export async function startServer(options) {
|
|
19
24
|
options = options || {};
|
|
20
25
|
var port = options.port || 9800;
|
|
@@ -23,9 +28,9 @@ export async function startServer(options) {
|
|
|
23
28
|
var relayStarted = await relay.start();
|
|
24
29
|
|
|
25
30
|
if (relayStarted) {
|
|
26
|
-
process.stderr.write('CONDUCTOR: MCP +
|
|
31
|
+
process.stderr.write('CONDUCTOR v0.3.0: MCP + orchestrator ready (' + TOOLS.length + ' tools, ws://localhost:' + port + ')\n');
|
|
27
32
|
} else {
|
|
28
|
-
process.stderr.write('CONDUCTOR: MCP
|
|
33
|
+
process.stderr.write('CONDUCTOR v0.3.0: MCP ready (' + TOOLS.length + ' tools, no relay — install ws)\n');
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
var buffer = '';
|
|
@@ -89,7 +94,47 @@ async function handleMessage(msg) {
|
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
async function handleToolCall(id, toolName, toolArgs) {
|
|
92
|
-
|
|
97
|
+
|
|
98
|
+
// ═══ ORCHESTRATED BLUEPRINTS ═══
|
|
99
|
+
// create_page and create_section generate 20-50 commands and execute them all
|
|
100
|
+
if (BLUEPRINT_TOOLS.has(toolName) && relay && relay.isConnected()) {
|
|
101
|
+
var blueprint = getBlueprint(toolName, toolArgs);
|
|
102
|
+
|
|
103
|
+
if (blueprint && blueprint.commands && blueprint.commands.length > 0) {
|
|
104
|
+
process.stderr.write('CONDUCTOR orchestrator: ' + toolName + ' -> ' + blueprint.commands.length + ' commands\n');
|
|
105
|
+
process.stderr.write('CONDUCTOR orchestrator: ' + blueprint.description + '\n');
|
|
106
|
+
|
|
107
|
+
var outcome = await executeSequence(relay, blueprint.commands);
|
|
108
|
+
|
|
109
|
+
var summary = {
|
|
110
|
+
tool: toolName,
|
|
111
|
+
description: blueprint.description,
|
|
112
|
+
totalCommands: outcome.totalSteps,
|
|
113
|
+
completed: outcome.completedSteps,
|
|
114
|
+
errors: outcome.errors.length,
|
|
115
|
+
success: outcome.success,
|
|
116
|
+
createdNodes: [],
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Collect all created node IDs and names
|
|
120
|
+
for (var r = 0; r < outcome.results.length; r++) {
|
|
121
|
+
var res = outcome.results[r];
|
|
122
|
+
if (res && res.id) {
|
|
123
|
+
summary.createdNodes.push({ id: res.id, name: res.name || '', type: res.type || '' });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (outcome.errors.length > 0) {
|
|
128
|
+
summary.errorDetails = outcome.errors;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
sendResult(id, { content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }] });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ═══ DIRECT FIGMA COMMANDS ═══
|
|
137
|
+
// Single commands forwarded directly to plugin
|
|
93
138
|
if (relay && relay.isFigmaCommand(toolName) && relay.isConnected()) {
|
|
94
139
|
process.stderr.write('CONDUCTOR: -> Figma: ' + toolName + '\n');
|
|
95
140
|
try {
|
|
@@ -102,10 +147,10 @@ async function handleToolCall(id, toolName, toolArgs) {
|
|
|
102
147
|
return;
|
|
103
148
|
}
|
|
104
149
|
|
|
105
|
-
//
|
|
150
|
+
// ═══ DESIGN INTELLIGENCE (local) ═══
|
|
106
151
|
var result = handleTool(toolName, toolArgs, null);
|
|
107
152
|
|
|
108
|
-
// Check if handler produced a Figma action
|
|
153
|
+
// Check if handler produced a Figma action to forward
|
|
109
154
|
if (relay && relay.isConnected()) {
|
|
110
155
|
try {
|
|
111
156
|
var data = JSON.parse(result.content[0].text);
|
|
@@ -120,7 +165,25 @@ async function handleToolCall(id, toolName, toolArgs) {
|
|
|
120
165
|
} catch (e) { /* not JSON or no action */ }
|
|
121
166
|
}
|
|
122
167
|
|
|
123
|
-
//
|
|
168
|
+
// Blueprint tool but plugin not connected — return the blueprint spec
|
|
169
|
+
if (BLUEPRINT_TOOLS.has(toolName)) {
|
|
170
|
+
var bp = getBlueprint(toolName, toolArgs);
|
|
171
|
+
if (bp) {
|
|
172
|
+
var spec = {
|
|
173
|
+
tool: toolName,
|
|
174
|
+
description: bp.description,
|
|
175
|
+
commandCount: bp.commands.length,
|
|
176
|
+
_note: relay && !relay.isConnected()
|
|
177
|
+
? 'Figma plugin not connected. Connect the CONDUCTOR plugin to execute these ' + bp.commands.length + ' commands on canvas.'
|
|
178
|
+
: 'WebSocket relay not available. Install ws package for Figma bridge.',
|
|
179
|
+
commands: bp.commands.map(function(c, i) { return { step: i, type: c.type, name: c.data.name || c.data.text || '' }; }),
|
|
180
|
+
};
|
|
181
|
+
sendResult(id, { content: [{ type: 'text', text: JSON.stringify(spec, null, 2) }] });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Figma command but not connected — add note
|
|
124
187
|
if (relay && relay.isFigmaCommand(toolName) && !relay.isConnected()) {
|
|
125
188
|
try {
|
|
126
189
|
var parsed = JSON.parse(result.content[0].text);
|