conductor-figma 3.0.4 → 3.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.
@@ -123,8 +123,9 @@ async function executeCommand(cmd, data) {
123
123
  frame.name = data.name || 'Frame'
124
124
  if (data.width) frame.resize(data.width, data.height || data.width)
125
125
  frame.layoutMode = data.direction || 'VERTICAL'
126
- frame.primaryAxisSizingMode = data.primaryAxisSizingMode === 'FILL' ? 'FIXED' : (data.primaryAxisSizingMode || 'HUG')
127
- frame.counterAxisSizingMode = data.counterAxisSizingMode || 'HUG'
126
+ frame.primaryAxisSizingMode = data.primaryAxisSizingMode === 'FILL' ? 'FIXED' : (data.primaryAxisSizingMode === 'FIXED' ? 'FIXED' : 'AUTO')
127
+ frame.counterAxisSizingMode = data.counterAxisSizingMode === 'FILL' ? 'FIXED' : (data.counterAxisSizingMode === 'FIXED' ? 'FIXED' : 'AUTO')
128
+ if (data.primaryAxisSizingMode === 'FILL') frame.layoutGrow = 1
128
129
  if (data.paddingTop !== undefined) frame.paddingTop = data.paddingTop
129
130
  if (data.paddingRight !== undefined) frame.paddingRight = data.paddingRight
130
131
  if (data.paddingBottom !== undefined) frame.paddingBottom = data.paddingBottom
@@ -303,8 +304,8 @@ async function executeCommand(cmd, data) {
303
304
  if (data.gap !== undefined) node.itemSpacing = data.gap
304
305
  if (data.primaryAxisAlignItems) node.primaryAxisAlignItems = data.primaryAxisAlignItems
305
306
  if (data.counterAxisAlignItems) node.counterAxisAlignItems = data.counterAxisAlignItems
306
- if (data.primaryAxisSizingMode) node.primaryAxisSizingMode = data.primaryAxisSizingMode === 'FILL' ? 'FIXED' : data.primaryAxisSizingMode
307
- if (data.counterAxisSizingMode) node.counterAxisSizingMode = data.counterAxisSizingMode === 'FILL' ? 'FIXED' : data.counterAxisSizingMode
307
+ if (data.primaryAxisSizingMode) node.primaryAxisSizingMode = data.primaryAxisSizingMode === 'FILL' ? 'FIXED' : (data.primaryAxisSizingMode === 'FIXED' ? 'FIXED' : 'AUTO')
308
+ if (data.counterAxisSizingMode) node.counterAxisSizingMode = data.counterAxisSizingMode === 'FILL' ? 'FIXED' : (data.counterAxisSizingMode === 'FIXED' ? 'FIXED' : 'AUTO')
308
309
  return { id: node.id, layout: 'set' }
309
310
  }
310
311
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conductor-figma",
3
- "version": "3.0.4",
3
+ "version": "3.3.1",
4
4
  "description": "Design-intelligent MCP server for Figma. 201 design-intelligent tools for Figma. Every tool knows typography, spacing, color, accessibility. Not a shape proxy — a design engine.",
5
5
  "author": "0xDragoon",
6
6
  "license": "MIT",
@@ -0,0 +1,638 @@
1
+ // ═══════════════════════════════════════════
2
+ // CONDUCTOR v3.1 — Composition Engine
3
+ // ═══════════════════════════════════════════
4
+ // Generates rich, multi-layered Figma designs.
5
+ // Each section produces 30-60+ elements.
6
+
7
+ import { snap, semanticColors, componentDefaults, typeScale, SPACING, RADIUS, SHADOWS } from './intelligence.js'
8
+
9
+ // ─── Sequence runner ───
10
+ async function runSequence(bridge, commands) {
11
+ var results = []
12
+ for (var i = 0; i < commands.length; i++) {
13
+ var cmd = commands[i]
14
+ var data = {}
15
+ var src = cmd.data
16
+ for (var k in src) {
17
+ if (src.hasOwnProperty(k)) {
18
+ var v = src[k]
19
+ if (typeof v === 'string' && v.charAt(0) === '$') {
20
+ var refIdx = parseInt(v.replace('$','').replace('.id',''))
21
+ if (results[refIdx] && results[refIdx].id) v = results[refIdx].id
22
+ }
23
+ data[k] = v
24
+ }
25
+ }
26
+ try {
27
+ var result = await bridge.send(cmd.type, data)
28
+ results.push(result)
29
+ } catch (e) {
30
+ results.push({ id: null, error: e.message })
31
+ }
32
+ }
33
+ return results
34
+ }
35
+
36
+ // ─── Shorthand builders ───
37
+ function F(name, opts) { opts.name = name; opts.direction = opts.direction || 'VERTICAL'; return { type:'create_frame', data:opts } }
38
+ function H(name, opts) { opts.direction = 'HORIZONTAL'; return F(name, opts) }
39
+ function T(text, opts) { opts.text = text; if (!opts.fontName) opts.fontName = { family:'Inter', style: opts._w || 'Regular' }; delete opts._w; return { type:'create_text', data:opts } }
40
+ function R(name, opts) { opts.name = name; return { type:'create_rectangle', data:opts } }
41
+ function $(idx) { return '$' + idx + '.id' }
42
+
43
+ // ═══════════════════════════════════════════
44
+ // NAVIGATION
45
+ // ═══════════════════════════════════════════
46
+ function composeNav(content, colors, brand, W) {
47
+ var c = [], brandName = (content && content.brand) || 'acme'
48
+ var navItems = (content && content.navItems) || ['Features','Pricing','Docs','Blog']
49
+
50
+ // 0: Nav bar
51
+ c.push(H('Navigation', { width:W, height:64, paddingLeft:40, paddingRight:40, paddingTop:0, paddingBottom:0, gap:0, fill:colors.bg, counterAxisAlignItems:'CENTER' }))
52
+ // 1: Logo mark
53
+ c.push(H('Logo Mark', { parentId:$(0), width:28, height:28, fill:brand, cornerRadius:7, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
54
+ // 2: Logo letter
55
+ c.push(T(brandName.charAt(0).toUpperCase(), { parentId:$(1), fontSize:14, _w:'Bold', color:'#ffffff' }))
56
+ // 3: Brand name
57
+ c.push(T(brandName, { parentId:$(0), fontSize:15, _w:'Semi Bold', color:colors.text1 }))
58
+ // 4: Spacer
59
+ c.push(F('_spacer', { parentId:$(0), width:1, height:1, fill:colors.bg, primaryAxisSizingMode:'FILL' }))
60
+ // 5: Nav links
61
+ c.push(H('Nav Links', { parentId:$(0), gap:28, counterAxisAlignItems:'CENTER' }))
62
+ for (var i = 0; i < navItems.length; i++) {
63
+ c.push(T(navItems[i], { parentId:$(5), fontSize:14, _w:'Medium', color:colors.text3 }))
64
+ }
65
+ // CTA button
66
+ var btnIdx = c.length
67
+ c.push(H('Nav CTA', { parentId:$(0), height:36, paddingLeft:16, paddingRight:16, paddingTop:0, paddingBottom:0, fill:brand, cornerRadius:8, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
68
+ c.push(T('Get started', { parentId:$(btnIdx), fontSize:13, _w:'Semi Bold', color:'#ffffff' }))
69
+ // Divider
70
+ c.push(R('Nav Divider', { parentId:$(0), width:W, height:1, fill:colors.border }))
71
+ return c
72
+ }
73
+
74
+ // ═══════════════════════════════════════════
75
+ // HERO
76
+ // ═══════════════════════════════════════════
77
+ function composeHero(content, colors, brand, W) {
78
+ var c = []
79
+ var title = (content && content.title) || 'Ship faster with\nless overhead'
80
+ var subtitle = (content && content.subtitle) || 'The modern platform for teams that move fast.\nEverything you need to build, deploy, and scale.'
81
+ var ctaText = (content && content.cta) || 'Start for free'
82
+
83
+ // 0: Hero section
84
+ c.push(F('Hero Section', { width:W, paddingTop:112, paddingBottom:96, paddingLeft:48, paddingRight:48, gap:28, fill:colors.bg, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
85
+
86
+ // 1: Overline badge
87
+ c.push(H('Badge', { parentId:$(0), paddingLeft:6, paddingRight:14, paddingTop:5, paddingBottom:5, gap:8, fill:colors.surface2, cornerRadius:20, counterAxisAlignItems:'CENTER' }))
88
+ // 2: Badge dot container
89
+ c.push(H('Badge Dot', { parentId:$(1), width:20, height:20, fill:colors.brand, cornerRadius:10, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
90
+ // 3: Dot inner (sparkle)
91
+ c.push(T('✦', { parentId:$(2), fontSize:9, color:'#ffffff' }))
92
+ // 4: Badge text
93
+ c.push(T('Introducing v3 — now with 201 tools', { parentId:$(1), fontSize:12, _w:'Medium', color:colors.text2 }))
94
+
95
+ // 5: Main heading
96
+ c.push(T(title, { parentId:$(0), fontSize:68, _w:'Bold', color:colors.text1, textAlignHorizontal:'CENTER', lineHeight:1.05 }))
97
+
98
+ // 6: Subtitle
99
+ c.push(T(subtitle, { parentId:$(0), fontSize:19, color:colors.text2, textAlignHorizontal:'CENTER', maxWidth:560 }))
100
+
101
+ // 7: Button row
102
+ c.push(H('Buttons', { parentId:$(0), gap:12, paddingTop:8 }))
103
+ // 8: Primary CTA
104
+ c.push(H('Primary CTA', { parentId:$(7), height:52, paddingLeft:28, paddingRight:28, paddingTop:0, paddingBottom:0, fill:brand, cornerRadius:12, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
105
+ // 9: Primary label
106
+ c.push(T(ctaText, { parentId:$(8), fontSize:16, _w:'Semi Bold', color:'#ffffff' }))
107
+ // 10: Secondary CTA
108
+ c.push(H('Secondary CTA', { parentId:$(7), height:52, paddingLeft:28, paddingRight:28, paddingTop:0, paddingBottom:0, fill:colors.surface, cornerRadius:12, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
109
+ // 11: Secondary label
110
+ c.push(T('See how it works →', { parentId:$(10), fontSize:16, _w:'Medium', color:colors.text2 }))
111
+
112
+ // 12: Social proof row
113
+ c.push(H('Social Proof', { parentId:$(0), gap:12, counterAxisAlignItems:'CENTER', paddingTop:12 }))
114
+ // 13: Avatar stack frame
115
+ c.push(H('Avatars', { parentId:$(12), gap:-8 }))
116
+ // 14-18: Five avatar circles
117
+ var avatarColors = [brand, '#10b981', '#f59e0b', '#ef4444', '#6366f1']
118
+ var avatarLetters = ['A','S','M','J','K']
119
+ for (var ai = 0; ai < 5; ai++) {
120
+ var avIdx = c.length
121
+ c.push(H('Avatar', { parentId:$(13), width:28, height:28, fill:avatarColors[ai], cornerRadius:14, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
122
+ c.push(T(avatarLetters[ai], { parentId:$(avIdx), fontSize:11, _w:'Semi Bold', color:'#ffffff' }))
123
+ }
124
+ // Stars
125
+ c.push(T('★★★★★', { parentId:$(12), fontSize:13, color:'#fbbf24' }))
126
+ // Proof text
127
+ c.push(T('Loved by 10,000+ teams', { parentId:$(12), fontSize:13, _w:'Medium', color:colors.text3 }))
128
+
129
+ return c
130
+ }
131
+
132
+ // ═══════════════════════════════════════════
133
+ // STATS BAR
134
+ // ═══════════════════════════════════════════
135
+ function composeStats(content, colors, brand, W) {
136
+ var c = [], contentW = 1120
137
+ var stats = (content && content.stats) || [
138
+ { value:'10,000+', label:'Teams worldwide' },
139
+ { value:'99.9%', label:'Uptime SLA' },
140
+ { value:'< 50ms', label:'Global latency' },
141
+ { value:'4.9/5', label:'Customer rating' },
142
+ ]
143
+
144
+ // 0: Stats bar
145
+ c.push(H('Stats Bar', { width:W, paddingTop:48, paddingBottom:48, paddingLeft:48, paddingRight:48, fill:colors.bg2, primaryAxisAlignItems:'CENTER' }))
146
+ // 1: Inner container
147
+ c.push(H('Stats Inner', { parentId:$(0), width:contentW }))
148
+
149
+ for (var si = 0; si < stats.length; si++) {
150
+ var sw = Math.floor(contentW / stats.length)
151
+ var sti = c.length
152
+ // Stat frame
153
+ c.push(F('Stat: ' + stats[si].label, { parentId:$(1), width:sw, paddingTop:16, paddingBottom:16, gap:4, counterAxisAlignItems:'CENTER' }))
154
+ // Value
155
+ c.push(T(stats[si].value, { parentId:$(sti), fontSize:36, _w:'Bold', color:colors.text1, textAlignHorizontal:'CENTER' }))
156
+ // Label
157
+ c.push(T(stats[si].label, { parentId:$(sti), fontSize:13, _w:'Medium', color:colors.text3, textAlignHorizontal:'CENTER' }))
158
+ // Vertical divider (except last)
159
+ if (si < stats.length - 1) {
160
+ c.push(R('Divider', { parentId:$(1), width:1, height:48, fill:colors.border }))
161
+ }
162
+ }
163
+ return c
164
+ }
165
+
166
+ // ═══════════════════════════════════════════
167
+ // FEATURES
168
+ // ═══════════════════════════════════════════
169
+ function composeFeatures(content, colors, brand, W) {
170
+ var c = [], contentW = 1120
171
+ var heading = (content && content.title) || 'Everything you need'
172
+ var sub = (content && content.subtitle) || 'Powerful features to help your team ship faster\nand with more confidence.'
173
+ var features = (content && content.features) || [
174
+ { icon:'⚡', title:'Instant Deploy', desc:'Push to deploy in seconds. Zero config.\nAutomatic HTTPS, global CDN, and instant rollbacks.' },
175
+ { icon:'📈', title:'Auto Scale', desc:'Scales to millions of requests automatically.\nPay only for what you use. No capacity planning.' },
176
+ { icon:'🔒', title:'Enterprise Security', desc:'SOC 2 Type II compliant. End-to-end encryption.\nSSO, RBAC, and audit logs built in.' },
177
+ ]
178
+
179
+ // 0: Section
180
+ c.push(F('Features Section', { width:W, paddingTop:96, paddingBottom:96, paddingLeft:48, paddingRight:48, gap:56, fill:colors.bg, counterAxisAlignItems:'CENTER' }))
181
+
182
+ // 1: Header group
183
+ c.push(F('Features Header', { parentId:$(0), gap:12, counterAxisAlignItems:'CENTER' }))
184
+ // 2: Overline
185
+ c.push(T('FEATURES', { parentId:$(1), fontSize:11, _w:'Semi Bold', color:brand, letterSpacing:2.5, textAlignHorizontal:'CENTER' }))
186
+ // 3: Heading
187
+ c.push(T(heading, { parentId:$(1), fontSize:44, _w:'Bold', color:colors.text1, textAlignHorizontal:'CENTER' }))
188
+ // 4: Subtitle
189
+ c.push(T(sub, { parentId:$(1), fontSize:18, color:colors.text2, textAlignHorizontal:'CENTER' }))
190
+
191
+ // 5: Card row
192
+ c.push(H('Feature Cards', { parentId:$(0), width:contentW, gap:20 }))
193
+
194
+ for (var fi = 0; fi < features.length; fi++) {
195
+ var f = features[fi]
196
+ var cw = Math.floor((contentW - (features.length - 1) * 20) / features.length)
197
+ var ci = c.length
198
+
199
+ // Card
200
+ c.push(F(f.title, { parentId:$(5), width:cw, paddingTop:32, paddingBottom:32, paddingLeft:28, paddingRight:28, gap:16, fill:colors.surface, cornerRadius:16 }))
201
+
202
+ // Icon circle
203
+ var iconI = c.length
204
+ c.push(H('Icon BG', { parentId:$(ci), width:48, height:48, fill:colors.surface3, cornerRadius:12, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
205
+ c.push(T(f.icon, { parentId:$(iconI), fontSize:22 }))
206
+
207
+ // Title
208
+ c.push(T(f.title, { parentId:$(ci), fontSize:20, _w:'Semi Bold', color:colors.text1 }))
209
+
210
+ // Description
211
+ c.push(T(f.desc, { parentId:$(ci), fontSize:14, color:colors.text2, lineHeight:1.6 }))
212
+
213
+ // Divider inside card
214
+ c.push(R('Card Divider', { parentId:$(ci), width:cw - 56, height:1, fill:colors.border }))
215
+
216
+ // Learn more link
217
+ c.push(T('Learn more →', { parentId:$(ci), fontSize:13, _w:'Medium', color:brand }))
218
+ }
219
+ return c
220
+ }
221
+
222
+ // ═══════════════════════════════════════════
223
+ // TESTIMONIALS
224
+ // ═══════════════════════════════════════════
225
+ function composeTestimonials(content, colors, brand, W) {
226
+ var c = [], contentW = 1120
227
+ var testimonials = (content && content.testimonials) || [
228
+ { quote:'This product transformed how our team works. The speed and reliability are unmatched.', author:'Sarah Chen', role:'CTO, TechCorp', initial:'S' },
229
+ { quote:'Best tool we adopted this year. Our deployment time dropped by 80%.', author:'Marcus Rivera', role:'VP Engineering, ScaleUp', initial:'M' },
230
+ { quote:'Finally something that actually understands what good design looks like.', author:'Aria Kim', role:'Design Lead, Craft', initial:'A' },
231
+ ]
232
+
233
+ // 0: Section
234
+ c.push(F('Testimonials', { width:W, paddingTop:96, paddingBottom:96, paddingLeft:48, paddingRight:48, gap:56, fill:colors.bg2, counterAxisAlignItems:'CENTER' }))
235
+ // 1: Header
236
+ c.push(F('Testimonials Header', { parentId:$(0), gap:12, counterAxisAlignItems:'CENTER' }))
237
+ c.push(T('TESTIMONIALS', { parentId:$(1), fontSize:11, _w:'Semi Bold', color:brand, letterSpacing:2.5 }))
238
+ c.push(T('Trusted by the best teams', { parentId:$(1), fontSize:44, _w:'Bold', color:colors.text1, textAlignHorizontal:'CENTER' }))
239
+
240
+ // Card row
241
+ var rowIdx = c.length
242
+ c.push(H('Testimonial Cards', { parentId:$(0), width:contentW, gap:16 }))
243
+
244
+ for (var ti = 0; ti < testimonials.length; ti++) {
245
+ var t = testimonials[ti]
246
+ var tw = Math.floor((contentW - (testimonials.length - 1) * 16) / testimonials.length)
247
+ var tidx = c.length
248
+
249
+ // Card
250
+ c.push(F('Testimonial', { parentId:$(rowIdx), width:tw, paddingTop:28, paddingBottom:28, paddingLeft:24, paddingRight:24, gap:16, fill:colors.surface, cornerRadius:16 }))
251
+
252
+ // Stars
253
+ c.push(T('★★★★★', { parentId:$(tidx), fontSize:14, color:'#fbbf24' }))
254
+
255
+ // Quote
256
+ c.push(T('"' + t.quote + '"', { parentId:$(tidx), fontSize:15, color:colors.text1, lineHeight:1.6 }))
257
+
258
+ // Divider
259
+ c.push(R('Divider', { parentId:$(tidx), width:tw - 48, height:1, fill:colors.border }))
260
+
261
+ // Author row
262
+ var authIdx = c.length
263
+ c.push(H('Author', { parentId:$(tidx), gap:10, counterAxisAlignItems:'CENTER' }))
264
+
265
+ // Avatar
266
+ var avIdx = c.length
267
+ c.push(H('Avatar', { parentId:$(authIdx), width:36, height:36, fill:brand, cornerRadius:18, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
268
+ c.push(T(t.initial, { parentId:$(avIdx), fontSize:14, _w:'Semi Bold', color:'#ffffff' }))
269
+
270
+ // Author info
271
+ var infoIdx = c.length
272
+ c.push(F('Author Info', { parentId:$(authIdx), gap:2 }))
273
+ c.push(T(t.author, { parentId:$(infoIdx), fontSize:14, _w:'Semi Bold', color:colors.text1 }))
274
+ c.push(T(t.role, { parentId:$(infoIdx), fontSize:12, color:colors.text3 }))
275
+ }
276
+ return c
277
+ }
278
+
279
+ // ═══════════════════════════════════════════
280
+ // PRICING
281
+ // ═══════════════════════════════════════════
282
+ function composePricing(content, colors, brand, W) {
283
+ var c = [], contentW = 1080
284
+ var tiers = (content && content.tiers) || [
285
+ { 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', hl:false },
286
+ { name:'Pro', price:'$29', period:'/mo', desc:'For growing teams that need more.', features:['Unlimited projects','100,000 API calls/day','Priority support','Advanced analytics','Team collaboration','Custom domains','Webhooks'], cta:'Start free trial', hl:true },
287
+ { 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', hl:false },
288
+ ]
289
+
290
+ // 0: Section
291
+ c.push(F('Pricing Section', { width:W, paddingTop:96, paddingBottom:96, paddingLeft:48, paddingRight:48, gap:56, fill:colors.bg, counterAxisAlignItems:'CENTER' }))
292
+ // Header
293
+ c.push(F('Pricing Header', { parentId:$(0), gap:12, counterAxisAlignItems:'CENTER' }))
294
+ c.push(T('PRICING', { parentId:$(1), fontSize:11, _w:'Semi Bold', color:brand, letterSpacing:2.5 }))
295
+ c.push(T('Simple, transparent pricing', { parentId:$(1), fontSize:44, _w:'Bold', color:colors.text1, textAlignHorizontal:'CENTER' }))
296
+ c.push(T('No hidden fees. Start free, scale as you grow.', { parentId:$(1), fontSize:18, color:colors.text2, textAlignHorizontal:'CENTER' }))
297
+
298
+ // Tier row
299
+ var rowIdx = c.length
300
+ c.push(H('Tiers', { parentId:$(0), width:contentW, gap:16, counterAxisAlignItems:'STRETCH' }))
301
+
302
+ for (var ti = 0; ti < tiers.length; ti++) {
303
+ var t = tiers[ti]
304
+ var tw = Math.floor((contentW - (tiers.length - 1) * 16) / tiers.length)
305
+ var isHl = t.hl
306
+ var tidx = c.length
307
+
308
+ // Card
309
+ c.push(F(t.name, { parentId:$(rowIdx), width:tw, paddingTop:32, paddingBottom:32, paddingLeft:28, paddingRight:28, gap:16, fill:isHl ? colors.surface2 : colors.surface, cornerRadius:16 }))
310
+
311
+ // Popular badge
312
+ if (isHl) {
313
+ var bidx = c.length
314
+ c.push(H('Popular', { parentId:$(tidx), paddingLeft:10, paddingRight:10, paddingTop:4, paddingBottom:4, fill:brand, cornerRadius:6, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
315
+ c.push(T('MOST POPULAR', { parentId:$(bidx), fontSize:9, _w:'Bold', color:'#ffffff' }))
316
+ }
317
+
318
+ // Name
319
+ c.push(T(t.name, { parentId:$(tidx), fontSize:22, _w:'Semi Bold', color:colors.text1 }))
320
+
321
+ // Price row
322
+ var pidx = c.length
323
+ c.push(H('Price', { parentId:$(tidx), gap:4, counterAxisAlignItems:'BASELINE' }))
324
+ c.push(T(t.price, { parentId:$(pidx), fontSize:44, _w:'Bold', color:colors.text1 }))
325
+ if (t.period) {
326
+ c.push(T(t.period, { parentId:$(pidx), fontSize:16, color:colors.text3 }))
327
+ }
328
+
329
+ // Description
330
+ c.push(T(t.desc, { parentId:$(tidx), fontSize:14, color:colors.text2 }))
331
+
332
+ // Divider
333
+ c.push(R('Divider', { parentId:$(tidx), width:tw - 56, height:1, fill:colors.border }))
334
+
335
+ // Features list
336
+ var flidx = c.length
337
+ c.push(F('Features', { parentId:$(tidx), gap:10 }))
338
+ for (var fi = 0; fi < t.features.length; fi++) {
339
+ var flrow = c.length
340
+ c.push(H('Feature', { parentId:$(flidx), gap:8, counterAxisAlignItems:'CENTER' }))
341
+ // Checkmark circle
342
+ var chkIdx = c.length
343
+ c.push(H('Check', { parentId:$(flrow), width:18, height:18, fill:isHl ? brand : colors.surface3, cornerRadius:9, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
344
+ c.push(T('✓', { parentId:$(chkIdx), fontSize:10, _w:'Bold', color:isHl ? '#ffffff' : colors.text2 }))
345
+ c.push(T(t.features[fi], { parentId:$(flrow), fontSize:13, color:colors.text2 }))
346
+ }
347
+
348
+ // Spacer to push button to bottom
349
+ c.push(F('_grow', { parentId:$(tidx), width:1, height:1, primaryAxisSizingMode:'FILL' }))
350
+
351
+ // CTA button
352
+ var btIdx = c.length
353
+ c.push(H(t.cta, { parentId:$(tidx), height:48, paddingLeft:24, paddingRight:24, paddingTop:0, paddingBottom:0, fill:isHl ? brand : 'transparent', cornerRadius:10, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER', primaryAxisSizingMode:'FILL' }))
354
+ c.push(T(t.cta, { parentId:$(btIdx), fontSize:15, _w:'Semi Bold', color:isHl ? '#ffffff' : brand }))
355
+ }
356
+ return c
357
+ }
358
+
359
+ // ═══════════════════════════════════════════
360
+ // CTA SECTION
361
+ // ═══════════════════════════════════════════
362
+ function composeCTA(content, colors, brand, W) {
363
+ var c = [], contentW = 1120
364
+ var title = (content && content.ctaTitle) || 'Ready to get started?'
365
+ var sub = (content && content.ctaSub) || 'Join thousands of teams already shipping faster.\nNo credit card required.'
366
+ var btnText = (content && content.cta) || 'Start for free'
367
+
368
+ // 0: Section bg
369
+ c.push(F('CTA Section', { width:W, paddingTop:96, paddingBottom:96, paddingLeft:48, paddingRight:48, fill:colors.bg2, counterAxisAlignItems:'CENTER' }))
370
+ // 1: CTA Card
371
+ c.push(F('CTA Card', { parentId:$(0), width:contentW, paddingTop:72, paddingBottom:72, paddingLeft:48, paddingRight:48, gap:24, fill:colors.surface, cornerRadius:24, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
372
+ // 2: Heading
373
+ c.push(T(title, { parentId:$(1), fontSize:44, _w:'Bold', color:colors.text1, textAlignHorizontal:'CENTER' }))
374
+ // 3: Subtitle
375
+ c.push(T(sub, { parentId:$(1), fontSize:18, color:colors.text2, textAlignHorizontal:'CENTER' }))
376
+ // 4: Button row
377
+ c.push(H('CTA Buttons', { parentId:$(1), gap:12, paddingTop:8 }))
378
+ // 5: Primary
379
+ c.push(H('CTA Primary', { parentId:$(4), height:56, paddingLeft:32, paddingRight:32, paddingTop:0, paddingBottom:0, fill:brand, cornerRadius:12, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
380
+ c.push(T(btnText, { parentId:$(5), fontSize:17, _w:'Semi Bold', color:'#ffffff' }))
381
+ // 7: Secondary
382
+ c.push(H('CTA Secondary', { parentId:$(4), height:56, paddingLeft:32, paddingRight:32, paddingTop:0, paddingBottom:0, fill:colors.surface2, cornerRadius:12, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
383
+ c.push(T('Talk to sales →', { parentId:$(7), fontSize:17, _w:'Medium', color:colors.text2 }))
384
+
385
+ return c
386
+ }
387
+
388
+ // ═══════════════════════════════════════════
389
+ // FOOTER
390
+ // ═══════════════════════════════════════════
391
+ function composeFooter(content, colors, brand, W) {
392
+ var c = [], brandName = (content && content.brand) || 'acme'
393
+ var cols = [
394
+ { title:'Product', links:['Features','Pricing','Docs','Changelog'] },
395
+ { title:'Company', links:['About','Blog','Careers','Press'] },
396
+ { title:'Legal', links:['Terms','Privacy','License'] },
397
+ ]
398
+
399
+ // 0: Footer top divider
400
+ c.push(R('Footer Divider', { width:W, height:1, fill:colors.border }))
401
+ // 1: Footer frame
402
+ c.push(F('Footer', { width:W, paddingTop:56, paddingBottom:40, paddingLeft:48, paddingRight:48, gap:40, fill:colors.bg }))
403
+
404
+ // 2: Footer columns row
405
+ c.push(H('Footer Columns', { parentId:$(1), gap:0 }))
406
+
407
+ // 3: Brand column (wider)
408
+ c.push(F('Brand Column', { parentId:$(2), width:360, gap:12 }))
409
+ // 4: Brand row
410
+ c.push(H('Brand', { parentId:$(3), gap:8, counterAxisAlignItems:'CENTER' }))
411
+ // Logo mark
412
+ var lmIdx = c.length
413
+ c.push(H('Logo', { parentId:$(4), width:22, height:22, fill:brand, cornerRadius:5, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
414
+ c.push(T(brandName.charAt(0).toUpperCase(), { parentId:$(lmIdx), fontSize:11, _w:'Bold', color:'#ffffff' }))
415
+ c.push(T(brandName, { parentId:$(4), fontSize:14, _w:'Semi Bold', color:colors.text1 }))
416
+ // Description
417
+ c.push(T('Design-intelligent tools for modern teams.\nBuilt with care, shipped with confidence.', { parentId:$(3), fontSize:13, color:colors.text3, lineHeight:1.6 }))
418
+
419
+ // Link columns
420
+ for (var ci2 = 0; ci2 < cols.length; ci2++) {
421
+ var col = cols[ci2]
422
+ var colIdx = c.length
423
+ c.push(F(col.title, { parentId:$(2), width:160, gap:10 }))
424
+ c.push(T(col.title, { parentId:$(colIdx), fontSize:12, _w:'Semi Bold', color:colors.text1 }))
425
+ // Spacer
426
+ c.push(F('_gap', { parentId:$(colIdx), width:1, height:4 }))
427
+ for (var li = 0; li < col.links.length; li++) {
428
+ c.push(T(col.links[li], { parentId:$(colIdx), fontSize:13, color:colors.text3 }))
429
+ }
430
+ }
431
+
432
+ // Bottom bar
433
+ c.push(R('Bottom Divider', { parentId:$(1), width:W - 96, height:1, fill:colors.border }))
434
+ var botIdx = c.length
435
+ c.push(H('Bottom', { parentId:$(1), counterAxisAlignItems:'CENTER' }))
436
+ c.push(T('© 2025 ' + brandName + '. All rights reserved.', { parentId:$(botIdx), fontSize:12, color:colors.text3 }))
437
+
438
+ return c
439
+ }
440
+
441
+ // ═══════════════════════════════════════════
442
+ // DASHBOARD
443
+ // ═══════════════════════════════════════════
444
+ function composeDashboard(content, colors, brand, W) {
445
+ var c = [], sideW = 260
446
+ var metrics = (content && content.metrics) || [
447
+ { label:'Total Revenue', value:'$48,290', change:'+12.5%', up:true },
448
+ { label:'Active Users', value:'2,420', change:'+8.3%', up:true },
449
+ { label:'Conversion', value:'3.24%', change:'-0.8%', up:false },
450
+ { label:'Avg. Session', value:'4m 32s', change:'+15.2%', up:true },
451
+ ]
452
+ var navItems = ['Overview','Analytics','Customers','Products','Settings']
453
+
454
+ // 0: Dashboard root
455
+ c.push(H('Dashboard', { width:W, height:900, fill:colors.bg, gap:0 }))
456
+
457
+ // 1: Sidebar
458
+ c.push(F('Sidebar', { parentId:$(0), width:sideW, height:900, paddingTop:20, paddingBottom:20, paddingLeft:16, paddingRight:16, gap:4, fill:colors.surface }))
459
+
460
+ // 2: Sidebar logo row
461
+ c.push(H('Sidebar Brand', { parentId:$(1), gap:8, counterAxisAlignItems:'CENTER', paddingLeft:8, paddingBottom:16 }))
462
+ var slmIdx = c.length
463
+ c.push(H('Logo', { parentId:$(2), width:24, height:24, fill:brand, cornerRadius:6, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
464
+ c.push(T('C', { parentId:$(slmIdx), fontSize:12, _w:'Bold', color:'#ffffff' }))
465
+ c.push(T('Conductor', { parentId:$(2), fontSize:14, _w:'Semi Bold', color:colors.text1 }))
466
+
467
+ // Nav items
468
+ for (var ni = 0; ni < navItems.length; ni++) {
469
+ var isActive = ni === 0
470
+ var niIdx = c.length
471
+ c.push(H(navItems[ni], { parentId:$(1), height:36, paddingLeft:10, paddingRight:10, paddingTop:0, paddingBottom:0, gap:8, cornerRadius:8, fill:isActive ? colors.surface3 : 'transparent', counterAxisAlignItems:'CENTER', primaryAxisSizingMode:'FILL' }))
472
+ // Dot for active
473
+ if (isActive) {
474
+ c.push(H('Active Dot', { parentId:$(niIdx), width:6, height:6, fill:brand, cornerRadius:3 }))
475
+ }
476
+ c.push(T(navItems[ni], { parentId:$(niIdx), fontSize:13, _w:isActive ? 'Medium' : 'Regular', color:isActive ? colors.text1 : colors.text3 }))
477
+ }
478
+
479
+ // Sidebar divider
480
+ c.push(R('Sidebar Divider', { parentId:$(0), width:1, height:900, fill:colors.border }))
481
+
482
+ // Main area
483
+ var mainIdx = c.length
484
+ c.push(F('Main', { parentId:$(0), height:900, paddingTop:28, paddingBottom:28, paddingLeft:32, paddingRight:32, gap:24, fill:colors.bg, primaryAxisSizingMode:'FILL' }))
485
+
486
+ // Header row
487
+ var hdrIdx = c.length
488
+ c.push(H('Header', { parentId:$(mainIdx), counterAxisAlignItems:'CENTER' }))
489
+ c.push(T('Overview', { parentId:$(hdrIdx), fontSize:24, _w:'Bold', color:colors.text1 }))
490
+ c.push(F('_spacer', { parentId:$(hdrIdx), width:1, height:1, primaryAxisSizingMode:'FILL' }))
491
+ var dlBtnIdx = c.length
492
+ c.push(H('Download', { parentId:$(hdrIdx), height:36, paddingLeft:14, paddingRight:14, paddingTop:0, paddingBottom:0, fill:colors.surface, cornerRadius:8, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
493
+ c.push(T('Export ↓', { parentId:$(dlBtnIdx), fontSize:12, _w:'Medium', color:colors.text2 }))
494
+
495
+ // Metrics row
496
+ var mRowIdx = c.length
497
+ c.push(H('Metrics', { parentId:$(mainIdx), gap:16 }))
498
+
499
+ for (var mi = 0; mi < metrics.length; mi++) {
500
+ var m = metrics[mi]
501
+ var mIdx = c.length
502
+ c.push(F(m.label, { parentId:$(mRowIdx), paddingTop:20, paddingBottom:20, paddingLeft:20, paddingRight:20, gap:8, fill:colors.surface, cornerRadius:12, primaryAxisSizingMode:'FILL' }))
503
+ c.push(T(m.label, { parentId:$(mIdx), fontSize:12, _w:'Medium', color:colors.text3 }))
504
+ c.push(T(m.value, { parentId:$(mIdx), fontSize:28, _w:'Bold', color:colors.text1 }))
505
+ // Change badge
506
+ var chgIdx = c.length
507
+ c.push(H('Change', { parentId:$(mIdx), paddingLeft:6, paddingRight:6, paddingTop:2, paddingBottom:2, fill:m.up ? 'rgba(74,222,128,0.1)' : 'rgba(248,113,113,0.1)', cornerRadius:4 }))
508
+ c.push(T(m.change, { parentId:$(chgIdx), fontSize:11, _w:'Semi Bold', color:m.up ? '#4ade80' : '#f87171' }))
509
+ }
510
+
511
+ // Chart area
512
+ var chartIdx = c.length
513
+ c.push(F('Chart Area', { parentId:$(mainIdx), paddingTop:24, paddingBottom:24, paddingLeft:24, paddingRight:24, gap:16, fill:colors.surface, cornerRadius:12, primaryAxisSizingMode:'FILL' }))
514
+ var chartHdrIdx = c.length
515
+ c.push(H('Chart Header', { parentId:$(chartIdx), counterAxisAlignItems:'CENTER' }))
516
+ c.push(T('Revenue Over Time', { parentId:$(chartHdrIdx), fontSize:16, _w:'Semi Bold', color:colors.text1 }))
517
+ c.push(F('_spacer', { parentId:$(chartHdrIdx), width:1, height:1, primaryAxisSizingMode:'FILL' }))
518
+ // Period pills
519
+ var pillsIdx = c.length
520
+ c.push(H('Period', { parentId:$(chartHdrIdx), gap:4 }))
521
+ var periods = ['7D','30D','90D','1Y']
522
+ for (var pi = 0; pi < periods.length; pi++) {
523
+ var pillIdx = c.length
524
+ c.push(H(periods[pi], { parentId:$(pillsIdx), height:28, paddingLeft:10, paddingRight:10, paddingTop:0, paddingBottom:0, fill:pi === 1 ? colors.surface3 : 'transparent', cornerRadius:6, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
525
+ c.push(T(periods[pi], { parentId:$(pillIdx), fontSize:11, _w:pi === 1 ? 'Medium' : 'Regular', color:pi === 1 ? colors.text1 : colors.text3 }))
526
+ }
527
+ // Chart placeholder
528
+ c.push(R('Chart', { parentId:$(chartIdx), width:800, height:220, fill:colors.surface2, cornerRadius:8 }))
529
+
530
+ return c
531
+ }
532
+
533
+ // ═══════════════════════════════════════════
534
+ // SMART COMPONENTS
535
+ // ═══════════════════════════════════════════
536
+ function composeSmartComponent(type, variant, label, brandColor, mode) {
537
+ var colors = semanticColors(brandColor || '#6366f1', mode || 'dark')
538
+ var brand = brandColor || colors.brand
539
+ var defs = componentDefaults(type, variant)
540
+ if (!defs) return null
541
+ var c = []
542
+
543
+ switch (type) {
544
+ case 'button': {
545
+ c.push(H((label || 'Button') + ' — ' + (variant || 'default'), { height:defs.h, paddingLeft:defs.px, paddingRight:defs.px, paddingTop:0, paddingBottom:0, fill:brand, cornerRadius:defs.radius, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER', gap:8 }))
546
+ c.push(T(label || 'Get started', { parentId:$(0), fontSize:defs.fontSize, _w:defs.fontWeight, color:'#ffffff' }))
547
+ break
548
+ }
549
+ case 'input': {
550
+ c.push(F('Input — ' + (variant || 'default'), { gap:6 }))
551
+ c.push(T(label || 'Email address', { parentId:$(0), fontSize:13, _w:'Medium', color:colors.text2 }))
552
+ c.push(H('Input Field', { parentId:$(0), height:defs.h, paddingLeft:defs.px, paddingRight:defs.px, paddingTop:0, paddingBottom:0, fill:colors.surface, cornerRadius:defs.radius, counterAxisAlignItems:'CENTER' }))
553
+ c.push(T('you@example.com', { parentId:$(2), fontSize:defs.fontSize, color:colors.text3 }))
554
+ break
555
+ }
556
+ case 'card': {
557
+ c.push(F(label || 'Card', { paddingTop:defs.py, paddingBottom:defs.py, paddingLeft:defs.px, paddingRight:defs.px, gap:defs.gap, fill:colors.surface, cornerRadius:defs.radius }))
558
+ var icIdx = c.length
559
+ c.push(H('Icon', { parentId:$(0), width:48, height:48, fill:colors.surface3, cornerRadius:12, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
560
+ c.push(T('⚡', { parentId:$(icIdx), fontSize:22 }))
561
+ c.push(T(label || 'Feature title', { parentId:$(0), fontSize:18, _w:'Semi Bold', color:colors.text1 }))
562
+ c.push(T('A short description of this feature and why it matters to your users.', { parentId:$(0), fontSize:14, color:colors.text2, lineHeight:1.6 }))
563
+ c.push(R('Divider', { parentId:$(0), width:200, height:1, fill:colors.border }))
564
+ c.push(T('Learn more →', { parentId:$(0), fontSize:13, _w:'Medium', color:brand }))
565
+ break
566
+ }
567
+ case 'modal': {
568
+ c.push(F('Modal Overlay', { width:1440, height:900, fill:'#00000080', primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
569
+ c.push(F('Modal', { parentId:$(0), width:defs.maxW, paddingTop:defs.py, paddingBottom:defs.py, paddingLeft:defs.px, paddingRight:defs.px, gap:defs.gap, fill:colors.surface, cornerRadius:defs.radius }))
570
+ c.push(T(label || 'Confirm action', { parentId:$(1), fontSize:18, _w:'Semi Bold', color:colors.text1 }))
571
+ c.push(T('Are you sure you want to proceed? This action cannot be undone.', { parentId:$(1), fontSize:15, color:colors.text2 }))
572
+ c.push(R('Divider', { parentId:$(1), width:defs.maxW - defs.px * 2, height:1, fill:colors.border }))
573
+ c.push(H('Actions', { parentId:$(1), gap:8, primaryAxisAlignItems:'MAX' }))
574
+ c.push(H('Cancel', { parentId:$(5), height:40, paddingLeft:16, paddingRight:16, paddingTop:0, paddingBottom:0, fill:colors.surface2, cornerRadius:8, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
575
+ c.push(T('Cancel', { parentId:$(6), fontSize:14, _w:'Medium', color:colors.text2 }))
576
+ c.push(H('Confirm', { parentId:$(5), height:40, paddingLeft:16, paddingRight:16, paddingTop:0, paddingBottom:0, fill:brand, cornerRadius:8, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
577
+ c.push(T('Confirm', { parentId:$(8), fontSize:14, _w:'Semi Bold', color:'#ffffff' }))
578
+ break
579
+ }
580
+ case 'avatar': {
581
+ c.push(H('Avatar', { width:defs.size, height:defs.size, fill:brand, cornerRadius:defs.radius, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
582
+ c.push(T((label || 'A').charAt(0).toUpperCase(), { parentId:$(0), fontSize:defs.fontSize, _w:defs.fontWeight, color:'#ffffff' }))
583
+ break
584
+ }
585
+ case 'badge': {
586
+ c.push(H('Badge', { height:defs.h, paddingLeft:defs.px, paddingRight:defs.px, paddingTop:0, paddingBottom:0, fill:colors.surface2, cornerRadius:defs.radius, primaryAxisAlignItems:'CENTER', counterAxisAlignItems:'CENTER' }))
587
+ c.push(T(label || 'New', { parentId:$(0), fontSize:defs.fontSize, _w:defs.fontWeight, color:brand }))
588
+ break
589
+ }
590
+ default: {
591
+ c.push(F(type, { paddingTop:16, paddingBottom:16, paddingLeft:16, paddingRight:16, gap:8, fill:colors.surface, cornerRadius:8 }))
592
+ c.push(T(label || type, { parentId:$(0), fontSize:14, color:colors.text1 }))
593
+ }
594
+ }
595
+ return c
596
+ }
597
+
598
+ // ═══════════════════════════════════════════
599
+ // SECTION ROUTER
600
+ // ═══════════════════════════════════════════
601
+ function composeSection(type, content, brandColor, mode, width) {
602
+ var W = width || 1440
603
+ var colors = semanticColors(brandColor || '#6366f1', mode || 'dark')
604
+ var brand = brandColor || '#6366f1'
605
+
606
+ switch (type) {
607
+ case 'nav': return composeNav(content, colors, brand, W)
608
+ case 'hero': return composeHero(content, colors, brand, W)
609
+ case 'stats': return composeStats(content, colors, brand, W)
610
+ case 'features': return composeFeatures(content, colors, brand, W)
611
+ case 'testimonials': return composeTestimonials(content, colors, brand, W)
612
+ case 'pricing': return composePricing(content, colors, brand, W)
613
+ case 'cta': return composeCTA(content, colors, brand, W)
614
+ case 'footer': return composeFooter(content, colors, brand, W)
615
+ case 'dashboard': return composeDashboard(content, colors, brand, W)
616
+ default:
617
+ var c = []
618
+ c.push(F(type + ' Section', { width:W, paddingTop:96, paddingBottom:96, paddingLeft:48, paddingRight:48, gap:32, fill:colors.bg, counterAxisAlignItems:'CENTER' }))
619
+ c.push(T((content && content.title) || type, { parentId:$(0), fontSize:40, _w:'Bold', color:colors.text1, textAlignHorizontal:'CENTER' }))
620
+ return c
621
+ }
622
+ }
623
+
624
+ // ═══════════════════════════════════════════
625
+ // PAGE COMPOSER
626
+ // ═══════════════════════════════════════════
627
+ function composePage(type, content, brandColor, mode, width) {
628
+ var sections = []
629
+ switch (type) {
630
+ case 'landing': sections = ['nav','hero','stats','features','testimonials','cta','footer']; break
631
+ case 'pricing': sections = ['nav','pricing','cta','footer']; break
632
+ case 'dashboard': sections = ['dashboard']; break
633
+ default: sections = ['nav','hero','features','cta','footer']
634
+ }
635
+ return { sections:sections, content:content, brand:brandColor || '#6366f1', mode:mode || 'dark', width:width || 1440 }
636
+ }
637
+
638
+ export { runSequence, composeSmartComponent, composeSection, composePage }
@@ -0,0 +1,295 @@
1
+ // ═══════════════════════════════════════════
2
+ // CONDUCTOR v3.2 — Design Interpreter
3
+ // ═══════════════════════════════════════════
4
+ // Takes any natural language prompt and extracts
5
+ // design intent: mood, palette, depth, density,
6
+ // typography character, layout strategy.
7
+ //
8
+ // This is the brain. The composer is the hands.
9
+
10
+ import { semanticColors, typeScale, snap, SHADOWS } from './intelligence.js'
11
+
12
+ // ─── Mood Detection ───
13
+ // Scans prompt text for style/mood signals
14
+ var MOOD_SIGNALS = {
15
+ // Mood → keywords that trigger it
16
+ minimal: ['minimal','clean','simple','stripped','bare','zen','quiet','calm','whitespace','restrained'],
17
+ bold: ['bold','strong','loud','impactful','powerful','striking','dramatic','intense','punchy'],
18
+ playful: ['playful','fun','friendly','casual','warm','cheerful','vibrant','colorful','quirky','whimsical'],
19
+ luxury: ['luxury','brand','premium','elegant','refined','sophisticated','exclusive','high-end','classy','prestige'],
20
+ corporate: ['corporate','professional','enterprise','business','formal','serious','trustworthy','reliable'],
21
+ dark: ['dark','moody','noir','night','shadow','deep','mysterious','brooding'],
22
+ techy: ['tech','developer','code','hacker','terminal','engineer','devtool','api','saas'],
23
+ organic: ['organic','natural','earthy','soft','gentle','rounded','flowing','human'],
24
+ brutalist: ['brutalist','raw','rough','unpolished','industrial','grunge','concrete'],
25
+ editorial: ['editorial','magazine','publication','article','blog','journal','story','content'],
26
+ }
27
+
28
+ // ─── Industry Detection ───
29
+ var INDUSTRY_SIGNALS = {
30
+ fintech: ['fintech','banking','finance','payment','money','wallet','trading','invest','crypto'],
31
+ health: ['health','medical','wellness','fitness','care','therapy','meditation','mindful','yoga'],
32
+ ecommerce: ['ecommerce','shop','store','product','retail','marketplace','buy','sell','cart'],
33
+ education: ['education','learn','course','student','school','teach','academy','tutorial'],
34
+ saas: ['saas','dashboard','analytics','tool','platform','software','app','manage','workflow'],
35
+ creative: ['creative','design','art','portfolio','agency','studio','photographer','gallery'],
36
+ food: ['food','restaurant','recipe','cook','menu','delivery','cafe','bar','dining'],
37
+ travel: ['travel','booking','hotel','flight','destination','trip','adventure','explore'],
38
+ social: ['social','community','network','chat','message','connect','share','follow'],
39
+ ai: ['ai','artificial','intelligence','machine','learning','model','neural','gpt','llm'],
40
+ }
41
+
42
+ // ─── Component Detection ───
43
+ var COMPONENT_SIGNALS = {
44
+ hero: ['hero','landing','header','above fold','headline','main banner','splash'],
45
+ features: ['features','capabilities','what we do','benefits','why us','highlights'],
46
+ pricing: ['pricing','plans','tiers','cost','subscription','free trial','enterprise'],
47
+ testimonials: ['testimonials','reviews','quotes','customers say','social proof','trust'],
48
+ dashboard: ['dashboard','admin','panel','metrics','kpi','analytics','overview','stats'],
49
+ form: ['form','signup','login','register','contact','input','submit','subscribe'],
50
+ gallery: ['gallery','portfolio','showcase','grid','images','photos','work'],
51
+ faq: ['faq','questions','answers','help','support'],
52
+ cta: ['cta','call to action','get started','sign up','try','start'],
53
+ footer: ['footer','bottom','links','copyright'],
54
+ nav: ['nav','navigation','menu','header bar','topbar'],
55
+ stats: ['stats','numbers','metrics','counters','data points'],
56
+ }
57
+
58
+ function detectSignals(text, signalMap) {
59
+ var lower = text.toLowerCase()
60
+ var scores = {}
61
+ for (var key in signalMap) {
62
+ if (signalMap.hasOwnProperty(key)) {
63
+ var keywords = signalMap[key]
64
+ var score = 0
65
+ for (var i = 0; i < keywords.length; i++) {
66
+ if (lower.indexOf(keywords[i]) !== -1) score++
67
+ }
68
+ if (score > 0) scores[key] = score
69
+ }
70
+ }
71
+ // Sort by score descending
72
+ var sorted = Object.keys(scores).sort(function(a, b) { return scores[b] - scores[a] })
73
+ return sorted
74
+ }
75
+
76
+ // ─── Design Parameters from Mood ───
77
+ var MOOD_PARAMS = {
78
+ minimal: {
79
+ cornerRadius: 8, shadowDepth: 'sm', density: 'spacious', typeRatio: 'major2',
80
+ headingWeight: 'Semi Bold', bodyWeight: 'Regular', bgContrast: 'low',
81
+ iconStyle: 'outline', dividers: true, badgeStyle: 'subtle',
82
+ sectionPadding: 120, cardPadding: 32, gap: 24, headingSize: 56,
83
+ },
84
+ bold: {
85
+ cornerRadius: 16, shadowDepth: 'lg', density: 'normal', typeRatio: 'major3',
86
+ headingWeight: 'Bold', bodyWeight: 'Regular', bgContrast: 'high',
87
+ iconStyle: 'filled', dividers: false, badgeStyle: 'filled',
88
+ sectionPadding: 96, cardPadding: 28, gap: 20, headingSize: 72,
89
+ },
90
+ playful: {
91
+ cornerRadius: 20, shadowDepth: 'md', density: 'normal', typeRatio: 'minor3',
92
+ headingWeight: 'Bold', bodyWeight: 'Regular', bgContrast: 'medium',
93
+ iconStyle: 'filled', dividers: false, badgeStyle: 'pill',
94
+ sectionPadding: 96, cardPadding: 28, gap: 20, headingSize: 64,
95
+ },
96
+ luxury: {
97
+ cornerRadius: 4, shadowDepth: 'sm', density: 'spacious', typeRatio: 'perfect4',
98
+ headingWeight: 'Medium', bodyWeight: 'Light', bgContrast: 'low',
99
+ iconStyle: 'outline', dividers: true, badgeStyle: 'outline',
100
+ sectionPadding: 140, cardPadding: 40, gap: 32, headingSize: 60,
101
+ },
102
+ corporate: {
103
+ cornerRadius: 8, shadowDepth: 'md', density: 'normal', typeRatio: 'major2',
104
+ headingWeight: 'Bold', bodyWeight: 'Regular', bgContrast: 'medium',
105
+ iconStyle: 'filled', dividers: true, badgeStyle: 'subtle',
106
+ sectionPadding: 96, cardPadding: 28, gap: 20, headingSize: 48,
107
+ },
108
+ dark: {
109
+ cornerRadius: 12, shadowDepth: 'lg', density: 'normal', typeRatio: 'major2',
110
+ headingWeight: 'Bold', bodyWeight: 'Regular', bgContrast: 'high',
111
+ iconStyle: 'filled', dividers: false, badgeStyle: 'filled',
112
+ sectionPadding: 96, cardPadding: 28, gap: 20, headingSize: 64,
113
+ },
114
+ techy: {
115
+ cornerRadius: 8, shadowDepth: 'sm', density: 'dense', typeRatio: 'major2',
116
+ headingWeight: 'Bold', bodyWeight: 'Regular', bgContrast: 'medium',
117
+ iconStyle: 'outline', dividers: true, badgeStyle: 'code',
118
+ sectionPadding: 80, cardPadding: 24, gap: 16, headingSize: 52,
119
+ },
120
+ organic: {
121
+ cornerRadius: 24, shadowDepth: 'md', density: 'spacious', typeRatio: 'minor3',
122
+ headingWeight: 'Medium', bodyWeight: 'Regular', bgContrast: 'low',
123
+ iconStyle: 'filled', dividers: false, badgeStyle: 'pill',
124
+ sectionPadding: 112, cardPadding: 32, gap: 24, headingSize: 56,
125
+ },
126
+ brutalist: {
127
+ cornerRadius: 0, shadowDepth: 'none', density: 'dense', typeRatio: 'perfect5',
128
+ headingWeight: 'Bold', bodyWeight: 'Regular', bgContrast: 'extreme',
129
+ iconStyle: 'none', dividers: true, badgeStyle: 'border',
130
+ sectionPadding: 64, cardPadding: 20, gap: 12, headingSize: 80,
131
+ },
132
+ editorial: {
133
+ cornerRadius: 4, shadowDepth: 'sm', density: 'spacious', typeRatio: 'major3',
134
+ headingWeight: 'Medium', bodyWeight: 'Regular', bgContrast: 'low',
135
+ iconStyle: 'none', dividers: true, badgeStyle: 'subtle',
136
+ sectionPadding: 120, cardPadding: 32, gap: 28, headingSize: 56,
137
+ },
138
+ }
139
+
140
+ // ─── Color Palettes by Industry ───
141
+ var INDUSTRY_COLORS = {
142
+ fintech: { brand:'#0ea5e9', accent:'#06b6d4', mode:'dark' },
143
+ health: { brand:'#10b981', accent:'#34d399', mode:'light' },
144
+ ecommerce: { brand:'#f59e0b', accent:'#fbbf24', mode:'light' },
145
+ education: { brand:'#6366f1', accent:'#818cf8', mode:'light' },
146
+ saas: { brand:'#6366f1', accent:'#a78bfa', mode:'dark' },
147
+ creative: { brand:'#ec4899', accent:'#f472b6', mode:'dark' },
148
+ food: { brand:'#ef4444', accent:'#f87171', mode:'light' },
149
+ travel: { brand:'#0ea5e9', accent:'#38bdf8', mode:'light' },
150
+ social: { brand:'#8b5cf6', accent:'#a78bfa', mode:'dark' },
151
+ ai: { brand:'#6366f1', accent:'#818cf8', mode:'dark' },
152
+ }
153
+
154
+ // ─── Main Interpreter ───
155
+ export function interpretDesign(prompt) {
156
+ // 1. Detect mood
157
+ var moods = detectSignals(prompt, MOOD_SIGNALS)
158
+ var primaryMood = moods[0] || 'techy'
159
+
160
+ // 2. Detect industry
161
+ var industries = detectSignals(prompt, INDUSTRY_SIGNALS)
162
+ var primaryIndustry = industries[0] || 'saas'
163
+
164
+ // 3. Detect needed components
165
+ var components = detectSignals(prompt, COMPONENT_SIGNALS)
166
+ // Always include nav and footer if building a page
167
+ var isPage = prompt.toLowerCase().indexOf('page') !== -1 || prompt.toLowerCase().indexOf('landing') !== -1 || prompt.toLowerCase().indexOf('website') !== -1 || prompt.toLowerCase().indexOf('site') !== -1
168
+ if (isPage) {
169
+ if (components.indexOf('nav') === -1) components.unshift('nav')
170
+ if (components.indexOf('footer') === -1) components.push('footer')
171
+ // Default sections if none detected
172
+ if (components.length <= 2) {
173
+ components = ['nav', 'hero', 'features', 'cta', 'footer']
174
+ }
175
+ }
176
+ if (components.length === 0) components = ['hero']
177
+
178
+ // 4. Get design parameters from mood
179
+ var params = MOOD_PARAMS[primaryMood] || MOOD_PARAMS.techy
180
+
181
+ // 5. Get color from industry (or extract from prompt)
182
+ var colorMatch = prompt.match(/#[0-9a-fA-F]{6}/)
183
+ var industryColor = INDUSTRY_COLORS[primaryIndustry] || INDUSTRY_COLORS.saas
184
+ var brandColor = colorMatch ? colorMatch[0] : industryColor.brand
185
+
186
+ // 6. Determine mode
187
+ var forceDark = prompt.toLowerCase().indexOf('dark') !== -1
188
+ var forceLight = prompt.toLowerCase().indexOf('light') !== -1
189
+ var mode = forceDark ? 'dark' : (forceLight ? 'light' : industryColor.mode)
190
+
191
+ // 7. Extract content hints from prompt
192
+ var contentHints = extractContentHints(prompt)
193
+
194
+ return {
195
+ mood: primaryMood,
196
+ industry: primaryIndustry,
197
+ sections: components,
198
+ params: params,
199
+ brandColor: brandColor,
200
+ mode: mode,
201
+ content: contentHints,
202
+ width: 1440,
203
+ meta: {
204
+ detectedMoods: moods.slice(0, 3),
205
+ detectedIndustries: industries.slice(0, 3),
206
+ detectedComponents: components,
207
+ }
208
+ }
209
+ }
210
+
211
+ // ─── Content Hint Extractor ───
212
+ function extractContentHints(prompt) {
213
+ var content = {}
214
+ var lower = prompt.toLowerCase()
215
+
216
+ // Try to find a brand name
217
+ var brandMatch = prompt.match(/(?:for|called|named)\s+["']?([A-Z][a-zA-Z]+)["']?/)
218
+ if (brandMatch) content.brand = brandMatch[1]
219
+
220
+ // Try to find a title/heading
221
+ var titleMatch = prompt.match(/(?:heading|title|headline)\s+["']([^"']+)["']/)
222
+ if (titleMatch) content.title = titleMatch[1]
223
+
224
+ // Try to find a CTA
225
+ var ctaMatch = prompt.match(/(?:button|cta)\s+(?:saying?\s+)?["']([^"']+)["']/)
226
+ if (ctaMatch) content.cta = ctaMatch[1]
227
+
228
+ // Feature count
229
+ var featureCountMatch = lower.match(/(\d+)\s+features?/)
230
+ if (featureCountMatch) content.featureCount = parseInt(featureCountMatch[1])
231
+
232
+ // Pricing tiers
233
+ var tierCountMatch = lower.match(/(\d+)\s+(?:pricing\s+)?tiers?/)
234
+ if (tierCountMatch) content.tierCount = parseInt(tierCountMatch[1])
235
+
236
+ return content
237
+ }
238
+
239
+ // ─── Apply Design Params to Section ───
240
+ // This modifies the section composition based on interpreted mood
241
+ export function applyDesignParams(sectionCmds, params) {
242
+ for (var i = 0; i < sectionCmds.length; i++) {
243
+ var cmd = sectionCmds[i]
244
+ if (cmd.type === 'create_frame' && cmd.data) {
245
+ // Apply corner radius from mood
246
+ if (cmd.data.cornerRadius !== undefined && cmd.data.cornerRadius > 0) {
247
+ cmd.data.cornerRadius = params.cornerRadius
248
+ }
249
+ // Apply density
250
+ if (params.density === 'spacious') {
251
+ if (cmd.data.paddingTop && cmd.data.paddingTop >= 48) cmd.data.paddingTop = snap(cmd.data.paddingTop * 1.3)
252
+ if (cmd.data.paddingBottom && cmd.data.paddingBottom >= 48) cmd.data.paddingBottom = snap(cmd.data.paddingBottom * 1.3)
253
+ if (cmd.data.gap && cmd.data.gap >= 16) cmd.data.gap = snap(cmd.data.gap * 1.2)
254
+ } else if (params.density === 'dense') {
255
+ if (cmd.data.paddingTop && cmd.data.paddingTop >= 48) cmd.data.paddingTop = snap(cmd.data.paddingTop * 0.75)
256
+ if (cmd.data.paddingBottom && cmd.data.paddingBottom >= 48) cmd.data.paddingBottom = snap(cmd.data.paddingBottom * 0.75)
257
+ if (cmd.data.gap && cmd.data.gap >= 16) cmd.data.gap = snap(cmd.data.gap * 0.8)
258
+ }
259
+ }
260
+ // Apply heading weight from mood
261
+ if (cmd.type === 'create_text' && cmd.data && cmd.data.fontName) {
262
+ if (cmd.data.fontSize >= 40 && cmd.data.fontName.style === 'Bold') {
263
+ cmd.data.fontName.style = params.headingWeight
264
+ }
265
+ // Apply heading size for hero headings
266
+ if (cmd.data.fontSize >= 56) {
267
+ cmd.data.fontSize = params.headingSize
268
+ }
269
+ }
270
+ }
271
+ return sectionCmds
272
+ }
273
+
274
+ // ─── Get Interpretation Summary ───
275
+ // Returns a human-readable description of what the interpreter decided
276
+ export function getInterpretationSummary(interpretation) {
277
+ var i = interpretation
278
+ return {
279
+ summary: 'Detected ' + i.mood + ' mood for ' + i.industry + ' industry. ' +
280
+ 'Building ' + i.sections.length + ' sections: ' + i.sections.join(', ') + '. ' +
281
+ 'Brand color: ' + i.brandColor + '. Mode: ' + i.mode + '.',
282
+ mood: i.mood,
283
+ industry: i.industry,
284
+ sections: i.sections,
285
+ brandColor: i.brandColor,
286
+ mode: i.mode,
287
+ params: {
288
+ cornerRadius: i.params.cornerRadius + 'px',
289
+ shadowDepth: i.params.shadowDepth,
290
+ density: i.params.density,
291
+ typeRatio: i.params.typeRatio,
292
+ headingSize: i.params.headingSize + 'px',
293
+ }
294
+ }
295
+ }
@@ -7,6 +7,8 @@ import {
7
7
  checkContrast, auditAccessibility, getDesignCraftGuide, resolveFontWeight,
8
8
  hexToFigmaColor, linearGradient, radialGradient, SPACING, RADIUS, SHADOWS,
9
9
  } from '../design/intelligence.js'
10
+ import { composeSmartComponent, composeSection, composePage, runSequence } from '../design/composer.js'
11
+ import { interpretDesign, applyDesignParams, getInterpretationSummary } from '../design/interpreter.js'
10
12
 
11
13
  // ─── Icon SVG Library ───
12
14
  const ICONS = {
@@ -58,6 +60,7 @@ export async function handleTool(name, args, bridge) {
58
60
  case 'check_contrast': return checkContrast(args.foreground, args.background)
59
61
  case 'suggest_color_palette': return semanticColors(args.brandColor, args.mode || 'dark')
60
62
  case 'suggest_type_scale': return typeScale(args.baseSize || 16, args.ratio || 'major2')
63
+ case 'interpret_prompt': return getInterpretationSummary(interpretDesign(args.prompt))
61
64
  }
62
65
 
63
66
  // Everything else needs Figma
@@ -65,6 +68,88 @@ export async function handleTool(name, args, bridge) {
65
68
  throw new Error('Figma plugin not connected. Open Figma → Plugins → Development → Conductor, then try again.')
66
69
  }
67
70
 
71
+ // ─── Design from prompt: the big one ───
72
+ if (name === 'design_from_prompt') {
73
+ var interpretation = interpretDesign(args.prompt)
74
+ var params = interpretation.params
75
+ var brandColor = interpretation.brandColor
76
+ var mode = interpretation.mode
77
+ var W = args.width || interpretation.width
78
+
79
+ // Create page root
80
+ var pageColors = semanticColors(brandColor, mode)
81
+ var rootResult = await bridge.send('create_frame', {
82
+ name: interpretation.industry + ' — ' + interpretation.mood,
83
+ direction: 'VERTICAL', width: W, gap: 0,
84
+ fill: pageColors.bg
85
+ })
86
+
87
+ var totalElements = 1
88
+ var sectionResults = []
89
+
90
+ // Build each detected section
91
+ for (var di = 0; di < interpretation.sections.length; di++) {
92
+ var secType = interpretation.sections[di]
93
+ var secCmds = composeSection(secType, interpretation.content, brandColor, mode, W)
94
+ if (secCmds && secCmds.length > 0) {
95
+ // Apply mood-based design params
96
+ secCmds = applyDesignParams(secCmds, params)
97
+ // Parent first element to page root
98
+ secCmds[0].data.parentId = rootResult.id
99
+ var secRes = await runSequence(bridge, secCmds)
100
+ totalElements += secRes.length
101
+ sectionResults.push({ section: secType, elements: secRes.length })
102
+ }
103
+ }
104
+
105
+ return {
106
+ interpretation: getInterpretationSummary(interpretation),
107
+ created: {
108
+ rootId: rootResult.id,
109
+ totalElements: totalElements,
110
+ sections: sectionResults,
111
+ }
112
+ }
113
+ }
114
+
115
+ // ─── Composition commands: generate multi-element designs ───
116
+ if (name === 'create_smart_component') {
117
+ var compCmds = composeSmartComponent(args.type, args.variant, args.label, args.brandColor, args.mode)
118
+ if (!compCmds) throw new Error('Unknown component type: ' + args.type)
119
+ var compResults = await runSequence(bridge, compCmds)
120
+ return { created: args.type, variant: args.variant || 'default', elements: compResults.length, rootId: compResults[0] && compResults[0].id }
121
+ }
122
+
123
+ if (name === 'create_section') {
124
+ var secCmds = composeSection(args.type, args.content, args.brandColor, args.mode, args.width)
125
+ if (!secCmds || secCmds.length === 0) throw new Error('Unknown section type: ' + args.type)
126
+ var secResults = await runSequence(bridge, secCmds)
127
+ return { created: args.type + ' section', elements: secResults.length, rootId: secResults[0] && secResults[0].id }
128
+ }
129
+
130
+ if (name === 'create_page') {
131
+ var pageSpec = composePage(args.type, args.content, args.brandColor, args.mode, args.width)
132
+ // Create page root frame
133
+ var rootResult = await bridge.send('create_frame', {
134
+ name: (args.type || 'Page').charAt(0).toUpperCase() + (args.type || 'page').slice(1) + ' Page',
135
+ direction: 'VERTICAL', width: pageSpec.width, gap: 0,
136
+ fill: semanticColors(pageSpec.brand, pageSpec.mode || 'dark').bg
137
+ })
138
+ // Build each section inside the page
139
+ var totalElements = 1
140
+ for (var si = 0; si < pageSpec.sections.length; si++) {
141
+ var secType = pageSpec.sections[si]
142
+ var secCmds2 = composeSection(secType, pageSpec.content, pageSpec.brand, pageSpec.mode, pageSpec.width)
143
+ if (secCmds2 && secCmds2.length > 0) {
144
+ // Set first command's parent to page root
145
+ secCmds2[0].data.parentId = rootResult.id
146
+ var secRes = await runSequence(bridge, secCmds2)
147
+ totalElements += secRes.length
148
+ }
149
+ }
150
+ return { created: args.type + ' page', sections: pageSpec.sections, elements: totalElements, rootId: rootResult.id }
151
+ }
152
+
68
153
  // Apply design intelligence to args before sending
69
154
  const enhanced = enhanceWithIntelligence(name, args)
70
155
 
@@ -1116,6 +1116,18 @@ const EFFECTS = {
1116
1116
  }, 'effects'),
1117
1117
  }
1118
1118
 
1119
+ // ═══ DESIGN FROM PROMPT (2) ═══
1120
+ const INTERPRET = {
1121
+ design_from_prompt: tool('design_from_prompt', 'Takes any natural language description and generates a complete Figma design. Analyzes mood (minimal, bold, playful, luxury, corporate, techy, organic, brutalist, editorial), detects industry (fintech, health, saas, ecommerce, etc.), picks appropriate colors, spacing density, shadow depth, corner radii, and typography. Then composes a multi-section design with 30-300+ elements.', {
1122
+ prompt: req('string', 'Natural language description. Examples: "A dark fintech dashboard with metrics and charts", "A playful education landing page with pricing", "A minimal luxury brand site for a candle company called Ember"'),
1123
+ width: opt('number', 'Frame width', 1440),
1124
+ }, 'interpret'),
1125
+
1126
+ interpret_prompt: tool('interpret_prompt', 'Analyzes a design prompt and returns the interpretation without generating anything. Shows what mood, industry, colors, and sections would be used. Good for previewing before generating.', {
1127
+ prompt: req('string', 'Design description to analyze'),
1128
+ }, 'interpret'),
1129
+ }
1130
+
1119
1131
  // ═══ ASSEMBLE ALL TOOLS ═══
1120
1132
  export const ALL_TOOLS = {
1121
1133
  ...CREATE, ...MODIFY, ...VECTOR, ...READ,
@@ -1123,6 +1135,7 @@ export const ALL_TOOLS = {
1123
1135
  ...BATCH, ...DESIGN_SYSTEM, ...RESPONSIVE,
1124
1136
  ...TYPOGRAPHY, ...COLOR, ...PROTOTYPE,
1125
1137
  ...PAGE, ...LIBRARY, ...ANNOTATION, ...EFFECTS,
1138
+ ...INTERPRET,
1126
1139
  }
1127
1140
 
1128
1141
  export const TOOL_LIST = Object.values(ALL_TOOLS)
@@ -1146,6 +1159,7 @@ export const CATEGORIES = {
1146
1159
  library: Object.keys(LIBRARY),
1147
1160
  annotation: Object.keys(ANNOTATION),
1148
1161
  effects: Object.keys(EFFECTS),
1162
+ interpret: Object.keys(INTERPRET),
1149
1163
  }
1150
1164
 
1151
1165
  export function getTool(name) { return ALL_TOOLS[name] || null }