@elementor/editor-canvas 4.2.0-beta2 → 4.3.0-1001

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.
Files changed (33) hide show
  1. package/dist/index.d.mts +1 -1
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +449 -1319
  4. package/dist/index.mjs +314 -1197
  5. package/package.json +22 -22
  6. package/src/index.ts +1 -1
  7. package/src/legacy/__tests__/init-legacy-views.test.ts +1 -1
  8. package/src/mcp/canvas-mcp.ts +4 -2
  9. package/src/mcp/resources/__tests__/available-widgets-resource.test.ts +85 -0
  10. package/src/mcp/resources/__tests__/dynamic-tags-resource.test.ts +27 -37
  11. package/src/mcp/resources/__tests__/widgets-schema-resource.test.ts +82 -74
  12. package/src/mcp/resources/available-widgets-resource.ts +35 -41
  13. package/src/mcp/resources/best-practices-resource.ts +34 -0
  14. package/src/mcp/resources/dynamic-tags-resource.ts +28 -29
  15. package/src/mcp/resources/widgets-schema-resource.ts +37 -177
  16. package/src/mcp/tools/build-composition/tool.ts +107 -218
  17. package/src/sync/element-added-event.ts +8 -0
  18. package/src/utils/__tests__/grid-outline-utils.test.ts +39 -0
  19. package/src/utils/grid-outline-utils.ts +13 -2
  20. package/src/composition-builder/__tests__/composition-builder.test.ts +0 -352
  21. package/src/composition-builder/composition-builder.ts +0 -350
  22. package/src/composition-builder/utils/__tests__/required-children-enforcer.test.ts +0 -79
  23. package/src/composition-builder/utils/__tests__/required-default-child-tags.test.ts +0 -35
  24. package/src/composition-builder/utils/required-children-enforcer.ts +0 -56
  25. package/src/composition-builder/utils/required-default-child-tags.ts +0 -22
  26. package/src/mcp/resources/best-practices.ts +0 -159
  27. package/src/mcp/resources/build-llm-guidance.ts +0 -110
  28. package/src/mcp/tools/build-composition/__tests__/xml-leaf-wrapper.test.ts +0 -117
  29. package/src/mcp/tools/build-composition/prompt.ts +0 -167
  30. package/src/mcp/tools/build-composition/schema.ts +0 -42
  31. package/src/mcp/tools/build-composition/xml-leaf-wrapper.ts +0 -68
  32. package/src/mcp/utils/__tests__/get-composition-target-container.test.ts +0 -59
  33. package/src/mcp/utils/get-composition-target-container.ts +0 -15
@@ -1,350 +0,0 @@
1
- import {
2
- createElement,
3
- type CreateElementParams,
4
- deleteElement,
5
- generateElementId,
6
- getContainer,
7
- getWidgetsCache,
8
- type V1Element,
9
- type V1ElementConfig,
10
- type V1ElementModelProps,
11
- } from '@elementor/editor-elements';
12
- import { type z } from '@elementor/schema';
13
-
14
- import {
15
- collectEmptyMessageErrors,
16
- collectFormAncestorErrors,
17
- collectSubmitButtonErrors,
18
- } from '../form-structure/utils';
19
- import { doUpdateElementProperty } from '../mcp/utils/do-update-element-property';
20
- import { mergeCustomCssText } from '../mcp/utils/merge-custom-css';
21
- import { RequiredChildrenEnforcer } from './utils/required-children-enforcer';
22
- import { getRequiredDefaultChildTemplates } from './utils/required-default-child-tags';
23
-
24
- type AnyValue = z.infer< z.ZodTypeAny >;
25
- type AnyConfig = Record< string, Record< string, AnyValue > >;
26
-
27
- const CREATE_ELEMENT_INVALID_CONTAINER_MESSAGE = 'createElement did not return an element container with a model.';
28
-
29
- type API = {
30
- createElement: typeof createElement;
31
- deleteElement: typeof deleteElement;
32
- getWidgetsCache: typeof getWidgetsCache;
33
- generateElementId: typeof generateElementId;
34
- getContainer: typeof getContainer;
35
- doUpdateElementProperty: typeof doUpdateElementProperty;
36
- };
37
-
38
- type CtorOptions = {
39
- xml: Document;
40
- api?: Partial< API >;
41
- elementConfig?: AnyConfig;
42
- stylesConfig?: AnyConfig;
43
- customCSS?: Record< string, string >;
44
- };
45
-
46
- export class CompositionBuilder {
47
- private elementConfig: Record< string, Record< string, AnyValue > > = {};
48
- private elementStylesConfig: Record< string, Record< string, AnyValue > > = {};
49
- private elementCustomCSS: Record< string, string > = {};
50
- private rootContainers: V1Element[] = [];
51
- private api: API = {
52
- createElement,
53
- deleteElement,
54
- getWidgetsCache,
55
- generateElementId,
56
- getContainer,
57
- doUpdateElementProperty,
58
- };
59
- private xml: Document;
60
-
61
- public static fromXMLString( xmlString: string, api: Partial< API > = {} ): CompositionBuilder {
62
- const parser = new DOMParser();
63
- const xmlDoc = parser.parseFromString( xmlString, 'application/xml' );
64
- const errorNode = xmlDoc.querySelector( 'parsererror' );
65
- if ( errorNode ) {
66
- throw new Error( 'Failed to parse XML string: ' + errorNode.textContent );
67
- }
68
- return new CompositionBuilder( {
69
- xml: xmlDoc,
70
- api,
71
- } );
72
- }
73
-
74
- constructor( opts: CtorOptions ) {
75
- const { api = {}, elementConfig = {}, stylesConfig = {}, customCSS = {}, xml } = opts;
76
- this.xml = xml;
77
- Object.assign( this.api, api );
78
- this.setElementConfig( elementConfig );
79
- this.setStylesConfig( stylesConfig );
80
- this.setCustomCSS( customCSS );
81
- }
82
-
83
- setElementConfig( config: Record< string, Record< string, AnyValue > > ) {
84
- this.elementConfig = config;
85
- }
86
-
87
- setStylesConfig( config: Record< string, Record< string, AnyValue > > ) {
88
- this.elementStylesConfig = config;
89
- }
90
-
91
- setCustomCSS( config: Record< string, string > ) {
92
- this.elementCustomCSS = config;
93
- }
94
-
95
- getXML() {
96
- return this.xml;
97
- }
98
-
99
- private buildModelTree(
100
- node: Element,
101
- widgetsCache: Record< string, V1ElementConfig >
102
- ): Record< string, unknown > {
103
- const elementTag = node.tagName;
104
- const isWidget = widgetsCache[ elementTag ]?.elType === 'widget';
105
- const id = this.api.generateElementId();
106
- const children = Array.from( node.children ).map( ( child ) => this.buildModelTree( child, widgetsCache ) );
107
-
108
- node.setAttribute( 'id', id );
109
-
110
- const base: V1ElementModelProps = {
111
- id,
112
- skipDefaultChildren: true,
113
- elements: children as V1ElementModelProps[ 'elements' ],
114
- editor_settings: {
115
- title: node.getAttribute( 'configuration-id' ) ?? undefined,
116
- },
117
- elType: 'widget',
118
- };
119
-
120
- // TODO: Restore this code once components are working in compositions
121
- // if ( elementTag === 'e-component' ) {
122
- // // apply component id before applying values
123
- // const elementConfig = this.elementConfig[ String( node.getAttribute( 'configuration-id' ) ) ];
124
- // if ( elementConfig ) {
125
- // base.settings = base.settings || {};
126
- // base.settings.component_instance = elementConfig.component_instance;
127
- // }
128
- // }
129
-
130
- if ( isWidget ) {
131
- return { ...base, elType: 'widget' as const, widgetType: elementTag };
132
- }
133
-
134
- return { ...base, elType: elementTag };
135
- }
136
-
137
- private async awaitViewRender( element: V1Element ) {
138
- const view = element.view as Record< string, unknown > | undefined;
139
- if ( view?._currentRenderPromise instanceof Promise ) {
140
- await view._currentRenderPromise;
141
- } else {
142
- await Promise.resolve();
143
- }
144
- }
145
-
146
- private validateChildTypes( node: Element, widgetsCache: Record< string, V1ElementConfig > ): string[] {
147
- const errors: string[] = [];
148
- const allowedChildTypes = widgetsCache[ node.tagName ]?.allowed_child_types;
149
-
150
- if ( allowedChildTypes?.length ) {
151
- for ( const child of Array.from( node.children ) ) {
152
- if ( ! allowedChildTypes.includes( child.tagName ) ) {
153
- errors.push(
154
- `"${ child.tagName }" is not allowed as a child of "${
155
- node.tagName
156
- }". Allowed: ${ allowedChildTypes.join( ', ' ) }`
157
- );
158
- }
159
- }
160
- }
161
-
162
- for ( const child of Array.from( node.children ) ) {
163
- errors.push( ...this.validateChildTypes( child, widgetsCache ) );
164
- }
165
-
166
- return errors;
167
- }
168
-
169
- private matchNodeByConfigId( configId: string ) {
170
- const node = this.xml.querySelector( `[configuration-id="${ configId }"]` );
171
- if ( ! node ) {
172
- throw new Error( `Configuration id "${ configId }" does not have target node.` );
173
- }
174
- const id = node.getAttribute( 'id' );
175
- if ( ! id ) {
176
- throw new Error( `Node with configuration id "${ configId }" does not have element id.` );
177
- }
178
- const element = this.api.getContainer( id );
179
- if ( ! element ) {
180
- throw new Error( `Element with id "${ id }" not found but should exist.` );
181
- }
182
- return {
183
- element,
184
- node,
185
- };
186
- }
187
-
188
- private async applyProperties() {
189
- const configErrors: string[] = [];
190
- const styleErrors: string[] = [];
191
-
192
- const allConfigIds = new Set( [
193
- ...Object.keys( this.elementConfig ),
194
- ...Object.keys( this.elementStylesConfig ),
195
- ...Object.keys( this.elementCustomCSS ),
196
- ] );
197
-
198
- for ( const configId of allConfigIds ) {
199
- let element, node;
200
- try {
201
- ( { element, node } = this.matchNodeByConfigId( configId ) );
202
- } catch ( matchErr ) {
203
- const msg = ( matchErr as Error ).message;
204
- if ( this.elementConfig[ configId ] ) {
205
- configErrors.push( msg );
206
- }
207
- if ( this.elementStylesConfig[ configId ] || this.elementCustomCSS[ configId ] ) {
208
- styleErrors.push( msg );
209
- }
210
- continue;
211
- }
212
-
213
- const config = this.elementConfig[ configId ];
214
- if ( config ) {
215
- for ( const [ propertyName, propertyValue ] of Object.entries( config ) ) {
216
- try {
217
- this.api.doUpdateElementProperty( {
218
- elementId: element.id,
219
- propertyName,
220
- propertyValue,
221
- elementType: node.tagName,
222
- } );
223
- } catch ( error ) {
224
- configErrors.push( ( error as Error ).message );
225
- }
226
- }
227
- }
228
-
229
- const styleConfig = this.elementStylesConfig[ configId ];
230
- const hasInvalidStyles = false;
231
- if ( styleConfig ) {
232
- const validStylesPropValues: Record< string, AnyValue > = {};
233
- for ( const [ styleName, stylePropValue ] of Object.entries( styleConfig ) ) {
234
- if ( styleName === '$intention' ) {
235
- continue;
236
- } else {
237
- // skipping actual validation - properies comes from the server
238
- validStylesPropValues[ styleName ] = stylePropValue;
239
- }
240
- }
241
- if ( Object.keys( validStylesPropValues ).length > 0 ) {
242
- try {
243
- this.api.doUpdateElementProperty( {
244
- elementId: element.id,
245
- propertyName: '_styles',
246
- propertyValue: validStylesPropValues,
247
- elementType: node.tagName,
248
- } );
249
- } catch ( error ) {
250
- styleErrors.push( String( error ) );
251
- }
252
- }
253
- }
254
-
255
- const intentionCss = typeof styleConfig?.$intention === 'string' ? styleConfig.$intention.trim() : '';
256
- const fallbackCss = hasInvalidStyles && intentionCss ? intentionCss : '';
257
- const mergedCustomCss = mergeCustomCssText( this.elementCustomCSS[ configId ], fallbackCss );
258
- if ( mergedCustomCss ) {
259
- try {
260
- this.api.doUpdateElementProperty( {
261
- elementId: element.id,
262
- propertyName: '_styles',
263
- propertyValue: { custom_css: mergedCustomCss },
264
- elementType: node.tagName,
265
- } );
266
- } catch ( cssErr ) {
267
- styleErrors.push( String( cssErr ) );
268
- }
269
- }
270
-
271
- await this.awaitViewRender( element );
272
- }
273
-
274
- return { configErrors, styleErrors };
275
- }
276
-
277
- async build( rootContainer: V1Element ) {
278
- const widgetsCache = this.api.getWidgetsCache() || {};
279
-
280
- new Set( this.xml.querySelectorAll( '*' ) ).forEach( ( node ) => {
281
- if ( ! widgetsCache[ node.tagName ] ) {
282
- throw new Error( `Unknown widget type: ${ node.tagName }` );
283
- }
284
- } );
285
-
286
- const typesWithRequiredChildren = Object.keys( widgetsCache ).filter(
287
- ( elementType ) => getRequiredDefaultChildTemplates( widgetsCache[ elementType ] ).length > 0
288
- );
289
-
290
- typesWithRequiredChildren.forEach( ( elementType ) => {
291
- new RequiredChildrenEnforcer( elementType, widgetsCache ).enforce( this.xml );
292
- } );
293
-
294
- const childTypeErrors: string[] = [];
295
- for ( const rootChild of Array.from( this.xml.children ) ) {
296
- childTypeErrors.push( ...this.validateChildTypes( rootChild, widgetsCache ) );
297
- }
298
- if ( childTypeErrors.length ) {
299
- throw new Error( `Invalid element structure:\n${ childTypeErrors.join( '\n' ) }` );
300
- }
301
-
302
- const formErrors = [
303
- ...collectFormAncestorErrors( this.xml ),
304
- ...collectSubmitButtonErrors( this.xml ),
305
- ...collectEmptyMessageErrors( this.xml ),
306
- ];
307
-
308
- const children = Array.from( this.xml.children );
309
- for ( const childNode of children ) {
310
- const modelTree = this.buildModelTree( childNode, widgetsCache );
311
-
312
- try {
313
- const newElement = this.api.createElement( {
314
- container: rootContainer,
315
- model: modelTree as CreateElementParams[ 'model' ],
316
- options: { useHistory: false },
317
- } );
318
- if ( ! newElement?.model ) {
319
- throw new Error( CREATE_ELEMENT_INVALID_CONTAINER_MESSAGE );
320
- }
321
- this.rootContainers.push( newElement );
322
- await this.awaitViewRender( newElement );
323
- } catch ( e: unknown ) {
324
- const attempToRestoreInvalidContainer = this.api.getContainer( modelTree.id as string );
325
- if ( attempToRestoreInvalidContainer ) {
326
- this.api.deleteElement( { container: attempToRestoreInvalidContainer } );
327
- }
328
- throw e;
329
- }
330
- }
331
-
332
- const { configErrors, styleErrors } = await this.applyProperties();
333
-
334
- if ( typeof window !== 'undefined' ) {
335
- const targetWindow = window.top || window;
336
- targetWindow.dispatchEvent(
337
- new CustomEvent( 'elementor/composition/built', {
338
- detail: { rootContainers: this.rootContainers.map( ( c ) => c.id ) },
339
- } )
340
- );
341
- }
342
-
343
- return {
344
- configErrors,
345
- styleErrors,
346
- formErrors,
347
- rootContainers: [ ...this.rootContainers ],
348
- };
349
- }
350
- }
@@ -1,79 +0,0 @@
1
- import { type V1ElementConfig } from '@elementor/editor-elements';
2
-
3
- import { RequiredChildrenEnforcer } from '../required-children-enforcer';
4
-
5
- describe( 'RequiredChildrenEnforcer', () => {
6
- const FULL_FORM_DIRECT_CHILD_COUNT = 3;
7
-
8
- const createWidgetsCache = (): Record< string, V1ElementConfig > => ( {
9
- 'e-form': {
10
- title: 'Form',
11
- controls: {},
12
- elType: 'widget',
13
- default_children: [
14
- {
15
- elType: 'e-form-success-message',
16
- meta: { required: true },
17
- elements: [],
18
- },
19
- {
20
- elType: 'e-form-error-message',
21
- meta: { required: true },
22
- elements: [],
23
- },
24
- {
25
- elType: 'widget',
26
- widgetType: 'e-form-input',
27
- elements: [],
28
- },
29
- ],
30
- } as V1ElementConfig,
31
- 'e-form-input': { title: 'Input', controls: {}, elType: 'widget' } as V1ElementConfig,
32
- 'e-form-success-message': {
33
- title: 'Success',
34
- controls: {},
35
- elType: 'e-form-success-message',
36
- } as V1ElementConfig,
37
- 'e-form-error-message': {
38
- title: 'Error',
39
- controls: {},
40
- elType: 'e-form-error-message',
41
- } as V1ElementConfig,
42
- } );
43
-
44
- it( 'throws when required direct children are missing', () => {
45
- // Arrange
46
- const xml = new DOMParser().parseFromString( '<e-form><e-form-input /></e-form>', 'application/xml' );
47
- const enforcer = new RequiredChildrenEnforcer( 'e-form', createWidgetsCache() );
48
-
49
- // Act & Assert
50
- expect( () => enforcer.enforce( xml ) ).toThrow(
51
- /Missing required direct child element tag\(s\): e-form-success-message, e-form-error-message/
52
- );
53
- } );
54
-
55
- it( 'throws when only some required direct children exist', () => {
56
- // Arrange
57
- const xml = new DOMParser().parseFromString( '<e-form><e-form-success-message /></e-form>', 'application/xml' );
58
- const enforcer = new RequiredChildrenEnforcer( 'e-form', createWidgetsCache() );
59
-
60
- // Act & Assert
61
- expect( () => enforcer.enforce( xml ) ).toThrow(
62
- /Missing required direct child element tag\(s\): e-form-error-message/
63
- );
64
- } );
65
-
66
- it( 'does not throw when all required direct children exist', () => {
67
- // Arrange
68
- const xmlStr = '<e-form><e-form-success-message /><e-form-error-message /><e-form-input /></e-form>';
69
- const xml = new DOMParser().parseFromString( xmlStr, 'application/xml' );
70
- const enforcer = new RequiredChildrenEnforcer( 'e-form', createWidgetsCache() );
71
-
72
- // Act
73
- expect( () => enforcer.enforce( xml ) ).not.toThrow();
74
-
75
- // Assert
76
- const form = xml.querySelector( 'e-form' );
77
- expect( form?.children.length ).toBe( FULL_FORM_DIRECT_CHILD_COUNT );
78
- } );
79
- } );
@@ -1,35 +0,0 @@
1
- import { type V1ElementConfig } from '@elementor/editor-elements';
2
-
3
- import { getRequiredDefaultChildTypes } from '../required-default-child-tags';
4
-
5
- describe( 'required-default-child-tags', () => {
6
- it( 'returns XML tag names for default children marked meta.required', () => {
7
- // Arrange
8
- const config = {
9
- title: 'Form',
10
- controls: {},
11
- default_children: [
12
- {
13
- elType: 'e-form-success-message',
14
- meta: { required: true },
15
- elements: [],
16
- },
17
- {
18
- elType: 'widget',
19
- widgetType: 'e-form-input',
20
- meta: { required: true },
21
- },
22
- {
23
- elType: 'e-form-label',
24
- elements: [],
25
- },
26
- ],
27
- } as unknown as V1ElementConfig;
28
-
29
- // Act
30
- const tags = getRequiredDefaultChildTypes( config );
31
-
32
- // Assert
33
- expect( tags ).toEqual( [ 'e-form-success-message', 'e-form-input' ] );
34
- } );
35
- } );
@@ -1,56 +0,0 @@
1
- import { type V1ElementConfig } from '@elementor/editor-elements';
2
-
3
- import { type ChildTemplate, getRequiredDefaultChildTemplates } from './required-default-child-tags';
4
-
5
- const REQUIRED_CHILD_SCHEMA_HINT =
6
- 'Use the widget schema resource; under llm_guidance.required_direct_children for V4 widgets.';
7
-
8
- export class RequiredChildrenEnforcer {
9
- private readonly elementType: string;
10
- private readonly requiredTemplates: ChildTemplate[];
11
-
12
- constructor( elementType: string, widgetsCache: Record< string, V1ElementConfig > ) {
13
- this.elementType = elementType;
14
- this.requiredTemplates = getRequiredDefaultChildTemplates( widgetsCache[ elementType ] );
15
- }
16
-
17
- enforce( xml: Document ) {
18
- if ( this.requiredTemplates.length === 0 ) {
19
- return;
20
- }
21
-
22
- const errors: string[] = [];
23
-
24
- for ( const rootNode of Array.from( xml.children ) ) {
25
- this.collectMissingRequiredErrors( rootNode, errors );
26
- }
27
-
28
- if ( errors.length ) {
29
- throw new Error( `${ errors.join( '\n' ) }\n${ REQUIRED_CHILD_SCHEMA_HINT }` );
30
- }
31
- }
32
-
33
- private collectMissingRequiredErrors( node: Element, errors: string[] ) {
34
- if ( node.tagName === this.elementType ) {
35
- const existingChildTags = new Set( Array.from( node.children ).map( ( child ) => child.tagName ) );
36
- const missingTags = this.requiredTemplates
37
- .map( ( child ) => child.widgetType ?? child.elType ?? '' )
38
- .filter( ( type ) => type && ! existingChildTags.has( type ) ) as string[];
39
-
40
- if ( missingTags.length ) {
41
- const configurationId = node.getAttribute( 'configuration-id' );
42
- const location = configurationId
43
- ? `<${ node.tagName } configuration-id="${ configurationId }">`
44
- : `<${ node.tagName }>`;
45
-
46
- errors.push(
47
- `${ location } Missing required direct child element tag(s): ${ missingTags.join( ', ' ) }.`
48
- );
49
- }
50
- }
51
-
52
- for ( const childNode of Array.from( node.children ) ) {
53
- this.collectMissingRequiredErrors( childNode, errors );
54
- }
55
- }
56
- }
@@ -1,22 +0,0 @@
1
- import { type V1ElementConfig } from '@elementor/editor-elements';
2
-
3
- export type ChildTemplate = {
4
- widgetType?: string;
5
- elType?: string;
6
- meta?: { required?: boolean };
7
- };
8
- export function getRequiredDefaultChildTemplates( elementConfig: V1ElementConfig | undefined ): ChildTemplate[] {
9
- const defaultChildren = elementConfig?.default_children as ChildTemplate[];
10
-
11
- if ( ! Array.isArray( defaultChildren ) ) {
12
- return [];
13
- }
14
-
15
- return defaultChildren.filter( ( child ) => child?.meta?.required ?? false );
16
- }
17
-
18
- export function getRequiredDefaultChildTypes( elementConfig: V1ElementConfig | undefined ): string[] {
19
- return getRequiredDefaultChildTemplates( elementConfig )
20
- .map( ( child ) => child.widgetType ?? child.elType ?? '' )
21
- .filter( ( type ) => Boolean( type ) );
22
- }
@@ -1,159 +0,0 @@
1
- export const BEST_PRACTICES_PROMPT = `
2
- # DESIGN QUALITY IMPERATIVE
3
-
4
- You are generating designs for real users who expect distinctive, intentional aesthetics - NOT generic AI output.
5
-
6
- **The Core Challenge**: Large language models naturally converge toward statistically common design patterns during generation. This creates predictable, uninspired results that users describe as "AI slop": safe color schemes, default typography hierarchies, minimal contrast, and timid spacing.
7
-
8
- **Your Mission**: Actively resist distributional convergence by making intentional, distinctive design choices across all aesthetic dimensions. Every design decision should have a clear purpose tied to visual hierarchy, brand personality, or user experience goals.
9
-
10
- When in doubt between "safe" and "distinctive," choose distinctive - users can always request refinements, but they cannot salvage generic foundations.
11
-
12
- ---
13
-
14
- # DESIGN VECTORS - Concrete Implementation Guidance
15
-
16
- ## 1. Typography & Visual Hierarchy
17
-
18
- ### Avoid Distributional Defaults:
19
- - NO generic sans-serifs as primary typefaces (Inter, Roboto, Arial, Helvetica)
20
- - NO timid size ratios (1.2x, 1.5x scaling)
21
- - NO uniform font weights (everything at 400 or 600)
22
-
23
- ### Intentional Alternatives:
24
- - **For Technical/Modern**: Consider monospace headlines (JetBrains Mono, SF Mono) paired with clean body text
25
- - **For Editorial/Elegant**: Consider serif headlines (Playfair Display, Crimson Text) with sans-serif body
26
- - **For Playful/Creative**: Consider display fonts with character, paired with highly legible body text
27
-
28
- ### Scale & Contrast Implementation:
29
- - Headline-to-body size ratios: 3x minimum (e.g., 48px headline vs 16px body)
30
- - Use extreme weight contrasts: pair weight-100 or 200 with weight-800 or 900
31
- - Line height contrasts: tight headlines (1.1) vs. generous body (1.7)
32
- - Letter spacing: compressed headlines (-0.02em to -0.05em) vs. open small text (0.03em+)
33
-
34
- ## 2. Color & Theme Strategy
35
-
36
- ### Avoid Distributional Defaults:
37
- - NO purple gradients or blue-purple color schemes (massively overrepresented in AI output)
38
- - NO evenly-distributed color palettes (3-4 colors used equally)
39
- - NO timid pastels or all-neutral schemes
40
- - NO #333333, #666666, #999999 grays
41
-
42
- ### Intentional Alternatives:
43
- - **Commit to a Dominant Color**: Choose ONE primary brand color that appears in 60-70% of colored elements
44
- - **Sharp Accent Strategy**: Use 1-2 high-contrast accent colors sparingly (10-15% of colored elements)
45
- - **Neutrals with Personality**: Replace pure grays with warm (#3d3228, #f5f1ed) or cool (#2a2f3d, #f0f2f5) tinted neutrals
46
-
47
- ### Color Psychology Mapping:
48
- - Energy/Action → Warm reds, oranges, yellows (NOT purple/blue)
49
- - Trust/Calm → Deep teals, forest greens (NOT generic blue)
50
- - Luxury/Premium → Deep burgundy, emerald, charcoal with gold accents
51
- - Playful/Creative → Unexpected combinations (coral + mint, mustard + navy)
52
-
53
- ## 3. Spatial Design & White Space
54
-
55
- ### Avoid Distributional Defaults:
56
- - NO uniform spacing (everything 16px or 24px)
57
- - NO cramped layouts that maximize content density
58
- - NO default container widths (1200px, 1440px)
59
-
60
- ### Intentional Alternatives:
61
- - **Breathing Room**: Use generous white space as a design element (80-120px vertical spacing between sections)
62
- - **Asymmetric Spacing**: Vary padding dramatically (small: 12px, medium: 48px, large: 96px)
63
- - **Content Width Strategy**:
64
- - Reading content: max 65-75 characters (600-700px)
65
- - Hero sections: asymmetric layouts, not centered blocks
66
- - Cards/components: vary sizes intentionally, not uniform grids
67
-
68
- ## 4. Motion & Interaction Design
69
-
70
- ### Avoid Distributional Defaults:
71
- - NO scattered micro-interactions on every element
72
- - NO generic fade-in animations
73
- - NO 0.3s ease-in-out transitions everywhere
74
-
75
- ### Intentional Alternatives:
76
- - **High-Impact Moments**: Use animation for 2-3 key moments (page load hero, primary CTA, section reveals)
77
- - **Staggered Reveals**: When animating multiple items, use staggered delays (0.1s increments)
78
- - **Purposeful Timing**: Fast interactions (0.15s) for responsiveness, slow reveals (0.6s+) for drama
79
-
80
- ## 5. Backgrounds & Atmospheric Depth
81
-
82
- ### Avoid Distributional Defaults:
83
- - NO solid white or light gray backgrounds
84
- - NO single-color backgrounds
85
- - NO generic gradient overlays
86
-
87
- ### Intentional Alternatives:
88
- - **Layered Gradients**: Combine 2-3 subtle gradients for depth
89
- - **Geometric Patterns**: SVG patterns, mesh gradients, or subtle noise textures
90
- - **Strategic Contrast**: Alternate between light and dark sections for rhythm
91
-
92
- ## 6. Visual Hierarchy Principles
93
-
94
- ### Clear Priority System:
95
- 1. **Primary Focus (1 element)**: Largest, highest contrast, most visual weight
96
- 2. **Secondary Elements (2-3 elements)**: 40-60% of primary size, reduced contrast
97
- 3. **Tertiary/Support (everything else)**: Minimal visual weight, muted colors
98
-
99
- ### Contrast Techniques:
100
- - Size: 3x+ differences between hierarchy levels
101
- - Weight: 300+ difference in font-weight values
102
- - Color: Primary gets brand color, secondary gets neutral, tertiary gets muted
103
- - Space: Primary gets 2x+ surrounding white space vs. secondary
104
-
105
-
106
- # IMPLEMENTATION WORKFLOW
107
-
108
- When building a composition:
109
-
110
- 1. **Define the Visual Goal First**
111
- - What emotion/brand personality? (Professional, playful, elegant, bold)
112
- - What's the single most important element?
113
- - What color family (warm/cool/neutral) supports the goal?
114
-
115
- 2. **Choose Typography Personality**
116
- - Select font pairings that match the personality (NOT defaults)
117
- - Define scale (3x+ headline ratio)
118
- - Set weight extremes (light/heavy contrast)
119
-
120
- 3. **Commit to Color Strategy**
121
- - Pick ONE dominant color (NOT purple, NOT generic blue)
122
- - Choose 1 sharp accent
123
- - Define warm or cool neutrals
124
-
125
- 4. **Design Spatial Rhythm**
126
- - Use generous white space (3-6rem between sections)
127
- - Create asymmetry (not everything centered)
128
- - Vary component sizes intentionally
129
-
130
- 5. **Add Strategic Motion**
131
- - Identify 2-3 high-impact animation moments
132
- - Use staggered timing for multiple elements
133
- - Keep interactions purposeful, not decorative
134
-
135
- 6. **Layer Atmospheric Depth**
136
- - Use gradient combinations for backgrounds
137
- - Add subtle patterns or textures
138
- - Alternate light/dark sections for rhythm
139
-
140
-
141
- # CONTEXT-AWARE DESIGN PERSONALITIES
142
-
143
- Brand personality should influence your choices:
144
-
145
- - **Corporate/Professional**: Deep neutrals, serif headlines, structured spacing
146
- - **Creative/Agency**: Bold color, display fonts, asymmetric layouts
147
- - **Tech/Startup**: Monospace accents, sharp colors, modern spacing
148
- - **Elegant/Luxury**: Serif dominance, muted colors with metallic accents, generous white space
149
-
150
- # USER OVERRIDE CAPABILITY
151
-
152
- Always respect user specifications:
153
-
154
- If user specifies:
155
- - Specific colors → use them
156
- - Specific fonts → use them
157
- - Specific spacing → use it
158
- - "Minimal/simple" → reduce ornamentation but maintain quality principles
159
- `;