Appearance
Gradient Enhancement Research
Research document for upgrading the gradient system from basic linear-only to a more capable, Figma-inspired implementation.
Current State
Data Model
AF type (Blocks/data/types.ts:214):
typescript
export type BackgroundSettings =
| { mode: 'none' }
| { mode: 'color'; color: string }
| { mode: 'gradient'; colors: string[] }
| { mode: 'image'; url: string; imageName: string; size: ObjectFit; enableManualUrl: boolean }CE type (interfaces/jsonTypes/payload-v2/index.ts:10):
typescript
export type BackgroundSettings =
| { mode: 'gradient'; colors: string[] }
| { mode: 'image'; url: string; size: string }
| { mode: 'color'; color: string }
| { mode: 'none' }Defaults (defaults.ts:331):
typescript
export const backgroundGradientDefaults = (
colors: string[] = ['#00C3FFFF', '#30AFD6FF']
): BackgroundSettings => ({
colors,
mode: 'gradient',
})Limitations
| Aspect | Current | Desired |
|---|---|---|
| Gradient type | Linear only | Linear, radial, conic |
| Direction | Hardcoded to bottom | User-configurable angle |
| Colors | Exactly 2 | 2+ color stops |
| Stop positions | None (even distribution) | Per-stop percentage |
| Preview | Basic linear preview | Reflects actual gradient |
Rendering
CE (styles/components/helpers.ts:159-162):
typescript
if (mode === 'gradient') {
return {
backgroundImage: `linear-gradient(to bottom, ${colors[0]}, ${colors[1]})`,
}
}AF preview (ImagePreview.vue:64-68):
typescript
generateGradient() {
const from = this.gradient[0]
const to = this.gradient[1]
return `background: linear-gradient(${from}, ${to})`
}Usage
BackgroundStyleSection is used in 15+ configuration files, all with identical event pattern:
vue
<BackgroundStyleSection
:background-settings="blockData.backgroundSettings"
@update:active="updateValue($event, 'background')"
@update:settings="updateValue($event, 'backgroundSettings')"
@update:color="updateValue($event, 'backgroundSettings.color')"
@update:gradient="updateValue($event, 'backgroundSettings.colors')"
@update:image="imageUploaded"
/>The @update:gradient event currently emits string[] (2-element array). This will need to change to support richer gradient data.
Reusable Components
Already exist and can be reused directly
| Component | Location | Reuse for |
|---|---|---|
ColorInput | components/common/ColorInput.vue | Color stop pickers (supports alpha) |
RotationSection | Configuration/components/RotationSection.vue | Angle input pattern (InputField number-input + "deg" suffix) |
OptionList + OptionButton | Configuration/components/OptionList.vue | Gradient type selector (linear/radial/conic) |
ImagePreview | components/common/ImagePreview.vue | Gradient preview (needs update to render new types) |
InputField | components/common/InputField.vue | Number inputs for angle, stop positions |
sectionLogic mixin | Configuration/mixins/sectionLogic.ts | Override system integration |
Border per-side pattern as reference
BorderStyleSection handles unified vs per-side modes via a perSide boolean. The same pattern could inspire a "simple vs advanced" toggle for gradient stops.
CSS Gradient Types
What CSS supports natively
css
/* Linear - direction via angle or keywords */
linear-gradient(135deg, #FF60FE, #5758D5)
linear-gradient(to top right, #FF60FE, #5758D5)
/* Radial - shape + position */
radial-gradient(circle at center, #FF60FE, #5758D5)
radial-gradient(ellipse at 30% 70%, #FF60FE, #5758D5)
/* Conic - angle + position */
conic-gradient(from 45deg at 50% 50%, #FF60FE, #5758D5)
/* Multi-stop with explicit positions */
linear-gradient(135deg, #FF60FE 0%, #5758D5 50%, #00C3FF 100%)What Figma supports
| Type | CSS equivalent | Complexity |
|---|---|---|
| Linear | linear-gradient() | Low - angle + stops |
| Radial | radial-gradient() | Medium - position + shape |
| Angular | conic-gradient() | Medium - start angle + position |
| Diamond | No CSS equivalent | High - requires SVG or multiple gradients |
Recommendation: Skip diamond. It has no native CSS support and would require complex workarounds (rotated element with radial gradient, or SVG). Linear, radial, and conic cover 99% of use cases.
Proposed Data Model
typescript
// -- Color stops --
type GradientStop = {
color: string // hex with alpha, e.g. '#FF60FEFF'
position: number // 0-100 percentage
}
// -- Gradient settings (new) --
type GradientSettings = {
type: 'linear' | 'radial' | 'conic'
angle: number // degrees, used by linear + conic
stops: GradientStop[] // 2+ stops
// Radial-specific
shape?: 'circle' | 'ellipse'
position?: { x: number; y: number } // percentage, default 50/50
}
// -- BackgroundSettings (updated) --
export type BackgroundSettings =
| { mode: 'none' }
| { mode: 'color'; color: string }
| { mode: 'gradient'; colors: string[]; gradient?: GradientSettings }
| { mode: 'image'; ... }Backwards compatibility
The colors array is kept for backwards compatibility with existing creatives. The engine checks for gradient first, falls back to colors:
typescript
if (mode === 'gradient') {
if (settings.gradient) {
return renderGradient(settings.gradient)
}
// Legacy fallback
return {
backgroundImage: `linear-gradient(to bottom, ${colors[0]}, ${colors[1]})`,
}
}New creatives always write both colors (first two stop colors, for legacy previews) and gradient (full settings).
Default values
typescript
export const gradientSettingsDefaults = (): GradientSettings => ({
type: 'linear',
angle: 180,
stops: [
{ color: '#00C3FFFF', position: 0 },
{ color: '#30AFD6FF', position: 100 },
],
})Rendering (CE)
CSS generation helper
typescript
const renderGradient = (gradient: GradientSettings): Styles => {
const stops = gradient.stops
.map((s) => `${s.color} ${s.position}%`)
.join(', ')
switch (gradient.type) {
case 'linear':
return { backgroundImage: `linear-gradient(${gradient.angle}deg, ${stops})` }
case 'radial': {
const shape = gradient.shape || 'ellipse'
const pos = gradient.position || { x: 50, y: 50 }
return { backgroundImage: `radial-gradient(${shape} at ${pos.x}% ${pos.y}%, ${stops})` }
}
case 'conic': {
const pos = gradient.position || { x: 50, y: 50 }
return { backgroundImage: `conic-gradient(from ${gradient.angle}deg at ${pos.x}% ${pos.y}%, ${stops})` }
}
}
}UI Design (AF)
BackgroundStyleSection changes
When gradient mode is selected, the section expands to show:
[None] [Color] [Gradient] [Image]
Type: [Linear] [Radial] [Conic]
Angle: [180] deg (linear + conic only)
Position: [50] % [50] % (radial + conic only)
Shape: [Circle] [Ellipse] (radial only)
Stops:
[ColorInput] ---- [0] %
[ColorInput] ---- [100] %
[+ Add stop]
[====== Preview ======]Component breakdown
- Gradient type selector - OptionList with 3 buttons, already exists as pattern
- Angle input - Reuse RotationSection pattern (InputField + "deg")
- Position inputs - Two InputField number-inputs with "%" suffix (radial/conic)
- Shape selector - OptionList with 2 buttons (radial only)
- Color stops - List of ColorInput + position InputField pairs, with add/remove
- Preview - Updated ImagePreview that renders all gradient types
Conditional visibility
| Control | Linear | Radial | Conic |
|---|---|---|---|
| Angle | Yes | No | Yes |
| Position | No | Yes | Yes |
| Shape | No | Yes | No |
| Stops | Yes | Yes | Yes |
ImagePreview update
ImagePreview needs a gradientSettings prop (or the existing gradient prop changes shape). It generates the CSS string the same way the engine does:
typescript
generateGradient() {
if (this.gradientSettings) {
return `background: ${renderGradientCSS(this.gradientSettings)}`
}
// Legacy
return `background: linear-gradient(${this.gradient[0]}, ${this.gradient[1]})`
}Scope
All features ship together in a single branch:
- Gradient type selector (linear/radial/conic)
- Circular angle dial (linear + conic)
- Unlimited color stops with position control
- Visual gradient bar with draggable stop handles
- Radial shape selector (circle/ellipse)
- Visual position picker for radial/conic (click on preview)
- Updated preview and engine rendering
- Backwards-compatible with existing creatives
Files to Change
Application-Frontend
| File | Change |
|---|---|
Blocks/data/types.ts | Add GradientStop, GradientSettings types, update BackgroundSettings |
Blocks/data/defaults.ts | Add gradientSettingsDefaults(), update backgroundGradientDefaults() |
Configuration/components/BackgroundStyleSection.vue | Add type/angle/stops UI |
components/common/ImagePreview.vue | Support new gradient types in preview |
assets/i18n/en.js | Add keys for linear, radial, conic, angle, position, shape, stops |
Blocks/utils.ts | Verify sectionSettings registration (already covers backgroundSettings) |
Creative-Engine
| File | Change |
|---|---|
interfaces/jsonTypes/payload-v2/index.ts | Add GradientStop, GradientSettings, update BackgroundSettings |
styles/components/helpers.ts | Add renderGradient(), update getBackgroundProperties() |
No changes needed
- Configuration files that use BackgroundStyleSection (they pass the full
backgroundSettingsobject) - Vuex store (uses generic
updateBlockValuemutation) - configurationLogic mixin (generic path-based updates)
- sectionSettings registry (already tracks
backgroundSettings)
Decisions
- Type selector inside gradient mode -- select Gradient first, then choose type (linear/radial/conic). Not top-level.
- Unlimited color stops -- no artificial limit.
- Circular dial for angle -- visual, Figma-style.
- Visual position picker -- click on preview to set center point for radial/conic.
- All in one go -- no phasing, ship everything together.