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.
- package/figma-plugin/code.js +5 -4
- package/package.json +1 -1
- package/src/design/composer.js +638 -0
- package/src/design/interpreter.js +295 -0
- package/src/tools/handlers.js +85 -0
- package/src/tools/registry.js +14 -0
package/figma-plugin/code.js
CHANGED
|
@@ -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
|
|
127
|
-
frame.counterAxisSizingMode = data.counterAxisSizingMode
|
|
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.
|
|
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
|
+
}
|
package/src/tools/handlers.js
CHANGED
|
@@ -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
|
|
package/src/tools/registry.js
CHANGED
|
@@ -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 }
|