qss 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +24 -0
- data/CONTRIBUTING.md +26 -0
- data/MIT-LICENSE +20 -0
- data/QSS-MANIFESTO.md +61 -0
- data/README.md +159 -0
- data/app/assets/javascripts/qss/qss-engine.js +1054 -0
- data/app/assets/stylesheets/qss/_qss_defaults.scss +74 -0
- data/app/assets/stylesheets/qss/qss_core.css +205042 -0
- data/app/assets/stylesheets/qss/qss_core.scss +614 -0
- data/config/grammar.yml +265 -0
- data/config/qss-objects.json +3 -0
- data/exe/qss-core-render-check +104 -0
- data/exe/qss-sync +205 -0
- data/lib/qss/blueprints.rb +106 -0
- data/lib/qss/configuration.rb +82 -0
- data/lib/qss/engine.rb +9 -0
- data/lib/qss/qss_auditor.rb +412 -0
- data/lib/qss/qss_native_auditor.rb +908 -0
- data/lib/qss/version.rb +10 -0
- data/lib/qss.rb +26 -0
- metadata +154 -0
|
@@ -0,0 +1,1054 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QSS Engine - Quantized Spatial Styling Transpiler
|
|
3
|
+
* A portable, lightweight engine for agent-aware spatial styling.
|
|
4
|
+
* Maps intent (x, y, w, h) to deterministic CSS using Quants (qnts).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const QSSEngine = (function() {
|
|
8
|
+
// QSS-GENERATED-BEGIN (exe/qss-sync rewrites everything below, up to the closing marker)
|
|
9
|
+
// GENERATED FROM grammar.yml - DO NOT EDIT
|
|
10
|
+
const BRAND_COLORS = {
|
|
11
|
+
'black': '#000000',
|
|
12
|
+
'white': '#ffffff',
|
|
13
|
+
'transparent': 'transparent',
|
|
14
|
+
'none': 'none',
|
|
15
|
+
'slate-50': '#f8fafc',
|
|
16
|
+
'slate-100': '#f1f5f9',
|
|
17
|
+
'slate-200': '#e2e8f0',
|
|
18
|
+
'slate-300': '#cbd5e1',
|
|
19
|
+
'slate-400': '#94a3b8',
|
|
20
|
+
'slate-500': '#64748b',
|
|
21
|
+
'slate-600': '#475569',
|
|
22
|
+
'slate-700': '#334155',
|
|
23
|
+
'slate-800': '#1e293b',
|
|
24
|
+
'slate-900': '#0f172a',
|
|
25
|
+
'slate-950': '#020617',
|
|
26
|
+
'rose-500': '#f43f5e'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const DEPTHS = {
|
|
30
|
+
'canvas': '0',
|
|
31
|
+
'content': '100',
|
|
32
|
+
'ui': '500',
|
|
33
|
+
'overlay': '900',
|
|
34
|
+
'modal': '1000'
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
let objectRegistry = {};
|
|
38
|
+
|
|
39
|
+
const FONTS = {
|
|
40
|
+
'sans': "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
|
|
41
|
+
'mono': "'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace"
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const DSL_WHITELIST = new Set([
|
|
45
|
+
'absolute',
|
|
46
|
+
'anchored',
|
|
47
|
+
'around',
|
|
48
|
+
'bg',
|
|
49
|
+
'bg-alpha',
|
|
50
|
+
'blend',
|
|
51
|
+
'blur',
|
|
52
|
+
'border',
|
|
53
|
+
'border-alpha',
|
|
54
|
+
'border-b',
|
|
55
|
+
'border-l',
|
|
56
|
+
'border-r',
|
|
57
|
+
'border-t',
|
|
58
|
+
'bright',
|
|
59
|
+
'busy',
|
|
60
|
+
'centered',
|
|
61
|
+
'clickable',
|
|
62
|
+
'contain',
|
|
63
|
+
'contrast',
|
|
64
|
+
'cover',
|
|
65
|
+
'cursor-col-resize',
|
|
66
|
+
'cursor-row-resize',
|
|
67
|
+
'detached',
|
|
68
|
+
'dev-border',
|
|
69
|
+
'evenly',
|
|
70
|
+
'flex',
|
|
71
|
+
'flex-1',
|
|
72
|
+
'flex-auto',
|
|
73
|
+
'flex-col',
|
|
74
|
+
'flex-none',
|
|
75
|
+
'flex-nowrap',
|
|
76
|
+
'flex-row',
|
|
77
|
+
'flex-wrap',
|
|
78
|
+
'floating',
|
|
79
|
+
'font-black',
|
|
80
|
+
'font-bold',
|
|
81
|
+
'font-medium',
|
|
82
|
+
'font-mono',
|
|
83
|
+
'font-sans',
|
|
84
|
+
'foreign',
|
|
85
|
+
'ghost',
|
|
86
|
+
'grayscale',
|
|
87
|
+
'grid',
|
|
88
|
+
'group',
|
|
89
|
+
'h-end',
|
|
90
|
+
'h-f',
|
|
91
|
+
'h-full',
|
|
92
|
+
'h-mid',
|
|
93
|
+
'h-start',
|
|
94
|
+
'horizontal',
|
|
95
|
+
'hue',
|
|
96
|
+
'icon-size',
|
|
97
|
+
'invert',
|
|
98
|
+
'invisible',
|
|
99
|
+
'italic',
|
|
100
|
+
'items-center',
|
|
101
|
+
'justify-between',
|
|
102
|
+
'justify-center',
|
|
103
|
+
'leading',
|
|
104
|
+
'lowercase',
|
|
105
|
+
'nowrap',
|
|
106
|
+
'opacity',
|
|
107
|
+
'outline-none',
|
|
108
|
+
'pos',
|
|
109
|
+
'pos-b',
|
|
110
|
+
'pos-r',
|
|
111
|
+
'pos-x',
|
|
112
|
+
'pos-y',
|
|
113
|
+
'qss',
|
|
114
|
+
'qss-content-driven',
|
|
115
|
+
'qss-dampen-scaling',
|
|
116
|
+
'qss-ergonomic-override',
|
|
117
|
+
'qss-legibility-override',
|
|
118
|
+
'qss-payload-override',
|
|
119
|
+
'relative',
|
|
120
|
+
'resize',
|
|
121
|
+
'ring',
|
|
122
|
+
'rounded',
|
|
123
|
+
'rounded-full',
|
|
124
|
+
'saturate',
|
|
125
|
+
'scroll',
|
|
126
|
+
'scroll-smooth',
|
|
127
|
+
'sepia',
|
|
128
|
+
'shadow',
|
|
129
|
+
'smooth',
|
|
130
|
+
'spaced',
|
|
131
|
+
'stuck',
|
|
132
|
+
'table-fixed',
|
|
133
|
+
'text-alpha',
|
|
134
|
+
'text-size',
|
|
135
|
+
'tracking',
|
|
136
|
+
'tracking-tighter',
|
|
137
|
+
'tracking-wide',
|
|
138
|
+
'tracking-wider',
|
|
139
|
+
'tracking-widest',
|
|
140
|
+
'transition-all',
|
|
141
|
+
'transition-colors',
|
|
142
|
+
'transition-none',
|
|
143
|
+
'truncate',
|
|
144
|
+
'underline',
|
|
145
|
+
'unselectable',
|
|
146
|
+
'uppercase',
|
|
147
|
+
'v-end',
|
|
148
|
+
'v-mid',
|
|
149
|
+
'v-start',
|
|
150
|
+
'vertical',
|
|
151
|
+
'visible',
|
|
152
|
+
'w-f',
|
|
153
|
+
'w-full'
|
|
154
|
+
]);
|
|
155
|
+
// QSS-GENERATED-END
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
// Constants
|
|
283
|
+
let qnt = 1;
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
let anchorSelector = '#qss-canvas';
|
|
289
|
+
let pulseInterval = null;
|
|
290
|
+
let observer = null;
|
|
291
|
+
let styleTag = null;
|
|
292
|
+
const knownAdjectives = new Set();
|
|
293
|
+
let pulseRequested = false;
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Performs a global scan for QSS adjectives in the DOM.
|
|
297
|
+
*/
|
|
298
|
+
function scanAndTranspile() {
|
|
299
|
+
const elements = document.querySelectorAll('.qss, [class*="["]');
|
|
300
|
+
elements.forEach(el => {
|
|
301
|
+
if (el.closest('.foreign')) return;
|
|
302
|
+
el.classList.forEach(cls => {
|
|
303
|
+
// Extract prefix if present (e.g., hover:opacity[1000])
|
|
304
|
+
const prefixMatch = cls.match(/^([a-z-]+):(.+)$/);
|
|
305
|
+
const baseClass = prefixMatch ? prefixMatch[2] : cls;
|
|
306
|
+
|
|
307
|
+
const isQSS = (baseClass.includes('[') && baseClass.includes(']')) ||
|
|
308
|
+
DSL_WHITELIST.has(baseClass) ||
|
|
309
|
+
/^(text|bg|border|font)-/.test(baseClass) ||
|
|
310
|
+
/^[A-Z]/.test(baseClass); // Matches Composite Objects
|
|
311
|
+
|
|
312
|
+
if (isQSS && !knownAdjectives.has(cls)) {
|
|
313
|
+
processNewClass(cls);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* The Layout Heartbeat.
|
|
321
|
+
* Recalculates --qnt and exports Noun coordinates.
|
|
322
|
+
*/
|
|
323
|
+
function updatePulse() {
|
|
324
|
+
if (pulseRequested) return;
|
|
325
|
+
pulseRequested = true;
|
|
326
|
+
|
|
327
|
+
requestAnimationFrame(() => {
|
|
328
|
+
pulseRequested = false;
|
|
329
|
+
|
|
330
|
+
// 1. Sync with JIT first - inject styles into DOM
|
|
331
|
+
scanAndTranspile();
|
|
332
|
+
|
|
333
|
+
// 2. Schedule measurement pass AFTER styles have been applied by the browser
|
|
334
|
+
requestAnimationFrame(() => {
|
|
335
|
+
measureAndExport();
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Measures Nouns and exports CSS variables.
|
|
342
|
+
*/
|
|
343
|
+
function measureAndExport() {
|
|
344
|
+
const anchor = document.querySelector(anchorSelector);
|
|
345
|
+
if (!anchor) return;
|
|
346
|
+
|
|
347
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
348
|
+
|
|
349
|
+
// The unit (--qnt) is computed by CSS alone (0.1cqw on the canvas's children, see qss_core.scss); read it
|
|
350
|
+
// back for the noun export instead of setting it. Ultra-wide dampening is pending a CSS-only decision.
|
|
351
|
+
const probe = anchor.firstElementChild || anchor;
|
|
352
|
+
const unit = parseFloat(getComputedStyle(probe).getPropertyValue('--qnt')) || anchorRect.width / 1000;
|
|
353
|
+
|
|
354
|
+
// Export Boundaries
|
|
355
|
+
const nouns = anchor.querySelectorAll('[id]');
|
|
356
|
+
nouns.forEach(noun => {
|
|
357
|
+
const rect = noun.getBoundingClientRect();
|
|
358
|
+
const l = (rect.left - anchorRect.left) / unit;
|
|
359
|
+
const t = (rect.top - anchorRect.top) / unit;
|
|
360
|
+
const w = rect.width / unit;
|
|
361
|
+
const h = rect.height / unit;
|
|
362
|
+
const r = (anchorRect.right - rect.right) / unit;
|
|
363
|
+
const b = (anchorRect.bottom - rect.bottom) / unit;
|
|
364
|
+
|
|
365
|
+
const prefix = `--noun-${noun.id}`;
|
|
366
|
+
document.documentElement.style.setProperty(`${prefix}-x1`, `${l}`);
|
|
367
|
+
document.documentElement.style.setProperty(`${prefix}-x2`, `${l + w}`);
|
|
368
|
+
document.documentElement.style.setProperty(`${prefix}-y1`, `${t}`);
|
|
369
|
+
document.documentElement.style.setProperty(`${prefix}-y2`, `${t + h}`);
|
|
370
|
+
document.documentElement.style.setProperty(`${prefix}-r1`, `${r}`);
|
|
371
|
+
document.documentElement.style.setProperty(`${prefix}-r2`, `${r + w}`);
|
|
372
|
+
document.documentElement.style.setProperty(`${prefix}-b1`, `${b}`);
|
|
373
|
+
document.documentElement.style.setProperty(`${prefix}-b2`, `${b + h}`);
|
|
374
|
+
document.documentElement.style.setProperty(`${prefix}-w`, `${w}`);
|
|
375
|
+
document.documentElement.style.setProperty(`${prefix}-h`, `${h}`);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Escapes special characters for CSS selector compatibility.
|
|
381
|
+
* @param {string} cls
|
|
382
|
+
* @returns {string} escapedCls
|
|
383
|
+
*/
|
|
384
|
+
function escapeSelector(cls) {
|
|
385
|
+
return cls.replace(/\[/g, '\\[').replace(/\]/g, '\\]')
|
|
386
|
+
.replace(/\|/g, '\\|')
|
|
387
|
+
.replace(/\./g, '\\.')
|
|
388
|
+
.replace(/\^/g, '\\^')
|
|
389
|
+
.replace(/:/g, '\\:')
|
|
390
|
+
.replace(/,/g, '\\,')
|
|
391
|
+
.replace(/\(/g, '\\(')
|
|
392
|
+
.replace(/\)/g, '\\)')
|
|
393
|
+
.replace(/#/g, '\\#')
|
|
394
|
+
.replace(/%/g, '\\%');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Initializes the Living Observer.
|
|
399
|
+
*/
|
|
400
|
+
function startObserver() {
|
|
401
|
+
if (observer) return;
|
|
402
|
+
|
|
403
|
+
styleTag = document.createElement('style');
|
|
404
|
+
styleTag.id = 'qss-jit-styles';
|
|
405
|
+
document.head.appendChild(styleTag);
|
|
406
|
+
|
|
407
|
+
observer = new MutationObserver((mutations) => {
|
|
408
|
+
const isPurelyForeign = mutations.every(mutation => {
|
|
409
|
+
const el = mutation.target.nodeType === 1 ? mutation.target : mutation.target.parentElement;
|
|
410
|
+
return el && el.closest('.foreign');
|
|
411
|
+
});
|
|
412
|
+
if (isPurelyForeign) return;
|
|
413
|
+
updatePulse();
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
observer.observe(document.body, {
|
|
417
|
+
attributes: true,
|
|
418
|
+
childList: true,
|
|
419
|
+
subtree: true,
|
|
420
|
+
attributeFilter: ['class']
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
updatePulse();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Transpiles a single QSS class and injects it into the JIT style tag.
|
|
428
|
+
*/
|
|
429
|
+
function processNewClass(cls) {
|
|
430
|
+
// 0. Extract Prefix
|
|
431
|
+
let baseClass = cls;
|
|
432
|
+
let prefix = null;
|
|
433
|
+
|
|
434
|
+
const prefixMatch = baseClass.match(/^([a-z-]+):(.+)$/);
|
|
435
|
+
if (prefixMatch) {
|
|
436
|
+
prefix = prefixMatch[1];
|
|
437
|
+
baseClass = prefixMatch[2];
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
let property, value;
|
|
441
|
+
|
|
442
|
+
// 1. Check for Composite Noun Type (PascalCase, e.g., Button[200|50])
|
|
443
|
+
const objectMatch = baseClass.match(/^(?:qss-)?([A-Z][a-zA-Z0-9]+)(?:\[(.+)\])?$/);
|
|
444
|
+
if (objectMatch && objectRegistry[objectMatch[1]]) {
|
|
445
|
+
expandObject(cls, objectMatch[1], objectMatch[2], prefix);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// hover/active/focus/focus-within and any group-* state are now handled at
|
|
450
|
+
// compile time (qss_core.scss's qss-state-boost/qss-group-boost mixins --
|
|
451
|
+
// architecture-decisions-2026-09-16.md section 8/9). Skip them here instead
|
|
452
|
+
// of injecting a redundant rule -- or, for focus-within/group-*, which the
|
|
453
|
+
// allowed-suffix list below never actually included, an incorrect always-on
|
|
454
|
+
// one (real pre-existing bug, fixed as a side effect of this removal).
|
|
455
|
+
// "visited" is deliberately left on the runtime path below -- A3 does not
|
|
456
|
+
// cover it, and no real usage exists today, but removing working support
|
|
457
|
+
// for a state nothing else replaces would be a real regression, not cleanup.
|
|
458
|
+
if (prefix && (['hover', 'active', 'focus', 'focus-within'].includes(prefix) || prefix.startsWith('group-'))) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// 2. Check for Atomic Adjective (lowercase with brackets, e.g., pos-x[10])
|
|
463
|
+
const atomicMatch = baseClass.match(/^(?:qss-)?([a-z-]+)\[(.+)\]$/);
|
|
464
|
+
|
|
465
|
+
if (atomicMatch) {
|
|
466
|
+
property = atomicMatch[1];
|
|
467
|
+
value = atomicMatch[2].replace(/_/g, ' '); // Replace underscores with spaces for CSS values
|
|
468
|
+
} else {
|
|
469
|
+
// 3. Check for Standard Adjective (e.g., flex, vertical, centered, text-indigo-500)
|
|
470
|
+
const standardMatch = baseClass.match(/^(?:qss-)?([a-z-]+)$/);
|
|
471
|
+
if (!standardMatch) return;
|
|
472
|
+
|
|
473
|
+
const raw = standardMatch[1];
|
|
474
|
+
if (raw.startsWith('text-')) {
|
|
475
|
+
property = 'text';
|
|
476
|
+
value = raw.replace('text-', '');
|
|
477
|
+
} else if (raw.startsWith('bg-')) {
|
|
478
|
+
property = 'bg';
|
|
479
|
+
value = raw.replace('bg-', '');
|
|
480
|
+
} else if (raw.startsWith('border-')) {
|
|
481
|
+
property = 'border-color';
|
|
482
|
+
value = raw.replace('border-', '');
|
|
483
|
+
} else if (raw.startsWith('font-')) {
|
|
484
|
+
property = 'font';
|
|
485
|
+
value = raw.replace('font-', '');
|
|
486
|
+
} else {
|
|
487
|
+
property = raw;
|
|
488
|
+
value = '';
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
let cssRule = mapProperty(property, value);
|
|
493
|
+
|
|
494
|
+
if (cssRule) {
|
|
495
|
+
const escapedCls = escapeSelector(cls);
|
|
496
|
+
let selector = `.${escapedCls}`;
|
|
497
|
+
|
|
498
|
+
if (prefix === 'visited') {
|
|
499
|
+
selector = `${selector}:${prefix}`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const fullRule = `${selector}, ${anchorSelector}${anchorSelector} ${selector} { ${cssRule} }\n`;
|
|
503
|
+
styleTag.appendChild(document.createTextNode(fullRule));
|
|
504
|
+
knownAdjectives.add(cls);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Expands a Composite Adjective using its Blueprint and optional Parameters.
|
|
510
|
+
*/
|
|
511
|
+
function expandObject(cls, typeName, paramString, prefix) {
|
|
512
|
+
const blueprint = objectRegistry[typeName];
|
|
513
|
+
if (!blueprint) return;
|
|
514
|
+
|
|
515
|
+
const params = paramString ? paramString.split('|') : [];
|
|
516
|
+
const paramMap = blueprint.params || [];
|
|
517
|
+
|
|
518
|
+
// Build base declarations from adjectives
|
|
519
|
+
let cssRules = (blueprint.adjectives || []).map(adj => {
|
|
520
|
+
const match = adj.match(/^([a-z-]+)(?:\[([^\]]+)\])?$/);
|
|
521
|
+
if (!match) return null;
|
|
522
|
+
return mapProperty(match[1], match[2] || '');
|
|
523
|
+
}).filter(r => r);
|
|
524
|
+
|
|
525
|
+
// Apply Intrinsic Dimensions (Defaults)
|
|
526
|
+
const dimensions = { ...blueprint.intrinsic };
|
|
527
|
+
|
|
528
|
+
// Apply Parametric Overrides with Clamping
|
|
529
|
+
params.forEach((val, idx) => {
|
|
530
|
+
const key = paramMap[idx];
|
|
531
|
+
if (key && val) {
|
|
532
|
+
let finalVal = val;
|
|
533
|
+
|
|
534
|
+
// Clamping Logic: Ensure parameters stay within min/max bounds
|
|
535
|
+
if (!isNaN(parseFloat(val))) {
|
|
536
|
+
const numVal = parseFloat(val);
|
|
537
|
+
if (blueprint[`min-${key}`] !== undefined) {
|
|
538
|
+
finalVal = Math.max(parseFloat(blueprint[`min-${key}`]), numVal);
|
|
539
|
+
}
|
|
540
|
+
if (blueprint[`max-${key}`] !== undefined) {
|
|
541
|
+
finalVal = Math.min(parseFloat(blueprint[`max-${key}`]), finalVal);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
dimensions[key] = finalVal;
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// Convert Dimensions to CSS (Support min/max properties)
|
|
550
|
+
['w', 'h'].forEach(dim => {
|
|
551
|
+
if (dimensions[dim]) cssRules.push(mapProperty(dim, dimensions[dim]));
|
|
552
|
+
if (blueprint[`min-${dim}`]) cssRules.push(mapProperty(`min-${dim}`, blueprint[`min-${dim}`]));
|
|
553
|
+
if (blueprint[`max-${dim}`]) cssRules.push(mapProperty(`max-${dim}`, blueprint[`max-${dim}`]));
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
const escapedCls = escapeSelector(cls);
|
|
557
|
+
let selector = `.${escapedCls}`;
|
|
558
|
+
|
|
559
|
+
if (prefix && ['hover', 'active', 'focus', 'visited'].includes(prefix)) {
|
|
560
|
+
selector = `${selector}:${prefix}`;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const fullRule = `${selector}, ${anchorSelector}${anchorSelector} ${selector} {\n ${cssRules.join(';\n ')};\n}\n`;
|
|
564
|
+
|
|
565
|
+
styleTag.appendChild(document.createTextNode(fullRule));
|
|
566
|
+
knownAdjectives.add(cls);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Initializes the Unit Pulse.
|
|
571
|
+
*/
|
|
572
|
+
function startPulse() {
|
|
573
|
+
if (pulseInterval) return;
|
|
574
|
+
window.addEventListener('resize', updatePulse);
|
|
575
|
+
updatePulse();
|
|
576
|
+
pulseInterval = true;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Configures the engine anchor
|
|
581
|
+
*/
|
|
582
|
+
function setAnchor(selector) {
|
|
583
|
+
anchorSelector = selector;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Registers a reusable spatial object definition
|
|
588
|
+
*/
|
|
589
|
+
function registerObject(name, definition) {
|
|
590
|
+
objectRegistry[name] = definition;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Calculates a grid of coordinates.
|
|
595
|
+
*/
|
|
596
|
+
function calculateGrid(count, cols, itemW, itemH, gapX = 0, gapY = 0) {
|
|
597
|
+
const items = [];
|
|
598
|
+
for (let i = 0; i < count; i++) {
|
|
599
|
+
const row = Math.floor(i / cols);
|
|
600
|
+
const col = i % cols;
|
|
601
|
+
items.push({
|
|
602
|
+
index: i,
|
|
603
|
+
x: col * (itemW + gapX),
|
|
604
|
+
y: row * (itemH + gapY),
|
|
605
|
+
w: itemW,
|
|
606
|
+
h: itemH
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
return items;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Transpiles a QSS string to valid CSS.
|
|
614
|
+
* @param {string} qssText
|
|
615
|
+
* @returns {string} cssOutput
|
|
616
|
+
*/
|
|
617
|
+
function transpile(qssText) {
|
|
618
|
+
if (!qssText) return '';
|
|
619
|
+
|
|
620
|
+
// Global Reset / Stage Styles (Applied automatically)
|
|
621
|
+
const header = `/* Generated by QSS Engine v${this.version} */\n\n`;
|
|
622
|
+
|
|
623
|
+
// Basic parser logic: find selector { block }
|
|
624
|
+
// Supports prefixes like hover:, active:, sm:, etc.
|
|
625
|
+
const rules = [];
|
|
626
|
+
const regex = /(([a-z-]+:)?([#\.][\w-]+))\s*\{([^}]+)\}/g;
|
|
627
|
+
let match;
|
|
628
|
+
|
|
629
|
+
while ((match = regex.exec(qssText)) !== null) {
|
|
630
|
+
const fullSelector = match[1];
|
|
631
|
+
const prefix = match[2] ? match[2].slice(0, -1) : null;
|
|
632
|
+
const baseSelector = match[3];
|
|
633
|
+
const declarations = match[4].split(';').map(d => d.trim()).filter(d => d);
|
|
634
|
+
|
|
635
|
+
const cssProps = declarations.map(decl => {
|
|
636
|
+
const [key, val] = decl.split(':').map(s => s.trim());
|
|
637
|
+
return mapProperty(key, val);
|
|
638
|
+
}).filter(p => p);
|
|
639
|
+
|
|
640
|
+
// Handle Pseudo-states and Breakpoints
|
|
641
|
+
let outputSelector = baseSelector.startsWith('#') ? `${baseSelector}.qss` : baseSelector;
|
|
642
|
+
|
|
643
|
+
// Apply anchor specificity
|
|
644
|
+
if (anchorSelector) {
|
|
645
|
+
outputSelector = `${anchorSelector} ${outputSelector}`;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (prefix) {
|
|
649
|
+
if (['hover', 'active', 'focus', 'visited'].includes(prefix)) {
|
|
650
|
+
outputSelector = `${outputSelector}:${prefix}`;
|
|
651
|
+
} else {
|
|
652
|
+
// It's a breakpoint - we'll need to wrap this in a media query eventually
|
|
653
|
+
// For now, let's just generate the class name
|
|
654
|
+
outputSelector = `.${prefix}\\:${outputSelector.slice(1)}`;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
rules.push(`${outputSelector} {\n ${cssProps.join(';\n ')};\n}`);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return header + rules.join('\n\n');
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Maps QSS shorthand to CSS logic.
|
|
666
|
+
* Supports '#' references for Reference Anchoring.
|
|
667
|
+
*/
|
|
668
|
+
function mapProperty(key, val) {
|
|
669
|
+
// --- Relational Midpoint Resolution ---
|
|
670
|
+
// Converts pos-x[^mid:explorer:inspector] -> left: calc((var(--noun-explorer-x2) + var(--noun-inspector-x1)) / 2 * var(--qnt))
|
|
671
|
+
const midMatch = val && val.match(/^\^mid\[([^|]+)\|([^\]]+)\]$/);
|
|
672
|
+
if (midMatch) {
|
|
673
|
+
const n1 = midMatch[1];
|
|
674
|
+
const n2 = midMatch[2];
|
|
675
|
+
const v1 = `--noun-${n1}`;
|
|
676
|
+
const v2 = `--noun-${n2}`;
|
|
677
|
+
|
|
678
|
+
switch(key) {
|
|
679
|
+
case 'x': case 'pos-x':
|
|
680
|
+
return `left: calc((var(${v1}-x2) + var(${v2}-x1)) / 2 * var(--qnt))`;
|
|
681
|
+
case 'r': case 'pos-r':
|
|
682
|
+
// From right, it's 1000 - Midpoint
|
|
683
|
+
return `right: calc((1000 - (var(${v1}-x2) + var(${v2}-x1)) / 2) * var(--qnt))`;
|
|
684
|
+
default:
|
|
685
|
+
return '';
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// --- Reference Anchor Resolution ---
|
|
690
|
+
// Converts pos-y[^header] -> top: calc(var(--noun-header-y2) * var(--qnt))
|
|
691
|
+
if (val && val.startsWith('^')) {
|
|
692
|
+
const nounId = val.slice(1);
|
|
693
|
+
const varPrefix = `--noun-${nounId}`;
|
|
694
|
+
|
|
695
|
+
switch(key) {
|
|
696
|
+
case 'x': case 'pos-x':
|
|
697
|
+
return `left: calc(var(${varPrefix}-x2) * var(--qnt))`;
|
|
698
|
+
case 'y': case 'pos-y':
|
|
699
|
+
return `top: calc(var(${varPrefix}-y2) * var(--qnt))`;
|
|
700
|
+
case 'r': case 'pos-r':
|
|
701
|
+
return `right: calc(var(${varPrefix}-r2) * var(--qnt))`;
|
|
702
|
+
case 'b': case 'pos-b':
|
|
703
|
+
return `bottom: calc(var(${varPrefix}-b2) * var(--qnt))`;
|
|
704
|
+
case 'w':
|
|
705
|
+
return `width: calc(var(${varPrefix}-w) * var(--qnt))`;
|
|
706
|
+
case 'h':
|
|
707
|
+
return `height: calc(var(${varPrefix}-h) * var(--qnt))`;
|
|
708
|
+
default:
|
|
709
|
+
return `${key}: var(${varPrefix}-${key})`;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
switch(key) {
|
|
714
|
+
// Positioning DSL
|
|
715
|
+
case 'detached':
|
|
716
|
+
case 'absolute':
|
|
717
|
+
return 'position: absolute';
|
|
718
|
+
case 'anchored':
|
|
719
|
+
case 'relative':
|
|
720
|
+
return 'position: relative';
|
|
721
|
+
case 'floating':
|
|
722
|
+
case 'fixed':
|
|
723
|
+
return 'position: fixed';
|
|
724
|
+
case 'stuck':
|
|
725
|
+
case 'sticky':
|
|
726
|
+
return 'position: sticky';
|
|
727
|
+
|
|
728
|
+
// Spatial Coordinates
|
|
729
|
+
case 'x':
|
|
730
|
+
case 'pos-x':
|
|
731
|
+
if (val === 'center') return 'left: 50%; transform: translateX(-50%)';
|
|
732
|
+
if (val.startsWith('right-')) return `left: auto; right: calc(${val.split('-')[1]} * var(--qnt))`;
|
|
733
|
+
return `left: calc(${val} * var(--qnt))`;
|
|
734
|
+
|
|
735
|
+
case 'y':
|
|
736
|
+
case 'pos-y':
|
|
737
|
+
if (val === 'center') return 'top: 50%; transform: translateY(-50%)';
|
|
738
|
+
if (val.startsWith('bottom-')) return `top: auto; bottom: calc(${val.split('-')[1]} * var(--qnt))`;
|
|
739
|
+
return `top: calc(${val} * var(--qnt))`;
|
|
740
|
+
|
|
741
|
+
case 'pos-r':
|
|
742
|
+
case 'r':
|
|
743
|
+
return `right: calc(${val} * var(--qnt))`;
|
|
744
|
+
case 'pos-b':
|
|
745
|
+
case 'b':
|
|
746
|
+
return `bottom: calc(${val} * var(--qnt))`;
|
|
747
|
+
|
|
748
|
+
// Dimensions
|
|
749
|
+
case 'w':
|
|
750
|
+
return `width: calc(${val} * var(--qnt))`;
|
|
751
|
+
case 'h':
|
|
752
|
+
return `height: calc(${val} * var(--qnt))`;
|
|
753
|
+
case 'w-pct':
|
|
754
|
+
return `width: ${parseFloat(val) / 10}%`;
|
|
755
|
+
case 'h-pct':
|
|
756
|
+
return `height: ${parseFloat(val) / 10}%`;
|
|
757
|
+
case 'w-f':
|
|
758
|
+
case 'w-full':
|
|
759
|
+
return 'width: 100%';
|
|
760
|
+
case 'h-f':
|
|
761
|
+
case 'h-full':
|
|
762
|
+
return 'height: 100%';
|
|
763
|
+
|
|
764
|
+
// Logical Positioning
|
|
765
|
+
case 'pos':
|
|
766
|
+
if (val === 'center') return 'left: 50%; top: 50%; transform: translate(-50%, -50%)';
|
|
767
|
+
return '';
|
|
768
|
+
|
|
769
|
+
// Category I: Layout & Spacing
|
|
770
|
+
case 'w-full':
|
|
771
|
+
case 'w-f':
|
|
772
|
+
return 'width: 100%';
|
|
773
|
+
case 'h-full':
|
|
774
|
+
case 'h-f':
|
|
775
|
+
return 'height: 100%';
|
|
776
|
+
case 'w':
|
|
777
|
+
if (val === '1000' || val === 'inherit' || val === 'full') return 'width: 100%';
|
|
778
|
+
return `width: calc(${val} * var(--qnt))`;
|
|
779
|
+
case 'h':
|
|
780
|
+
if (val === '1000' || val === 'inherit' || val === 'full') return 'height: 100%';
|
|
781
|
+
return `height: calc(${val} * var(--qnt))`;
|
|
782
|
+
case 'min-w':
|
|
783
|
+
return `min-width: calc(${val} * var(--qnt))`;
|
|
784
|
+
case 'max-w':
|
|
785
|
+
return `max-width: calc(${val} * var(--qnt))`;
|
|
786
|
+
case 'min-h':
|
|
787
|
+
return `min-height: calc(${val} * var(--qnt))`;
|
|
788
|
+
case 'max-h':
|
|
789
|
+
return `max-height: calc(${val} * var(--qnt))`;
|
|
790
|
+
case 'margin':
|
|
791
|
+
case 'm':
|
|
792
|
+
return `margin: calc(${val} * var(--qnt))`;
|
|
793
|
+
case 'mt':
|
|
794
|
+
return `margin-top: calc(${val} * var(--qnt))`;
|
|
795
|
+
case 'mb':
|
|
796
|
+
return `margin-bottom: calc(${val} * var(--qnt))`;
|
|
797
|
+
case 'ml':
|
|
798
|
+
return `margin-left: calc(${val} * var(--qnt))`;
|
|
799
|
+
case 'mr':
|
|
800
|
+
return `margin-right: calc(${val} * var(--qnt))`;
|
|
801
|
+
case 'padding':
|
|
802
|
+
case 'p':
|
|
803
|
+
return `padding: calc(${val} * var(--qnt))`;
|
|
804
|
+
case 'pt':
|
|
805
|
+
return `padding-top: calc(${val} * var(--qnt))`;
|
|
806
|
+
case 'pb':
|
|
807
|
+
return `padding-bottom: calc(${val} * var(--qnt))`;
|
|
808
|
+
case 'pl':
|
|
809
|
+
return `padding-left: calc(${val} * var(--qnt))`;
|
|
810
|
+
case 'pr':
|
|
811
|
+
return `padding-right: calc(${val} * var(--qnt))`;
|
|
812
|
+
case 'px':
|
|
813
|
+
return `padding-left: calc(${val} * var(--qnt)); padding-right: calc(${val} * var(--qnt))`;
|
|
814
|
+
case 'py':
|
|
815
|
+
return `padding-top: calc(${val} * var(--qnt)); padding-bottom: calc(${val} * var(--qnt))`;
|
|
816
|
+
case 'gap':
|
|
817
|
+
return `gap: calc(${val} * var(--qnt))`;
|
|
818
|
+
|
|
819
|
+
// Category B: Backgrounds & Visuals
|
|
820
|
+
case 'bg':
|
|
821
|
+
// Note: Actual palette mapping is baked in by sync script
|
|
822
|
+
return `background-color: ${val}`;
|
|
823
|
+
case 'blur':
|
|
824
|
+
return `backdrop-filter: blur(calc(${val} * var(--qnt)))`;
|
|
825
|
+
case 'shadow':
|
|
826
|
+
const shadows = {
|
|
827
|
+
'sm': '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
|
828
|
+
'md': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
|
829
|
+
'lg': '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
|
|
830
|
+
'xl': '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
|
|
831
|
+
'inner': 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
|
|
832
|
+
'none': 'none'
|
|
833
|
+
};
|
|
834
|
+
return `box-shadow: ${shadows[val] || val}`;
|
|
835
|
+
case 'opacity':
|
|
836
|
+
const op = parseFloat(val);
|
|
837
|
+
return `opacity: ${op > 1 ? op / 1000 : op}`;
|
|
838
|
+
case 'z':
|
|
839
|
+
case 'z-index':
|
|
840
|
+
case 'depth':
|
|
841
|
+
return `z-index: ${val}`;
|
|
842
|
+
|
|
843
|
+
// Category E: Effects & Filters
|
|
844
|
+
case 'bright':
|
|
845
|
+
return `filter: brightness(${val}%)`;
|
|
846
|
+
case 'contrast':
|
|
847
|
+
return `filter: contrast(${val}%)`;
|
|
848
|
+
case 'grayscale':
|
|
849
|
+
return `filter: grayscale(${val}%)`;
|
|
850
|
+
case 'hue':
|
|
851
|
+
return `filter: hue-rotate(${val}deg)`;
|
|
852
|
+
case 'invert':
|
|
853
|
+
return `filter: invert(${val}%)`;
|
|
854
|
+
case 'saturate':
|
|
855
|
+
return `filter: saturate(${val}%)`;
|
|
856
|
+
case 'sepia':
|
|
857
|
+
return `filter: sepia(${val}%)`;
|
|
858
|
+
case 'blend':
|
|
859
|
+
return `mix-blend-mode: ${val}`;
|
|
860
|
+
|
|
861
|
+
// Category C: Borders & Shapes
|
|
862
|
+
case 'border':
|
|
863
|
+
return `border: calc(${val || 1} * var(--qnt)) solid currentColor`;
|
|
864
|
+
case 'border-t':
|
|
865
|
+
return `border-top: calc(${val || 1} * var(--qnt)) solid currentColor`;
|
|
866
|
+
case 'border-b':
|
|
867
|
+
return `border-bottom: calc(${val || 1} * var(--qnt)) solid currentColor`;
|
|
868
|
+
case 'border-l':
|
|
869
|
+
return `border-left: calc(${val || 1} * var(--qnt)) solid currentColor`;
|
|
870
|
+
case 'border-r':
|
|
871
|
+
return `border-right: calc(${val || 1} * var(--qnt)) solid currentColor`;
|
|
872
|
+
case 'border-color':
|
|
873
|
+
return `border-color: ${val}`;
|
|
874
|
+
case 'rounded':
|
|
875
|
+
if (val === 'full') return 'border-radius: 9999px';
|
|
876
|
+
return `border-radius: calc(${val} * var(--qnt))`;
|
|
877
|
+
case 'rounded-full':
|
|
878
|
+
return 'border-radius: 9999px';
|
|
879
|
+
case 'dev-border':
|
|
880
|
+
return `border: 1px solid #f43f5e; box-sizing: border-box`;
|
|
881
|
+
|
|
882
|
+
// Category F: Fonts & Typography
|
|
883
|
+
case 'text-size':
|
|
884
|
+
return `font-size: calc(${val} * var(--qnt))`;
|
|
885
|
+
case 'font':
|
|
886
|
+
// Note: Actual font mapping is baked in by sync script
|
|
887
|
+
if (val === 'black') return 'font-weight: 900';
|
|
888
|
+
if (val === 'bold') return 'font-weight: 700';
|
|
889
|
+
return `font-weight: ${val}`;
|
|
890
|
+
case 'weight':
|
|
891
|
+
return `font-weight: ${val}`;
|
|
892
|
+
case 'italic':
|
|
893
|
+
return 'font-style: italic';
|
|
894
|
+
case 'text':
|
|
895
|
+
if (['left', 'right', 'center', 'justify'].includes(val)) return `text-align: ${val}`;
|
|
896
|
+
return `color: ${val}`;
|
|
897
|
+
case 'text-color':
|
|
898
|
+
return `color: ${val}`;
|
|
899
|
+
case 'uppercase':
|
|
900
|
+
return 'text-transform: uppercase';
|
|
901
|
+
case 'lowercase':
|
|
902
|
+
return 'text-transform: lowercase';
|
|
903
|
+
case 'underline':
|
|
904
|
+
return 'text-decoration: underline';
|
|
905
|
+
case 'leading':
|
|
906
|
+
return `line-height: ${val}`;
|
|
907
|
+
case 'tracking':
|
|
908
|
+
return `letter-spacing: ${val}em`;
|
|
909
|
+
case 'nowrap':
|
|
910
|
+
return 'white-space: nowrap';
|
|
911
|
+
|
|
912
|
+
// Category D: Display & Flex Logic
|
|
913
|
+
case 'horizontal':
|
|
914
|
+
return 'display: flex; flex-direction: row';
|
|
915
|
+
case 'vertical':
|
|
916
|
+
return 'display: flex; flex-direction: column';
|
|
917
|
+
case 'centered':
|
|
918
|
+
return 'display: flex; align-items: center; justify-content: center';
|
|
919
|
+
case 'v-start':
|
|
920
|
+
return 'align-items: flex-start';
|
|
921
|
+
case 'v-mid':
|
|
922
|
+
case 'items-center':
|
|
923
|
+
return 'align-items: center';
|
|
924
|
+
case 'v-end':
|
|
925
|
+
return 'align-items: flex-end';
|
|
926
|
+
case 'h-start':
|
|
927
|
+
return 'justify-content: flex-start';
|
|
928
|
+
case 'h-mid':
|
|
929
|
+
case 'justify-center':
|
|
930
|
+
return 'justify-content: center';
|
|
931
|
+
case 'h-end':
|
|
932
|
+
return 'justify-content: flex-end';
|
|
933
|
+
case 'spaced':
|
|
934
|
+
case 'justify-between':
|
|
935
|
+
return 'justify-content: space-between';
|
|
936
|
+
case 'around':
|
|
937
|
+
return 'justify-content: space-around';
|
|
938
|
+
case 'evenly':
|
|
939
|
+
return 'justify-content: space-evenly';
|
|
940
|
+
case 'flex-wrap':
|
|
941
|
+
return 'flex-wrap: wrap';
|
|
942
|
+
case 'flex-nowrap':
|
|
943
|
+
return 'flex-wrap: nowrap';
|
|
944
|
+
case 'flex-1':
|
|
945
|
+
return 'flex: 1 1 0%';
|
|
946
|
+
case 'flex-auto':
|
|
947
|
+
return 'flex: 1 1 auto';
|
|
948
|
+
case 'flex-none':
|
|
949
|
+
return 'flex: none';
|
|
950
|
+
case 'order':
|
|
951
|
+
return `order: ${val}`;
|
|
952
|
+
case 'flex':
|
|
953
|
+
if (val === 'col' || val === 'column') return 'display: flex; flex-direction: column';
|
|
954
|
+
return val === 'true' || val === '' || val === 'flex' ? 'display: flex' : `display: ${val}`;
|
|
955
|
+
case 'flex-col':
|
|
956
|
+
return 'display: flex; flex-direction: column';
|
|
957
|
+
case 'hidden':
|
|
958
|
+
return 'display: none';
|
|
959
|
+
|
|
960
|
+
// Category J: Miscellaneous
|
|
961
|
+
case 'visible':
|
|
962
|
+
return 'visibility: visible';
|
|
963
|
+
case 'invisible':
|
|
964
|
+
return 'visibility: hidden';
|
|
965
|
+
|
|
966
|
+
case 'aspect':
|
|
967
|
+
const ratios = {
|
|
968
|
+
'square': '1 / 1',
|
|
969
|
+
'video': '16 / 9',
|
|
970
|
+
'portrait': '3 / 4',
|
|
971
|
+
'landscape': '4 / 3'
|
|
972
|
+
};
|
|
973
|
+
return `aspect-ratio: ${ratios[val] || val.replace('|', ' / ')}`;
|
|
974
|
+
|
|
975
|
+
case 'contain':
|
|
976
|
+
return 'object-fit: contain';
|
|
977
|
+
case 'cover':
|
|
978
|
+
return 'object-fit: cover';
|
|
979
|
+
|
|
980
|
+
case 'busy':
|
|
981
|
+
return 'cursor: wait';
|
|
982
|
+
|
|
983
|
+
case 'mask':
|
|
984
|
+
return `clip-path: ${val}`;
|
|
985
|
+
|
|
986
|
+
// Category G: Grid & Tables
|
|
987
|
+
case 'grid':
|
|
988
|
+
return 'display: grid';
|
|
989
|
+
case 'cols':
|
|
990
|
+
return `grid-template-columns: ${val}`;
|
|
991
|
+
case 'rows':
|
|
992
|
+
return `grid-template-rows: ${val}`;
|
|
993
|
+
case 'table-fixed':
|
|
994
|
+
return 'table-layout: fixed';
|
|
995
|
+
|
|
996
|
+
// Category H: Interactivity
|
|
997
|
+
case 'ghost':
|
|
998
|
+
return 'pointer-events: none';
|
|
999
|
+
case 'unselectable':
|
|
1000
|
+
return 'user-select: none';
|
|
1001
|
+
case 'ring':
|
|
1002
|
+
return `outline: calc(${val || 2} * var(--qnt)) solid #6366f1`;
|
|
1003
|
+
case 'resize':
|
|
1004
|
+
return `resize: ${val === 'x' ? 'horizontal' : val === 'y' ? 'vertical' : 'both'}; overflow: auto`;
|
|
1005
|
+
case 'collapsed':
|
|
1006
|
+
return 'width: 0; min-width: 0; overflow: hidden';
|
|
1007
|
+
case 'scroll-smooth':
|
|
1008
|
+
return 'scroll-behavior: smooth';
|
|
1009
|
+
case 'scroll':
|
|
1010
|
+
if (val === 'x') return 'overflow-x: auto; overflow-y: hidden';
|
|
1011
|
+
if (val === 'y') return 'overflow-y: auto; overflow-x: hidden';
|
|
1012
|
+
return `overflow: ${val || 'auto'}`;
|
|
1013
|
+
case 'clickable':
|
|
1014
|
+
case 'cursor-pointer':
|
|
1015
|
+
return 'cursor: pointer';
|
|
1016
|
+
|
|
1017
|
+
// Category A: Animations & Transitions
|
|
1018
|
+
case 'smooth':
|
|
1019
|
+
case 'transition-all':
|
|
1020
|
+
return 'transition: all 0.2s ease-in-out';
|
|
1021
|
+
case 'transition-colors':
|
|
1022
|
+
return 'transition: background-color 0.2s, border-color 0.2s';
|
|
1023
|
+
case 'transition-none':
|
|
1024
|
+
return 'transition: none';
|
|
1025
|
+
|
|
1026
|
+
default:
|
|
1027
|
+
// Fallback for raw CSS properties
|
|
1028
|
+
return `${key}: ${val}`;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function toggleDropdown(id) {
|
|
1033
|
+
const dropdown = document.getElementById(id);
|
|
1034
|
+
if (dropdown) {
|
|
1035
|
+
dropdown.classList.toggle('hidden');
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
return {
|
|
1040
|
+
transpile,
|
|
1041
|
+
setAnchor,
|
|
1042
|
+
registerObject,
|
|
1043
|
+
calculateGrid,
|
|
1044
|
+
startPulse,
|
|
1045
|
+
startObserver,
|
|
1046
|
+
toggleDropdown,
|
|
1047
|
+
version: '0.5.1-hardened'
|
|
1048
|
+
};
|
|
1049
|
+
})();
|
|
1050
|
+
|
|
1051
|
+
// Export for Node/CommonJS if needed, else it stays in global scope
|
|
1052
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
1053
|
+
module.exports = QSSEngine;
|
|
1054
|
+
}
|