stylpp 1.0.0
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/README.md +475 -0
- package/bin/stylpp.js +2 -0
- package/dist/stylpp.esm.js +726 -0
- package/dist/stylpp.esm.js.map +1 -0
- package/dist/stylpp.js +734 -0
- package/dist/stylpp.js.map +1 -0
- package/dist/stylpp.min.js +2 -0
- package/dist/stylpp.min.js.map +1 -0
- package/package.json +59 -0
- package/src/cli/index.js +400 -0
- package/src/compiler/animation.js +346 -0
- package/src/compiler/effects.js +344 -0
- package/src/compiler/generator.js +157 -0
- package/src/compiler/index.js +119 -0
- package/src/compiler/parser.js +210 -0
- package/src/compiler/physics.js +286 -0
- package/src/compiler/transformer.js +228 -0
- package/src/runtime/advanced.js +293 -0
- package/src/runtime/browser.js +239 -0
- package/stylpp-1.0.0.tgz +0 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STYL++ Physics Engine
|
|
3
|
+
* Provides physics-based animations, constraints, and layout calculations
|
|
4
|
+
* Transcends CSS limitations with real physics simulation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class PhysicsEngine {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.options = {
|
|
10
|
+
gravity: 9.81,
|
|
11
|
+
damping: 0.9,
|
|
12
|
+
friction: 0.2,
|
|
13
|
+
timeStep: 0.016,
|
|
14
|
+
iterations: 4,
|
|
15
|
+
...options
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
this.bodies = new Map();
|
|
19
|
+
this.constraints = new Map();
|
|
20
|
+
this.forces = new Map();
|
|
21
|
+
this.time = 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a physics body for an element
|
|
26
|
+
* @param {string} id - Element ID
|
|
27
|
+
* @param {Object} config - Body configuration
|
|
28
|
+
*/
|
|
29
|
+
createBody(id, config = {}) {
|
|
30
|
+
const body = {
|
|
31
|
+
id,
|
|
32
|
+
position: { x: config.x || 0, y: config.y || 0 },
|
|
33
|
+
velocity: { x: config.vx || 0, y: config.vy || 0 },
|
|
34
|
+
acceleration: { x: 0, y: 0 },
|
|
35
|
+
mass: config.mass || 1,
|
|
36
|
+
bounce: config.bounce || 0.6,
|
|
37
|
+
friction: config.friction || this.options.friction,
|
|
38
|
+
pinned: config.pinned || false,
|
|
39
|
+
width: config.width || 0,
|
|
40
|
+
height: config.height || 0,
|
|
41
|
+
constraints: []
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
this.bodies.set(id, body);
|
|
45
|
+
return body;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Add force to body (gravity, wind, custom forces)
|
|
50
|
+
* @param {string} bodyId - Body ID
|
|
51
|
+
* @param {Object} force - Force vector {x, y}
|
|
52
|
+
*/
|
|
53
|
+
addForce(bodyId, force) {
|
|
54
|
+
const body = this.bodies.get(bodyId);
|
|
55
|
+
if (!body) return;
|
|
56
|
+
|
|
57
|
+
body.acceleration.x += force.x / body.mass;
|
|
58
|
+
body.acceleration.y += force.y / body.mass;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create constraint between bodies (springs, rods, etc.)
|
|
63
|
+
* @param {string} id - Constraint ID
|
|
64
|
+
* @param {string} body1Id - First body ID
|
|
65
|
+
* @param {string} body2Id - Second body ID
|
|
66
|
+
* @param {Object} config - Constraint configuration
|
|
67
|
+
*/
|
|
68
|
+
createConstraint(id, body1Id, body2Id, config = {}) {
|
|
69
|
+
const constraint = {
|
|
70
|
+
id,
|
|
71
|
+
body1Id,
|
|
72
|
+
body2Id,
|
|
73
|
+
type: config.type || 'spring', // 'spring', 'rod', 'distance', 'angle'
|
|
74
|
+
restLength: config.restLength || 100,
|
|
75
|
+
stiffness: config.stiffness || 0.5,
|
|
76
|
+
damping: config.damping || 0.3,
|
|
77
|
+
maxForce: config.maxForce || 1000
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
this.constraints.set(id, constraint);
|
|
81
|
+
const body1 = this.bodies.get(body1Id);
|
|
82
|
+
if (body1) body1.constraints.push(id);
|
|
83
|
+
return constraint;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Spring-based layout constraint
|
|
88
|
+
* Simulates attractive/repulsive forces between elements
|
|
89
|
+
*/
|
|
90
|
+
createSpringLayout(elementIds, config = {}) {
|
|
91
|
+
const stiffness = config.stiffness || 0.3;
|
|
92
|
+
const damping = config.damping || 0.2;
|
|
93
|
+
const restDistance = config.restDistance || 150;
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < elementIds.length; i++) {
|
|
96
|
+
for (let j = i + 1; j < elementIds.length; j++) {
|
|
97
|
+
this.createConstraint(
|
|
98
|
+
`spring-${i}-${j}`,
|
|
99
|
+
elementIds[i],
|
|
100
|
+
elementIds[j],
|
|
101
|
+
{
|
|
102
|
+
type: 'spring',
|
|
103
|
+
restLength: restDistance,
|
|
104
|
+
stiffness: stiffness,
|
|
105
|
+
damping: damping
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Simulate gravity field
|
|
114
|
+
*/
|
|
115
|
+
simulateGravity() {
|
|
116
|
+
this.bodies.forEach((body) => {
|
|
117
|
+
if (!body.pinned) {
|
|
118
|
+
this.addForce(body.id, { x: 0, y: this.options.gravity * body.mass });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Apply Verlet integration for physics simulation
|
|
125
|
+
*/
|
|
126
|
+
step(deltaTime = this.options.timeStep) {
|
|
127
|
+
// Apply gravity
|
|
128
|
+
this.simulateGravity();
|
|
129
|
+
|
|
130
|
+
// Update velocity and position (Verlet)
|
|
131
|
+
this.bodies.forEach((body) => {
|
|
132
|
+
if (body.pinned) return;
|
|
133
|
+
|
|
134
|
+
// Apply damping
|
|
135
|
+
body.velocity.x *= this.options.damping;
|
|
136
|
+
body.velocity.y *= this.options.damping;
|
|
137
|
+
|
|
138
|
+
// Update velocity
|
|
139
|
+
body.velocity.x += body.acceleration.x * deltaTime;
|
|
140
|
+
body.velocity.y += body.acceleration.y * deltaTime;
|
|
141
|
+
|
|
142
|
+
// Update position
|
|
143
|
+
body.position.x += body.velocity.x * deltaTime;
|
|
144
|
+
body.position.y += body.velocity.y * deltaTime;
|
|
145
|
+
|
|
146
|
+
// Reset acceleration
|
|
147
|
+
body.acceleration.x = 0;
|
|
148
|
+
body.acceleration.y = 0;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Resolve constraints
|
|
152
|
+
for (let i = 0; i < this.options.iterations; i++) {
|
|
153
|
+
this.constraints.forEach((constraint) => {
|
|
154
|
+
this.resolveConstraint(constraint);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.time += deltaTime;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Resolve a constraint
|
|
163
|
+
*/
|
|
164
|
+
resolveConstraint(constraint) {
|
|
165
|
+
const body1 = this.bodies.get(constraint.body1Id);
|
|
166
|
+
const body2 = this.bodies.get(constraint.body2Id);
|
|
167
|
+
|
|
168
|
+
if (!body1 || !body2) return;
|
|
169
|
+
|
|
170
|
+
const dx = body2.position.x - body1.position.x;
|
|
171
|
+
const dy = body2.position.y - body1.position.y;
|
|
172
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
173
|
+
|
|
174
|
+
if (constraint.type === 'spring') {
|
|
175
|
+
this.resolveSpring(body1, body2, constraint, distance);
|
|
176
|
+
} else if (constraint.type === 'rod') {
|
|
177
|
+
this.resolveRod(body1, body2, constraint, distance);
|
|
178
|
+
} else if (constraint.type === 'distance') {
|
|
179
|
+
this.resolveDistance(body1, body2, constraint, distance);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Resolve spring constraint
|
|
185
|
+
*/
|
|
186
|
+
resolveSpring(body1, body2, constraint, distance) {
|
|
187
|
+
const dx = body2.position.x - body1.position.x;
|
|
188
|
+
const dy = body2.position.y - body1.position.y;
|
|
189
|
+
|
|
190
|
+
if (distance === 0) return;
|
|
191
|
+
|
|
192
|
+
// Spring force: F = -k * (d - r)
|
|
193
|
+
const force = -constraint.stiffness * (distance - constraint.restLength);
|
|
194
|
+
const fx = (force * dx) / distance;
|
|
195
|
+
const fy = (force * dy) / distance;
|
|
196
|
+
|
|
197
|
+
// Apply damping
|
|
198
|
+
const dvx = body2.velocity.x - body1.velocity.x;
|
|
199
|
+
const dvy = body2.velocity.y - body1.velocity.y;
|
|
200
|
+
const dampingForce =
|
|
201
|
+
constraint.damping * ((dvx * dx + dvy * dy) / distance);
|
|
202
|
+
|
|
203
|
+
if (!body1.pinned) {
|
|
204
|
+
body1.acceleration.x += (fx + dampingForce * dx / distance) / body1.mass;
|
|
205
|
+
body1.acceleration.y += (fy + dampingForce * dy / distance) / body1.mass;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!body2.pinned) {
|
|
209
|
+
body2.acceleration.x -= (fx + dampingForce * dx / distance) / body2.mass;
|
|
210
|
+
body2.acceleration.y -= (fy + dampingForce * dy / distance) / body2.mass;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Resolve rod constraint (fixed distance)
|
|
216
|
+
*/
|
|
217
|
+
resolveRod(body1, body2, constraint, distance) {
|
|
218
|
+
const dx = body2.position.x - body1.position.x;
|
|
219
|
+
const dy = body2.position.y - body1.position.y;
|
|
220
|
+
|
|
221
|
+
if (distance === 0) return;
|
|
222
|
+
|
|
223
|
+
const delta = (distance - constraint.restLength) / distance;
|
|
224
|
+
|
|
225
|
+
if (!body1.pinned) {
|
|
226
|
+
body1.position.x += (delta * dx) / 2;
|
|
227
|
+
body1.position.y += (delta * dy) / 2;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!body2.pinned) {
|
|
231
|
+
body2.position.x -= (delta * dx) / 2;
|
|
232
|
+
body2.position.y -= (delta * dy) / 2;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Resolve distance constraint
|
|
238
|
+
*/
|
|
239
|
+
resolveDistance(body1, body2, constraint, distance) {
|
|
240
|
+
const targetDistance = constraint.restLength;
|
|
241
|
+
|
|
242
|
+
if (distance > targetDistance) {
|
|
243
|
+
const dx = body2.position.x - body1.position.x;
|
|
244
|
+
const dy = body2.position.y - body1.position.y;
|
|
245
|
+
const delta = (distance - targetDistance) / distance;
|
|
246
|
+
|
|
247
|
+
if (!body1.pinned) {
|
|
248
|
+
body1.position.x += (delta * dx) / 2;
|
|
249
|
+
body1.position.y += (delta * dy) / 2;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!body2.pinned) {
|
|
253
|
+
body2.position.x -= (delta * dx) / 2;
|
|
254
|
+
body2.position.y -= (delta * dy) / 2;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get body state
|
|
261
|
+
*/
|
|
262
|
+
getBodyState(id) {
|
|
263
|
+
return this.bodies.get(id);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Simulate multiple steps at once
|
|
268
|
+
*/
|
|
269
|
+
simulate(steps = 60) {
|
|
270
|
+
for (let i = 0; i < steps; i++) {
|
|
271
|
+
this.step();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Convert physics state to CSS transforms
|
|
277
|
+
*/
|
|
278
|
+
toCSS(bodyId) {
|
|
279
|
+
const body = this.bodies.get(bodyId);
|
|
280
|
+
if (!body) return '';
|
|
281
|
+
|
|
282
|
+
return `transform: translate3d(${body.position.x.toFixed(2)}px, ${body.position.y.toFixed(2)}px, 0);`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
module.exports = PhysicsEngine;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STYL++ Transformer
|
|
3
|
+
* Transforms the AST to handle:
|
|
4
|
+
* - Variable resolution
|
|
5
|
+
* - Math operations
|
|
6
|
+
* - Loop expansion
|
|
7
|
+
* - Conditional processing
|
|
8
|
+
* - Function expansion
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
class StylppTransformer {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
this.variables = {};
|
|
15
|
+
this.errors = [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Transform AST
|
|
20
|
+
* @param {Object} ast - Original AST
|
|
21
|
+
* @returns {Object} Transformed AST
|
|
22
|
+
*/
|
|
23
|
+
transform(ast) {
|
|
24
|
+
// First pass: collect all variables
|
|
25
|
+
this.variables = { ...ast.variables };
|
|
26
|
+
|
|
27
|
+
// Second pass: transform rules
|
|
28
|
+
const transformedRules = ast.rules.map(rule => this.transformRule(rule));
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
type: 'stylesheet',
|
|
32
|
+
rules: transformedRules,
|
|
33
|
+
variables: this.variables
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Transform a single rule
|
|
39
|
+
* @param {Object} rule - Rule object
|
|
40
|
+
* @returns {Object} Transformed rule
|
|
41
|
+
*/
|
|
42
|
+
transformRule(rule) {
|
|
43
|
+
const transformed = {
|
|
44
|
+
type: rule.type,
|
|
45
|
+
selector: rule.selector,
|
|
46
|
+
declarations: [],
|
|
47
|
+
rules: []
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Transform declarations
|
|
51
|
+
if (rule.declarations) {
|
|
52
|
+
rule.declarations.forEach(decl => {
|
|
53
|
+
const transformedDecl = this.transformDeclaration(decl);
|
|
54
|
+
if (Array.isArray(transformedDecl)) {
|
|
55
|
+
transformed.declarations.push(...transformedDecl);
|
|
56
|
+
} else {
|
|
57
|
+
transformed.declarations.push(transformedDecl);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Transform nested rules
|
|
63
|
+
if (rule.rules) {
|
|
64
|
+
rule.rules.forEach(nestedRule => {
|
|
65
|
+
const transformed = this.transformRule(nestedRule);
|
|
66
|
+
if (Array.isArray(transformed)) {
|
|
67
|
+
transformed.forEach(r => rule.rules.push(r));
|
|
68
|
+
} else {
|
|
69
|
+
rule.rules.push(transformed);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
transformed.rules = rule.rules;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return transformed;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Transform a declaration (property-value pair)
|
|
80
|
+
* @param {Object} decl - Declaration object
|
|
81
|
+
* @returns {Object|Array} Transformed declaration(s)
|
|
82
|
+
*/
|
|
83
|
+
transformDeclaration(decl) {
|
|
84
|
+
let value = decl.value;
|
|
85
|
+
|
|
86
|
+
// Resolve variables
|
|
87
|
+
value = this.resolveVariables(value);
|
|
88
|
+
|
|
89
|
+
// Handle math operations
|
|
90
|
+
if (this.hasMathOperation(value)) {
|
|
91
|
+
value = this.processMathOperation(value);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Handle responsive values
|
|
95
|
+
if (this.isResponsiveValue(value)) {
|
|
96
|
+
value = this.processResponsiveValue(value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Handle color operations
|
|
100
|
+
if (this.isColorOperation(value)) {
|
|
101
|
+
value = this.processColorOperation(value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
type: 'declaration',
|
|
106
|
+
property: decl.property,
|
|
107
|
+
value: value
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Resolve variables in value (e.g., var(primary) -> #007bff)
|
|
113
|
+
* @param {string} value - Value string
|
|
114
|
+
* @returns {string} Resolved value
|
|
115
|
+
*/
|
|
116
|
+
resolveVariables(value) {
|
|
117
|
+
return value.replace(/var\((\w+)\)/g, (match, varName) => {
|
|
118
|
+
return this.variables[varName] || match;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if value contains math operations
|
|
124
|
+
* @param {string} value - Value string
|
|
125
|
+
* @returns {boolean}
|
|
126
|
+
*/
|
|
127
|
+
hasMathOperation(value) {
|
|
128
|
+
return /[\+\-\*\/\^]/.test(value) && /\d/.test(value);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Process math operations: 100% - 40px -> calc(100% - 40px)
|
|
133
|
+
* @param {string} value - Value with math
|
|
134
|
+
* @returns {string} CSS calc expression
|
|
135
|
+
*/
|
|
136
|
+
processMathOperation(value) {
|
|
137
|
+
// Already has calc? Return as-is
|
|
138
|
+
if (value.includes('calc(')) return value;
|
|
139
|
+
|
|
140
|
+
// Check for mathematical expressions
|
|
141
|
+
const mathPattern = /^[\d\w\s\+\-\*\/\%\(\)\.]+$/;
|
|
142
|
+
if (mathPattern.test(value.replace(/px|em|rem|%|vh|vw|ex|ch|deg|s|ms/g, ''))) {
|
|
143
|
+
return `calc(${value})`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Check if value is a responsive function
|
|
151
|
+
* @param {string} value - Value string
|
|
152
|
+
* @returns {boolean}
|
|
153
|
+
*/
|
|
154
|
+
isResponsiveValue(value) {
|
|
155
|
+
return /responsive\(|fluid\(/.test(value);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Process responsive value: responsive(300px, 800px) -> clamp(...)
|
|
160
|
+
* @param {string} value - Value string
|
|
161
|
+
* @returns {string} Clamp expression
|
|
162
|
+
*/
|
|
163
|
+
processResponsiveValue(value) {
|
|
164
|
+
// responsive(300px, 800px) -> clamp(300px, 50vw + 100px, 800px)
|
|
165
|
+
const match = value.match(/responsive\(([^,]+),\s*([^)]+)\)/);
|
|
166
|
+
if (match) {
|
|
167
|
+
const min = match[1].trim();
|
|
168
|
+
const max = match[2].trim();
|
|
169
|
+
const mid = `${(parseInt(min) + parseInt(max)) / 4}px`;
|
|
170
|
+
return `clamp(${min}, 50vw + ${mid}, ${max})`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// fluid(300px, 800px)
|
|
174
|
+
const fluidMatch = value.match(/fluid\(([^,]+),\s*([^)]+)\)/);
|
|
175
|
+
if (fluidMatch) {
|
|
176
|
+
const min = fluidMatch[1].trim();
|
|
177
|
+
const max = fluidMatch[2].trim();
|
|
178
|
+
return `clamp(${min}, 5vw, ${max})`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Check if value uses color operations
|
|
186
|
+
* @param {string} value - Value string
|
|
187
|
+
* @returns {boolean}
|
|
188
|
+
*/
|
|
189
|
+
isColorOperation(value) {
|
|
190
|
+
return /darken\(|lighten\(|mix\(/.test(value);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Process color operations
|
|
195
|
+
* @param {string} value - Value string
|
|
196
|
+
* @returns {string} Processed color
|
|
197
|
+
*/
|
|
198
|
+
processColorOperation(value) {
|
|
199
|
+
// darken(#007bff, 20%) - simplified implementation
|
|
200
|
+
const darkenMatch = value.match(/darken\(([^,]+),\s*(\d+)%\)/);
|
|
201
|
+
if (darkenMatch) {
|
|
202
|
+
const color = darkenMatch[1].trim();
|
|
203
|
+
const amount = parseInt(darkenMatch[2]);
|
|
204
|
+
// Simple approximation - in real implementation would use color library
|
|
205
|
+
return `${color}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Expand for loops
|
|
213
|
+
* @param {Object} rule - Rule with for loop
|
|
214
|
+
* @param {number} iterations - Number of iterations
|
|
215
|
+
* @returns {Array} Expanded rules
|
|
216
|
+
*/
|
|
217
|
+
expandForLoop(rule, iterations) {
|
|
218
|
+
const expanded = [];
|
|
219
|
+
for (let i = 0; i < iterations; i++) {
|
|
220
|
+
const expandedRule = { ...rule };
|
|
221
|
+
expandedRule.selector = rule.selector.replace(/{i}/g, i);
|
|
222
|
+
expanded.push(expandedRule);
|
|
223
|
+
}
|
|
224
|
+
return expanded;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = StylppTransformer;
|