chaincss 2.1.39 → 2.3.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/dist/compiler/accessibility-engine.d.ts +57 -0
- package/dist/compiler/constraint-solver.d.ts +85 -0
- package/dist/compiler/css-if-transpiler.d.ts +33 -0
- package/dist/compiler/design-orchestrator.d.ts +119 -0
- package/dist/compiler/intent-api.d.ts +73 -0
- package/dist/compiler/intent-engine.d.ts +19 -1
- package/dist/compiler/layout-intelligence.d.ts +71 -0
- package/dist/compiler/pass-manager.d.ts +157 -0
- package/dist/compiler/pattern-learner.d.ts +112 -0
- package/dist/compiler/responsive-inference.d.ts +63 -0
- package/dist/compiler/scroll-timeline.d.ts +91 -0
- package/dist/compiler/semantic-tokens.d.ts +57 -0
- package/dist/compiler/source-optimizer.d.ts +109 -0
- package/dist/compiler/style-ir.d.ts +183 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +4126 -2
- package/package.json +1 -1
- package/src/compiler/accessibility-engine.ts +502 -0
- package/src/compiler/constraint-solver.ts +407 -0
- package/src/compiler/css-if-transpiler.ts +117 -0
- package/src/compiler/design-orchestrator.ts +322 -0
- package/src/compiler/intent-api.ts +505 -0
- package/src/compiler/intent-engine.ts +291 -1
- package/src/compiler/layout-intelligence.ts +697 -0
- package/src/compiler/pass-manager.ts +657 -0
- package/src/compiler/pattern-learner.ts +398 -0
- package/src/compiler/responsive-inference.ts +415 -0
- package/src/compiler/scroll-timeline.ts +284 -0
- package/src/compiler/semantic-tokens.ts +468 -0
- package/src/compiler/source-optimizer.ts +541 -0
- package/src/compiler/style-ir.ts +495 -0
- package/src/index.ts +209 -0
- package/ROADMAP.md +0 -31
package/dist/index.js
CHANGED
|
@@ -7330,6 +7330,3728 @@ init_shorthands();
|
|
|
7330
7330
|
init_helpers();
|
|
7331
7331
|
init_animations();
|
|
7332
7332
|
init_suggestions();
|
|
7333
|
+
|
|
7334
|
+
// src/compiler/design-orchestrator.ts
|
|
7335
|
+
function parseColor(color) {
|
|
7336
|
+
const trimmed = color.trim().toLowerCase();
|
|
7337
|
+
const hexMatch = trimmed.match(/^#([a-f0-9]{3}|[a-f0-9]{6}|[a-f0-9]{8})$/);
|
|
7338
|
+
if (hexMatch) {
|
|
7339
|
+
let hex = hexMatch[1];
|
|
7340
|
+
if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
7341
|
+
if (hex.length === 8) {
|
|
7342
|
+
return {
|
|
7343
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
7344
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
7345
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
7346
|
+
a: parseInt(hex.slice(6, 8), 16) / 255
|
|
7347
|
+
};
|
|
7348
|
+
}
|
|
7349
|
+
return {
|
|
7350
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
7351
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
7352
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
7353
|
+
a: 1
|
|
7354
|
+
};
|
|
7355
|
+
}
|
|
7356
|
+
const rgbMatch = trimmed.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)$/);
|
|
7357
|
+
if (rgbMatch) {
|
|
7358
|
+
return {
|
|
7359
|
+
r: parseInt(rgbMatch[1]),
|
|
7360
|
+
g: parseInt(rgbMatch[2]),
|
|
7361
|
+
b: parseInt(rgbMatch[3]),
|
|
7362
|
+
a: rgbMatch[4] ? parseFloat(rgbMatch[4]) : 1
|
|
7363
|
+
};
|
|
7364
|
+
}
|
|
7365
|
+
const named = {
|
|
7366
|
+
white: [255, 255, 255],
|
|
7367
|
+
black: [0, 0, 0],
|
|
7368
|
+
red: [255, 0, 0],
|
|
7369
|
+
green: [0, 128, 0],
|
|
7370
|
+
blue: [0, 0, 255],
|
|
7371
|
+
gray: [128, 128, 128],
|
|
7372
|
+
grey: [128, 128, 128],
|
|
7373
|
+
transparent: [0, 0, 0]
|
|
7374
|
+
};
|
|
7375
|
+
if (named[trimmed]) {
|
|
7376
|
+
const [r, g, b] = named[trimmed];
|
|
7377
|
+
return { r, g, b, a: trimmed === "transparent" ? 0 : 1 };
|
|
7378
|
+
}
|
|
7379
|
+
return null;
|
|
7380
|
+
}
|
|
7381
|
+
function relativeLuminance(r, g, b) {
|
|
7382
|
+
const rsrgb = r / 255;
|
|
7383
|
+
const gsrgb = g / 255;
|
|
7384
|
+
const bsrgb = b / 255;
|
|
7385
|
+
const rLin = rsrgb <= 0.04045 ? rsrgb / 12.92 : Math.pow((rsrgb + 0.055) / 1.055, 2.4);
|
|
7386
|
+
const gLin = gsrgb <= 0.04045 ? gsrgb / 12.92 : Math.pow((gsrgb + 0.055) / 1.055, 2.4);
|
|
7387
|
+
const bLin = bsrgb <= 0.04045 ? bsrgb / 12.92 : Math.pow((bsrgb + 0.055) / 1.055, 2.4);
|
|
7388
|
+
return 0.2126 * rLin + 0.7152 * gLin + 0.0722 * bLin;
|
|
7389
|
+
}
|
|
7390
|
+
function contrastRatio(foreground, background) {
|
|
7391
|
+
const fg = parseColor(foreground);
|
|
7392
|
+
const bg = parseColor(background);
|
|
7393
|
+
if (!fg || !bg) return -1;
|
|
7394
|
+
const lumFg = relativeLuminance(fg.r, fg.g, fg.b) + 0.05;
|
|
7395
|
+
const lumBg = relativeLuminance(bg.r, bg.g, bg.b) + 0.05;
|
|
7396
|
+
const lighter = Math.max(lumFg, lumBg);
|
|
7397
|
+
const darker = Math.min(lumFg, lumBg);
|
|
7398
|
+
return lighter / darker;
|
|
7399
|
+
}
|
|
7400
|
+
function checkContrast(foreground, background) {
|
|
7401
|
+
const ratio = contrastRatio(foreground, background);
|
|
7402
|
+
return {
|
|
7403
|
+
foreground,
|
|
7404
|
+
background,
|
|
7405
|
+
ratio: Math.round(ratio * 100) / 100,
|
|
7406
|
+
passes: {
|
|
7407
|
+
AA: ratio >= 4.5,
|
|
7408
|
+
AALarge: ratio >= 3,
|
|
7409
|
+
AAA: ratio >= 7,
|
|
7410
|
+
AAALarge: ratio >= 4.5
|
|
7411
|
+
},
|
|
7412
|
+
suggestion: ratio < 4.5 ? `Contrast ratio ${Math.round(ratio * 100) / 100} fails AA. Need ${Math.round((4.5 - ratio) * 100) / 100} more. Consider darkening/lightening.` : void 0
|
|
7413
|
+
};
|
|
7414
|
+
}
|
|
7415
|
+
function auditContrast(styles) {
|
|
7416
|
+
const checks = [];
|
|
7417
|
+
for (const style of styles) {
|
|
7418
|
+
if (style.color && style.backgroundColor) {
|
|
7419
|
+
checks.push(checkContrast(style.color, style.backgroundColor));
|
|
7420
|
+
}
|
|
7421
|
+
}
|
|
7422
|
+
const failures = checks.filter((c) => !c.passes.AA);
|
|
7423
|
+
const warnings = checks.filter((c) => c.passes.AA && !c.passes.AAA);
|
|
7424
|
+
return {
|
|
7425
|
+
checks,
|
|
7426
|
+
failures,
|
|
7427
|
+
warnings,
|
|
7428
|
+
passCount: checks.length - failures.length,
|
|
7429
|
+
failCount: failures.length,
|
|
7430
|
+
summary: failures.length === 0 ? "All " + checks.length + " contrast checks pass AA." : failures.length + " of " + checks.length + " contrast checks FAIL AA."
|
|
7431
|
+
};
|
|
7432
|
+
}
|
|
7433
|
+
function createContextualToken(defaultValue, contexts = {}) {
|
|
7434
|
+
const name = "ctx-" + Math.random().toString(36).slice(2, 8);
|
|
7435
|
+
return { name, default: defaultValue, contexts };
|
|
7436
|
+
}
|
|
7437
|
+
function resolveContextual(token, selectorPath) {
|
|
7438
|
+
let bestMatch = token.default;
|
|
7439
|
+
let bestLength = 0;
|
|
7440
|
+
for (const [context, value] of Object.entries(token.contexts)) {
|
|
7441
|
+
if (selectorPath.includes(context) && context.length > bestLength) {
|
|
7442
|
+
bestMatch = value;
|
|
7443
|
+
bestLength = context.length;
|
|
7444
|
+
}
|
|
7445
|
+
}
|
|
7446
|
+
return bestMatch;
|
|
7447
|
+
}
|
|
7448
|
+
function generateContextualCSS(propertyName, token, baseSelector) {
|
|
7449
|
+
let css = "";
|
|
7450
|
+
css += baseSelector + " { " + propertyName + ": " + token.default + "; }\n";
|
|
7451
|
+
for (const [context, value] of Object.entries(token.contexts)) {
|
|
7452
|
+
css += context + " " + baseSelector + " { " + propertyName + ": " + value + "; }\n";
|
|
7453
|
+
}
|
|
7454
|
+
return css;
|
|
7455
|
+
}
|
|
7456
|
+
function validateTokenRelationships(tokens3, pairs) {
|
|
7457
|
+
const styles = [];
|
|
7458
|
+
for (const pair of pairs) {
|
|
7459
|
+
const fg = resolveTokenPath2(tokens3, pair.foreground);
|
|
7460
|
+
const bg = resolveTokenPath2(tokens3, pair.background);
|
|
7461
|
+
if (fg && bg) {
|
|
7462
|
+
styles.push({ selector: pair.label, color: fg, backgroundColor: bg });
|
|
7463
|
+
}
|
|
7464
|
+
}
|
|
7465
|
+
return auditContrast(styles);
|
|
7466
|
+
}
|
|
7467
|
+
function resolveTokenPath2(tokens3, path6) {
|
|
7468
|
+
const parts = path6.split(".");
|
|
7469
|
+
let current = tokens3;
|
|
7470
|
+
for (const part of parts) {
|
|
7471
|
+
if (current === void 0 || current === null) return null;
|
|
7472
|
+
current = current[part];
|
|
7473
|
+
}
|
|
7474
|
+
return typeof current === "string" ? current : null;
|
|
7475
|
+
}
|
|
7476
|
+
var orchestrator = {
|
|
7477
|
+
contrastRatio,
|
|
7478
|
+
checkContrast,
|
|
7479
|
+
auditContrast,
|
|
7480
|
+
createContextualToken,
|
|
7481
|
+
resolveContextual,
|
|
7482
|
+
generateContextualCSS,
|
|
7483
|
+
validateTokenRelationships,
|
|
7484
|
+
parseColor
|
|
7485
|
+
};
|
|
7486
|
+
|
|
7487
|
+
// src/compiler/scroll-timeline.ts
|
|
7488
|
+
var SCROLL_PRESETS = {
|
|
7489
|
+
fadeIn: {
|
|
7490
|
+
selector: "",
|
|
7491
|
+
timeline: { name: "fade-in", source: "view", range: "entry" },
|
|
7492
|
+
keyframes: [
|
|
7493
|
+
{ offset: "0%", properties: { opacity: "0", transform: "translateY(20px)" } },
|
|
7494
|
+
{ offset: "100%", properties: { opacity: "1", transform: "translateY(0)" } }
|
|
7495
|
+
]
|
|
7496
|
+
},
|
|
7497
|
+
fadeOut: {
|
|
7498
|
+
selector: "",
|
|
7499
|
+
timeline: { name: "fade-out", source: "view", range: "exit" },
|
|
7500
|
+
keyframes: [
|
|
7501
|
+
{ offset: "0%", properties: { opacity: "1" } },
|
|
7502
|
+
{ offset: "100%", properties: { opacity: "0" } }
|
|
7503
|
+
]
|
|
7504
|
+
},
|
|
7505
|
+
scaleIn: {
|
|
7506
|
+
selector: "",
|
|
7507
|
+
timeline: { name: "scale-in", source: "view", range: "entry" },
|
|
7508
|
+
keyframes: [
|
|
7509
|
+
{ offset: "0%", properties: { opacity: "0", transform: "scale(0.8)" } },
|
|
7510
|
+
{ offset: "100%", properties: { opacity: "1", transform: "scale(1)" } }
|
|
7511
|
+
]
|
|
7512
|
+
},
|
|
7513
|
+
slideLeft: {
|
|
7514
|
+
selector: "",
|
|
7515
|
+
timeline: { name: "slide-left", source: "view", range: "entry" },
|
|
7516
|
+
keyframes: [
|
|
7517
|
+
{ offset: "0%", properties: { opacity: "0", transform: "translateX(-40px)" } },
|
|
7518
|
+
{ offset: "100%", properties: { opacity: "1", transform: "translateX(0)" } }
|
|
7519
|
+
]
|
|
7520
|
+
},
|
|
7521
|
+
slideRight: {
|
|
7522
|
+
selector: "",
|
|
7523
|
+
timeline: { name: "slide-right", source: "view", range: "entry" },
|
|
7524
|
+
keyframes: [
|
|
7525
|
+
{ offset: "0%", properties: { opacity: "0", transform: "translateX(40px)" } },
|
|
7526
|
+
{ offset: "100%", properties: { opacity: "1", transform: "translateX(0)" } }
|
|
7527
|
+
]
|
|
7528
|
+
},
|
|
7529
|
+
parallax: {
|
|
7530
|
+
selector: "",
|
|
7531
|
+
timeline: { name: "parallax", source: "scroll", scroller: "root" },
|
|
7532
|
+
keyframes: [
|
|
7533
|
+
{ offset: "0%", properties: { transform: "translateY(0)" } },
|
|
7534
|
+
{ offset: "100%", properties: { transform: "translateY(-20%)" } }
|
|
7535
|
+
]
|
|
7536
|
+
},
|
|
7537
|
+
stickyReveal: {
|
|
7538
|
+
selector: "",
|
|
7539
|
+
timeline: { name: "sticky-reveal", source: "view", range: "contain" },
|
|
7540
|
+
keyframes: [
|
|
7541
|
+
{ offset: "0%", properties: { opacity: "0", clipPath: "inset(0 0 100% 0)" } },
|
|
7542
|
+
{ offset: "50%", properties: { opacity: "1", clipPath: "inset(0 0 0% 0)" } },
|
|
7543
|
+
{ offset: "100%", properties: { opacity: "1", clipPath: "inset(0 0 0% 0)" } }
|
|
7544
|
+
]
|
|
7545
|
+
}
|
|
7546
|
+
};
|
|
7547
|
+
var animCounter = 0;
|
|
7548
|
+
function generateName(prefix) {
|
|
7549
|
+
return prefix + "-" + (animCounter++).toString(36);
|
|
7550
|
+
}
|
|
7551
|
+
function compileScrollAnimation(animation) {
|
|
7552
|
+
const animName = animation.timeline.name || generateName("scroll-anim");
|
|
7553
|
+
const timelineName = "--" + animName + "-tl";
|
|
7554
|
+
let css = "";
|
|
7555
|
+
css += "/* Scroll Timeline: " + animName + " */\n";
|
|
7556
|
+
if (animation.timeline.source === "view") {
|
|
7557
|
+
const range = animation.timeline.range || "entry";
|
|
7558
|
+
css += animation.selector + " {\n";
|
|
7559
|
+
css += " view-timeline-name: " + timelineName + ";\n";
|
|
7560
|
+
css += " view-timeline-axis: " + (animation.timeline.axis || "block") + ";\n";
|
|
7561
|
+
if (animation.timeline.inset) {
|
|
7562
|
+
const inset = typeof animation.timeline.inset === "string" ? animation.timeline.inset : animation.timeline.inset.start + " " + animation.timeline.inset.end;
|
|
7563
|
+
css += " view-timeline-inset: " + inset + ";\n";
|
|
7564
|
+
}
|
|
7565
|
+
css += "}\n\n";
|
|
7566
|
+
} else {
|
|
7567
|
+
const scroller = animation.timeline.scroller === "root" ? "root" : animation.timeline.scroller === "self" ? "self" : animation.timeline.scroller || "nearest";
|
|
7568
|
+
css += animation.selector + " {\n";
|
|
7569
|
+
if (scroller === "root" || scroller === "self" || scroller === "nearest") {
|
|
7570
|
+
css += " scroll-timeline-name: " + timelineName + ";\n";
|
|
7571
|
+
css += " scroll-timeline-axis: " + (animation.timeline.axis || "block") + ";\n";
|
|
7572
|
+
}
|
|
7573
|
+
css += "}\n\n";
|
|
7574
|
+
}
|
|
7575
|
+
css += "@keyframes " + animName + " {\n";
|
|
7576
|
+
for (const step of animation.keyframes) {
|
|
7577
|
+
css += " " + step.offset + " {\n";
|
|
7578
|
+
for (const [prop, value] of Object.entries(step.properties)) {
|
|
7579
|
+
const kebabProp = prop.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
7580
|
+
css += " " + kebabProp + ": " + value + ";\n";
|
|
7581
|
+
}
|
|
7582
|
+
css += " }\n";
|
|
7583
|
+
}
|
|
7584
|
+
css += "}\n\n";
|
|
7585
|
+
const targetSelector = animation.selector + " > *" || animation.selector;
|
|
7586
|
+
css += "/* Apply animation to children */\n";
|
|
7587
|
+
css += targetSelector + " {\n";
|
|
7588
|
+
css += " animation: " + animName + " linear both;\n";
|
|
7589
|
+
css += " animation-timeline: " + timelineName + ";\n";
|
|
7590
|
+
if (animation.timeline.source === "view") {
|
|
7591
|
+
const range = animation.timeline.range || "entry";
|
|
7592
|
+
css += " animation-range: " + range + ";\n";
|
|
7593
|
+
}
|
|
7594
|
+
if (animation.delay) css += " animation-delay: " + animation.delay + ";\n";
|
|
7595
|
+
css += "}\n\n";
|
|
7596
|
+
css += "/* Fallback for browsers without scroll-timeline */\n";
|
|
7597
|
+
css += "@supports not (animation-timeline: scroll()) {\n";
|
|
7598
|
+
css += " " + targetSelector + " {\n";
|
|
7599
|
+
css += " /* Use JS polyfill: https://github.com/flackr/scroll-timeline */\n";
|
|
7600
|
+
css += " animation: " + animName + " 1s ease both;\n";
|
|
7601
|
+
css += " }\n";
|
|
7602
|
+
css += "}\n";
|
|
7603
|
+
return {
|
|
7604
|
+
css,
|
|
7605
|
+
animationName: animName,
|
|
7606
|
+
timelineName,
|
|
7607
|
+
fallback: ""
|
|
7608
|
+
};
|
|
7609
|
+
}
|
|
7610
|
+
function compileScrollAnimations(animations) {
|
|
7611
|
+
let css = "/* ============================================================\n";
|
|
7612
|
+
css += " ChainCSS Scroll-Driven Animations\n";
|
|
7613
|
+
css += " Generated: " + (/* @__PURE__ */ new Date()).toISOString() + "\n";
|
|
7614
|
+
css += " ============================================================ */\n\n";
|
|
7615
|
+
for (const animation of animations) {
|
|
7616
|
+
const result = compileScrollAnimation(animation);
|
|
7617
|
+
css += result.css;
|
|
7618
|
+
}
|
|
7619
|
+
return css;
|
|
7620
|
+
}
|
|
7621
|
+
function createScrollAnimation(preset, selector, overrides) {
|
|
7622
|
+
const base = SCROLL_PRESETS[preset];
|
|
7623
|
+
if (!base) throw new Error("Unknown scroll preset: " + preset);
|
|
7624
|
+
return {
|
|
7625
|
+
...base,
|
|
7626
|
+
selector,
|
|
7627
|
+
timeline: { ...base.timeline, ...overrides?.timeline },
|
|
7628
|
+
keyframes: overrides?.keyframes || base.keyframes,
|
|
7629
|
+
...overrides
|
|
7630
|
+
};
|
|
7631
|
+
}
|
|
7632
|
+
function getScrollPresets() {
|
|
7633
|
+
return Object.keys(SCROLL_PRESETS);
|
|
7634
|
+
}
|
|
7635
|
+
var scrollTimeline = {
|
|
7636
|
+
compile: compileScrollAnimation,
|
|
7637
|
+
compileAll: compileScrollAnimations,
|
|
7638
|
+
create: createScrollAnimation,
|
|
7639
|
+
presets: SCROLL_PRESETS,
|
|
7640
|
+
getPresets: getScrollPresets
|
|
7641
|
+
};
|
|
7642
|
+
|
|
7643
|
+
// src/compiler/style-ir.ts
|
|
7644
|
+
var idCounter = 0;
|
|
7645
|
+
function nextId(prefix = "ir") {
|
|
7646
|
+
return prefix + "-" + (idCounter++).toString(36) + "-" + Date.now().toString(36);
|
|
7647
|
+
}
|
|
7648
|
+
function resetIdCounter() {
|
|
7649
|
+
idCounter = 0;
|
|
7650
|
+
}
|
|
7651
|
+
function record(pass, action, previous, reason) {
|
|
7652
|
+
return { pass, action, timestamp: Date.now(), previous, reason };
|
|
7653
|
+
}
|
|
7654
|
+
function createDeclaration(property, value, source, meta = {}) {
|
|
7655
|
+
return {
|
|
7656
|
+
id: nextId("decl"),
|
|
7657
|
+
property,
|
|
7658
|
+
value,
|
|
7659
|
+
source,
|
|
7660
|
+
history: [record("parser", "created", void 0, "Parsed from StyleDefinition")],
|
|
7661
|
+
meta
|
|
7662
|
+
};
|
|
7663
|
+
}
|
|
7664
|
+
function createRule(selector, source) {
|
|
7665
|
+
return {
|
|
7666
|
+
id: nextId("rule"),
|
|
7667
|
+
selector,
|
|
7668
|
+
declarations: [],
|
|
7669
|
+
pseudoClasses: [],
|
|
7670
|
+
atRules: [],
|
|
7671
|
+
nestedRules: [],
|
|
7672
|
+
conditions: [],
|
|
7673
|
+
isDead: false,
|
|
7674
|
+
specificity: 0,
|
|
7675
|
+
hash: "",
|
|
7676
|
+
source: source || {},
|
|
7677
|
+
history: [record("parser", "created", void 0, "Parsed from StyleDefinition")],
|
|
7678
|
+
meta: {}
|
|
7679
|
+
};
|
|
7680
|
+
}
|
|
7681
|
+
function createIR(sourceFiles = []) {
|
|
7682
|
+
return {
|
|
7683
|
+
id: nextId("ir"),
|
|
7684
|
+
rules: [],
|
|
7685
|
+
diagnostics: [],
|
|
7686
|
+
meta: {
|
|
7687
|
+
version: "1.0.0",
|
|
7688
|
+
createdAt: Date.now(),
|
|
7689
|
+
sourceFiles,
|
|
7690
|
+
passCount: 0,
|
|
7691
|
+
passes: []
|
|
7692
|
+
}
|
|
7693
|
+
};
|
|
7694
|
+
}
|
|
7695
|
+
function parseIR(styles, sourceFile) {
|
|
7696
|
+
const ir = createIR(sourceFile ? [sourceFile] : []);
|
|
7697
|
+
for (const [componentName, styleDef] of Object.entries(styles)) {
|
|
7698
|
+
if (!styleDef || typeof styleDef !== "object") continue;
|
|
7699
|
+
const selectors = Array.isArray(styleDef.selectors) ? styleDef.selectors : styleDef.selector ? [styleDef.selector] : ["." + componentName];
|
|
7700
|
+
for (const selector of selectors) {
|
|
7701
|
+
const rule = createRule(selector, {
|
|
7702
|
+
file: sourceFile,
|
|
7703
|
+
component: componentName
|
|
7704
|
+
});
|
|
7705
|
+
for (const [prop, value] of Object.entries(styleDef)) {
|
|
7706
|
+
if (prop === "selectors" || prop === "selector" || prop.startsWith("_")) continue;
|
|
7707
|
+
if (prop === "hover" || prop === "atRules" || prop === "nestedRules" || prop === "themes") continue;
|
|
7708
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
7709
|
+
rule.declarations.push(createDeclaration(prop, value, rule.source));
|
|
7710
|
+
}
|
|
7711
|
+
}
|
|
7712
|
+
if (styleDef.hover && typeof styleDef.hover === "object") {
|
|
7713
|
+
const pc = {
|
|
7714
|
+
id: nextId("hover"),
|
|
7715
|
+
name: "hover",
|
|
7716
|
+
declarations: [],
|
|
7717
|
+
source: rule.source,
|
|
7718
|
+
history: [record("parser", "created", void 0, "Parsed hover block")]
|
|
7719
|
+
};
|
|
7720
|
+
for (const [prop, value] of Object.entries(styleDef.hover)) {
|
|
7721
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
7722
|
+
pc.declarations.push(createDeclaration(prop, value, rule.source));
|
|
7723
|
+
}
|
|
7724
|
+
}
|
|
7725
|
+
rule.pseudoClasses.push(pc);
|
|
7726
|
+
}
|
|
7727
|
+
if (styleDef.atRules && Array.isArray(styleDef.atRules)) {
|
|
7728
|
+
for (const atRule of styleDef.atRules) {
|
|
7729
|
+
const irAtRule = {
|
|
7730
|
+
id: nextId("atrule"),
|
|
7731
|
+
type: atRule.type || "media",
|
|
7732
|
+
query: atRule.query,
|
|
7733
|
+
name: atRule.name,
|
|
7734
|
+
declarations: [],
|
|
7735
|
+
nestedRules: [],
|
|
7736
|
+
source: rule.source,
|
|
7737
|
+
history: [record("parser", "created", void 0, "Parsed at-rule")]
|
|
7738
|
+
};
|
|
7739
|
+
if (atRule.styles && typeof atRule.styles === "object") {
|
|
7740
|
+
for (const [prop, value] of Object.entries(atRule.styles)) {
|
|
7741
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
7742
|
+
irAtRule.declarations.push(createDeclaration(prop, value, rule.source));
|
|
7743
|
+
}
|
|
7744
|
+
}
|
|
7745
|
+
}
|
|
7746
|
+
rule.atRules.push(irAtRule);
|
|
7747
|
+
}
|
|
7748
|
+
}
|
|
7749
|
+
if (styleDef._ifConditions && Array.isArray(styleDef._ifConditions)) {
|
|
7750
|
+
for (const cond of styleDef._ifConditions) {
|
|
7751
|
+
rule.conditions.push({
|
|
7752
|
+
id: nextId("cond"),
|
|
7753
|
+
property: cond.property,
|
|
7754
|
+
variable: cond.variable,
|
|
7755
|
+
conditions: cond.conditions || {},
|
|
7756
|
+
defaultValue: cond.defaultValue || "",
|
|
7757
|
+
source: rule.source
|
|
7758
|
+
});
|
|
7759
|
+
}
|
|
7760
|
+
}
|
|
7761
|
+
ir.rules.push(rule);
|
|
7762
|
+
}
|
|
7763
|
+
}
|
|
7764
|
+
return ir;
|
|
7765
|
+
}
|
|
7766
|
+
function kebab3(prop) {
|
|
7767
|
+
return prop.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
7768
|
+
}
|
|
7769
|
+
function generateCSS(ir, options) {
|
|
7770
|
+
let css = "";
|
|
7771
|
+
for (const rule of ir.rules) {
|
|
7772
|
+
if (rule.isDead) continue;
|
|
7773
|
+
if (rule.declarations.length > 0) {
|
|
7774
|
+
css += rule.selector + " {\n";
|
|
7775
|
+
for (const decl of rule.declarations) {
|
|
7776
|
+
css += " " + kebab3(decl.property) + ": " + decl.value + ";\n";
|
|
7777
|
+
}
|
|
7778
|
+
css += "}\n";
|
|
7779
|
+
}
|
|
7780
|
+
for (const pc of rule.pseudoClasses) {
|
|
7781
|
+
if (pc.declarations.length > 0) {
|
|
7782
|
+
css += rule.selector + ":" + pc.name + " {\n";
|
|
7783
|
+
for (const decl of pc.declarations) {
|
|
7784
|
+
css += " " + kebab3(decl.property) + ": " + decl.value + ";\n";
|
|
7785
|
+
}
|
|
7786
|
+
css += "}\n";
|
|
7787
|
+
}
|
|
7788
|
+
}
|
|
7789
|
+
for (const atRule of rule.atRules) {
|
|
7790
|
+
if (atRule.type === "media" && atRule.query) {
|
|
7791
|
+
css += "@media " + atRule.query + " {\n";
|
|
7792
|
+
css += rule.selector + " {\n";
|
|
7793
|
+
for (const decl of atRule.declarations) {
|
|
7794
|
+
css += " " + kebab3(decl.property) + ": " + decl.value + ";\n";
|
|
7795
|
+
}
|
|
7796
|
+
css += "}\n}\n";
|
|
7797
|
+
} else if (atRule.type === "keyframes" && atRule.name) {
|
|
7798
|
+
css += "@keyframes " + atRule.name + " {\n";
|
|
7799
|
+
for (const decl of atRule.declarations) {
|
|
7800
|
+
css += " " + decl.property + " { " + kebab3(decl.property) + ": " + decl.value + "; }\n";
|
|
7801
|
+
}
|
|
7802
|
+
css += "}\n";
|
|
7803
|
+
}
|
|
7804
|
+
}
|
|
7805
|
+
if (rule.conditions.length > 0) {
|
|
7806
|
+
css += "/* Native CSS if() */\n";
|
|
7807
|
+
css += rule.selector + " {\n";
|
|
7808
|
+
for (const cond of rule.conditions) {
|
|
7809
|
+
const entries = Object.entries(cond.conditions);
|
|
7810
|
+
if (entries.length === 1) {
|
|
7811
|
+
const [c, v] = entries[0];
|
|
7812
|
+
css += " " + kebab3(cond.property) + ": if(style(" + cond.variable + ": " + c + "): " + v + " else " + cond.defaultValue + ");\n";
|
|
7813
|
+
}
|
|
7814
|
+
}
|
|
7815
|
+
css += "}\n";
|
|
7816
|
+
}
|
|
7817
|
+
}
|
|
7818
|
+
return css;
|
|
7819
|
+
}
|
|
7820
|
+
function countNodes(ir) {
|
|
7821
|
+
let declarations = 0, pseudoClasses = 0, atRules = 0, conditions = 0;
|
|
7822
|
+
for (const rule of ir.rules) {
|
|
7823
|
+
declarations += rule.declarations.length;
|
|
7824
|
+
pseudoClasses += rule.pseudoClasses.length;
|
|
7825
|
+
atRules += rule.atRules.length;
|
|
7826
|
+
conditions += rule.conditions.length;
|
|
7827
|
+
}
|
|
7828
|
+
return { rules: ir.rules.length, declarations, pseudoClasses, atRules, conditions };
|
|
7829
|
+
}
|
|
7830
|
+
function findRule(ir, selector) {
|
|
7831
|
+
return ir.rules.find((r) => r.selector === selector);
|
|
7832
|
+
}
|
|
7833
|
+
function cloneIR(ir) {
|
|
7834
|
+
return JSON.parse(JSON.stringify(ir));
|
|
7835
|
+
}
|
|
7836
|
+
function debugIR(ir) {
|
|
7837
|
+
const counts = countNodes(ir);
|
|
7838
|
+
return [
|
|
7839
|
+
"StyleIR {",
|
|
7840
|
+
" id: " + ir.id,
|
|
7841
|
+
" rules: " + counts.rules,
|
|
7842
|
+
" declarations: " + counts.declarations,
|
|
7843
|
+
" pseudoClasses: " + counts.pseudoClasses,
|
|
7844
|
+
" atRules: " + counts.atRules,
|
|
7845
|
+
" conditions: " + counts.conditions,
|
|
7846
|
+
" diagnostics: " + ir.diagnostics.length,
|
|
7847
|
+
" passes: [" + ir.meta.passes.join(", ") + "]",
|
|
7848
|
+
"}"
|
|
7849
|
+
].join("\n");
|
|
7850
|
+
}
|
|
7851
|
+
function applyPass(ir, pass, passName) {
|
|
7852
|
+
const result = pass(ir);
|
|
7853
|
+
result.meta.passCount++;
|
|
7854
|
+
result.meta.passes.push(passName);
|
|
7855
|
+
return result;
|
|
7856
|
+
}
|
|
7857
|
+
function applyPasses(ir, passes) {
|
|
7858
|
+
let current = ir;
|
|
7859
|
+
for (const { name, pass } of passes) {
|
|
7860
|
+
current = applyPass(current, pass, name);
|
|
7861
|
+
}
|
|
7862
|
+
return current;
|
|
7863
|
+
}
|
|
7864
|
+
function compileViaIR(styles, passes = [], options) {
|
|
7865
|
+
let ir = parseIR(styles, options?.sourceFile);
|
|
7866
|
+
for (const { name, pass } of passes) {
|
|
7867
|
+
ir = applyPass(ir, pass, name);
|
|
7868
|
+
}
|
|
7869
|
+
const css = generateCSS(ir, options);
|
|
7870
|
+
return { css, ir };
|
|
7871
|
+
}
|
|
7872
|
+
var styleIR = {
|
|
7873
|
+
createIR,
|
|
7874
|
+
parseIR,
|
|
7875
|
+
generateCSS,
|
|
7876
|
+
createRule,
|
|
7877
|
+
createDeclaration,
|
|
7878
|
+
countNodes,
|
|
7879
|
+
findRule,
|
|
7880
|
+
cloneIR,
|
|
7881
|
+
debugIR,
|
|
7882
|
+
applyPass,
|
|
7883
|
+
applyPasses,
|
|
7884
|
+
compileViaIR,
|
|
7885
|
+
resetIdCounter
|
|
7886
|
+
};
|
|
7887
|
+
|
|
7888
|
+
// src/compiler/pass-manager.ts
|
|
7889
|
+
var intentRecoveryPass = (ir) => {
|
|
7890
|
+
for (const rule of ir.rules) {
|
|
7891
|
+
for (const decl of rule.declarations) {
|
|
7892
|
+
if (decl.property === "display" && decl.value === "flexbox") {
|
|
7893
|
+
decl.value = "flex";
|
|
7894
|
+
decl.history.push({
|
|
7895
|
+
pass: "intent-recovery",
|
|
7896
|
+
action: "corrected-value",
|
|
7897
|
+
timestamp: Date.now(),
|
|
7898
|
+
previous: "flexbox",
|
|
7899
|
+
reason: "flexbox \u2192 flex"
|
|
7900
|
+
});
|
|
7901
|
+
const hasJustify = rule.declarations.some((d) => d.property === "justifyContent");
|
|
7902
|
+
const hasAlign = rule.declarations.some((d) => d.property === "alignItems");
|
|
7903
|
+
if (!hasJustify) {
|
|
7904
|
+
rule.declarations.push({
|
|
7905
|
+
id: "ir-auto-" + Date.now(),
|
|
7906
|
+
property: "justifyContent",
|
|
7907
|
+
value: "center",
|
|
7908
|
+
history: [{
|
|
7909
|
+
pass: "intent-recovery",
|
|
7910
|
+
action: "added-default",
|
|
7911
|
+
timestamp: Date.now(),
|
|
7912
|
+
reason: "Added flexbox centering default"
|
|
7913
|
+
}],
|
|
7914
|
+
meta: {}
|
|
7915
|
+
});
|
|
7916
|
+
}
|
|
7917
|
+
if (!hasAlign) {
|
|
7918
|
+
rule.declarations.push({
|
|
7919
|
+
id: "ir-auto-" + Date.now() + 1,
|
|
7920
|
+
property: "alignItems",
|
|
7921
|
+
value: "center",
|
|
7922
|
+
history: [{
|
|
7923
|
+
pass: "intent-recovery",
|
|
7924
|
+
action: "added-default",
|
|
7925
|
+
timestamp: Date.now(),
|
|
7926
|
+
reason: "Added flexbox centering default"
|
|
7927
|
+
}],
|
|
7928
|
+
meta: {}
|
|
7929
|
+
});
|
|
7930
|
+
}
|
|
7931
|
+
}
|
|
7932
|
+
if (decl.property === "position" && decl.value === "abs") {
|
|
7933
|
+
decl.value = "absolute";
|
|
7934
|
+
decl.history.push({
|
|
7935
|
+
pass: "intent-recovery",
|
|
7936
|
+
action: "corrected-value",
|
|
7937
|
+
timestamp: Date.now(),
|
|
7938
|
+
previous: "abs",
|
|
7939
|
+
reason: "abs \u2192 absolute"
|
|
7940
|
+
});
|
|
7941
|
+
}
|
|
7942
|
+
}
|
|
7943
|
+
}
|
|
7944
|
+
return ir;
|
|
7945
|
+
};
|
|
7946
|
+
var unitResolutionPass = (ir) => {
|
|
7947
|
+
for (const rule of ir.rules) {
|
|
7948
|
+
for (const decl of rule.declarations) {
|
|
7949
|
+
if (typeof decl.value === "number") {
|
|
7950
|
+
const unitless = ["opacity", "zIndex", "flex", "fontWeight", "lineHeight", "order"];
|
|
7951
|
+
if (!unitless.includes(decl.property)) {
|
|
7952
|
+
decl.value = decl.value + "px";
|
|
7953
|
+
decl.history.push({
|
|
7954
|
+
pass: "unit-resolution",
|
|
7955
|
+
action: "added-unit",
|
|
7956
|
+
timestamp: Date.now(),
|
|
7957
|
+
previous: decl.value,
|
|
7958
|
+
reason: "Added px unit to number value"
|
|
7959
|
+
});
|
|
7960
|
+
}
|
|
7961
|
+
}
|
|
7962
|
+
}
|
|
7963
|
+
}
|
|
7964
|
+
return ir;
|
|
7965
|
+
};
|
|
7966
|
+
var validationPass = (ir) => {
|
|
7967
|
+
for (const rule of ir.rules) {
|
|
7968
|
+
const position = rule.declarations.find((d) => d.property === "position");
|
|
7969
|
+
const zIndex = rule.declarations.find((d) => d.property === "zIndex" || d.property === "z-index");
|
|
7970
|
+
if (position && position.value === "static" && zIndex) {
|
|
7971
|
+
ir.diagnostics.push({
|
|
7972
|
+
id: "diag-" + Date.now(),
|
|
7973
|
+
nodeId: rule.id,
|
|
7974
|
+
severity: "warning",
|
|
7975
|
+
message: "z-index has no effect on static positioned elements",
|
|
7976
|
+
suggestion: "Change position to relative, absolute, or fixed",
|
|
7977
|
+
pass: "validation"
|
|
7978
|
+
});
|
|
7979
|
+
}
|
|
7980
|
+
const display = rule.declarations.find((d) => d.property === "display");
|
|
7981
|
+
const hasFlexProps = rule.declarations.some(
|
|
7982
|
+
(d) => ["justifyContent", "alignItems", "flexDirection", "flexWrap"].includes(d.property)
|
|
7983
|
+
);
|
|
7984
|
+
if (hasFlexProps && (!display || display.value !== "flex" && display.value !== "inline-flex")) {
|
|
7985
|
+
ir.diagnostics.push({
|
|
7986
|
+
id: "diag-" + Date.now() + 1,
|
|
7987
|
+
nodeId: rule.id,
|
|
7988
|
+
severity: "warning",
|
|
7989
|
+
message: "Flex properties require display: flex or display: inline-flex",
|
|
7990
|
+
pass: "validation"
|
|
7991
|
+
});
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7994
|
+
return ir;
|
|
7995
|
+
};
|
|
7996
|
+
var specificitySortPass = (ir) => {
|
|
7997
|
+
for (const rule of ir.rules) {
|
|
7998
|
+
let a = 0, b = 0, c = 0;
|
|
7999
|
+
const idMatches = rule.selector.match(/#[a-zA-Z0-9_-]+/g);
|
|
8000
|
+
if (idMatches) a += idMatches.length;
|
|
8001
|
+
const classMatches = rule.selector.match(/\.[a-zA-Z0-9_-]+/g);
|
|
8002
|
+
if (classMatches) b += classMatches.length;
|
|
8003
|
+
const pseudoMatches = rule.selector.match(/:[a-zA-Z-]+/g);
|
|
8004
|
+
if (pseudoMatches) b += pseudoMatches.length;
|
|
8005
|
+
const elemMatches = rule.selector.match(/^[a-zA-Z]+|[a-zA-Z]+(?=[.#[:])/g);
|
|
8006
|
+
if (elemMatches) c += elemMatches.length;
|
|
8007
|
+
rule.specificity = a * 1e4 + b * 100 + c;
|
|
8008
|
+
}
|
|
8009
|
+
ir.rules.sort((a, b) => a.specificity - b.specificity);
|
|
8010
|
+
return ir;
|
|
8011
|
+
};
|
|
8012
|
+
var deadEliminationPass = (ir) => {
|
|
8013
|
+
const before = ir.rules.length;
|
|
8014
|
+
ir.rules = ir.rules.filter((r) => !r.isDead);
|
|
8015
|
+
const eliminated = before - ir.rules.length;
|
|
8016
|
+
if (eliminated > 0) {
|
|
8017
|
+
ir.diagnostics.push({
|
|
8018
|
+
id: "diag-dead-" + Date.now(),
|
|
8019
|
+
nodeId: ir.id,
|
|
8020
|
+
severity: "info",
|
|
8021
|
+
message: "Eliminated " + eliminated + " dead rules",
|
|
8022
|
+
pass: "dead-elimination"
|
|
8023
|
+
});
|
|
8024
|
+
}
|
|
8025
|
+
return ir;
|
|
8026
|
+
};
|
|
8027
|
+
var atomicExtractionPass = (ir) => {
|
|
8028
|
+
const usageMap = /* @__PURE__ */ new Map();
|
|
8029
|
+
for (const rule of ir.rules) {
|
|
8030
|
+
for (const decl of rule.declarations) {
|
|
8031
|
+
const key = decl.property + ":" + decl.value;
|
|
8032
|
+
usageMap.set(key, (usageMap.get(key) || 0) + 1);
|
|
8033
|
+
}
|
|
8034
|
+
}
|
|
8035
|
+
for (const rule of ir.rules) {
|
|
8036
|
+
for (const decl of rule.declarations) {
|
|
8037
|
+
const key = decl.property + ":" + decl.value;
|
|
8038
|
+
const usage = usageMap.get(key) || 0;
|
|
8039
|
+
decl.meta.atomic = usage >= 3;
|
|
8040
|
+
decl.meta.usageCount = usage;
|
|
8041
|
+
}
|
|
8042
|
+
}
|
|
8043
|
+
return ir;
|
|
8044
|
+
};
|
|
8045
|
+
var mediaQueryPackingPass = (ir) => {
|
|
8046
|
+
const queryMap = /* @__PURE__ */ new Map();
|
|
8047
|
+
for (const rule of ir.rules) {
|
|
8048
|
+
for (const atRule of rule.atRules) {
|
|
8049
|
+
if (atRule.type === "media" && atRule.query) {
|
|
8050
|
+
const existing = queryMap.get(atRule.query) || [];
|
|
8051
|
+
existing.push(rule);
|
|
8052
|
+
queryMap.set(atRule.query, existing);
|
|
8053
|
+
}
|
|
8054
|
+
}
|
|
8055
|
+
}
|
|
8056
|
+
return ir;
|
|
8057
|
+
};
|
|
8058
|
+
var cssIfTranspilePass = (ir) => {
|
|
8059
|
+
for (const rule of ir.rules) {
|
|
8060
|
+
if (rule.conditions.length > 0) {
|
|
8061
|
+
rule.meta.hasCSSIf = true;
|
|
8062
|
+
}
|
|
8063
|
+
}
|
|
8064
|
+
return ir;
|
|
8065
|
+
};
|
|
8066
|
+
var cssCompressionPass = (ir) => {
|
|
8067
|
+
for (const rule of ir.rules) {
|
|
8068
|
+
for (const decl of rule.declarations) {
|
|
8069
|
+
if (typeof decl.value === "string" && /^#[0-9a-fA-F]{6}$/.test(decl.value)) {
|
|
8070
|
+
const hex = decl.value;
|
|
8071
|
+
if (hex[1] === hex[2] && hex[3] === hex[4] && hex[5] === hex[6]) {
|
|
8072
|
+
decl.value = "#" + hex[1] + hex[3] + hex[5];
|
|
8073
|
+
decl.history.push({
|
|
8074
|
+
pass: "css-compression",
|
|
8075
|
+
action: "shortened-hex",
|
|
8076
|
+
timestamp: Date.now(),
|
|
8077
|
+
previous: hex,
|
|
8078
|
+
reason: "Shortened hex color"
|
|
8079
|
+
});
|
|
8080
|
+
}
|
|
8081
|
+
}
|
|
8082
|
+
if (typeof decl.value === "string" && /^0\.\d+/.test(decl.value)) {
|
|
8083
|
+
const shortened = decl.value.replace(/^0\./, ".");
|
|
8084
|
+
decl.value = shortened;
|
|
8085
|
+
}
|
|
8086
|
+
}
|
|
8087
|
+
}
|
|
8088
|
+
return ir;
|
|
8089
|
+
};
|
|
8090
|
+
var diagnosticsExportPass = (ir) => {
|
|
8091
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8092
|
+
const unique = [];
|
|
8093
|
+
for (const diag of ir.diagnostics) {
|
|
8094
|
+
const key = diag.nodeId + ":" + diag.message;
|
|
8095
|
+
if (!seen.has(key)) {
|
|
8096
|
+
seen.add(key);
|
|
8097
|
+
unique.push(diag);
|
|
8098
|
+
}
|
|
8099
|
+
}
|
|
8100
|
+
ir.diagnostics = unique;
|
|
8101
|
+
return ir;
|
|
8102
|
+
};
|
|
8103
|
+
var DEFAULT_PIPELINE = [
|
|
8104
|
+
{
|
|
8105
|
+
name: "intent-recovery",
|
|
8106
|
+
priority: 1,
|
|
8107
|
+
description: "Fix typos and add defaults for common patterns",
|
|
8108
|
+
pass: intentRecoveryPass,
|
|
8109
|
+
requires: [],
|
|
8110
|
+
enabled: true
|
|
8111
|
+
},
|
|
8112
|
+
{
|
|
8113
|
+
name: "unit-resolution",
|
|
8114
|
+
priority: 2,
|
|
8115
|
+
description: "Resolve units and normalize values",
|
|
8116
|
+
pass: unitResolutionPass,
|
|
8117
|
+
requires: [],
|
|
8118
|
+
enabled: true
|
|
8119
|
+
},
|
|
8120
|
+
{
|
|
8121
|
+
name: "validation",
|
|
8122
|
+
priority: 3,
|
|
8123
|
+
description: "Run contrast checks and conflict detection",
|
|
8124
|
+
pass: validationPass,
|
|
8125
|
+
requires: ["intent-recovery"],
|
|
8126
|
+
enabled: true
|
|
8127
|
+
},
|
|
8128
|
+
{
|
|
8129
|
+
name: "specificity-sort",
|
|
8130
|
+
priority: 4,
|
|
8131
|
+
description: "Order rules by specificity",
|
|
8132
|
+
pass: specificitySortPass,
|
|
8133
|
+
requires: [],
|
|
8134
|
+
enabled: true
|
|
8135
|
+
},
|
|
8136
|
+
{
|
|
8137
|
+
name: "dead-elimination",
|
|
8138
|
+
priority: 5,
|
|
8139
|
+
description: "Remove unused selectors",
|
|
8140
|
+
pass: deadEliminationPass,
|
|
8141
|
+
requires: ["specificity-sort"],
|
|
8142
|
+
enabled: true
|
|
8143
|
+
},
|
|
8144
|
+
{
|
|
8145
|
+
name: "atomic-extraction",
|
|
8146
|
+
priority: 6,
|
|
8147
|
+
description: "Extract shared properties into atomic classes",
|
|
8148
|
+
pass: atomicExtractionPass,
|
|
8149
|
+
requires: ["unit-resolution"],
|
|
8150
|
+
enabled: true
|
|
8151
|
+
},
|
|
8152
|
+
{
|
|
8153
|
+
name: "media-query-packing",
|
|
8154
|
+
priority: 7,
|
|
8155
|
+
description: "Group same-query rules together",
|
|
8156
|
+
pass: mediaQueryPackingPass,
|
|
8157
|
+
requires: ["specificity-sort"],
|
|
8158
|
+
enabled: true
|
|
8159
|
+
},
|
|
8160
|
+
{
|
|
8161
|
+
name: "css-if-transpile",
|
|
8162
|
+
priority: 8,
|
|
8163
|
+
description: "Transpile conditional patterns to native CSS if()",
|
|
8164
|
+
pass: cssIfTranspilePass,
|
|
8165
|
+
requires: ["intent-recovery"],
|
|
8166
|
+
enabled: true
|
|
8167
|
+
},
|
|
8168
|
+
{
|
|
8169
|
+
name: "css-compression",
|
|
8170
|
+
priority: 9,
|
|
8171
|
+
description: "Minify CSS output",
|
|
8172
|
+
pass: cssCompressionPass,
|
|
8173
|
+
requires: [],
|
|
8174
|
+
enabled: true
|
|
8175
|
+
},
|
|
8176
|
+
{
|
|
8177
|
+
name: "diagnostics-export",
|
|
8178
|
+
priority: 10,
|
|
8179
|
+
description: "Collect and organize diagnostics",
|
|
8180
|
+
pass: diagnosticsExportPass,
|
|
8181
|
+
requires: ["validation"],
|
|
8182
|
+
enabled: true
|
|
8183
|
+
}
|
|
8184
|
+
];
|
|
8185
|
+
var PassManager = class {
|
|
8186
|
+
passes = [];
|
|
8187
|
+
results = [];
|
|
8188
|
+
constructor(passes = DEFAULT_PIPELINE) {
|
|
8189
|
+
this.passes = passes.filter((p) => p.enabled);
|
|
8190
|
+
this.validateDependencies();
|
|
8191
|
+
}
|
|
8192
|
+
/**
|
|
8193
|
+
* Validate that all pass dependencies are satisfied.
|
|
8194
|
+
*/
|
|
8195
|
+
validateDependencies() {
|
|
8196
|
+
const passNames = new Set(this.passes.map((p) => p.name));
|
|
8197
|
+
for (const pass of this.passes) {
|
|
8198
|
+
for (const req of pass.requires) {
|
|
8199
|
+
if (!passNames.has(req)) {
|
|
8200
|
+
throw new Error(
|
|
8201
|
+
'Pass "' + pass.name + '" requires "' + req + '" but it is not in the pipeline'
|
|
8202
|
+
);
|
|
8203
|
+
}
|
|
8204
|
+
}
|
|
8205
|
+
}
|
|
8206
|
+
}
|
|
8207
|
+
/**
|
|
8208
|
+
* Topological sort passes by dependencies.
|
|
8209
|
+
* Passes with no dependencies run first.
|
|
8210
|
+
*/
|
|
8211
|
+
sortByDependencies() {
|
|
8212
|
+
const sorted = [];
|
|
8213
|
+
const remaining = [...this.passes];
|
|
8214
|
+
const satisfied = /* @__PURE__ */ new Set();
|
|
8215
|
+
while (remaining.length > 0) {
|
|
8216
|
+
const ready = remaining.findIndex(
|
|
8217
|
+
(p) => p.requires.every((req) => satisfied.has(req))
|
|
8218
|
+
);
|
|
8219
|
+
if (ready === -1) {
|
|
8220
|
+
throw new Error("Circular dependency detected in pass pipeline");
|
|
8221
|
+
}
|
|
8222
|
+
const pass = remaining.splice(ready, 1)[0];
|
|
8223
|
+
sorted.push(pass);
|
|
8224
|
+
satisfied.add(pass.name);
|
|
8225
|
+
}
|
|
8226
|
+
return sorted;
|
|
8227
|
+
}
|
|
8228
|
+
/**
|
|
8229
|
+
* Run the full pipeline on an IR.
|
|
8230
|
+
*/
|
|
8231
|
+
run(ir) {
|
|
8232
|
+
const startTime = Date.now();
|
|
8233
|
+
const sorted = this.sortByDependencies();
|
|
8234
|
+
this.results = [];
|
|
8235
|
+
let current = ir;
|
|
8236
|
+
const from = countNodes(ir);
|
|
8237
|
+
for (const pass of sorted) {
|
|
8238
|
+
const passStart = Date.now();
|
|
8239
|
+
const before = countNodes(current);
|
|
8240
|
+
try {
|
|
8241
|
+
current = pass.pass(current);
|
|
8242
|
+
} catch (err) {
|
|
8243
|
+
this.results.push({
|
|
8244
|
+
name: pass.name,
|
|
8245
|
+
duration: Date.now() - passStart,
|
|
8246
|
+
nodesBefore: before.rules + before.declarations,
|
|
8247
|
+
nodesAfter: before.rules + before.declarations,
|
|
8248
|
+
changes: 0,
|
|
8249
|
+
errors: [err.message]
|
|
8250
|
+
});
|
|
8251
|
+
continue;
|
|
8252
|
+
}
|
|
8253
|
+
const after = countNodes(current);
|
|
8254
|
+
this.results.push({
|
|
8255
|
+
name: pass.name,
|
|
8256
|
+
duration: Date.now() - passStart,
|
|
8257
|
+
nodesBefore: before.rules + before.declarations,
|
|
8258
|
+
nodesAfter: after.rules + after.declarations,
|
|
8259
|
+
changes: Math.abs(after.rules + after.declarations - (before.rules + before.declarations)),
|
|
8260
|
+
errors: []
|
|
8261
|
+
});
|
|
8262
|
+
}
|
|
8263
|
+
const totalDuration = Date.now() - startTime;
|
|
8264
|
+
const to = countNodes(current);
|
|
8265
|
+
return {
|
|
8266
|
+
ir: current,
|
|
8267
|
+
css: "",
|
|
8268
|
+
// Will be generated separately
|
|
8269
|
+
results: this.results,
|
|
8270
|
+
totalDuration,
|
|
8271
|
+
summary: "Pipeline complete: " + this.results.length + " passes in " + totalDuration + "ms. Nodes: " + (from.rules + from.declarations) + " \u2192 " + (to.rules + to.declarations)
|
|
8272
|
+
};
|
|
8273
|
+
}
|
|
8274
|
+
/**
|
|
8275
|
+
* Get results from the last run.
|
|
8276
|
+
*/
|
|
8277
|
+
getResults() {
|
|
8278
|
+
return this.results;
|
|
8279
|
+
}
|
|
8280
|
+
/**
|
|
8281
|
+
* Print a human-readable report of pass results.
|
|
8282
|
+
*/
|
|
8283
|
+
report() {
|
|
8284
|
+
const lines = [
|
|
8285
|
+
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
8286
|
+
" ChainCSS Multi-Pass Pipeline Report",
|
|
8287
|
+
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"
|
|
8288
|
+
];
|
|
8289
|
+
for (const result of this.results) {
|
|
8290
|
+
const status = result.errors.length > 0 ? "\u274C" : "\u2713";
|
|
8291
|
+
lines.push(
|
|
8292
|
+
" " + status + " " + result.name.padEnd(22) + " " + result.duration.toString().padStart(4) + "ms nodes: " + result.nodesBefore + " \u2192 " + result.nodesAfter
|
|
8293
|
+
);
|
|
8294
|
+
if (result.errors.length > 0) {
|
|
8295
|
+
for (const err of result.errors) {
|
|
8296
|
+
lines.push(" \u26A0 " + err);
|
|
8297
|
+
}
|
|
8298
|
+
}
|
|
8299
|
+
}
|
|
8300
|
+
lines.push("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
8301
|
+
return lines.join("\n");
|
|
8302
|
+
}
|
|
8303
|
+
/**
|
|
8304
|
+
* Add a custom pass to the pipeline.
|
|
8305
|
+
*/
|
|
8306
|
+
addPass(pass) {
|
|
8307
|
+
this.passes.push(pass);
|
|
8308
|
+
return this;
|
|
8309
|
+
}
|
|
8310
|
+
/**
|
|
8311
|
+
* Remove a pass by name.
|
|
8312
|
+
*/
|
|
8313
|
+
removePass(name) {
|
|
8314
|
+
this.passes = this.passes.filter((p) => p.name !== name);
|
|
8315
|
+
return this;
|
|
8316
|
+
}
|
|
8317
|
+
/**
|
|
8318
|
+
* Enable/disable a pass.
|
|
8319
|
+
*/
|
|
8320
|
+
setPassEnabled(name, enabled) {
|
|
8321
|
+
const pass = this.passes.find((p) => p.name === name);
|
|
8322
|
+
if (pass) pass.enabled = enabled;
|
|
8323
|
+
this.passes = this.passes.filter((p) => p.enabled);
|
|
8324
|
+
return this;
|
|
8325
|
+
}
|
|
8326
|
+
/**
|
|
8327
|
+
* Get the list of pass names in execution order.
|
|
8328
|
+
*/
|
|
8329
|
+
getPassOrder() {
|
|
8330
|
+
return this.sortByDependencies().map((p) => p.name);
|
|
8331
|
+
}
|
|
8332
|
+
};
|
|
8333
|
+
function runDefaultPipeline(ir) {
|
|
8334
|
+
const manager = new PassManager(DEFAULT_PIPELINE.map((p) => ({ ...p })));
|
|
8335
|
+
return manager.run(ir);
|
|
8336
|
+
}
|
|
8337
|
+
|
|
8338
|
+
// src/compiler/constraint-solver.ts
|
|
8339
|
+
function tokenize(expr) {
|
|
8340
|
+
const tokens3 = [];
|
|
8341
|
+
let current = "";
|
|
8342
|
+
let inParens = 0;
|
|
8343
|
+
for (const char of expr) {
|
|
8344
|
+
if (char === "(") {
|
|
8345
|
+
inParens++;
|
|
8346
|
+
current += char;
|
|
8347
|
+
} else if (char === ")") {
|
|
8348
|
+
inParens--;
|
|
8349
|
+
current += char;
|
|
8350
|
+
} else if (char === " " && inParens === 0) {
|
|
8351
|
+
if (current) tokens3.push(current);
|
|
8352
|
+
current = "";
|
|
8353
|
+
} else {
|
|
8354
|
+
current += char;
|
|
8355
|
+
}
|
|
8356
|
+
}
|
|
8357
|
+
if (current) tokens3.push(current);
|
|
8358
|
+
return tokens3;
|
|
8359
|
+
}
|
|
8360
|
+
function parseExpression(expr) {
|
|
8361
|
+
const trimmed = expr.trim();
|
|
8362
|
+
const funcMatch = trimmed.match(/^([a-zA-Z]+)\((.+)\)$/);
|
|
8363
|
+
if (funcMatch) {
|
|
8364
|
+
const args = funcMatch[2].split(",").map((a) => a.trim());
|
|
8365
|
+
return {
|
|
8366
|
+
left: "",
|
|
8367
|
+
operator: "function",
|
|
8368
|
+
right: "",
|
|
8369
|
+
isFunction: true,
|
|
8370
|
+
functionName: funcMatch[1],
|
|
8371
|
+
functionArgs: args
|
|
8372
|
+
};
|
|
8373
|
+
}
|
|
8374
|
+
const tokens3 = tokenize(trimmed);
|
|
8375
|
+
if (tokens3.length === 3) {
|
|
8376
|
+
return {
|
|
8377
|
+
left: tokens3[0],
|
|
8378
|
+
operator: tokens3[1],
|
|
8379
|
+
right: tokens3[2],
|
|
8380
|
+
isFunction: false
|
|
8381
|
+
};
|
|
8382
|
+
}
|
|
8383
|
+
return {
|
|
8384
|
+
left: trimmed,
|
|
8385
|
+
operator: "",
|
|
8386
|
+
right: "",
|
|
8387
|
+
isFunction: false
|
|
8388
|
+
};
|
|
8389
|
+
}
|
|
8390
|
+
var KNOWN_REFERENCES = {
|
|
8391
|
+
"parent": "100%",
|
|
8392
|
+
"parent.width": "100%",
|
|
8393
|
+
"parent.height": "100%",
|
|
8394
|
+
"viewport": "100vw",
|
|
8395
|
+
"viewport.width": "100vw",
|
|
8396
|
+
"viewport.height": "100vh",
|
|
8397
|
+
"self": "100%",
|
|
8398
|
+
"self.width": "100%",
|
|
8399
|
+
"self.height": "100%"
|
|
8400
|
+
};
|
|
8401
|
+
function resolveReference(ref) {
|
|
8402
|
+
return KNOWN_REFERENCES[ref] || ref;
|
|
8403
|
+
}
|
|
8404
|
+
function resolveConstraint(constraint, context) {
|
|
8405
|
+
const { property, operator, expression } = constraint;
|
|
8406
|
+
const parsed = parseExpression(expression);
|
|
8407
|
+
if (operator === "<" && expression === "parent") {
|
|
8408
|
+
return {
|
|
8409
|
+
constraint,
|
|
8410
|
+
cssProperty: "max-" + property,
|
|
8411
|
+
cssValue: "100%",
|
|
8412
|
+
method: "direct",
|
|
8413
|
+
explanation: property + " < parent \u2192 max-" + property + ": 100%"
|
|
8414
|
+
};
|
|
8415
|
+
}
|
|
8416
|
+
if (operator === ">" && expression === "parent") {
|
|
8417
|
+
return {
|
|
8418
|
+
constraint,
|
|
8419
|
+
cssProperty: "min-" + property,
|
|
8420
|
+
cssValue: "100%",
|
|
8421
|
+
method: "direct",
|
|
8422
|
+
explanation: property + " > parent \u2192 min-" + property + ": 100%"
|
|
8423
|
+
};
|
|
8424
|
+
}
|
|
8425
|
+
if (operator === "=" && parsed.operator === "*") {
|
|
8426
|
+
const leftRef = resolveReference(parsed.left);
|
|
8427
|
+
const rightNum = parseFloat(parsed.right);
|
|
8428
|
+
if (!isNaN(rightNum)) {
|
|
8429
|
+
if (property === "height" && parsed.left === "width" || property === "width" && parsed.left === "height") {
|
|
8430
|
+
const ratio = rightNum;
|
|
8431
|
+
const gcd = findGCD(Math.round(ratio * 100), 100);
|
|
8432
|
+
const num = Math.round(ratio * 100) / gcd;
|
|
8433
|
+
const den = 100 / gcd;
|
|
8434
|
+
return {
|
|
8435
|
+
constraint,
|
|
8436
|
+
cssProperty: "aspect-ratio",
|
|
8437
|
+
cssValue: num + " / " + den,
|
|
8438
|
+
method: "aspect-ratio",
|
|
8439
|
+
explanation: property + " = " + expression + " \u2192 aspect-ratio: " + num + "/" + den
|
|
8440
|
+
};
|
|
8441
|
+
}
|
|
8442
|
+
return {
|
|
8443
|
+
constraint,
|
|
8444
|
+
cssProperty: property,
|
|
8445
|
+
cssValue: "calc(" + leftRef + " * " + rightNum + ")",
|
|
8446
|
+
method: "calc",
|
|
8447
|
+
explanation: property + " = " + expression + " \u2192 calc(" + leftRef + " * " + rightNum + ")"
|
|
8448
|
+
};
|
|
8449
|
+
}
|
|
8450
|
+
}
|
|
8451
|
+
if (operator === "=" && parsed.operator === "/") {
|
|
8452
|
+
const leftRef = resolveReference(parsed.left);
|
|
8453
|
+
const rightNum = parseFloat(parsed.right);
|
|
8454
|
+
if (!isNaN(rightNum)) {
|
|
8455
|
+
return {
|
|
8456
|
+
constraint,
|
|
8457
|
+
cssProperty: property,
|
|
8458
|
+
cssValue: "calc(" + leftRef + " / " + rightNum + ")",
|
|
8459
|
+
method: "calc",
|
|
8460
|
+
explanation: property + " = " + expression + " \u2192 calc(" + leftRef + " / " + rightNum + ")"
|
|
8461
|
+
};
|
|
8462
|
+
}
|
|
8463
|
+
}
|
|
8464
|
+
if (parsed.isFunction && parsed.functionName) {
|
|
8465
|
+
const resolvedArgs = (parsed.functionArgs || []).map(resolveReference);
|
|
8466
|
+
return {
|
|
8467
|
+
constraint,
|
|
8468
|
+
cssProperty: property,
|
|
8469
|
+
cssValue: parsed.functionName + "(" + resolvedArgs.join(", ") + ")",
|
|
8470
|
+
method: parsed.functionName,
|
|
8471
|
+
explanation: property + " = " + expression + " \u2192 " + parsed.functionName + "()"
|
|
8472
|
+
};
|
|
8473
|
+
}
|
|
8474
|
+
if (operator === "=" && !parsed.operator) {
|
|
8475
|
+
const resolved = resolveReference(expression);
|
|
8476
|
+
return {
|
|
8477
|
+
constraint,
|
|
8478
|
+
cssProperty: property,
|
|
8479
|
+
cssValue: resolved,
|
|
8480
|
+
method: "direct",
|
|
8481
|
+
explanation: property + " = " + expression + " \u2192 " + resolved
|
|
8482
|
+
};
|
|
8483
|
+
}
|
|
8484
|
+
if (operator === "=" && expression.includes("vw") || expression.includes("vh")) {
|
|
8485
|
+
return {
|
|
8486
|
+
constraint,
|
|
8487
|
+
cssProperty: property,
|
|
8488
|
+
cssValue: expression,
|
|
8489
|
+
method: "direct",
|
|
8490
|
+
explanation: property + " = " + expression
|
|
8491
|
+
};
|
|
8492
|
+
}
|
|
8493
|
+
return {
|
|
8494
|
+
constraint,
|
|
8495
|
+
cssProperty: property,
|
|
8496
|
+
cssValue: expression,
|
|
8497
|
+
method: "direct",
|
|
8498
|
+
explanation: property + " = " + expression + " (passthrough)"
|
|
8499
|
+
};
|
|
8500
|
+
}
|
|
8501
|
+
function resolveStickyUntil(selector, untilSelector) {
|
|
8502
|
+
return {
|
|
8503
|
+
constraint: {
|
|
8504
|
+
property: "position",
|
|
8505
|
+
operator: "=",
|
|
8506
|
+
expression: "sticky until " + untilSelector
|
|
8507
|
+
},
|
|
8508
|
+
cssProperty: "position",
|
|
8509
|
+
cssValue: "sticky; top: 0; animation: sticky-" + selector.replace(".", "") + " 1s linear both; animation-timeline: scroll(); animation-range: contain 0% contain 100%",
|
|
8510
|
+
method: "sticky",
|
|
8511
|
+
explanation: "sticky until " + untilSelector + " \u2192 position: sticky + scroll-timeline"
|
|
8512
|
+
};
|
|
8513
|
+
}
|
|
8514
|
+
function resolveContainerQuery(property, operator, value, condition) {
|
|
8515
|
+
const widthMatch = condition.match(/>\s*(\d+)(px|rem|em)?/);
|
|
8516
|
+
if (widthMatch) {
|
|
8517
|
+
const width = widthMatch[1] + (widthMatch[2] || "px");
|
|
8518
|
+
return {
|
|
8519
|
+
atRule: {
|
|
8520
|
+
type: "container",
|
|
8521
|
+
query: "(min-width: " + width + ")",
|
|
8522
|
+
declarations: [{ property, value }]
|
|
8523
|
+
},
|
|
8524
|
+
explanation: property + " " + operator + " " + value + " when > " + width + " \u2192 @container (min-width: " + width + ")"
|
|
8525
|
+
};
|
|
8526
|
+
}
|
|
8527
|
+
return {
|
|
8528
|
+
atRule: { type: "container", query: condition, declarations: [{ property, value }] },
|
|
8529
|
+
explanation: property + " when " + condition + " \u2192 @container " + condition
|
|
8530
|
+
};
|
|
8531
|
+
}
|
|
8532
|
+
var constraintSolverPass = (ir) => {
|
|
8533
|
+
for (const rule of ir.rules) {
|
|
8534
|
+
const constraints = rule.meta._constraints || [];
|
|
8535
|
+
if (constraints.length === 0) continue;
|
|
8536
|
+
for (const constraint of constraints) {
|
|
8537
|
+
const resolved = resolveConstraint(constraint);
|
|
8538
|
+
rule.declarations.push({
|
|
8539
|
+
id: "constraint-" + Date.now() + "-" + Math.random().toString(36).slice(2, 6),
|
|
8540
|
+
property: resolved.cssProperty,
|
|
8541
|
+
value: resolved.cssValue,
|
|
8542
|
+
history: [{
|
|
8543
|
+
pass: "constraint-solver",
|
|
8544
|
+
action: "resolved-constraint",
|
|
8545
|
+
timestamp: Date.now(),
|
|
8546
|
+
reason: resolved.explanation
|
|
8547
|
+
}],
|
|
8548
|
+
meta: { constraint }
|
|
8549
|
+
});
|
|
8550
|
+
}
|
|
8551
|
+
}
|
|
8552
|
+
return ir;
|
|
8553
|
+
};
|
|
8554
|
+
function parseConstraint(property, expression) {
|
|
8555
|
+
let operator = "=";
|
|
8556
|
+
let cleanExpr = expression;
|
|
8557
|
+
const opMatch = expression.match(/^([<>=!≈]+)\s*(.*)/);
|
|
8558
|
+
if (opMatch) {
|
|
8559
|
+
operator = opMatch[1];
|
|
8560
|
+
cleanExpr = opMatch[2];
|
|
8561
|
+
}
|
|
8562
|
+
let condition;
|
|
8563
|
+
const condMatch = cleanExpr.match(/^(.+)\s+when\s+(.+)$/);
|
|
8564
|
+
if (condMatch) {
|
|
8565
|
+
cleanExpr = condMatch[1];
|
|
8566
|
+
condition = condMatch[2];
|
|
8567
|
+
}
|
|
8568
|
+
return {
|
|
8569
|
+
property,
|
|
8570
|
+
operator,
|
|
8571
|
+
expression: cleanExpr,
|
|
8572
|
+
condition
|
|
8573
|
+
};
|
|
8574
|
+
}
|
|
8575
|
+
function findGCD(a, b) {
|
|
8576
|
+
return b === 0 ? a : findGCD(b, a % b);
|
|
8577
|
+
}
|
|
8578
|
+
var constraintSolver = {
|
|
8579
|
+
resolve: resolveConstraint,
|
|
8580
|
+
resolveStickyUntil,
|
|
8581
|
+
resolveContainerQuery,
|
|
8582
|
+
parseConstraint,
|
|
8583
|
+
parseExpression,
|
|
8584
|
+
resolveReference,
|
|
8585
|
+
pass: constraintSolverPass
|
|
8586
|
+
};
|
|
8587
|
+
|
|
8588
|
+
// src/compiler/layout-intelligence.ts
|
|
8589
|
+
var LAYOUT_PATTERNS = [
|
|
8590
|
+
// --- Flexbox Patterns ---
|
|
8591
|
+
{
|
|
8592
|
+
name: "stack-center",
|
|
8593
|
+
description: "Vertical stack with centered items",
|
|
8594
|
+
macro: "stack('vertical center')",
|
|
8595
|
+
example: "chain.stack('vertical center')",
|
|
8596
|
+
required: {
|
|
8597
|
+
display: "flex",
|
|
8598
|
+
flexDirection: "column",
|
|
8599
|
+
justifyContent: "center",
|
|
8600
|
+
alignItems: "center"
|
|
8601
|
+
},
|
|
8602
|
+
optional: ["gap", "padding"],
|
|
8603
|
+
minMatches: 4
|
|
8604
|
+
},
|
|
8605
|
+
{
|
|
8606
|
+
name: "stack-horizontal",
|
|
8607
|
+
description: "Horizontal stack with centered items",
|
|
8608
|
+
macro: "stack('horizontal center')",
|
|
8609
|
+
example: "chain.stack('horizontal center')",
|
|
8610
|
+
required: {
|
|
8611
|
+
display: "flex",
|
|
8612
|
+
flexDirection: "row",
|
|
8613
|
+
justifyContent: "center",
|
|
8614
|
+
alignItems: "center"
|
|
8615
|
+
},
|
|
8616
|
+
optional: ["gap"],
|
|
8617
|
+
minMatches: 4
|
|
8618
|
+
},
|
|
8619
|
+
{
|
|
8620
|
+
name: "flex-center",
|
|
8621
|
+
description: "Flexbox absolute centering",
|
|
8622
|
+
macro: "center()",
|
|
8623
|
+
example: "chain.center()",
|
|
8624
|
+
required: {
|
|
8625
|
+
display: "flex",
|
|
8626
|
+
justifyContent: "center",
|
|
8627
|
+
alignItems: "center"
|
|
8628
|
+
},
|
|
8629
|
+
minMatches: 3
|
|
8630
|
+
},
|
|
8631
|
+
{
|
|
8632
|
+
name: "flex-between",
|
|
8633
|
+
description: "Flexbox space-between alignment",
|
|
8634
|
+
macro: "stack('between')",
|
|
8635
|
+
example: "chain.stack('between')",
|
|
8636
|
+
required: {
|
|
8637
|
+
display: "flex",
|
|
8638
|
+
justifyContent: "space-between",
|
|
8639
|
+
alignItems: "center"
|
|
8640
|
+
},
|
|
8641
|
+
minMatches: 3
|
|
8642
|
+
},
|
|
8643
|
+
{
|
|
8644
|
+
name: "flex-row-wrap",
|
|
8645
|
+
description: "Flex row with wrapping",
|
|
8646
|
+
macro: "chain.flex().flexDir('row').flexWrap('wrap')",
|
|
8647
|
+
example: "chain.flex().flexDir('row').flexWrap('wrap')",
|
|
8648
|
+
required: {
|
|
8649
|
+
display: "flex",
|
|
8650
|
+
flexDirection: "row",
|
|
8651
|
+
flexWrap: "wrap"
|
|
8652
|
+
},
|
|
8653
|
+
minMatches: 3
|
|
8654
|
+
},
|
|
8655
|
+
// --- Grid Patterns ---
|
|
8656
|
+
{
|
|
8657
|
+
name: "grid-center",
|
|
8658
|
+
description: "Grid with centered items",
|
|
8659
|
+
macro: "gridCenter()",
|
|
8660
|
+
example: "chain.gridCenter()",
|
|
8661
|
+
required: {
|
|
8662
|
+
display: "grid",
|
|
8663
|
+
placeItems: "center"
|
|
8664
|
+
},
|
|
8665
|
+
minMatches: 2
|
|
8666
|
+
},
|
|
8667
|
+
{
|
|
8668
|
+
name: "grid-auto-fit",
|
|
8669
|
+
description: "Responsive auto-fit grid",
|
|
8670
|
+
macro: "gridList()",
|
|
8671
|
+
example: "chain.gridList()",
|
|
8672
|
+
required: {
|
|
8673
|
+
display: "grid"
|
|
8674
|
+
},
|
|
8675
|
+
optional: ["gridTemplateColumns", "gap"],
|
|
8676
|
+
minMatches: 1
|
|
8677
|
+
// Lower because gridTemplateColumns varies
|
|
8678
|
+
},
|
|
8679
|
+
// --- Positioning Patterns ---
|
|
8680
|
+
{
|
|
8681
|
+
name: "absolute-center",
|
|
8682
|
+
description: "Absolute positioning centering",
|
|
8683
|
+
macro: "absolute({ top: '50%', left: '50%' })",
|
|
8684
|
+
example: "chain.absolute({ top: '50%', left: '50%' }).transform('translate(-50%, -50%)')",
|
|
8685
|
+
required: {
|
|
8686
|
+
position: "absolute",
|
|
8687
|
+
top: "50%",
|
|
8688
|
+
left: "50%"
|
|
8689
|
+
},
|
|
8690
|
+
optional: ["transform"],
|
|
8691
|
+
minMatches: 3
|
|
8692
|
+
},
|
|
8693
|
+
{
|
|
8694
|
+
name: "sticky-top",
|
|
8695
|
+
description: "Sticky element at top",
|
|
8696
|
+
macro: "stickyHeader()",
|
|
8697
|
+
example: "chain.stickyHeader()",
|
|
8698
|
+
// from intent macros
|
|
8699
|
+
required: {
|
|
8700
|
+
position: "sticky",
|
|
8701
|
+
top: "0"
|
|
8702
|
+
},
|
|
8703
|
+
optional: ["zIndex", "backgroundColor", "backdropFilter"],
|
|
8704
|
+
minMatches: 2
|
|
8705
|
+
},
|
|
8706
|
+
// --- Sizing Patterns ---
|
|
8707
|
+
{
|
|
8708
|
+
name: "full-size",
|
|
8709
|
+
description: "Full width and height",
|
|
8710
|
+
macro: "chain.size('100%')",
|
|
8711
|
+
example: "chain.size('100%')",
|
|
8712
|
+
required: {
|
|
8713
|
+
width: "100%",
|
|
8714
|
+
height: "100%"
|
|
8715
|
+
},
|
|
8716
|
+
minMatches: 2
|
|
8717
|
+
},
|
|
8718
|
+
{
|
|
8719
|
+
name: "pill-shape",
|
|
8720
|
+
description: "Fully rounded pill element",
|
|
8721
|
+
macro: "pill()",
|
|
8722
|
+
example: "chain.pill()",
|
|
8723
|
+
required: {
|
|
8724
|
+
borderRadius: "9999px"
|
|
8725
|
+
},
|
|
8726
|
+
optional: ["padding", "display"],
|
|
8727
|
+
minMatches: 1
|
|
8728
|
+
},
|
|
8729
|
+
// --- Intent Macros (from intent-engine.ts) ---
|
|
8730
|
+
{
|
|
8731
|
+
name: "sticky-header",
|
|
8732
|
+
description: "Sticky header with backdrop blur",
|
|
8733
|
+
macro: "stickyHeader()",
|
|
8734
|
+
example: "chain.stickyHeader()",
|
|
8735
|
+
required: {
|
|
8736
|
+
position: "sticky",
|
|
8737
|
+
top: "0"
|
|
8738
|
+
},
|
|
8739
|
+
optional: ["zIndex", "backgroundColor", "backdropFilter", "borderBottom", "padding"],
|
|
8740
|
+
minMatches: 2
|
|
8741
|
+
},
|
|
8742
|
+
{
|
|
8743
|
+
name: "card-layout",
|
|
8744
|
+
description: "Card container with shadow and hover lift",
|
|
8745
|
+
macro: "card()",
|
|
8746
|
+
example: "chain.card()",
|
|
8747
|
+
required: {
|
|
8748
|
+
borderRadius: "12px",
|
|
8749
|
+
overflow: "hidden"
|
|
8750
|
+
},
|
|
8751
|
+
optional: ["display", "flexDirection", "backgroundColor", "boxShadow", "transition"],
|
|
8752
|
+
minMatches: 2
|
|
8753
|
+
},
|
|
8754
|
+
{
|
|
8755
|
+
name: "hero-section",
|
|
8756
|
+
description: "Full-width centered hero",
|
|
8757
|
+
macro: "hero()",
|
|
8758
|
+
example: "chain.hero()",
|
|
8759
|
+
required: {
|
|
8760
|
+
display: "flex",
|
|
8761
|
+
flexDirection: "column",
|
|
8762
|
+
justifyContent: "center",
|
|
8763
|
+
alignItems: "center",
|
|
8764
|
+
width: "100%"
|
|
8765
|
+
},
|
|
8766
|
+
optional: ["minHeight", "padding", "textAlign"],
|
|
8767
|
+
minMatches: 4
|
|
8768
|
+
},
|
|
8769
|
+
{
|
|
8770
|
+
name: "container-layout",
|
|
8771
|
+
description: "Centered max-width container",
|
|
8772
|
+
macro: "container()",
|
|
8773
|
+
example: "chain.container()",
|
|
8774
|
+
required: {
|
|
8775
|
+
marginLeft: "auto",
|
|
8776
|
+
marginRight: "auto"
|
|
8777
|
+
},
|
|
8778
|
+
optional: ["width", "maxWidth", "paddingLeft", "paddingRight"],
|
|
8779
|
+
minMatches: 2
|
|
8780
|
+
},
|
|
8781
|
+
{
|
|
8782
|
+
name: "sidebar-layout",
|
|
8783
|
+
description: "Sidebar + main content grid",
|
|
8784
|
+
macro: "sidebar()",
|
|
8785
|
+
example: "chain.sidebar()",
|
|
8786
|
+
required: {
|
|
8787
|
+
display: "grid"
|
|
8788
|
+
},
|
|
8789
|
+
optional: ["gridTemplateColumns", "gap", "minHeight"],
|
|
8790
|
+
minMatches: 1
|
|
8791
|
+
},
|
|
8792
|
+
{
|
|
8793
|
+
name: "grid-list",
|
|
8794
|
+
description: "Responsive auto-fit grid list",
|
|
8795
|
+
macro: "gridList()",
|
|
8796
|
+
example: "chain.gridList()",
|
|
8797
|
+
required: {
|
|
8798
|
+
display: "grid"
|
|
8799
|
+
},
|
|
8800
|
+
optional: ["gridTemplateColumns", "gap"],
|
|
8801
|
+
minMatches: 1
|
|
8802
|
+
},
|
|
8803
|
+
{
|
|
8804
|
+
name: "truncate-text",
|
|
8805
|
+
description: "Single-line text truncation with ellipsis",
|
|
8806
|
+
macro: "truncate()",
|
|
8807
|
+
example: "chain.truncate()",
|
|
8808
|
+
required: {
|
|
8809
|
+
overflow: "hidden",
|
|
8810
|
+
textOverflow: "ellipsis",
|
|
8811
|
+
whiteSpace: "nowrap"
|
|
8812
|
+
},
|
|
8813
|
+
minMatches: 3
|
|
8814
|
+
},
|
|
8815
|
+
{
|
|
8816
|
+
name: "sr-only",
|
|
8817
|
+
description: "Screen-reader only element",
|
|
8818
|
+
macro: "srOnly()",
|
|
8819
|
+
example: "chain.srOnly()",
|
|
8820
|
+
required: {
|
|
8821
|
+
position: "absolute",
|
|
8822
|
+
width: "1px",
|
|
8823
|
+
height: "1px"
|
|
8824
|
+
},
|
|
8825
|
+
optional: ["padding", "margin", "overflow", "clip"],
|
|
8826
|
+
minMatches: 2
|
|
8827
|
+
},
|
|
8828
|
+
// --- Chain.ts Special Methods ---
|
|
8829
|
+
{
|
|
8830
|
+
name: "inline-flex",
|
|
8831
|
+
description: "Inline flex container",
|
|
8832
|
+
macro: "inlineFlex()",
|
|
8833
|
+
example: "chain.inlineFlex()",
|
|
8834
|
+
required: {
|
|
8835
|
+
display: "inline-flex"
|
|
8836
|
+
},
|
|
8837
|
+
minMatches: 1
|
|
8838
|
+
},
|
|
8839
|
+
{
|
|
8840
|
+
name: "inline-grid",
|
|
8841
|
+
description: "Inline grid container",
|
|
8842
|
+
macro: "inlineGrid()",
|
|
8843
|
+
example: "chain.inlineGrid()",
|
|
8844
|
+
required: {
|
|
8845
|
+
display: "inline-grid"
|
|
8846
|
+
},
|
|
8847
|
+
minMatches: 1
|
|
8848
|
+
},
|
|
8849
|
+
{
|
|
8850
|
+
name: "flex-center-direction",
|
|
8851
|
+
description: "Flex centering with direction",
|
|
8852
|
+
macro: "flexCenter('row')",
|
|
8853
|
+
example: "chain.flexCenter('col')",
|
|
8854
|
+
required: {
|
|
8855
|
+
display: "flex",
|
|
8856
|
+
justifyContent: "center",
|
|
8857
|
+
alignItems: "center"
|
|
8858
|
+
},
|
|
8859
|
+
optional: ["flexDirection"],
|
|
8860
|
+
minMatches: 3
|
|
8861
|
+
},
|
|
8862
|
+
{
|
|
8863
|
+
name: "fixed-position",
|
|
8864
|
+
description: "Fixed positioning",
|
|
8865
|
+
macro: "fixed()",
|
|
8866
|
+
example: "chain.fixed({ top: 0 })",
|
|
8867
|
+
required: {
|
|
8868
|
+
position: "fixed"
|
|
8869
|
+
},
|
|
8870
|
+
optional: ["top", "right", "bottom", "left", "zIndex"],
|
|
8871
|
+
minMatches: 1
|
|
8872
|
+
},
|
|
8873
|
+
{
|
|
8874
|
+
name: "relative-position",
|
|
8875
|
+
description: "Relative positioning",
|
|
8876
|
+
macro: "relative()",
|
|
8877
|
+
example: "chain.relative()",
|
|
8878
|
+
required: {
|
|
8879
|
+
position: "relative"
|
|
8880
|
+
},
|
|
8881
|
+
minMatches: 1
|
|
8882
|
+
},
|
|
8883
|
+
{
|
|
8884
|
+
name: "hidden-element",
|
|
8885
|
+
description: "Hidden element",
|
|
8886
|
+
macro: "hide()",
|
|
8887
|
+
example: "chain.hide()",
|
|
8888
|
+
required: {
|
|
8889
|
+
display: "none"
|
|
8890
|
+
},
|
|
8891
|
+
minMatches: 1
|
|
8892
|
+
},
|
|
8893
|
+
{
|
|
8894
|
+
name: "unselectable",
|
|
8895
|
+
description: "Unselectable text",
|
|
8896
|
+
macro: "unselectable()",
|
|
8897
|
+
example: "chain.unselectable()",
|
|
8898
|
+
required: {
|
|
8899
|
+
userSelect: "none"
|
|
8900
|
+
},
|
|
8901
|
+
minMatches: 1
|
|
8902
|
+
},
|
|
8903
|
+
{
|
|
8904
|
+
name: "scrollable",
|
|
8905
|
+
description: "Scrollable container",
|
|
8906
|
+
macro: "scrollable()",
|
|
8907
|
+
example: "chain.scrollable()",
|
|
8908
|
+
required: {
|
|
8909
|
+
overflow: "auto"
|
|
8910
|
+
},
|
|
8911
|
+
minMatches: 1
|
|
8912
|
+
},
|
|
8913
|
+
{
|
|
8914
|
+
name: "square-shape",
|
|
8915
|
+
description: "Square element with equal sides",
|
|
8916
|
+
macro: "square()",
|
|
8917
|
+
example: "chain.square(100)",
|
|
8918
|
+
required: {
|
|
8919
|
+
width: "100px",
|
|
8920
|
+
height: "100px"
|
|
8921
|
+
},
|
|
8922
|
+
optional: ["borderRadius"],
|
|
8923
|
+
minMatches: 2
|
|
8924
|
+
},
|
|
8925
|
+
{
|
|
8926
|
+
name: "circle-shape",
|
|
8927
|
+
description: "Circle element",
|
|
8928
|
+
macro: "circle()",
|
|
8929
|
+
example: "chain.circle(50)",
|
|
8930
|
+
required: {
|
|
8931
|
+
borderRadius: "50%"
|
|
8932
|
+
},
|
|
8933
|
+
optional: ["width", "height"],
|
|
8934
|
+
minMatches: 1
|
|
8935
|
+
},
|
|
8936
|
+
{
|
|
8937
|
+
name: "bento-grid",
|
|
8938
|
+
description: "Bento box grid layout",
|
|
8939
|
+
macro: "bento()",
|
|
8940
|
+
example: "chain.bento(3)",
|
|
8941
|
+
required: {
|
|
8942
|
+
display: "grid"
|
|
8943
|
+
},
|
|
8944
|
+
optional: ["gridTemplateColumns", "gap", "gridAutoRows"],
|
|
8945
|
+
minMatches: 1
|
|
8946
|
+
},
|
|
8947
|
+
{
|
|
8948
|
+
name: "focus-ring",
|
|
8949
|
+
description: "Focus ring outline",
|
|
8950
|
+
macro: "focusRing()",
|
|
8951
|
+
example: "chain.focusRing()",
|
|
8952
|
+
required: {
|
|
8953
|
+
outline: "2px solid #3b82f6"
|
|
8954
|
+
},
|
|
8955
|
+
optional: ["outlineOffset"],
|
|
8956
|
+
minMatches: 1
|
|
8957
|
+
},
|
|
8958
|
+
{
|
|
8959
|
+
name: "shimmer-effect",
|
|
8960
|
+
description: "Shimmer loading animation",
|
|
8961
|
+
macro: "shimmer()",
|
|
8962
|
+
example: "chain.shimmer()",
|
|
8963
|
+
required: {
|
|
8964
|
+
background: "linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%)"
|
|
8965
|
+
},
|
|
8966
|
+
optional: ["backgroundSize", "animation"],
|
|
8967
|
+
minMatches: 1
|
|
8968
|
+
},
|
|
8969
|
+
{
|
|
8970
|
+
name: "skeleton-loader",
|
|
8971
|
+
description: "Skeleton loading state",
|
|
8972
|
+
macro: "skeleton()",
|
|
8973
|
+
example: "chain.skeleton(true)",
|
|
8974
|
+
required: {
|
|
8975
|
+
animation: "pulse 1.5s ease-in-out infinite"
|
|
8976
|
+
},
|
|
8977
|
+
optional: ["backgroundColor", "borderRadius"],
|
|
8978
|
+
minMatches: 1
|
|
8979
|
+
},
|
|
8980
|
+
{
|
|
8981
|
+
name: "safe-area-bottom",
|
|
8982
|
+
description: "Safe area padding for notched devices",
|
|
8983
|
+
macro: "safeArea('bottom')",
|
|
8984
|
+
example: "chain.safeArea('bottom')",
|
|
8985
|
+
required: {
|
|
8986
|
+
paddingBottom: "env(safe-area-inset-bottom)"
|
|
8987
|
+
},
|
|
8988
|
+
minMatches: 1
|
|
8989
|
+
},
|
|
8990
|
+
// --- Glass Morphism ---
|
|
8991
|
+
{
|
|
8992
|
+
name: "glass-effect",
|
|
8993
|
+
description: "Frosted glass effect",
|
|
8994
|
+
macro: "glass()",
|
|
8995
|
+
example: "chain.glass()",
|
|
8996
|
+
required: {
|
|
8997
|
+
backdropFilter: "blur(16px)"
|
|
8998
|
+
},
|
|
8999
|
+
optional: ["backgroundColor", "border", "borderRadius"],
|
|
9000
|
+
minMatches: 1
|
|
9001
|
+
}
|
|
9002
|
+
];
|
|
9003
|
+
function matchPattern(rule, pattern) {
|
|
9004
|
+
const declarations = rule.declarations;
|
|
9005
|
+
const propMap = new Map(declarations.map((d) => [d.property, String(d.value)]));
|
|
9006
|
+
const matchedProperties = [];
|
|
9007
|
+
let totalRequired = Object.keys(pattern.required).length;
|
|
9008
|
+
let matched = 0;
|
|
9009
|
+
for (const [prop, expectedValue] of Object.entries(pattern.required)) {
|
|
9010
|
+
const actualValue = propMap.get(prop);
|
|
9011
|
+
if (actualValue === String(expectedValue)) {
|
|
9012
|
+
matched++;
|
|
9013
|
+
matchedProperties.push(prop);
|
|
9014
|
+
}
|
|
9015
|
+
}
|
|
9016
|
+
if (pattern.optional) {
|
|
9017
|
+
for (const prop of pattern.optional) {
|
|
9018
|
+
if (propMap.has(prop)) {
|
|
9019
|
+
matchedProperties.push(prop);
|
|
9020
|
+
}
|
|
9021
|
+
}
|
|
9022
|
+
}
|
|
9023
|
+
const minMatches = pattern.minMatches || Object.keys(pattern.required).length;
|
|
9024
|
+
const confidence = matched >= minMatches ? Math.min(1, matched / totalRequired) : 0;
|
|
9025
|
+
if (confidence >= 0.75 && matched >= minMatches) {
|
|
9026
|
+
return {
|
|
9027
|
+
pattern,
|
|
9028
|
+
ruleId: rule.id,
|
|
9029
|
+
selector: rule.selector,
|
|
9030
|
+
matchedProperties,
|
|
9031
|
+
confidence
|
|
9032
|
+
};
|
|
9033
|
+
}
|
|
9034
|
+
return null;
|
|
9035
|
+
}
|
|
9036
|
+
function findDuplicates(matches) {
|
|
9037
|
+
const patternGroups = /* @__PURE__ */ new Map();
|
|
9038
|
+
for (const match of matches) {
|
|
9039
|
+
const key = match.pattern.name;
|
|
9040
|
+
const group = patternGroups.get(key) || [];
|
|
9041
|
+
group.push(match);
|
|
9042
|
+
patternGroups.set(key, group);
|
|
9043
|
+
}
|
|
9044
|
+
const duplicates = [];
|
|
9045
|
+
for (const [patternName, group] of patternGroups) {
|
|
9046
|
+
if (group.length >= 2) {
|
|
9047
|
+
duplicates.push({
|
|
9048
|
+
pattern: patternName,
|
|
9049
|
+
selectors: group.map((m) => m.selector),
|
|
9050
|
+
count: group.length
|
|
9051
|
+
});
|
|
9052
|
+
}
|
|
9053
|
+
}
|
|
9054
|
+
return duplicates;
|
|
9055
|
+
}
|
|
9056
|
+
function generateSuggestions(matches) {
|
|
9057
|
+
const suggestions = [];
|
|
9058
|
+
for (const match of matches) {
|
|
9059
|
+
if (match.confidence >= 0.85) {
|
|
9060
|
+
const propsCount = match.matchedProperties.length;
|
|
9061
|
+
suggestions.push({
|
|
9062
|
+
selector: match.selector,
|
|
9063
|
+
suggestion: match.pattern.macro,
|
|
9064
|
+
savings: propsCount - 1
|
|
9065
|
+
// Saving N-1 declarations
|
|
9066
|
+
});
|
|
9067
|
+
}
|
|
9068
|
+
}
|
|
9069
|
+
return suggestions;
|
|
9070
|
+
}
|
|
9071
|
+
var layoutIntelligencePass = (ir) => {
|
|
9072
|
+
const allMatches = [];
|
|
9073
|
+
for (const rule of ir.rules) {
|
|
9074
|
+
for (const pattern of LAYOUT_PATTERNS) {
|
|
9075
|
+
const match = matchPattern(rule, pattern);
|
|
9076
|
+
if (match) {
|
|
9077
|
+
allMatches.push(match);
|
|
9078
|
+
rule.meta.layoutPattern = pattern.name;
|
|
9079
|
+
rule.meta.layoutConfidence = match.confidence;
|
|
9080
|
+
}
|
|
9081
|
+
}
|
|
9082
|
+
}
|
|
9083
|
+
const duplicates = findDuplicates(allMatches);
|
|
9084
|
+
for (const dup of duplicates) {
|
|
9085
|
+
ir.diagnostics.push({
|
|
9086
|
+
id: "layout-dup-" + Date.now() + "-" + dup.pattern,
|
|
9087
|
+
nodeId: ir.rules[0]?.id || ir.id,
|
|
9088
|
+
severity: "info",
|
|
9089
|
+
message: 'Layout pattern "' + dup.pattern + '" found ' + dup.count + " times: " + dup.selectors.join(", "),
|
|
9090
|
+
suggestion: "Consider extracting: " + (LAYOUT_PATTERNS.find((p) => p.name === dup.pattern)?.macro || ""),
|
|
9091
|
+
pass: "layout-intelligence"
|
|
9092
|
+
});
|
|
9093
|
+
}
|
|
9094
|
+
const suggestions = generateSuggestions(allMatches);
|
|
9095
|
+
for (const sug of suggestions) {
|
|
9096
|
+
ir.diagnostics.push({
|
|
9097
|
+
id: "layout-sug-" + Date.now() + "-" + sug.selector.replace(/[.#]/g, ""),
|
|
9098
|
+
nodeId: ir.rules.find((r) => r.selector === sug.selector)?.id || ir.id,
|
|
9099
|
+
severity: "hint",
|
|
9100
|
+
message: '"' + sug.selector + '" could use ' + sug.suggestion + " (save " + sug.savings + " declarations)",
|
|
9101
|
+
suggestion: sug.suggestion,
|
|
9102
|
+
pass: "layout-intelligence"
|
|
9103
|
+
});
|
|
9104
|
+
}
|
|
9105
|
+
ir.meta = ir.meta || {};
|
|
9106
|
+
ir.meta.layoutMatches = allMatches;
|
|
9107
|
+
ir.meta.layoutDuplicates = duplicates;
|
|
9108
|
+
return ir;
|
|
9109
|
+
};
|
|
9110
|
+
function recognizeLayout(declarations) {
|
|
9111
|
+
const rule = {
|
|
9112
|
+
id: "temp-rule",
|
|
9113
|
+
selector: ".temp",
|
|
9114
|
+
declarations: Object.entries(declarations).map(([prop, value]) => ({
|
|
9115
|
+
id: "temp-decl-" + prop,
|
|
9116
|
+
property: prop,
|
|
9117
|
+
value,
|
|
9118
|
+
history: [],
|
|
9119
|
+
meta: {}
|
|
9120
|
+
})),
|
|
9121
|
+
pseudoClasses: [],
|
|
9122
|
+
atRules: [],
|
|
9123
|
+
nestedRules: [],
|
|
9124
|
+
conditions: [],
|
|
9125
|
+
isDead: false,
|
|
9126
|
+
specificity: 0,
|
|
9127
|
+
hash: "",
|
|
9128
|
+
source: {},
|
|
9129
|
+
history: [],
|
|
9130
|
+
meta: {}
|
|
9131
|
+
};
|
|
9132
|
+
const matches = [];
|
|
9133
|
+
for (const pattern of LAYOUT_PATTERNS) {
|
|
9134
|
+
const match = matchPattern(rule, pattern);
|
|
9135
|
+
if (match) matches.push(match);
|
|
9136
|
+
}
|
|
9137
|
+
return matches;
|
|
9138
|
+
}
|
|
9139
|
+
function suggestMacro(declarations) {
|
|
9140
|
+
const matches = recognizeLayout(declarations);
|
|
9141
|
+
if (matches.length === 0) return null;
|
|
9142
|
+
const best = matches.sort((a, b) => b.confidence - a.confidence)[0];
|
|
9143
|
+
return best.confidence >= 0.85 ? best.pattern.macro : null;
|
|
9144
|
+
}
|
|
9145
|
+
function getLayoutPatterns() {
|
|
9146
|
+
return [...LAYOUT_PATTERNS];
|
|
9147
|
+
}
|
|
9148
|
+
var layoutIntelligence = {
|
|
9149
|
+
recognize: recognizeLayout,
|
|
9150
|
+
suggestMacro,
|
|
9151
|
+
getPatterns: getLayoutPatterns,
|
|
9152
|
+
pass: layoutIntelligencePass,
|
|
9153
|
+
patterns: LAYOUT_PATTERNS
|
|
9154
|
+
};
|
|
9155
|
+
|
|
9156
|
+
// src/compiler/responsive-inference.ts
|
|
9157
|
+
var MOBILE_BREAKPOINT = 768;
|
|
9158
|
+
var TABLET_BREAKPOINT = 1024;
|
|
9159
|
+
var LARGE_FONT_THRESHOLD = 32;
|
|
9160
|
+
var LARGE_PADDING_THRESHOLD = 48;
|
|
9161
|
+
var LARGE_GAP_THRESHOLD = 32;
|
|
9162
|
+
var MAX_GRID_COLUMNS = 2;
|
|
9163
|
+
function detectFixedWidth(rule) {
|
|
9164
|
+
const issues = [];
|
|
9165
|
+
for (const decl of rule.declarations) {
|
|
9166
|
+
if ((decl.property === "width" || decl.property === "max-width") && typeof decl.value === "string") {
|
|
9167
|
+
const pxMatch = decl.value.match(/^(\d+(\.\d+)?)px$/);
|
|
9168
|
+
if (pxMatch) {
|
|
9169
|
+
const px = parseFloat(pxMatch[1]);
|
|
9170
|
+
if (px > MOBILE_BREAKPOINT) {
|
|
9171
|
+
issues.push({
|
|
9172
|
+
ruleId: rule.id,
|
|
9173
|
+
selector: rule.selector,
|
|
9174
|
+
property: decl.property,
|
|
9175
|
+
currentValue: decl.value,
|
|
9176
|
+
severity: px > TABLET_BREAKPOINT ? "error" : "warning",
|
|
9177
|
+
category: "overflow",
|
|
9178
|
+
message: "Fixed " + decl.property + ": " + decl.value + " will overflow on viewports < " + px + "px",
|
|
9179
|
+
suggestedFix: decl.property + ": min(100%, " + decl.value + ");",
|
|
9180
|
+
autoFixAvailable: true,
|
|
9181
|
+
affectedViewports: ["mobile", "tablet"]
|
|
9182
|
+
});
|
|
9183
|
+
}
|
|
9184
|
+
}
|
|
9185
|
+
}
|
|
9186
|
+
}
|
|
9187
|
+
return issues;
|
|
9188
|
+
}
|
|
9189
|
+
function detectGridColumns(rule) {
|
|
9190
|
+
const issues = [];
|
|
9191
|
+
for (const decl of rule.declarations) {
|
|
9192
|
+
if ((decl.property === "gridTemplateColumns" || decl.property === "grid-template-columns") && typeof decl.value === "string") {
|
|
9193
|
+
const columns = decl.value.split(/\s+/).filter(
|
|
9194
|
+
(c) => c.includes("fr") || c.includes("px") || c.includes("%")
|
|
9195
|
+
);
|
|
9196
|
+
const explicitColumns = columns.length;
|
|
9197
|
+
if (explicitColumns > MAX_GRID_COLUMNS) {
|
|
9198
|
+
const isFixed = columns.every((c) => c.includes("px"));
|
|
9199
|
+
const suggestion = isFixed ? "repeat(auto-fit, minmax(" + columns[0] + ", 1fr))" : "repeat(auto-fit, minmax(250px, 1fr))";
|
|
9200
|
+
issues.push({
|
|
9201
|
+
ruleId: rule.id,
|
|
9202
|
+
selector: rule.selector,
|
|
9203
|
+
property: decl.property,
|
|
9204
|
+
currentValue: decl.value,
|
|
9205
|
+
severity: explicitColumns >= 4 ? "error" : "warning",
|
|
9206
|
+
category: "grid",
|
|
9207
|
+
message: explicitColumns + " columns will not fit on mobile screens (\u2264 " + MOBILE_BREAKPOINT + "px)",
|
|
9208
|
+
suggestedFix: "grid-template-columns: " + suggestion + ";",
|
|
9209
|
+
autoFixAvailable: true,
|
|
9210
|
+
affectedViewports: ["mobile"]
|
|
9211
|
+
});
|
|
9212
|
+
}
|
|
9213
|
+
}
|
|
9214
|
+
if ((decl.property === "gridTemplateColumns" || decl.property === "grid-template-columns") && typeof decl.value === "string" && decl.value.includes("auto-fit") && !decl.value.includes("minmax")) {
|
|
9215
|
+
issues.push({
|
|
9216
|
+
ruleId: rule.id,
|
|
9217
|
+
selector: rule.selector,
|
|
9218
|
+
property: decl.property,
|
|
9219
|
+
currentValue: decl.value,
|
|
9220
|
+
severity: "info",
|
|
9221
|
+
category: "grid",
|
|
9222
|
+
message: "auto-fit without minmax() may collapse to 0 on empty containers. Consider: repeat(auto-fit, minmax(250px, 1fr))",
|
|
9223
|
+
suggestedFix: "grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));",
|
|
9224
|
+
autoFixAvailable: true,
|
|
9225
|
+
affectedViewports: ["all"]
|
|
9226
|
+
});
|
|
9227
|
+
}
|
|
9228
|
+
}
|
|
9229
|
+
return issues;
|
|
9230
|
+
}
|
|
9231
|
+
function detectLargeTypography(rule) {
|
|
9232
|
+
const issues = [];
|
|
9233
|
+
for (const decl of rule.declarations) {
|
|
9234
|
+
if ((decl.property === "fontSize" || decl.property === "font-size") && typeof decl.value === "string") {
|
|
9235
|
+
const pxMatch = decl.value.match(/^(\d+(\.\d+)?)px$/);
|
|
9236
|
+
if (pxMatch) {
|
|
9237
|
+
const px = parseFloat(pxMatch[1]);
|
|
9238
|
+
if (px > LARGE_FONT_THRESHOLD) {
|
|
9239
|
+
const minSize = Math.round(px * 0.5);
|
|
9240
|
+
issues.push({
|
|
9241
|
+
ruleId: rule.id,
|
|
9242
|
+
selector: rule.selector,
|
|
9243
|
+
property: decl.property,
|
|
9244
|
+
currentValue: decl.value,
|
|
9245
|
+
severity: "warning",
|
|
9246
|
+
category: "typography",
|
|
9247
|
+
message: "font-size: " + decl.value + " may be too large on mobile. Consider responsive scaling.",
|
|
9248
|
+
suggestedFix: "font-size: clamp(" + minSize + "px, " + Math.round(px / TABLET_BREAKPOINT * 100) + "vw, " + px + "px);",
|
|
9249
|
+
autoFixAvailable: true,
|
|
9250
|
+
affectedViewports: ["mobile", "tablet"]
|
|
9251
|
+
});
|
|
9252
|
+
}
|
|
9253
|
+
}
|
|
9254
|
+
}
|
|
9255
|
+
}
|
|
9256
|
+
return issues;
|
|
9257
|
+
}
|
|
9258
|
+
function detectLargeSpacing(rule) {
|
|
9259
|
+
const issues = [];
|
|
9260
|
+
for (const decl of rule.declarations) {
|
|
9261
|
+
if ((decl.property === "padding" || decl.property.startsWith("padding")) && typeof decl.value === "string") {
|
|
9262
|
+
const pxMatch = decl.value.match(/^(\d+(\.\d+)?)px$/);
|
|
9263
|
+
if (pxMatch) {
|
|
9264
|
+
const px = parseFloat(pxMatch[1]);
|
|
9265
|
+
if (px > LARGE_PADDING_THRESHOLD) {
|
|
9266
|
+
issues.push({
|
|
9267
|
+
ruleId: rule.id,
|
|
9268
|
+
selector: rule.selector,
|
|
9269
|
+
property: decl.property,
|
|
9270
|
+
currentValue: decl.value,
|
|
9271
|
+
severity: "info",
|
|
9272
|
+
category: "spacing",
|
|
9273
|
+
message: decl.property + ": " + decl.value + " may be excessive on mobile. Consider reducing to " + Math.round(px * 0.5) + "px on small screens.",
|
|
9274
|
+
suggestedFix: "@media (max-width: 768px) { " + rule.selector + " { " + decl.property + ": " + Math.round(px * 0.5) + "px; } }",
|
|
9275
|
+
autoFixAvailable: true,
|
|
9276
|
+
affectedViewports: ["mobile"]
|
|
9277
|
+
});
|
|
9278
|
+
}
|
|
9279
|
+
}
|
|
9280
|
+
}
|
|
9281
|
+
}
|
|
9282
|
+
return issues;
|
|
9283
|
+
}
|
|
9284
|
+
function detectViewportUnits(rule) {
|
|
9285
|
+
const issues = [];
|
|
9286
|
+
for (const decl of rule.declarations) {
|
|
9287
|
+
if ((decl.property === "height" || decl.property === "min-height") && typeof decl.value === "string" && decl.value.includes("100vh")) {
|
|
9288
|
+
issues.push({
|
|
9289
|
+
ruleId: rule.id,
|
|
9290
|
+
selector: rule.selector,
|
|
9291
|
+
property: decl.property,
|
|
9292
|
+
currentValue: decl.value,
|
|
9293
|
+
severity: "warning",
|
|
9294
|
+
category: "viewport",
|
|
9295
|
+
message: "100vh can cause issues on mobile browsers with dynamic toolbars. Consider 100dvh instead.",
|
|
9296
|
+
suggestedFix: decl.property + ": 100dvh;",
|
|
9297
|
+
autoFixAvailable: true,
|
|
9298
|
+
affectedViewports: ["mobile"]
|
|
9299
|
+
});
|
|
9300
|
+
}
|
|
9301
|
+
}
|
|
9302
|
+
return issues;
|
|
9303
|
+
}
|
|
9304
|
+
function detectLargeGaps(rule) {
|
|
9305
|
+
const issues = [];
|
|
9306
|
+
for (const decl of rule.declarations) {
|
|
9307
|
+
if ((decl.property === "gap" || decl.property === "grid-gap") && typeof decl.value === "string") {
|
|
9308
|
+
const pxMatch = decl.value.match(/^(\d+(\.\d+)?)px$/);
|
|
9309
|
+
if (pxMatch) {
|
|
9310
|
+
const px = parseFloat(pxMatch[1]);
|
|
9311
|
+
if (px > LARGE_GAP_THRESHOLD) {
|
|
9312
|
+
issues.push({
|
|
9313
|
+
ruleId: rule.id,
|
|
9314
|
+
selector: rule.selector,
|
|
9315
|
+
property: decl.property,
|
|
9316
|
+
currentValue: decl.value,
|
|
9317
|
+
severity: "info",
|
|
9318
|
+
category: "spacing",
|
|
9319
|
+
message: "gap: " + decl.value + " may be too large on mobile. Consider reducing to " + Math.round(px * 0.5) + "px on small screens.",
|
|
9320
|
+
suggestedFix: "@media (max-width: 768px) { " + rule.selector + " { gap: " + Math.round(px * 0.5) + "px; } }",
|
|
9321
|
+
autoFixAvailable: true,
|
|
9322
|
+
affectedViewports: ["mobile"]
|
|
9323
|
+
});
|
|
9324
|
+
}
|
|
9325
|
+
}
|
|
9326
|
+
}
|
|
9327
|
+
}
|
|
9328
|
+
return issues;
|
|
9329
|
+
}
|
|
9330
|
+
var responsiveInferencePass = (ir) => {
|
|
9331
|
+
const allIssues = [];
|
|
9332
|
+
for (const rule of ir.rules) {
|
|
9333
|
+
if (rule.isDead) continue;
|
|
9334
|
+
allIssues.push(...detectFixedWidth(rule));
|
|
9335
|
+
allIssues.push(...detectGridColumns(rule));
|
|
9336
|
+
allIssues.push(...detectLargeTypography(rule));
|
|
9337
|
+
allIssues.push(...detectLargeSpacing(rule));
|
|
9338
|
+
allIssues.push(...detectViewportUnits(rule));
|
|
9339
|
+
allIssues.push(...detectLargeGaps(rule));
|
|
9340
|
+
}
|
|
9341
|
+
for (const issue of allIssues) {
|
|
9342
|
+
ir.diagnostics.push({
|
|
9343
|
+
id: "responsive-" + issue.category + "-" + Date.now() + "-" + Math.random().toString(36).slice(2, 6),
|
|
9344
|
+
nodeId: issue.ruleId,
|
|
9345
|
+
severity: issue.severity,
|
|
9346
|
+
message: issue.message,
|
|
9347
|
+
suggestion: issue.suggestedFix,
|
|
9348
|
+
pass: "responsive-inference"
|
|
9349
|
+
});
|
|
9350
|
+
}
|
|
9351
|
+
ir.meta = ir.meta || {};
|
|
9352
|
+
ir.meta.responsiveIssues = allIssues;
|
|
9353
|
+
return ir;
|
|
9354
|
+
};
|
|
9355
|
+
function analyzeResponsive(selector, declarations) {
|
|
9356
|
+
const rule = {
|
|
9357
|
+
id: "temp-responsive",
|
|
9358
|
+
selector,
|
|
9359
|
+
declarations: Object.entries(declarations).map(([prop, value]) => ({
|
|
9360
|
+
id: "temp-" + prop,
|
|
9361
|
+
property: prop,
|
|
9362
|
+
value,
|
|
9363
|
+
history: [],
|
|
9364
|
+
meta: {}
|
|
9365
|
+
})),
|
|
9366
|
+
pseudoClasses: [],
|
|
9367
|
+
atRules: [],
|
|
9368
|
+
nestedRules: [],
|
|
9369
|
+
conditions: [],
|
|
9370
|
+
isDead: false,
|
|
9371
|
+
specificity: 0,
|
|
9372
|
+
hash: "",
|
|
9373
|
+
source: {},
|
|
9374
|
+
history: [],
|
|
9375
|
+
meta: {}
|
|
9376
|
+
};
|
|
9377
|
+
return [
|
|
9378
|
+
...detectFixedWidth(rule),
|
|
9379
|
+
...detectGridColumns(rule),
|
|
9380
|
+
...detectLargeTypography(rule),
|
|
9381
|
+
...detectLargeSpacing(rule),
|
|
9382
|
+
...detectViewportUnits(rule),
|
|
9383
|
+
...detectLargeGaps(rule)
|
|
9384
|
+
];
|
|
9385
|
+
}
|
|
9386
|
+
function generateResponsiveReport(issues) {
|
|
9387
|
+
const critical = issues.filter((i) => i.severity === "error");
|
|
9388
|
+
const warnings = issues.filter((i) => i.severity === "warning");
|
|
9389
|
+
const info = issues.filter((i) => i.severity === "info");
|
|
9390
|
+
let summary;
|
|
9391
|
+
if (issues.length === 0) {
|
|
9392
|
+
summary = "\u2705 No responsive issues detected.";
|
|
9393
|
+
} else if (critical.length > 0) {
|
|
9394
|
+
summary = "\u274C " + critical.length + " critical, " + warnings.length + " warnings, " + info.length + " suggestions.";
|
|
9395
|
+
} else if (warnings.length > 0) {
|
|
9396
|
+
summary = "\u26A0\uFE0F " + warnings.length + " warnings, " + info.length + " suggestions.";
|
|
9397
|
+
} else {
|
|
9398
|
+
summary = "\u{1F4A1} " + info.length + " responsive suggestions.";
|
|
9399
|
+
}
|
|
9400
|
+
return {
|
|
9401
|
+
issues,
|
|
9402
|
+
criticalCount: critical.length,
|
|
9403
|
+
warningCount: warnings.length,
|
|
9404
|
+
infoCount: info.length,
|
|
9405
|
+
summary
|
|
9406
|
+
};
|
|
9407
|
+
}
|
|
9408
|
+
function autoFixIssue(issue) {
|
|
9409
|
+
return issue.suggestedFix;
|
|
9410
|
+
}
|
|
9411
|
+
function autoFixAll(issues) {
|
|
9412
|
+
return issues.filter((i) => i.autoFixAvailable).map((i) => i.suggestedFix);
|
|
9413
|
+
}
|
|
9414
|
+
var responsiveInference = {
|
|
9415
|
+
analyze: analyzeResponsive,
|
|
9416
|
+
report: generateResponsiveReport,
|
|
9417
|
+
autoFix: autoFixIssue,
|
|
9418
|
+
autoFixAll,
|
|
9419
|
+
pass: responsiveInferencePass
|
|
9420
|
+
};
|
|
9421
|
+
|
|
9422
|
+
// src/compiler/pattern-learner.ts
|
|
9423
|
+
import crypto6 from "crypto";
|
|
9424
|
+
function fingerprintDeclarations(declarations) {
|
|
9425
|
+
const sorted = [...declarations].sort((a, b) => a.property.localeCompare(b.property));
|
|
9426
|
+
const properties = {};
|
|
9427
|
+
const propertyList = [];
|
|
9428
|
+
for (const decl of sorted) {
|
|
9429
|
+
properties[decl.property] = decl.value;
|
|
9430
|
+
propertyList.push(decl.property + ":" + decl.value);
|
|
9431
|
+
}
|
|
9432
|
+
const signature = propertyList.join("; ");
|
|
9433
|
+
const hash = crypto6.createHash("md5").update(signature).digest("hex").slice(0, 12);
|
|
9434
|
+
return {
|
|
9435
|
+
hash,
|
|
9436
|
+
signature,
|
|
9437
|
+
properties,
|
|
9438
|
+
propertyCount: sorted.length
|
|
9439
|
+
};
|
|
9440
|
+
}
|
|
9441
|
+
function clusterPatterns(rules, options = {}) {
|
|
9442
|
+
const minProperties = options.minProperties || 3;
|
|
9443
|
+
const minFrequency = options.minFrequency || 2;
|
|
9444
|
+
const groups = /* @__PURE__ */ new Map();
|
|
9445
|
+
for (const rule of rules) {
|
|
9446
|
+
if (rule.isDead) continue;
|
|
9447
|
+
if (rule.declarations.length < minProperties) continue;
|
|
9448
|
+
const fp = fingerprintDeclarations(rule.declarations);
|
|
9449
|
+
const existing = groups.get(fp.hash);
|
|
9450
|
+
if (existing) {
|
|
9451
|
+
existing.occurrences.push({
|
|
9452
|
+
ruleId: rule.id,
|
|
9453
|
+
selector: rule.selector,
|
|
9454
|
+
sourceFile: rule.source.file,
|
|
9455
|
+
component: rule.source.component
|
|
9456
|
+
});
|
|
9457
|
+
if (rule.source.file) existing.files.add(rule.source.file);
|
|
9458
|
+
} else {
|
|
9459
|
+
groups.set(fp.hash, {
|
|
9460
|
+
fingerprint: fp,
|
|
9461
|
+
occurrences: [{
|
|
9462
|
+
ruleId: rule.id,
|
|
9463
|
+
selector: rule.selector,
|
|
9464
|
+
sourceFile: rule.source.file,
|
|
9465
|
+
component: rule.source.component
|
|
9466
|
+
}],
|
|
9467
|
+
files: new Set(rule.source.file ? [rule.source.file] : [])
|
|
9468
|
+
});
|
|
9469
|
+
}
|
|
9470
|
+
}
|
|
9471
|
+
const clusters = [];
|
|
9472
|
+
for (const [, group] of groups) {
|
|
9473
|
+
if (group.occurrences.length < minFrequency) continue;
|
|
9474
|
+
const frequency = group.occurrences.length;
|
|
9475
|
+
const score = frequency * group.fingerprint.propertyCount;
|
|
9476
|
+
const suggestedName = generatePatternName(group.fingerprint.properties);
|
|
9477
|
+
const declarations = frequency * group.fingerprint.propertyCount;
|
|
9478
|
+
const linesEliminated = declarations - 1;
|
|
9479
|
+
const bundleReduction = estimateBundleSavings(declarations, frequency);
|
|
9480
|
+
clusters.push({
|
|
9481
|
+
fingerprint: group.fingerprint,
|
|
9482
|
+
occurrences: group.occurrences,
|
|
9483
|
+
frequency,
|
|
9484
|
+
fileCount: group.files.size,
|
|
9485
|
+
score,
|
|
9486
|
+
suggestedName,
|
|
9487
|
+
suggestedRecipe: generateRecipeCode(suggestedName, group.fingerprint.properties),
|
|
9488
|
+
savings: {
|
|
9489
|
+
declarations,
|
|
9490
|
+
linesEliminated,
|
|
9491
|
+
bundleReduction
|
|
9492
|
+
}
|
|
9493
|
+
});
|
|
9494
|
+
}
|
|
9495
|
+
clusters.sort((a, b) => b.score - a.score);
|
|
9496
|
+
return clusters;
|
|
9497
|
+
}
|
|
9498
|
+
function generatePatternName(properties) {
|
|
9499
|
+
const keys = Object.keys(properties);
|
|
9500
|
+
if (hasAll(keys, ["display", "justifyContent", "alignItems"]) && properties["display"] === "flex") {
|
|
9501
|
+
return "flexCenter";
|
|
9502
|
+
}
|
|
9503
|
+
if (hasAll(keys, ["display", "flexDirection", "justifyContent", "alignItems"]) && properties["flexDirection"] === "column") {
|
|
9504
|
+
return "stack";
|
|
9505
|
+
}
|
|
9506
|
+
if (hasAll(keys, ["padding", "borderRadius", "backgroundColor", "color"])) {
|
|
9507
|
+
const bg = String(properties["backgroundColor"] || "");
|
|
9508
|
+
if (bg.includes("2563eb") || bg.includes("blue")) return "primaryButton";
|
|
9509
|
+
if (bg.includes("e5e7eb") || bg.includes("gray")) return "secondaryButton";
|
|
9510
|
+
return "button";
|
|
9511
|
+
}
|
|
9512
|
+
if (hasAll(keys, ["position", "top"]) && properties["position"] === "sticky") {
|
|
9513
|
+
return "stickyHeader";
|
|
9514
|
+
}
|
|
9515
|
+
if (hasAll(keys, ["position", "top", "left"]) && properties["position"] === "absolute") {
|
|
9516
|
+
return "absoluteOverlay";
|
|
9517
|
+
}
|
|
9518
|
+
if (hasAll(keys, ["borderRadius"]) && properties["borderRadius"] === "9999px") {
|
|
9519
|
+
return "pill";
|
|
9520
|
+
}
|
|
9521
|
+
if (hasAll(keys, ["overflow", "textOverflow", "whiteSpace"])) {
|
|
9522
|
+
return "truncate";
|
|
9523
|
+
}
|
|
9524
|
+
if (hasAll(keys, ["backdropFilter"])) {
|
|
9525
|
+
return "glass";
|
|
9526
|
+
}
|
|
9527
|
+
if (keys.includes("display")) return String(properties["display"]) + "Layout";
|
|
9528
|
+
if (keys.includes("position")) return String(properties["position"]) + "Element";
|
|
9529
|
+
return "pattern-" + keys.slice(0, 3).join("-");
|
|
9530
|
+
}
|
|
9531
|
+
function hasAll(haystack, needles) {
|
|
9532
|
+
return needles.every((n) => haystack.includes(n));
|
|
9533
|
+
}
|
|
9534
|
+
function generateRecipeCode(name, properties) {
|
|
9535
|
+
const lines = Object.entries(properties).map(
|
|
9536
|
+
([prop, value]) => " " + prop + ": '" + value + "',"
|
|
9537
|
+
);
|
|
9538
|
+
return "chain.recipe('" + name + "', {\n" + lines.join("\n") + "\n})";
|
|
9539
|
+
}
|
|
9540
|
+
function estimateBundleSavings(declarations, frequency) {
|
|
9541
|
+
const avgBytesPerDecl = 25;
|
|
9542
|
+
const totalBytes = declarations * avgBytesPerDecl;
|
|
9543
|
+
if (totalBytes > 1e4) return "~" + Math.round(totalBytes / 1e3) + "KB";
|
|
9544
|
+
return "~" + totalBytes + "B";
|
|
9545
|
+
}
|
|
9546
|
+
function generateReport(clusters) {
|
|
9547
|
+
const highValue = clusters.filter((c) => c.score >= 10);
|
|
9548
|
+
const totalDeclarations = clusters.reduce((sum, c) => sum + c.savings.declarations, 0);
|
|
9549
|
+
const totalBytes = totalDeclarations * 25;
|
|
9550
|
+
let summary;
|
|
9551
|
+
if (clusters.length === 0) {
|
|
9552
|
+
summary = "No repeated patterns found. Your styles are already unique!";
|
|
9553
|
+
} else if (highValue.length > 0) {
|
|
9554
|
+
summary = "Found " + clusters.length + " patterns. " + highValue.length + " high-value patterns worth extracting.";
|
|
9555
|
+
} else {
|
|
9556
|
+
summary = "Found " + clusters.length + " patterns. None high-value enough to suggest extraction yet.";
|
|
9557
|
+
}
|
|
9558
|
+
return {
|
|
9559
|
+
clusters,
|
|
9560
|
+
totalPatterns: clusters.length,
|
|
9561
|
+
highValuePatterns: highValue,
|
|
9562
|
+
totalSavings: {
|
|
9563
|
+
declarations: totalDeclarations,
|
|
9564
|
+
estimatedBytes: totalBytes
|
|
9565
|
+
},
|
|
9566
|
+
summary
|
|
9567
|
+
};
|
|
9568
|
+
}
|
|
9569
|
+
var patternLearningPass = (ir) => {
|
|
9570
|
+
const clusters = clusterPatterns(ir.rules, {
|
|
9571
|
+
minProperties: 3,
|
|
9572
|
+
minFrequency: 2
|
|
9573
|
+
});
|
|
9574
|
+
if (clusters.length === 0) return ir;
|
|
9575
|
+
for (const cluster of clusters.slice(0, 5)) {
|
|
9576
|
+
ir.diagnostics.push({
|
|
9577
|
+
id: "pattern-" + cluster.fingerprint.hash,
|
|
9578
|
+
nodeId: ir.rules[0]?.id || ir.id,
|
|
9579
|
+
severity: "info",
|
|
9580
|
+
message: 'Pattern "' + cluster.suggestedName + '" found ' + cluster.frequency + " times across " + cluster.fileCount + " files. " + cluster.savings.bundleReduction + " potential savings.",
|
|
9581
|
+
suggestion: cluster.suggestedRecipe,
|
|
9582
|
+
pass: "pattern-learner"
|
|
9583
|
+
});
|
|
9584
|
+
}
|
|
9585
|
+
ir.meta = ir.meta || {};
|
|
9586
|
+
ir.meta.patternClusters = clusters;
|
|
9587
|
+
ir.meta.learningReport = generateReport(clusters);
|
|
9588
|
+
return ir;
|
|
9589
|
+
};
|
|
9590
|
+
function learnPatterns(rules, options) {
|
|
9591
|
+
const irRules = rules.map((r) => ({
|
|
9592
|
+
id: "learn-" + Math.random().toString(36).slice(2, 8),
|
|
9593
|
+
selector: r.selector,
|
|
9594
|
+
declarations: Object.entries(r.declarations).map(([prop, value]) => ({
|
|
9595
|
+
id: "learn-decl-" + prop,
|
|
9596
|
+
property: prop,
|
|
9597
|
+
value,
|
|
9598
|
+
history: [],
|
|
9599
|
+
meta: {}
|
|
9600
|
+
})),
|
|
9601
|
+
pseudoClasses: [],
|
|
9602
|
+
atRules: [],
|
|
9603
|
+
nestedRules: [],
|
|
9604
|
+
conditions: [],
|
|
9605
|
+
isDead: false,
|
|
9606
|
+
specificity: 0,
|
|
9607
|
+
hash: "",
|
|
9608
|
+
source: { file: r.sourceFile },
|
|
9609
|
+
history: [],
|
|
9610
|
+
meta: {}
|
|
9611
|
+
}));
|
|
9612
|
+
const clusters = clusterPatterns(irRules, options);
|
|
9613
|
+
return generateReport(clusters);
|
|
9614
|
+
}
|
|
9615
|
+
function getExtractionCandidates(rules, minScore) {
|
|
9616
|
+
const report = learnPatterns(rules);
|
|
9617
|
+
const threshold = minScore || 10;
|
|
9618
|
+
return report.clusters.filter((c) => c.score >= threshold);
|
|
9619
|
+
}
|
|
9620
|
+
var patternLearner = {
|
|
9621
|
+
learn: learnPatterns,
|
|
9622
|
+
extract: getExtractionCandidates,
|
|
9623
|
+
fingerprint: fingerprintDeclarations,
|
|
9624
|
+
cluster: clusterPatterns,
|
|
9625
|
+
report: generateReport,
|
|
9626
|
+
pass: patternLearningPass
|
|
9627
|
+
};
|
|
9628
|
+
|
|
9629
|
+
// src/compiler/source-optimizer.ts
|
|
9630
|
+
function findDuplicates2(rules) {
|
|
9631
|
+
const signatureMap = /* @__PURE__ */ new Map();
|
|
9632
|
+
for (const rule of rules) {
|
|
9633
|
+
if (rule.isDead) continue;
|
|
9634
|
+
if (rule.declarations.length < 3) continue;
|
|
9635
|
+
const sorted = [...rule.declarations].sort((a, b) => a.property.localeCompare(b.property));
|
|
9636
|
+
const signature = sorted.map((d) => d.property + ":" + d.value).join(";");
|
|
9637
|
+
const existing = signatureMap.get(signature);
|
|
9638
|
+
if (existing) {
|
|
9639
|
+
existing.occurrences.push({
|
|
9640
|
+
selector: rule.selector,
|
|
9641
|
+
file: rule.source.file,
|
|
9642
|
+
line: rule.source.line,
|
|
9643
|
+
component: rule.source.component
|
|
9644
|
+
});
|
|
9645
|
+
existing.count++;
|
|
9646
|
+
existing.savingsBytes += estimateRuleBytes(rule);
|
|
9647
|
+
} else {
|
|
9648
|
+
signatureMap.set(signature, {
|
|
9649
|
+
signature,
|
|
9650
|
+
occurrences: [{
|
|
9651
|
+
selector: rule.selector,
|
|
9652
|
+
file: rule.source.file,
|
|
9653
|
+
line: rule.source.line,
|
|
9654
|
+
component: rule.source.component
|
|
9655
|
+
}],
|
|
9656
|
+
count: 1,
|
|
9657
|
+
suggestion: "",
|
|
9658
|
+
savingsBytes: 0
|
|
9659
|
+
});
|
|
9660
|
+
}
|
|
9661
|
+
}
|
|
9662
|
+
const duplicates = [];
|
|
9663
|
+
for (const [, group] of signatureMap) {
|
|
9664
|
+
if (group.count >= 2) {
|
|
9665
|
+
group.suggestion = generateExtractSuggestion(group);
|
|
9666
|
+
duplicates.push(group);
|
|
9667
|
+
}
|
|
9668
|
+
}
|
|
9669
|
+
return duplicates.sort((a, b) => b.savingsBytes - a.savingsBytes);
|
|
9670
|
+
}
|
|
9671
|
+
function estimateRuleBytes(rule) {
|
|
9672
|
+
let bytes = rule.selector.length + 3;
|
|
9673
|
+
for (const decl of rule.declarations) {
|
|
9674
|
+
bytes += decl.property.length + String(decl.value).length + 6;
|
|
9675
|
+
}
|
|
9676
|
+
return bytes;
|
|
9677
|
+
}
|
|
9678
|
+
function generateExtractSuggestion(group) {
|
|
9679
|
+
const selectors = group.occurrences.map((o) => o.selector).join(", ");
|
|
9680
|
+
const files = [...new Set(group.occurrences.map((o) => o.file).filter(Boolean))];
|
|
9681
|
+
return "Extract as shared recipe or component. Found in: " + (files.length > 0 ? files.join(", ") : selectors);
|
|
9682
|
+
}
|
|
9683
|
+
function findDeadRules(rules) {
|
|
9684
|
+
const dead = [];
|
|
9685
|
+
for (const rule of rules) {
|
|
9686
|
+
if (!rule.isDead) continue;
|
|
9687
|
+
dead.push({
|
|
9688
|
+
ruleId: rule.id,
|
|
9689
|
+
selector: rule.selector,
|
|
9690
|
+
file: rule.source.file,
|
|
9691
|
+
line: rule.source.line,
|
|
9692
|
+
reason: rule.meta.deathReason || "Marked as dead by optimization pass",
|
|
9693
|
+
bytesWasted: estimateRuleBytes(rule)
|
|
9694
|
+
});
|
|
9695
|
+
}
|
|
9696
|
+
return dead;
|
|
9697
|
+
}
|
|
9698
|
+
function findSpecificityConflicts(rules) {
|
|
9699
|
+
const conflicts = [];
|
|
9700
|
+
const alive = rules.filter((r) => !r.isDead);
|
|
9701
|
+
for (let i = 0; i < alive.length; i++) {
|
|
9702
|
+
for (let j = i + 1; j < alive.length; j++) {
|
|
9703
|
+
const a = alive[i];
|
|
9704
|
+
const b = alive[j];
|
|
9705
|
+
if (!selectorsOverlap(a.selector, b.selector)) continue;
|
|
9706
|
+
const aProps = new Set(a.declarations.map((d) => d.property));
|
|
9707
|
+
const bProps = new Set(b.declarations.map((d) => d.property));
|
|
9708
|
+
const overlap = [...aProps].filter((p) => bProps.has(p));
|
|
9709
|
+
if (overlap.length === 0) continue;
|
|
9710
|
+
const diff = Math.abs(a.specificity - b.specificity);
|
|
9711
|
+
if (diff >= 100) {
|
|
9712
|
+
const higher = a.specificity > b.specificity ? a : b;
|
|
9713
|
+
const lower = a.specificity > b.specificity ? b : a;
|
|
9714
|
+
conflicts.push({
|
|
9715
|
+
higher: {
|
|
9716
|
+
selector: higher.selector,
|
|
9717
|
+
specificity: higher.specificity,
|
|
9718
|
+
file: higher.source.file,
|
|
9719
|
+
line: higher.source.line
|
|
9720
|
+
},
|
|
9721
|
+
lower: {
|
|
9722
|
+
selector: lower.selector,
|
|
9723
|
+
specificity: lower.specificity,
|
|
9724
|
+
file: lower.source.file,
|
|
9725
|
+
line: lower.source.line
|
|
9726
|
+
},
|
|
9727
|
+
property: overlap[0],
|
|
9728
|
+
severity: diff >= 1e4 ? "warning" : "info"
|
|
9729
|
+
});
|
|
9730
|
+
}
|
|
9731
|
+
}
|
|
9732
|
+
}
|
|
9733
|
+
return conflicts;
|
|
9734
|
+
}
|
|
9735
|
+
function selectorsOverlap(a, b) {
|
|
9736
|
+
const partsA = a.split(/[\s>+~]+/).filter(Boolean);
|
|
9737
|
+
const partsB = b.split(/[\s>+~]+/).filter(Boolean);
|
|
9738
|
+
for (const pa of partsA) {
|
|
9739
|
+
for (const pb of partsB) {
|
|
9740
|
+
if (pa.startsWith(".") && pb.startsWith(".") && pa === pb) return true;
|
|
9741
|
+
if (pa === pb && !pa.startsWith(".") && !pa.startsWith("#")) return true;
|
|
9742
|
+
}
|
|
9743
|
+
}
|
|
9744
|
+
return false;
|
|
9745
|
+
}
|
|
9746
|
+
function findAnimationConflicts(rules) {
|
|
9747
|
+
const animationMap = /* @__PURE__ */ new Map();
|
|
9748
|
+
for (const rule of rules) {
|
|
9749
|
+
if (rule.isDead) continue;
|
|
9750
|
+
for (const atRule of rule.atRules) {
|
|
9751
|
+
if (atRule.type === "keyframes" && atRule.name) {
|
|
9752
|
+
const existing = animationMap.get(atRule.name);
|
|
9753
|
+
if (existing) {
|
|
9754
|
+
existing.locations.push({
|
|
9755
|
+
file: rule.source.file,
|
|
9756
|
+
line: rule.source.line,
|
|
9757
|
+
selector: rule.selector
|
|
9758
|
+
});
|
|
9759
|
+
existing.count++;
|
|
9760
|
+
} else {
|
|
9761
|
+
animationMap.set(atRule.name, {
|
|
9762
|
+
name: atRule.name,
|
|
9763
|
+
locations: [{
|
|
9764
|
+
file: rule.source.file,
|
|
9765
|
+
line: rule.source.line,
|
|
9766
|
+
selector: rule.selector
|
|
9767
|
+
}],
|
|
9768
|
+
count: 1
|
|
9769
|
+
});
|
|
9770
|
+
}
|
|
9771
|
+
}
|
|
9772
|
+
}
|
|
9773
|
+
}
|
|
9774
|
+
return [...animationMap.values()].filter((a) => a.count >= 2);
|
|
9775
|
+
}
|
|
9776
|
+
function findMediaQueryRedundancies(rules) {
|
|
9777
|
+
const queryMap = /* @__PURE__ */ new Map();
|
|
9778
|
+
for (const rule of rules) {
|
|
9779
|
+
if (rule.isDead) continue;
|
|
9780
|
+
for (const atRule of rule.atRules) {
|
|
9781
|
+
if (atRule.type === "media" && atRule.query) {
|
|
9782
|
+
const normalized = atRule.query.replace(/\s+/g, " ").trim();
|
|
9783
|
+
const existing = queryMap.get(normalized);
|
|
9784
|
+
if (existing) {
|
|
9785
|
+
existing.count++;
|
|
9786
|
+
if (rule.source.file) existing.files.add(rule.source.file);
|
|
9787
|
+
} else {
|
|
9788
|
+
queryMap.set(normalized, {
|
|
9789
|
+
count: 1,
|
|
9790
|
+
files: new Set(rule.source.file ? [rule.source.file] : [])
|
|
9791
|
+
});
|
|
9792
|
+
}
|
|
9793
|
+
}
|
|
9794
|
+
}
|
|
9795
|
+
}
|
|
9796
|
+
const redundancies = [];
|
|
9797
|
+
for (const [query, data] of queryMap) {
|
|
9798
|
+
if (data.count >= 3) {
|
|
9799
|
+
redundancies.push({
|
|
9800
|
+
query,
|
|
9801
|
+
count: data.count,
|
|
9802
|
+
files: [...data.files],
|
|
9803
|
+
suggestion: "Extract as shared breakpoint: $breakpoints." + generateBreakpointName(query),
|
|
9804
|
+
savingsBytes: estimateMQSavings(query, data.count)
|
|
9805
|
+
});
|
|
9806
|
+
}
|
|
9807
|
+
}
|
|
9808
|
+
return redundancies.sort((a, b) => b.savingsBytes - a.savingsBytes);
|
|
9809
|
+
}
|
|
9810
|
+
function generateBreakpointName(query) {
|
|
9811
|
+
if (query.includes("768")) return "md";
|
|
9812
|
+
if (query.includes("1024")) return "lg";
|
|
9813
|
+
if (query.includes("1280")) return "xl";
|
|
9814
|
+
if (query.includes("640")) return "sm";
|
|
9815
|
+
return "custom";
|
|
9816
|
+
}
|
|
9817
|
+
function estimateMQSavings(query, count) {
|
|
9818
|
+
const queryBytes = query.length + 12;
|
|
9819
|
+
return (count - 1) * queryBytes;
|
|
9820
|
+
}
|
|
9821
|
+
function formatReport(report) {
|
|
9822
|
+
const lines = [
|
|
9823
|
+
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
9824
|
+
" ChainCSS Source-Aware Optimization Report",
|
|
9825
|
+
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
9826
|
+
""
|
|
9827
|
+
];
|
|
9828
|
+
if (report.duplicates.length > 0) {
|
|
9829
|
+
lines.push("\u{1F501} DUPLICATES (" + report.duplicates.length + " found)");
|
|
9830
|
+
for (const dup of report.duplicates.slice(0, 5)) {
|
|
9831
|
+
const selectors = dup.occurrences.map((o) => o.selector).join(" = ");
|
|
9832
|
+
lines.push(" \u2022 " + selectors);
|
|
9833
|
+
lines.push(" \u2192 " + dup.suggestion);
|
|
9834
|
+
lines.push(" \u2192 Savings: ~" + dup.savingsBytes + "B");
|
|
9835
|
+
}
|
|
9836
|
+
lines.push("");
|
|
9837
|
+
}
|
|
9838
|
+
if (report.deadRules.length > 0) {
|
|
9839
|
+
lines.push("\u{1F480} DEAD CODE (" + report.deadRules.length + " rules, ~" + report.deadRules.reduce((s2, d) => s2 + d.bytesWasted, 0) + "B)");
|
|
9840
|
+
for (const dead of report.deadRules.slice(0, 5)) {
|
|
9841
|
+
const location = dead.file ? dead.file + (dead.line ? ":" + dead.line : "") : "unknown";
|
|
9842
|
+
lines.push(" \u2022 " + dead.selector + " (" + location + ") \u2014 " + dead.reason);
|
|
9843
|
+
}
|
|
9844
|
+
lines.push("");
|
|
9845
|
+
}
|
|
9846
|
+
if (report.specificityConflicts.length > 0) {
|
|
9847
|
+
lines.push("\u2694\uFE0F SPECIFICITY WARS (" + report.specificityConflicts.length + " conflicts)");
|
|
9848
|
+
for (const conflict of report.specificityConflicts.slice(0, 5)) {
|
|
9849
|
+
lines.push(" \u2022 " + conflict.higher.selector + " (" + conflict.higher.specificity + ")");
|
|
9850
|
+
lines.push(" overrides: " + conflict.lower.selector + " (" + conflict.lower.specificity + ")");
|
|
9851
|
+
if (conflict.property) lines.push(" Property: " + conflict.property);
|
|
9852
|
+
}
|
|
9853
|
+
lines.push("");
|
|
9854
|
+
}
|
|
9855
|
+
if (report.animationConflicts.length > 0) {
|
|
9856
|
+
lines.push("\u{1F3AC} ANIMATION CONFLICTS (" + report.animationConflicts.length + " found)");
|
|
9857
|
+
for (const ac of report.animationConflicts.slice(0, 5)) {
|
|
9858
|
+
lines.push(" \u2022 @keyframes " + ac.name + " \u2014 defined " + ac.count + " times");
|
|
9859
|
+
for (const loc of ac.locations) {
|
|
9860
|
+
lines.push(" " + (loc.file || "unknown") + " \u2192 " + loc.selector);
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
lines.push("");
|
|
9864
|
+
}
|
|
9865
|
+
if (report.mediaQueryRedundancies.length > 0) {
|
|
9866
|
+
lines.push("\u{1F4F1} MEDIA QUERY CONSOLIDATION (" + report.mediaQueryRedundancies.length + " redundant)");
|
|
9867
|
+
for (const mq of report.mediaQueryRedundancies.slice(0, 5)) {
|
|
9868
|
+
lines.push(" \u2022 " + mq.query + " \u2014 used " + mq.count + " times");
|
|
9869
|
+
lines.push(" \u2192 " + mq.suggestion);
|
|
9870
|
+
lines.push(" \u2192 Savings: ~" + mq.savingsBytes + "B");
|
|
9871
|
+
}
|
|
9872
|
+
lines.push("");
|
|
9873
|
+
}
|
|
9874
|
+
const s = report.summary;
|
|
9875
|
+
lines.push("\u{1F4CA} SUMMARY");
|
|
9876
|
+
lines.push(" \u2022 " + s.duplicatesCount + " duplicates \u2192 extract recipes");
|
|
9877
|
+
lines.push(" \u2022 " + s.deadCount + " dead rules \u2192 remove");
|
|
9878
|
+
lines.push(" \u2022 " + s.specificityCount + " specificity issues \u2192 fix cascade");
|
|
9879
|
+
lines.push(" \u2022 " + s.animationCount + " animation conflicts \u2192 scope names");
|
|
9880
|
+
lines.push(" \u2022 " + s.mediaQueryCount + " redundant media queries \u2192 consolidate");
|
|
9881
|
+
lines.push(" \u2022 Total potential savings: " + s.totalSavingsKB);
|
|
9882
|
+
lines.push("");
|
|
9883
|
+
lines.push("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
9884
|
+
return lines.join("\n");
|
|
9885
|
+
}
|
|
9886
|
+
function generateOptimizationReport(rules) {
|
|
9887
|
+
const duplicates = findDuplicates2(rules);
|
|
9888
|
+
const deadRules = findDeadRules(rules);
|
|
9889
|
+
const specificityConflicts = findSpecificityConflicts(rules);
|
|
9890
|
+
const animationConflicts = findAnimationConflicts(rules);
|
|
9891
|
+
const mediaQueryRedundancies = findMediaQueryRedundancies(rules);
|
|
9892
|
+
const totalSavingsBytes = duplicates.reduce((s, d) => s + d.savingsBytes, 0) + deadRules.reduce((s, d) => s + d.bytesWasted, 0) + mediaQueryRedundancies.reduce((s, m) => s + m.savingsBytes, 0);
|
|
9893
|
+
const report = {
|
|
9894
|
+
duplicates,
|
|
9895
|
+
deadRules,
|
|
9896
|
+
specificityConflicts,
|
|
9897
|
+
animationConflicts,
|
|
9898
|
+
mediaQueryRedundancies,
|
|
9899
|
+
summary: {
|
|
9900
|
+
totalIssues: duplicates.length + deadRules.length + specificityConflicts.length + animationConflicts.length + mediaQueryRedundancies.length,
|
|
9901
|
+
duplicatesCount: duplicates.length,
|
|
9902
|
+
deadCount: deadRules.length,
|
|
9903
|
+
specificityCount: specificityConflicts.length,
|
|
9904
|
+
animationCount: animationConflicts.length,
|
|
9905
|
+
mediaQueryCount: mediaQueryRedundancies.length,
|
|
9906
|
+
totalSavingsBytes,
|
|
9907
|
+
totalSavingsKB: totalSavingsBytes > 1e3 ? (totalSavingsBytes / 1e3).toFixed(1) + "KB" : totalSavingsBytes + "B"
|
|
9908
|
+
},
|
|
9909
|
+
formattedReport: ""
|
|
9910
|
+
};
|
|
9911
|
+
report.formattedReport = formatReport(report);
|
|
9912
|
+
return report;
|
|
9913
|
+
}
|
|
9914
|
+
var sourceOptimizerPass = (ir) => {
|
|
9915
|
+
const report = generateOptimizationReport(ir.rules);
|
|
9916
|
+
for (const dup of report.duplicates.slice(0, 3)) {
|
|
9917
|
+
ir.diagnostics.push({
|
|
9918
|
+
id: "source-dup-" + Date.now(),
|
|
9919
|
+
nodeId: ir.rules[0]?.id || ir.id,
|
|
9920
|
+
severity: "warning",
|
|
9921
|
+
message: "Duplicate: " + dup.occurrences.map((o) => o.selector).join(" = ") + " (" + dup.count + "\xD7)",
|
|
9922
|
+
suggestion: dup.suggestion,
|
|
9923
|
+
pass: "source-optimizer"
|
|
9924
|
+
});
|
|
9925
|
+
}
|
|
9926
|
+
for (const dead of report.deadRules.slice(0, 3)) {
|
|
9927
|
+
ir.diagnostics.push({
|
|
9928
|
+
id: "source-dead-" + Date.now(),
|
|
9929
|
+
nodeId: dead.ruleId,
|
|
9930
|
+
severity: "info",
|
|
9931
|
+
message: "Dead rule: " + dead.selector + " \u2014 " + dead.reason,
|
|
9932
|
+
pass: "source-optimizer"
|
|
9933
|
+
});
|
|
9934
|
+
}
|
|
9935
|
+
ir.meta = ir.meta || {};
|
|
9936
|
+
ir.meta.optimizationReport = report;
|
|
9937
|
+
return ir;
|
|
9938
|
+
};
|
|
9939
|
+
function optimizeSource(rules) {
|
|
9940
|
+
return generateOptimizationReport(rules);
|
|
9941
|
+
}
|
|
9942
|
+
var sourceOptimizer = {
|
|
9943
|
+
optimize: optimizeSource,
|
|
9944
|
+
findDuplicates: findDuplicates2,
|
|
9945
|
+
findDeadRules,
|
|
9946
|
+
findSpecificityConflicts,
|
|
9947
|
+
findAnimationConflicts,
|
|
9948
|
+
findMediaQueryRedundancies,
|
|
9949
|
+
report: generateOptimizationReport,
|
|
9950
|
+
format: formatReport,
|
|
9951
|
+
pass: sourceOptimizerPass
|
|
9952
|
+
};
|
|
9953
|
+
|
|
9954
|
+
// src/compiler/semantic-tokens.ts
|
|
9955
|
+
var DEFAULT_THEME = {
|
|
9956
|
+
// --- SURFACES ---
|
|
9957
|
+
surface: {
|
|
9958
|
+
interactive: {
|
|
9959
|
+
properties: {
|
|
9960
|
+
backgroundColor: "$colors.primary.500",
|
|
9961
|
+
color: "$colors.white",
|
|
9962
|
+
borderRadius: "$radii.md",
|
|
9963
|
+
cursor: "pointer",
|
|
9964
|
+
transition: "all 0.2s ease"
|
|
9965
|
+
},
|
|
9966
|
+
description: "Clickable surface \u2014 buttons, CTAs, links"
|
|
9967
|
+
},
|
|
9968
|
+
container: {
|
|
9969
|
+
properties: {
|
|
9970
|
+
backgroundColor: "$colors.neutral.100",
|
|
9971
|
+
color: "$colors.neutral.900",
|
|
9972
|
+
borderRadius: "$radii.lg",
|
|
9973
|
+
border: "1px solid $colors.neutral.200"
|
|
9974
|
+
},
|
|
9975
|
+
description: "Content container \u2014 cards, sections, panels"
|
|
9976
|
+
},
|
|
9977
|
+
overlay: {
|
|
9978
|
+
properties: {
|
|
9979
|
+
backgroundColor: "$colors.white",
|
|
9980
|
+
color: "$colors.neutral.900",
|
|
9981
|
+
borderRadius: "$radii.xl",
|
|
9982
|
+
boxShadow: "$shadows.xl",
|
|
9983
|
+
zIndex: "50"
|
|
9984
|
+
},
|
|
9985
|
+
description: "Modal overlay \u2014 dialogs, drawers"
|
|
9986
|
+
},
|
|
9987
|
+
sheet: {
|
|
9988
|
+
properties: {
|
|
9989
|
+
backgroundColor: "$colors.neutral.50",
|
|
9990
|
+
color: "$colors.neutral.800",
|
|
9991
|
+
borderRadius: "$radii.lg $radii.lg 0 0",
|
|
9992
|
+
boxShadow: "$shadows.lg"
|
|
9993
|
+
},
|
|
9994
|
+
description: "Bottom sheet \u2014 mobile menus, action sheets"
|
|
9995
|
+
},
|
|
9996
|
+
tooltip: {
|
|
9997
|
+
properties: {
|
|
9998
|
+
backgroundColor: "$colors.neutral.900",
|
|
9999
|
+
color: "$colors.white",
|
|
10000
|
+
borderRadius: "$radii.sm",
|
|
10001
|
+
padding: "4px 8px",
|
|
10002
|
+
fontSize: "12px",
|
|
10003
|
+
boxShadow: "$shadows.md"
|
|
10004
|
+
},
|
|
10005
|
+
description: "Tooltip \u2014 hover information popups"
|
|
10006
|
+
},
|
|
10007
|
+
input: {
|
|
10008
|
+
properties: {
|
|
10009
|
+
backgroundColor: "$colors.white",
|
|
10010
|
+
color: "$colors.neutral.900",
|
|
10011
|
+
borderRadius: "$radii.md",
|
|
10012
|
+
border: "1px solid $colors.neutral.300",
|
|
10013
|
+
padding: "8px 12px"
|
|
10014
|
+
},
|
|
10015
|
+
description: "Input field \u2014 text inputs, selects, textareas"
|
|
10016
|
+
}
|
|
10017
|
+
},
|
|
10018
|
+
// --- TEXT ---
|
|
10019
|
+
text: {
|
|
10020
|
+
primary: {
|
|
10021
|
+
properties: {
|
|
10022
|
+
color: "$colors.neutral.900",
|
|
10023
|
+
fontWeight: "400"
|
|
10024
|
+
},
|
|
10025
|
+
description: "Primary body text \u2014 main content"
|
|
10026
|
+
},
|
|
10027
|
+
secondary: {
|
|
10028
|
+
properties: {
|
|
10029
|
+
color: "$colors.neutral.600",
|
|
10030
|
+
fontWeight: "400"
|
|
10031
|
+
},
|
|
10032
|
+
description: "Secondary text \u2014 descriptions, captions"
|
|
10033
|
+
},
|
|
10034
|
+
muted: {
|
|
10035
|
+
properties: {
|
|
10036
|
+
color: "$colors.neutral.400",
|
|
10037
|
+
fontSize: "14px",
|
|
10038
|
+
fontWeight: "400"
|
|
10039
|
+
},
|
|
10040
|
+
description: "Muted text \u2014 placeholders, hints, meta info"
|
|
10041
|
+
},
|
|
10042
|
+
link: {
|
|
10043
|
+
properties: {
|
|
10044
|
+
color: "$colors.primary.500",
|
|
10045
|
+
fontWeight: "500",
|
|
10046
|
+
textDecoration: "underline",
|
|
10047
|
+
cursor: "pointer"
|
|
10048
|
+
},
|
|
10049
|
+
description: "Link text \u2014 hyperlinks, navigational text"
|
|
10050
|
+
},
|
|
10051
|
+
inverse: {
|
|
10052
|
+
properties: {
|
|
10053
|
+
color: "$colors.white",
|
|
10054
|
+
fontWeight: "500"
|
|
10055
|
+
},
|
|
10056
|
+
description: "Inverse text \u2014 text on dark backgrounds"
|
|
10057
|
+
},
|
|
10058
|
+
code: {
|
|
10059
|
+
properties: {
|
|
10060
|
+
fontFamily: "monospace",
|
|
10061
|
+
fontSize: "14px",
|
|
10062
|
+
color: "$colors.neutral.800",
|
|
10063
|
+
backgroundColor: "$colors.neutral.100",
|
|
10064
|
+
borderRadius: "$radii.sm",
|
|
10065
|
+
padding: "2px 6px"
|
|
10066
|
+
},
|
|
10067
|
+
description: "Code text \u2014 inline code, code blocks"
|
|
10068
|
+
}
|
|
10069
|
+
},
|
|
10070
|
+
// --- ELEVATION ---
|
|
10071
|
+
elevation: {
|
|
10072
|
+
flat: {
|
|
10073
|
+
properties: {
|
|
10074
|
+
boxShadow: "none",
|
|
10075
|
+
zIndex: "0"
|
|
10076
|
+
},
|
|
10077
|
+
description: "Flat \u2014 no elevation, base level"
|
|
10078
|
+
},
|
|
10079
|
+
raised: {
|
|
10080
|
+
properties: {
|
|
10081
|
+
boxShadow: "$shadows.sm",
|
|
10082
|
+
zIndex: "10"
|
|
10083
|
+
},
|
|
10084
|
+
description: "Raised \u2014 subtle lift, cards on light backgrounds"
|
|
10085
|
+
},
|
|
10086
|
+
floating: {
|
|
10087
|
+
properties: {
|
|
10088
|
+
boxShadow: "$shadows.md",
|
|
10089
|
+
zIndex: "20"
|
|
10090
|
+
},
|
|
10091
|
+
description: "Floating \u2014 dropdowns, popovers"
|
|
10092
|
+
},
|
|
10093
|
+
sticky: {
|
|
10094
|
+
properties: {
|
|
10095
|
+
boxShadow: "$shadows.md",
|
|
10096
|
+
zIndex: "30",
|
|
10097
|
+
position: "sticky",
|
|
10098
|
+
top: "0"
|
|
10099
|
+
},
|
|
10100
|
+
description: "Sticky \u2014 sticky headers, persistent nav"
|
|
10101
|
+
},
|
|
10102
|
+
overlay: {
|
|
10103
|
+
properties: {
|
|
10104
|
+
boxShadow: "$shadows.xl",
|
|
10105
|
+
zIndex: "50",
|
|
10106
|
+
position: "fixed"
|
|
10107
|
+
},
|
|
10108
|
+
description: "Overlay \u2014 modals, dialogs"
|
|
10109
|
+
},
|
|
10110
|
+
modal: {
|
|
10111
|
+
properties: {
|
|
10112
|
+
boxShadow: "$shadows.2xl",
|
|
10113
|
+
zIndex: "100",
|
|
10114
|
+
position: "fixed",
|
|
10115
|
+
backdropFilter: "blur(4px)"
|
|
10116
|
+
},
|
|
10117
|
+
description: "Modal \u2014 full-screen overlay with backdrop"
|
|
10118
|
+
}
|
|
10119
|
+
},
|
|
10120
|
+
// --- STATES ---
|
|
10121
|
+
state: {
|
|
10122
|
+
hover: {
|
|
10123
|
+
properties: {
|
|
10124
|
+
filter: "brightness(1.1)",
|
|
10125
|
+
transition: "filter 0.2s ease"
|
|
10126
|
+
},
|
|
10127
|
+
pseudoClass: "hover",
|
|
10128
|
+
description: "Hover state \u2014 slight brightening"
|
|
10129
|
+
},
|
|
10130
|
+
active: {
|
|
10131
|
+
properties: {
|
|
10132
|
+
filter: "brightness(0.95)",
|
|
10133
|
+
transform: "scale(0.98)",
|
|
10134
|
+
transition: "all 0.1s ease"
|
|
10135
|
+
},
|
|
10136
|
+
pseudoClass: "active",
|
|
10137
|
+
description: "Active/pressed state \u2014 slight darkening + press"
|
|
10138
|
+
},
|
|
10139
|
+
focus: {
|
|
10140
|
+
properties: {
|
|
10141
|
+
outline: "2px solid $colors.primary.500",
|
|
10142
|
+
outlineOffset: "2px"
|
|
10143
|
+
},
|
|
10144
|
+
pseudoClass: "focus-visible",
|
|
10145
|
+
description: "Focus state \u2014 accessible focus ring"
|
|
10146
|
+
},
|
|
10147
|
+
disabled: {
|
|
10148
|
+
properties: {
|
|
10149
|
+
opacity: "0.5",
|
|
10150
|
+
cursor: "not-allowed",
|
|
10151
|
+
pointerEvents: "none"
|
|
10152
|
+
},
|
|
10153
|
+
pseudoClass: "disabled",
|
|
10154
|
+
description: "Disabled state \u2014 reduced opacity, no interaction"
|
|
10155
|
+
},
|
|
10156
|
+
loading: {
|
|
10157
|
+
properties: {
|
|
10158
|
+
cursor: "wait",
|
|
10159
|
+
opacity: "0.7",
|
|
10160
|
+
pointerEvents: "none"
|
|
10161
|
+
},
|
|
10162
|
+
description: "Loading state \u2014 waiting cursor, partially transparent"
|
|
10163
|
+
},
|
|
10164
|
+
selected: {
|
|
10165
|
+
properties: {
|
|
10166
|
+
backgroundColor: "$colors.primary.50",
|
|
10167
|
+
color: "$colors.primary.700",
|
|
10168
|
+
fontWeight: "600"
|
|
10169
|
+
},
|
|
10170
|
+
pseudoClass: "selected",
|
|
10171
|
+
description: "Selected state \u2014 highlighted background"
|
|
10172
|
+
}
|
|
10173
|
+
},
|
|
10174
|
+
// --- SPACING ---
|
|
10175
|
+
spacing: {
|
|
10176
|
+
none: {
|
|
10177
|
+
properties: { padding: "0", gap: "0" },
|
|
10178
|
+
description: "No spacing"
|
|
10179
|
+
},
|
|
10180
|
+
tight: {
|
|
10181
|
+
properties: { padding: "4px 8px", gap: "4px" },
|
|
10182
|
+
description: "Tight spacing \u2014 icon buttons, chips, badges"
|
|
10183
|
+
},
|
|
10184
|
+
compact: {
|
|
10185
|
+
properties: { padding: "8px 12px", gap: "8px" },
|
|
10186
|
+
description: "Compact spacing \u2014 dense lists, toolbars"
|
|
10187
|
+
},
|
|
10188
|
+
comfortable: {
|
|
10189
|
+
properties: { padding: "12px 16px", gap: "12px" },
|
|
10190
|
+
description: "Comfortable spacing \u2014 form fields, cards (default)"
|
|
10191
|
+
},
|
|
10192
|
+
spacious: {
|
|
10193
|
+
properties: { padding: "24px 32px", gap: "24px" },
|
|
10194
|
+
description: "Spacious spacing \u2014 hero sections, feature cards"
|
|
10195
|
+
},
|
|
10196
|
+
generous: {
|
|
10197
|
+
properties: { padding: "48px 64px", gap: "32px" },
|
|
10198
|
+
description: "Generous spacing \u2014 landing pages, large sections"
|
|
10199
|
+
}
|
|
10200
|
+
}
|
|
10201
|
+
};
|
|
10202
|
+
var DARK_OVERRIDES = {
|
|
10203
|
+
surface: {
|
|
10204
|
+
interactive: { backgroundColor: "$colors.primary.400", color: "$colors.white" },
|
|
10205
|
+
container: { backgroundColor: "$colors.neutral.800", color: "$colors.neutral.100", border: "1px solid $colors.neutral.700" },
|
|
10206
|
+
overlay: { backgroundColor: "$colors.neutral.850", color: "$colors.neutral.100" },
|
|
10207
|
+
sheet: { backgroundColor: "$colors.neutral.800", color: "$colors.neutral.100" },
|
|
10208
|
+
input: { backgroundColor: "$colors.neutral.800", color: "$colors.neutral.100", border: "1px solid $colors.neutral.600" }
|
|
10209
|
+
},
|
|
10210
|
+
text: {
|
|
10211
|
+
primary: { color: "$colors.neutral.100" },
|
|
10212
|
+
secondary: { color: "$colors.neutral.400" },
|
|
10213
|
+
muted: { color: "$colors.neutral.500" },
|
|
10214
|
+
inverse: { color: "$colors.neutral.900" },
|
|
10215
|
+
code: { backgroundColor: "$colors.neutral.800", color: "$colors.neutral.200" }
|
|
10216
|
+
}
|
|
10217
|
+
};
|
|
10218
|
+
var HIGH_CONTRAST_OVERRIDES = {
|
|
10219
|
+
surface: {
|
|
10220
|
+
interactive: { backgroundColor: "$colors.black", color: "$colors.white", border: "2px solid $colors.white" },
|
|
10221
|
+
container: { backgroundColor: "$colors.white", color: "$colors.black", border: "2px solid $colors.black" },
|
|
10222
|
+
input: { backgroundColor: "$colors.white", color: "$colors.black", border: "2px solid $colors.black" }
|
|
10223
|
+
},
|
|
10224
|
+
text: {
|
|
10225
|
+
primary: { color: "$colors.black", fontWeight: "500" },
|
|
10226
|
+
secondary: { color: "$colors.neutral.800", fontWeight: "500" },
|
|
10227
|
+
muted: { color: "$colors.neutral.700" },
|
|
10228
|
+
link: { color: "$colors.blue.800", textDecoration: "underline" }
|
|
10229
|
+
}
|
|
10230
|
+
};
|
|
10231
|
+
function resolveSemantic(category, intent2, themeContext = { mode: "light" }) {
|
|
10232
|
+
const baseMap = DEFAULT_THEME[category];
|
|
10233
|
+
if (!baseMap) return null;
|
|
10234
|
+
const mapping = baseMap[intent2];
|
|
10235
|
+
if (!mapping) return null;
|
|
10236
|
+
const properties = { ...mapping.properties };
|
|
10237
|
+
if (themeContext.mode === "dark" && DARK_OVERRIDES[category]?.[intent2]) {
|
|
10238
|
+
Object.assign(properties, DARK_OVERRIDES[category][intent2]);
|
|
10239
|
+
}
|
|
10240
|
+
if (themeContext.mode === "high-contrast" && HIGH_CONTRAST_OVERRIDES[category]?.[intent2]) {
|
|
10241
|
+
Object.assign(properties, HIGH_CONTRAST_OVERRIDES[category][intent2]);
|
|
10242
|
+
}
|
|
10243
|
+
if (themeContext.containerContext === "dark" && category === "surface") {
|
|
10244
|
+
if (intent2 === "interactive") {
|
|
10245
|
+
properties.backgroundColor = "$colors.primary.300";
|
|
10246
|
+
}
|
|
10247
|
+
}
|
|
10248
|
+
return {
|
|
10249
|
+
properties,
|
|
10250
|
+
pseudoClass: mapping.pseudoClass,
|
|
10251
|
+
description: mapping.description
|
|
10252
|
+
};
|
|
10253
|
+
}
|
|
10254
|
+
function getSemanticIntents(category) {
|
|
10255
|
+
const map = DEFAULT_THEME[category];
|
|
10256
|
+
return map ? Object.keys(map) : [];
|
|
10257
|
+
}
|
|
10258
|
+
function getSemanticDescription(category, intent2) {
|
|
10259
|
+
return DEFAULT_THEME[category]?.[intent2]?.description || null;
|
|
10260
|
+
}
|
|
10261
|
+
var semanticTokensPass = (ir) => {
|
|
10262
|
+
for (const rule of ir.rules) {
|
|
10263
|
+
const semanticIntents = rule.meta._semantic || [];
|
|
10264
|
+
for (const { category, intent: intent2, theme } of semanticIntents) {
|
|
10265
|
+
const resolved = resolveSemantic(category, intent2, theme);
|
|
10266
|
+
if (!resolved) continue;
|
|
10267
|
+
for (const [prop, value] of Object.entries(resolved.properties)) {
|
|
10268
|
+
const decl = createDeclaration(prop, value);
|
|
10269
|
+
decl.history.push({
|
|
10270
|
+
pass: "semantic-tokens",
|
|
10271
|
+
action: "resolved-intent",
|
|
10272
|
+
timestamp: Date.now(),
|
|
10273
|
+
reason: category + ":" + intent2 + " \u2192 " + prop + ": " + value
|
|
10274
|
+
});
|
|
10275
|
+
decl.meta.semantic = { category, intent: intent2 };
|
|
10276
|
+
if (resolved.pseudoClass) {
|
|
10277
|
+
let pc = rule.pseudoClasses.find((p) => p.name === resolved.pseudoClass);
|
|
10278
|
+
if (!pc) {
|
|
10279
|
+
pc = {
|
|
10280
|
+
id: "semantic-pc-" + Date.now(),
|
|
10281
|
+
name: resolved.pseudoClass,
|
|
10282
|
+
declarations: [],
|
|
10283
|
+
source: rule.source,
|
|
10284
|
+
history: []
|
|
10285
|
+
};
|
|
10286
|
+
rule.pseudoClasses.push(pc);
|
|
10287
|
+
}
|
|
10288
|
+
pc.declarations.push(decl);
|
|
10289
|
+
} else {
|
|
10290
|
+
rule.declarations.push(decl);
|
|
10291
|
+
}
|
|
10292
|
+
}
|
|
10293
|
+
}
|
|
10294
|
+
}
|
|
10295
|
+
return ir;
|
|
10296
|
+
};
|
|
10297
|
+
var semanticTokens = {
|
|
10298
|
+
resolve: resolveSemantic,
|
|
10299
|
+
getIntents: getSemanticIntents,
|
|
10300
|
+
getDescription: getSemanticDescription,
|
|
10301
|
+
pass: semanticTokensPass,
|
|
10302
|
+
theme: DEFAULT_THEME,
|
|
10303
|
+
darkOverrides: DARK_OVERRIDES,
|
|
10304
|
+
highContrastOverrides: HIGH_CONTRAST_OVERRIDES
|
|
10305
|
+
};
|
|
10306
|
+
|
|
10307
|
+
// src/compiler/accessibility-engine.ts
|
|
10308
|
+
var WCAG = {
|
|
10309
|
+
MIN_CONTRAST_AA: 4.5,
|
|
10310
|
+
MIN_CONTRAST_AA_LARGE: 3,
|
|
10311
|
+
MIN_CONTRAST_AAA: 7,
|
|
10312
|
+
MIN_FONT_SIZE: 12,
|
|
10313
|
+
// px
|
|
10314
|
+
MIN_TOUCH_TARGET: 44,
|
|
10315
|
+
// px
|
|
10316
|
+
CRITERIA: {
|
|
10317
|
+
contrast: "1.4.3 Contrast (Minimum) \u2014 AA",
|
|
10318
|
+
fontSize: "1.4.4 Resize Text \u2014 AA",
|
|
10319
|
+
touchTarget: "2.5.8 Target Size \u2014 AA",
|
|
10320
|
+
focus: "2.4.7 Focus Visible \u2014 AA",
|
|
10321
|
+
motion: "2.3.3 Animation from Interactions \u2014 AAA",
|
|
10322
|
+
hoverOnly: "1.4.13 Content on Hover or Focus \u2014 AA",
|
|
10323
|
+
colorOnly: "1.4.1 Use of Color \u2014 A"
|
|
10324
|
+
}
|
|
10325
|
+
};
|
|
10326
|
+
function detectContrast(rule) {
|
|
10327
|
+
const issues = [];
|
|
10328
|
+
const color = rule.declarations.find(
|
|
10329
|
+
(d) => d.property === "color" && typeof d.value === "string"
|
|
10330
|
+
);
|
|
10331
|
+
const bg = rule.declarations.find(
|
|
10332
|
+
(d) => (d.property === "backgroundColor" || d.property === "background") && typeof d.value === "string"
|
|
10333
|
+
);
|
|
10334
|
+
if (color && bg) {
|
|
10335
|
+
const ratio = contrastRatio(String(color.value), String(bg.value));
|
|
10336
|
+
if (ratio > 0 && ratio < WCAG.MIN_CONTRAST_AA) {
|
|
10337
|
+
issues.push({
|
|
10338
|
+
ruleId: rule.id,
|
|
10339
|
+
selector: rule.selector,
|
|
10340
|
+
category: "contrast",
|
|
10341
|
+
severity: "error",
|
|
10342
|
+
wcagCriterion: WCAG.CRITERIA.contrast,
|
|
10343
|
+
message: "Contrast ratio " + ratio.toFixed(1) + ":1 fails WCAG AA (needs " + WCAG.MIN_CONTRAST_AA + ":1)",
|
|
10344
|
+
suggestion: "Darken text or lighten background. Current: " + color.value + " on " + bg.value,
|
|
10345
|
+
autoFixable: false
|
|
10346
|
+
// Can't auto-fix without knowing design intent
|
|
10347
|
+
});
|
|
10348
|
+
} else if (ratio > 0 && ratio < WCAG.MIN_CONTRAST_AAA) {
|
|
10349
|
+
issues.push({
|
|
10350
|
+
ruleId: rule.id,
|
|
10351
|
+
selector: rule.selector,
|
|
10352
|
+
category: "contrast",
|
|
10353
|
+
severity: "warning",
|
|
10354
|
+
wcagCriterion: WCAG.CRITERIA.contrast,
|
|
10355
|
+
message: "Contrast ratio " + ratio.toFixed(1) + ":1 passes AA but fails AAA (" + WCAG.MIN_CONTRAST_AAA + ":1)",
|
|
10356
|
+
suggestion: "Consider increasing contrast for better readability.",
|
|
10357
|
+
autoFixable: false
|
|
10358
|
+
});
|
|
10359
|
+
}
|
|
10360
|
+
}
|
|
10361
|
+
return issues;
|
|
10362
|
+
}
|
|
10363
|
+
function detectMinimumFontSize(rule) {
|
|
10364
|
+
const issues = [];
|
|
10365
|
+
for (const decl of rule.declarations) {
|
|
10366
|
+
if ((decl.property === "fontSize" || decl.property === "font-size") && typeof decl.value === "string") {
|
|
10367
|
+
const pxMatch = decl.value.match(/^(\d+(\.\d+)?)px$/);
|
|
10368
|
+
if (pxMatch) {
|
|
10369
|
+
const px = parseFloat(pxMatch[1]);
|
|
10370
|
+
if (px < WCAG.MIN_FONT_SIZE) {
|
|
10371
|
+
issues.push({
|
|
10372
|
+
ruleId: rule.id,
|
|
10373
|
+
selector: rule.selector,
|
|
10374
|
+
category: "font-size",
|
|
10375
|
+
severity: "warning",
|
|
10376
|
+
wcagCriterion: WCAG.CRITERIA.fontSize,
|
|
10377
|
+
message: "font-size: " + decl.value + " is below WCAG minimum of " + WCAG.MIN_FONT_SIZE + "px",
|
|
10378
|
+
suggestion: "font-size: max(" + WCAG.MIN_FONT_SIZE + "px, " + decl.value + ")",
|
|
10379
|
+
autoFixable: true
|
|
10380
|
+
});
|
|
10381
|
+
}
|
|
10382
|
+
}
|
|
10383
|
+
}
|
|
10384
|
+
}
|
|
10385
|
+
return issues;
|
|
10386
|
+
}
|
|
10387
|
+
function detectTouchTargets(rule) {
|
|
10388
|
+
const issues = [];
|
|
10389
|
+
const isInteractive = rule.declarations.some(
|
|
10390
|
+
(d) => d.property === "cursor" && d.value === "pointer"
|
|
10391
|
+
);
|
|
10392
|
+
const isButton = rule.selector.includes("btn") || rule.selector.includes("button");
|
|
10393
|
+
const isLink = rule.selector.includes("link") || rule.selector.includes("a");
|
|
10394
|
+
if (!isInteractive && !isButton && !isLink) return issues;
|
|
10395
|
+
const width = rule.declarations.find(
|
|
10396
|
+
(d) => (d.property === "width" || d.property === "min-width") && typeof d.value === "string"
|
|
10397
|
+
);
|
|
10398
|
+
const height = rule.declarations.find(
|
|
10399
|
+
(d) => (d.property === "height" || d.property === "min-height") && typeof d.value === "string"
|
|
10400
|
+
);
|
|
10401
|
+
const hasWidthIssue = width && extractPx(String(width.value)) < WCAG.MIN_TOUCH_TARGET;
|
|
10402
|
+
const hasHeightIssue = height && extractPx(String(height.value)) < WCAG.MIN_TOUCH_TARGET;
|
|
10403
|
+
if (hasWidthIssue || hasHeightIssue) {
|
|
10404
|
+
issues.push({
|
|
10405
|
+
ruleId: rule.id,
|
|
10406
|
+
selector: rule.selector,
|
|
10407
|
+
category: "touch-target",
|
|
10408
|
+
severity: "warning",
|
|
10409
|
+
wcagCriterion: WCAG.CRITERIA.touchTarget,
|
|
10410
|
+
message: 'Interactive element "' + rule.selector + '" may be too small for touch (needs \u2265 ' + WCAG.MIN_TOUCH_TARGET + "\xD7" + WCAG.MIN_TOUCH_TARGET + "px)",
|
|
10411
|
+
suggestion: "Add min-width: " + WCAG.MIN_TOUCH_TARGET + "px; min-height: " + WCAG.MIN_TOUCH_TARGET + "px;",
|
|
10412
|
+
autoFixable: true
|
|
10413
|
+
});
|
|
10414
|
+
}
|
|
10415
|
+
return issues;
|
|
10416
|
+
}
|
|
10417
|
+
function detectMissingFocus(rule) {
|
|
10418
|
+
const issues = [];
|
|
10419
|
+
const isInteractive = rule.declarations.some(
|
|
10420
|
+
(d) => d.property === "cursor" && d.value === "pointer"
|
|
10421
|
+
);
|
|
10422
|
+
if (!isInteractive) return issues;
|
|
10423
|
+
const outline = rule.declarations.find(
|
|
10424
|
+
(d) => d.property === "outline"
|
|
10425
|
+
);
|
|
10426
|
+
const hasFocusStyle = rule.pseudoClasses.some(
|
|
10427
|
+
(pc) => (pc.name === "focus" || pc.name === "focus-visible") && pc.declarations.length > 0
|
|
10428
|
+
);
|
|
10429
|
+
if (outline && String(outline.value) === "none" && !hasFocusStyle) {
|
|
10430
|
+
issues.push({
|
|
10431
|
+
ruleId: rule.id,
|
|
10432
|
+
selector: rule.selector,
|
|
10433
|
+
category: "focus",
|
|
10434
|
+
severity: "error",
|
|
10435
|
+
wcagCriterion: WCAG.CRITERIA.focus,
|
|
10436
|
+
message: '"' + rule.selector + '" has outline: none with no :focus-visible fallback',
|
|
10437
|
+
suggestion: 'Add .focusVisible(c => c.outline("2px solid #3b82f6").outlineOffset("2px"))',
|
|
10438
|
+
autoFixable: false
|
|
10439
|
+
});
|
|
10440
|
+
}
|
|
10441
|
+
if (!outline && !hasFocusStyle && isInteractive) {
|
|
10442
|
+
issues.push({
|
|
10443
|
+
ruleId: rule.id,
|
|
10444
|
+
selector: rule.selector,
|
|
10445
|
+
category: "focus",
|
|
10446
|
+
severity: "warning",
|
|
10447
|
+
wcagCriterion: WCAG.CRITERIA.focus,
|
|
10448
|
+
message: 'Interactive element "' + rule.selector + '" has no visible focus indicator',
|
|
10449
|
+
suggestion: "Add :focus-visible { outline: 2px solid #3b82f6; outline-offset: 2px; }",
|
|
10450
|
+
autoFixable: true
|
|
10451
|
+
});
|
|
10452
|
+
}
|
|
10453
|
+
return issues;
|
|
10454
|
+
}
|
|
10455
|
+
function detectMotionRespect(rule) {
|
|
10456
|
+
const issues = [];
|
|
10457
|
+
const hasAnimation = rule.declarations.some(
|
|
10458
|
+
(d) => (d.property === "animation" || d.property === "animation-name") && typeof d.value === "string" && String(d.value) !== "none"
|
|
10459
|
+
);
|
|
10460
|
+
const hasTransition = rule.declarations.some(
|
|
10461
|
+
(d) => d.property === "transition" && typeof d.value === "string"
|
|
10462
|
+
);
|
|
10463
|
+
const hasReducedMotionWrapper = rule.atRules.some(
|
|
10464
|
+
(at) => at.type === "media" && at.query && at.query.includes("prefers-reduced-motion")
|
|
10465
|
+
);
|
|
10466
|
+
if ((hasAnimation || hasTransition) && !hasReducedMotionWrapper) {
|
|
10467
|
+
issues.push({
|
|
10468
|
+
ruleId: rule.id,
|
|
10469
|
+
selector: rule.selector,
|
|
10470
|
+
category: "motion",
|
|
10471
|
+
severity: "warning",
|
|
10472
|
+
wcagCriterion: WCAG.CRITERIA.motion,
|
|
10473
|
+
message: 'Animations/transitions on "' + rule.selector + '" should respect prefers-reduced-motion',
|
|
10474
|
+
suggestion: "Wrap in @media (prefers-reduced-motion: no-preference) { ... }",
|
|
10475
|
+
autoFixable: true
|
|
10476
|
+
});
|
|
10477
|
+
}
|
|
10478
|
+
return issues;
|
|
10479
|
+
}
|
|
10480
|
+
function detectHoverOnly(rule) {
|
|
10481
|
+
const issues = [];
|
|
10482
|
+
const hasHover = rule.pseudoClasses.some((pc) => pc.name === "hover");
|
|
10483
|
+
const hasFocusVisible = rule.pseudoClasses.some(
|
|
10484
|
+
(pc) => pc.name === "focus" || pc.name === "focus-visible"
|
|
10485
|
+
);
|
|
10486
|
+
if (hasHover && !hasFocusVisible) {
|
|
10487
|
+
issues.push({
|
|
10488
|
+
ruleId: rule.id,
|
|
10489
|
+
selector: rule.selector,
|
|
10490
|
+
category: "hover-only",
|
|
10491
|
+
severity: "warning",
|
|
10492
|
+
wcagCriterion: WCAG.CRITERIA.hoverOnly,
|
|
10493
|
+
message: '"' + rule.selector + '" has hover styles but no :focus-visible fallback. Keyboard users cannot access this interaction.',
|
|
10494
|
+
suggestion: "Add the same styles to :focus-visible for keyboard accessibility.",
|
|
10495
|
+
autoFixable: true
|
|
10496
|
+
});
|
|
10497
|
+
}
|
|
10498
|
+
return issues;
|
|
10499
|
+
}
|
|
10500
|
+
function extractPx(value) {
|
|
10501
|
+
const match = value.match(/^(\d+(\.\d+)?)px$/);
|
|
10502
|
+
return match ? parseFloat(match[1]) : Infinity;
|
|
10503
|
+
}
|
|
10504
|
+
function auditRule(rule) {
|
|
10505
|
+
if (rule.isDead) return [];
|
|
10506
|
+
return [
|
|
10507
|
+
...detectContrast(rule),
|
|
10508
|
+
...detectMinimumFontSize(rule),
|
|
10509
|
+
...detectTouchTargets(rule),
|
|
10510
|
+
...detectMissingFocus(rule),
|
|
10511
|
+
...detectMotionRespect(rule),
|
|
10512
|
+
...detectHoverOnly(rule)
|
|
10513
|
+
];
|
|
10514
|
+
}
|
|
10515
|
+
function generateAccessibilityReport(rules) {
|
|
10516
|
+
const allIssues = [];
|
|
10517
|
+
for (const rule of rules) {
|
|
10518
|
+
allIssues.push(...auditRule(rule));
|
|
10519
|
+
}
|
|
10520
|
+
const errors = allIssues.filter((i) => i.severity === "error");
|
|
10521
|
+
const warnings = allIssues.filter((i) => i.severity === "warning");
|
|
10522
|
+
let summary;
|
|
10523
|
+
if (allIssues.length === 0) {
|
|
10524
|
+
summary = "\u2705 All accessibility checks passed.";
|
|
10525
|
+
} else if (errors.length > 0) {
|
|
10526
|
+
summary = "\u274C " + errors.length + " errors, " + warnings.length + " warnings \u2014 fix errors before shipping.";
|
|
10527
|
+
} else {
|
|
10528
|
+
summary = "\u26A0\uFE0F " + warnings.length + " warnings \u2014 recommended fixes available.";
|
|
10529
|
+
}
|
|
10530
|
+
return {
|
|
10531
|
+
issues: allIssues,
|
|
10532
|
+
errorCount: errors.length,
|
|
10533
|
+
warningCount: warnings.length,
|
|
10534
|
+
passedCount: rules.filter((r) => !r.isDead).length - allIssues.length,
|
|
10535
|
+
summary
|
|
10536
|
+
};
|
|
10537
|
+
}
|
|
10538
|
+
function autoFixFontSize(decl) {
|
|
10539
|
+
return "max(" + WCAG.MIN_FONT_SIZE + "px, " + decl.value + ")";
|
|
10540
|
+
}
|
|
10541
|
+
function autoFixTouchTarget() {
|
|
10542
|
+
return {
|
|
10543
|
+
"min-width": WCAG.MIN_TOUCH_TARGET + "px",
|
|
10544
|
+
"min-height": WCAG.MIN_TOUCH_TARGET + "px"
|
|
10545
|
+
};
|
|
10546
|
+
}
|
|
10547
|
+
var accessibilityPass = (ir) => {
|
|
10548
|
+
for (const rule of ir.rules) {
|
|
10549
|
+
const issues = auditRule(rule);
|
|
10550
|
+
for (const issue of issues) {
|
|
10551
|
+
ir.diagnostics.push({
|
|
10552
|
+
id: "a11y-" + issue.category + "-" + Date.now() + "-" + Math.random().toString(36).slice(2, 6),
|
|
10553
|
+
nodeId: issue.ruleId,
|
|
10554
|
+
severity: issue.severity,
|
|
10555
|
+
message: "[" + issue.wcagCriterion + "] " + issue.message,
|
|
10556
|
+
suggestion: issue.suggestion,
|
|
10557
|
+
pass: "accessibility"
|
|
10558
|
+
});
|
|
10559
|
+
if (issue.autoFixable) {
|
|
10560
|
+
if (issue.category === "font-size") {
|
|
10561
|
+
const decl = rule.declarations.find(
|
|
10562
|
+
(d) => d.property === "fontSize" || d.property === "font-size"
|
|
10563
|
+
);
|
|
10564
|
+
if (decl) {
|
|
10565
|
+
decl.value = autoFixFontSize(decl);
|
|
10566
|
+
decl.history.push({
|
|
10567
|
+
pass: "accessibility",
|
|
10568
|
+
action: "auto-fix-min-font",
|
|
10569
|
+
timestamp: Date.now(),
|
|
10570
|
+
reason: issue.message
|
|
10571
|
+
});
|
|
10572
|
+
}
|
|
10573
|
+
}
|
|
10574
|
+
if (issue.category === "touch-target") {
|
|
10575
|
+
const fixes = autoFixTouchTarget();
|
|
10576
|
+
for (const [prop, value] of Object.entries(fixes)) {
|
|
10577
|
+
rule.declarations.push({
|
|
10578
|
+
id: "a11y-fix-" + Date.now(),
|
|
10579
|
+
property: prop,
|
|
10580
|
+
value,
|
|
10581
|
+
history: [{
|
|
10582
|
+
pass: "accessibility",
|
|
10583
|
+
action: "auto-fix-touch-target",
|
|
10584
|
+
timestamp: Date.now(),
|
|
10585
|
+
reason: issue.message
|
|
10586
|
+
}],
|
|
10587
|
+
meta: { a11y: true }
|
|
10588
|
+
});
|
|
10589
|
+
}
|
|
10590
|
+
}
|
|
10591
|
+
if (issue.category === "focus" && issue.autoFixable) {
|
|
10592
|
+
rule.pseudoClasses.push({
|
|
10593
|
+
id: "a11y-focus-" + Date.now(),
|
|
10594
|
+
name: "focus-visible",
|
|
10595
|
+
declarations: [{
|
|
10596
|
+
id: "a11y-focus-outline",
|
|
10597
|
+
property: "outline",
|
|
10598
|
+
value: "2px solid #3b82f6",
|
|
10599
|
+
history: [{
|
|
10600
|
+
pass: "accessibility",
|
|
10601
|
+
action: "auto-fix-focus",
|
|
10602
|
+
timestamp: Date.now(),
|
|
10603
|
+
reason: issue.message
|
|
10604
|
+
}],
|
|
10605
|
+
meta: {}
|
|
10606
|
+
}, {
|
|
10607
|
+
id: "a11y-focus-offset",
|
|
10608
|
+
property: "outlineOffset",
|
|
10609
|
+
value: "2px",
|
|
10610
|
+
history: [],
|
|
10611
|
+
meta: {}
|
|
10612
|
+
}],
|
|
10613
|
+
source: rule.source,
|
|
10614
|
+
history: []
|
|
10615
|
+
});
|
|
10616
|
+
}
|
|
10617
|
+
if (issue.category === "motion" && issue.autoFixable) {
|
|
10618
|
+
const motionDecls = rule.declarations.filter(
|
|
10619
|
+
(d) => d.property === "animation" || d.property === "transition"
|
|
10620
|
+
);
|
|
10621
|
+
if (motionDecls.length > 0) {
|
|
10622
|
+
rule.atRules.push({
|
|
10623
|
+
id: "a11y-motion-" + Date.now(),
|
|
10624
|
+
type: "media",
|
|
10625
|
+
query: "(prefers-reduced-motion: no-preference)",
|
|
10626
|
+
declarations: motionDecls.map((d) => ({ ...d, id: d.id + "-motion" })),
|
|
10627
|
+
nestedRules: [],
|
|
10628
|
+
source: rule.source,
|
|
10629
|
+
history: [{
|
|
10630
|
+
pass: "accessibility",
|
|
10631
|
+
action: "auto-fix-motion",
|
|
10632
|
+
timestamp: Date.now(),
|
|
10633
|
+
reason: "Wrapped in prefers-reduced-motion query"
|
|
10634
|
+
}]
|
|
10635
|
+
});
|
|
10636
|
+
rule.declarations = rule.declarations.filter(
|
|
10637
|
+
(d) => !motionDecls.includes(d)
|
|
10638
|
+
);
|
|
10639
|
+
}
|
|
10640
|
+
}
|
|
10641
|
+
}
|
|
10642
|
+
}
|
|
10643
|
+
}
|
|
10644
|
+
ir.meta = ir.meta || {};
|
|
10645
|
+
ir.meta.accessibilityReport = generateAccessibilityReport(ir.rules);
|
|
10646
|
+
return ir;
|
|
10647
|
+
};
|
|
10648
|
+
function auditAccessibility(rules) {
|
|
10649
|
+
return generateAccessibilityReport(rules);
|
|
10650
|
+
}
|
|
10651
|
+
function checkRule(rule) {
|
|
10652
|
+
return auditRule(rule);
|
|
10653
|
+
}
|
|
10654
|
+
var accessibilityEngine = {
|
|
10655
|
+
audit: auditAccessibility,
|
|
10656
|
+
checkRule,
|
|
10657
|
+
pass: accessibilityPass,
|
|
10658
|
+
wcag: WCAG
|
|
10659
|
+
};
|
|
10660
|
+
|
|
10661
|
+
// src/compiler/intent-api.ts
|
|
10662
|
+
var INTENT_CATALOG = {
|
|
10663
|
+
// ==========================================================================
|
|
10664
|
+
// LAYOUT INTENTS
|
|
10665
|
+
// ==========================================================================
|
|
10666
|
+
"center-content": {
|
|
10667
|
+
name: "center-content",
|
|
10668
|
+
category: "layout",
|
|
10669
|
+
description: "Center content both horizontally and vertically",
|
|
10670
|
+
semantics: [
|
|
10671
|
+
{ category: "surface", intent: "container" }
|
|
10672
|
+
],
|
|
10673
|
+
properties: {
|
|
10674
|
+
display: "flex",
|
|
10675
|
+
justifyContent: "center",
|
|
10676
|
+
alignItems: "center"
|
|
10677
|
+
}
|
|
10678
|
+
},
|
|
10679
|
+
"stack": {
|
|
10680
|
+
name: "stack",
|
|
10681
|
+
category: "layout",
|
|
10682
|
+
description: "Vertical stack with consistent spacing",
|
|
10683
|
+
properties: {
|
|
10684
|
+
display: "flex",
|
|
10685
|
+
flexDirection: "column"
|
|
10686
|
+
},
|
|
10687
|
+
semantics: [
|
|
10688
|
+
{ category: "spacing", intent: "comfortable" }
|
|
10689
|
+
]
|
|
10690
|
+
},
|
|
10691
|
+
"sidebar-layout": {
|
|
10692
|
+
name: "sidebar-layout",
|
|
10693
|
+
category: "layout",
|
|
10694
|
+
description: "Two-column layout with mobile collapse",
|
|
10695
|
+
properties: {
|
|
10696
|
+
display: "grid",
|
|
10697
|
+
gridTemplateColumns: "280px 1fr",
|
|
10698
|
+
minHeight: "100vh"
|
|
10699
|
+
},
|
|
10700
|
+
semantics: [
|
|
10701
|
+
{ category: "spacing", intent: "comfortable" }
|
|
10702
|
+
],
|
|
10703
|
+
responsive: {
|
|
10704
|
+
"mobile": { gridTemplateColumns: "1fr" }
|
|
10705
|
+
}
|
|
10706
|
+
},
|
|
10707
|
+
"grid-list": {
|
|
10708
|
+
name: "grid-list",
|
|
10709
|
+
category: "layout",
|
|
10710
|
+
description: "Responsive auto-fit grid",
|
|
10711
|
+
properties: {
|
|
10712
|
+
display: "grid",
|
|
10713
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))"
|
|
10714
|
+
},
|
|
10715
|
+
semantics: [
|
|
10716
|
+
{ category: "spacing", intent: "comfortable" }
|
|
10717
|
+
]
|
|
10718
|
+
},
|
|
10719
|
+
// ==========================================================================
|
|
10720
|
+
// COMPONENT INTENTS
|
|
10721
|
+
// ==========================================================================
|
|
10722
|
+
"card": {
|
|
10723
|
+
name: "card",
|
|
10724
|
+
category: "component",
|
|
10725
|
+
description: "Content card with shadow, radius, and hover lift",
|
|
10726
|
+
semantics: [
|
|
10727
|
+
{ category: "surface", intent: "container" },
|
|
10728
|
+
{ category: "elevation", intent: "raised" },
|
|
10729
|
+
{ category: "spacing", intent: "comfortable" }
|
|
10730
|
+
],
|
|
10731
|
+
properties: {
|
|
10732
|
+
display: "flex",
|
|
10733
|
+
flexDirection: "column",
|
|
10734
|
+
overflow: "hidden",
|
|
10735
|
+
transition: "box-shadow 0.2s ease, transform 0.2s ease"
|
|
10736
|
+
},
|
|
10737
|
+
states: {
|
|
10738
|
+
hover: {
|
|
10739
|
+
boxShadow: "0 10px 30px rgba(0,0,0,0.15)",
|
|
10740
|
+
transform: "translateY(-2px)"
|
|
10741
|
+
}
|
|
10742
|
+
},
|
|
10743
|
+
responsive: {
|
|
10744
|
+
"mobile": { padding: "16px" }
|
|
10745
|
+
},
|
|
10746
|
+
a11y: ["contrast", "focus-visible"]
|
|
10747
|
+
},
|
|
10748
|
+
"button-primary": {
|
|
10749
|
+
name: "button-primary",
|
|
10750
|
+
category: "component",
|
|
10751
|
+
description: "Primary call-to-action button",
|
|
10752
|
+
semantics: [
|
|
10753
|
+
{ category: "surface", intent: "interactive" },
|
|
10754
|
+
{ category: "spacing", intent: "compact" },
|
|
10755
|
+
{ category: "state", intent: "hover" },
|
|
10756
|
+
{ category: "state", intent: "focus" },
|
|
10757
|
+
{ category: "state", intent: "active" },
|
|
10758
|
+
{ category: "state", intent: "disabled" }
|
|
10759
|
+
],
|
|
10760
|
+
properties: {
|
|
10761
|
+
display: "inline-flex",
|
|
10762
|
+
alignItems: "center",
|
|
10763
|
+
justifyContent: "center",
|
|
10764
|
+
fontWeight: "600",
|
|
10765
|
+
border: "none",
|
|
10766
|
+
userSelect: "none"
|
|
10767
|
+
},
|
|
10768
|
+
a11y: ["contrast", "touch-target", "focus-visible"]
|
|
10769
|
+
},
|
|
10770
|
+
"button-secondary": {
|
|
10771
|
+
name: "button-secondary",
|
|
10772
|
+
category: "component",
|
|
10773
|
+
description: "Secondary outlined button",
|
|
10774
|
+
semantics: [
|
|
10775
|
+
{ category: "spacing", intent: "compact" },
|
|
10776
|
+
{ category: "state", intent: "focus" },
|
|
10777
|
+
{ category: "state", intent: "disabled" }
|
|
10778
|
+
],
|
|
10779
|
+
properties: {
|
|
10780
|
+
display: "inline-flex",
|
|
10781
|
+
alignItems: "center",
|
|
10782
|
+
justifyContent: "center",
|
|
10783
|
+
fontWeight: "500",
|
|
10784
|
+
backgroundColor: "transparent",
|
|
10785
|
+
border: "1px solid $colors.neutral.300",
|
|
10786
|
+
color: "$colors.neutral.700",
|
|
10787
|
+
userSelect: "none"
|
|
10788
|
+
},
|
|
10789
|
+
states: {
|
|
10790
|
+
hover: { backgroundColor: "$colors.neutral.50" }
|
|
10791
|
+
},
|
|
10792
|
+
a11y: ["contrast", "touch-target", "focus-visible"]
|
|
10793
|
+
},
|
|
10794
|
+
"input-field": {
|
|
10795
|
+
name: "input-field",
|
|
10796
|
+
category: "component",
|
|
10797
|
+
description: "Text input with focus and error states",
|
|
10798
|
+
semantics: [
|
|
10799
|
+
{ category: "surface", intent: "input" },
|
|
10800
|
+
{ category: "spacing", intent: "compact" },
|
|
10801
|
+
{ category: "state", intent: "focus" },
|
|
10802
|
+
{ category: "state", intent: "disabled" }
|
|
10803
|
+
],
|
|
10804
|
+
properties: {
|
|
10805
|
+
width: "100%",
|
|
10806
|
+
fontSize: "16px",
|
|
10807
|
+
lineHeight: "1.5",
|
|
10808
|
+
transition: "border-color 0.2s ease, box-shadow 0.2s ease"
|
|
10809
|
+
},
|
|
10810
|
+
a11y: ["contrast"]
|
|
10811
|
+
},
|
|
10812
|
+
"modal": {
|
|
10813
|
+
name: "modal",
|
|
10814
|
+
category: "component",
|
|
10815
|
+
description: "Modal dialog with overlay backdrop",
|
|
10816
|
+
semantics: [
|
|
10817
|
+
{ category: "surface", intent: "overlay" },
|
|
10818
|
+
{ category: "elevation", intent: "modal" },
|
|
10819
|
+
{ category: "spacing", intent: "spacious" }
|
|
10820
|
+
],
|
|
10821
|
+
properties: {
|
|
10822
|
+
display: "flex",
|
|
10823
|
+
flexDirection: "column",
|
|
10824
|
+
maxWidth: "560px",
|
|
10825
|
+
margin: "auto"
|
|
10826
|
+
},
|
|
10827
|
+
a11y: ["contrast", "focus-visible"]
|
|
10828
|
+
},
|
|
10829
|
+
"tooltip": {
|
|
10830
|
+
name: "tooltip",
|
|
10831
|
+
category: "component",
|
|
10832
|
+
description: "Hover tooltip",
|
|
10833
|
+
semantics: [
|
|
10834
|
+
{ category: "surface", intent: "tooltip" }
|
|
10835
|
+
],
|
|
10836
|
+
properties: {
|
|
10837
|
+
position: "absolute",
|
|
10838
|
+
zIndex: "50",
|
|
10839
|
+
pointerEvents: "none"
|
|
10840
|
+
},
|
|
10841
|
+
a11y: ["contrast"]
|
|
10842
|
+
},
|
|
10843
|
+
// ==========================================================================
|
|
10844
|
+
// SEMANTIC INTENTS
|
|
10845
|
+
// ==========================================================================
|
|
10846
|
+
"hero-section": {
|
|
10847
|
+
name: "hero-section",
|
|
10848
|
+
category: "semantic",
|
|
10849
|
+
description: "Full-width hero banner",
|
|
10850
|
+
semantics: [
|
|
10851
|
+
{ category: "spacing", intent: "generous" }
|
|
10852
|
+
],
|
|
10853
|
+
properties: {
|
|
10854
|
+
display: "flex",
|
|
10855
|
+
flexDirection: "column",
|
|
10856
|
+
justifyContent: "center",
|
|
10857
|
+
alignItems: "center",
|
|
10858
|
+
width: "100%",
|
|
10859
|
+
minHeight: "60vh",
|
|
10860
|
+
textAlign: "center"
|
|
10861
|
+
},
|
|
10862
|
+
responsive: {
|
|
10863
|
+
"mobile": { minHeight: "40vh", padding: "32px 16px" }
|
|
10864
|
+
}
|
|
10865
|
+
},
|
|
10866
|
+
"sticky-header": {
|
|
10867
|
+
name: "sticky-header",
|
|
10868
|
+
category: "semantic",
|
|
10869
|
+
description: "Sticky header with backdrop blur",
|
|
10870
|
+
semantics: [
|
|
10871
|
+
{ category: "elevation", intent: "sticky" },
|
|
10872
|
+
{ category: "spacing", intent: "compact" }
|
|
10873
|
+
],
|
|
10874
|
+
properties: {
|
|
10875
|
+
backgroundColor: "rgba(255,255,255,0.9)",
|
|
10876
|
+
backdropFilter: "blur(8px)",
|
|
10877
|
+
borderBottom: "1px solid rgba(0,0,0,0.05)"
|
|
10878
|
+
}
|
|
10879
|
+
},
|
|
10880
|
+
"call-to-action": {
|
|
10881
|
+
name: "call-to-action",
|
|
10882
|
+
category: "semantic",
|
|
10883
|
+
description: "Attention-grabbing CTA section",
|
|
10884
|
+
semantics: [
|
|
10885
|
+
{ category: "surface", intent: "interactive" },
|
|
10886
|
+
{ category: "spacing", intent: "spacious" }
|
|
10887
|
+
],
|
|
10888
|
+
properties: {
|
|
10889
|
+
textAlign: "center"
|
|
10890
|
+
}
|
|
10891
|
+
},
|
|
10892
|
+
"muted-text": {
|
|
10893
|
+
name: "muted-text",
|
|
10894
|
+
category: "semantic",
|
|
10895
|
+
description: "Secondary, less prominent text",
|
|
10896
|
+
semantics: [
|
|
10897
|
+
{ category: "text", intent: "muted" }
|
|
10898
|
+
]
|
|
10899
|
+
},
|
|
10900
|
+
"visually-hidden": {
|
|
10901
|
+
name: "visually-hidden",
|
|
10902
|
+
category: "semantic",
|
|
10903
|
+
description: "Visible only to screen readers",
|
|
10904
|
+
properties: {
|
|
10905
|
+
position: "absolute",
|
|
10906
|
+
width: "1px",
|
|
10907
|
+
height: "1px",
|
|
10908
|
+
padding: "0",
|
|
10909
|
+
margin: "-1px",
|
|
10910
|
+
overflow: "hidden",
|
|
10911
|
+
clip: "rect(0, 0, 0, 0)",
|
|
10912
|
+
whiteSpace: "nowrap",
|
|
10913
|
+
borderWidth: "0"
|
|
10914
|
+
}
|
|
10915
|
+
},
|
|
10916
|
+
// ==========================================================================
|
|
10917
|
+
// INTERACTION INTENTS
|
|
10918
|
+
// ==========================================================================
|
|
10919
|
+
"hover-lift": {
|
|
10920
|
+
name: "hover-lift",
|
|
10921
|
+
category: "interaction",
|
|
10922
|
+
description: "Subtle lift on hover",
|
|
10923
|
+
states: {
|
|
10924
|
+
hover: {
|
|
10925
|
+
transform: "translateY(-2px)",
|
|
10926
|
+
boxShadow: "0 8px 25px rgba(0,0,0,0.12)",
|
|
10927
|
+
transition: "all 0.2s ease"
|
|
10928
|
+
}
|
|
10929
|
+
},
|
|
10930
|
+
a11y: ["focus-visible"]
|
|
10931
|
+
},
|
|
10932
|
+
"focus-ring": {
|
|
10933
|
+
name: "focus-ring",
|
|
10934
|
+
category: "interaction",
|
|
10935
|
+
description: "Accessible focus indicator",
|
|
10936
|
+
states: {
|
|
10937
|
+
"focus-visible": {
|
|
10938
|
+
outline: "2px solid $colors.primary.500",
|
|
10939
|
+
outlineOffset: "2px"
|
|
10940
|
+
}
|
|
10941
|
+
}
|
|
10942
|
+
}
|
|
10943
|
+
};
|
|
10944
|
+
function resolveIntent(intentName, options) {
|
|
10945
|
+
const intent2 = INTENT_CATALOG[intentName];
|
|
10946
|
+
if (!intent2) return null;
|
|
10947
|
+
const properties = {};
|
|
10948
|
+
const states = {};
|
|
10949
|
+
const responsive = {};
|
|
10950
|
+
if (intent2.semantics) {
|
|
10951
|
+
for (const sem of intent2.semantics) {
|
|
10952
|
+
const resolved = resolveSemantic(sem.category, sem.intent, {
|
|
10953
|
+
mode: options?.theme || "light"
|
|
10954
|
+
});
|
|
10955
|
+
if (resolved) {
|
|
10956
|
+
for (const [prop, value] of Object.entries(resolved.properties)) {
|
|
10957
|
+
if (resolved.pseudoClass) {
|
|
10958
|
+
if (!states[resolved.pseudoClass]) states[resolved.pseudoClass] = {};
|
|
10959
|
+
states[resolved.pseudoClass][prop] = value;
|
|
10960
|
+
} else {
|
|
10961
|
+
properties[prop] = value;
|
|
10962
|
+
}
|
|
10963
|
+
}
|
|
10964
|
+
}
|
|
10965
|
+
}
|
|
10966
|
+
}
|
|
10967
|
+
if (intent2.properties) {
|
|
10968
|
+
Object.assign(properties, intent2.properties);
|
|
10969
|
+
}
|
|
10970
|
+
if (intent2.states) {
|
|
10971
|
+
for (const [state, props] of Object.entries(intent2.states)) {
|
|
10972
|
+
if (!states[state]) states[state] = {};
|
|
10973
|
+
Object.assign(states[state], props);
|
|
10974
|
+
}
|
|
10975
|
+
}
|
|
10976
|
+
if (intent2.responsive) {
|
|
10977
|
+
Object.assign(responsive, intent2.responsive);
|
|
10978
|
+
}
|
|
10979
|
+
return {
|
|
10980
|
+
properties,
|
|
10981
|
+
states,
|
|
10982
|
+
responsive,
|
|
10983
|
+
a11y: intent2.a11y || [],
|
|
10984
|
+
description: intent2.description
|
|
10985
|
+
};
|
|
10986
|
+
}
|
|
10987
|
+
function getAvailableIntents() {
|
|
10988
|
+
return Object.keys(INTENT_CATALOG);
|
|
10989
|
+
}
|
|
10990
|
+
function getIntentsByCategory(category) {
|
|
10991
|
+
return Object.entries(INTENT_CATALOG).filter(([, def]) => def.category === category).map(([name]) => name);
|
|
10992
|
+
}
|
|
10993
|
+
function getIntentDescription(intentName) {
|
|
10994
|
+
return INTENT_CATALOG[intentName]?.description || null;
|
|
10995
|
+
}
|
|
10996
|
+
var intentAPIPass = (ir) => {
|
|
10997
|
+
for (const rule of ir.rules) {
|
|
10998
|
+
const intentName = rule.meta._intent;
|
|
10999
|
+
if (!intentName) continue;
|
|
11000
|
+
const resolved = resolveIntent(intentName);
|
|
11001
|
+
if (!resolved) continue;
|
|
11002
|
+
for (const [prop, value] of Object.entries(resolved.properties)) {
|
|
11003
|
+
rule.declarations.push({
|
|
11004
|
+
id: "intent-prop-" + Date.now() + "-" + prop,
|
|
11005
|
+
property: prop,
|
|
11006
|
+
value,
|
|
11007
|
+
history: [{
|
|
11008
|
+
pass: "intent-api",
|
|
11009
|
+
action: "resolved-intent",
|
|
11010
|
+
timestamp: Date.now(),
|
|
11011
|
+
reason: 'intent("' + intentName + '") \u2192 ' + prop + ": " + value
|
|
11012
|
+
}],
|
|
11013
|
+
meta: { intent: intentName }
|
|
11014
|
+
});
|
|
11015
|
+
}
|
|
11016
|
+
for (const [stateName, stateProps] of Object.entries(resolved.states)) {
|
|
11017
|
+
rule.pseudoClasses.push({
|
|
11018
|
+
id: "intent-state-" + Date.now() + "-" + stateName,
|
|
11019
|
+
name: stateName,
|
|
11020
|
+
declarations: Object.entries(stateProps).map(([prop, value]) => ({
|
|
11021
|
+
id: "intent-decl-" + prop,
|
|
11022
|
+
property: prop,
|
|
11023
|
+
value,
|
|
11024
|
+
history: [{
|
|
11025
|
+
pass: "intent-api",
|
|
11026
|
+
action: "resolved-state",
|
|
11027
|
+
timestamp: Date.now(),
|
|
11028
|
+
reason: 'intent("' + intentName + '") state:' + stateName
|
|
11029
|
+
}],
|
|
11030
|
+
meta: {}
|
|
11031
|
+
})),
|
|
11032
|
+
source: rule.source,
|
|
11033
|
+
history: []
|
|
11034
|
+
});
|
|
11035
|
+
}
|
|
11036
|
+
if (Object.keys(resolved.responsive).length > 0) {
|
|
11037
|
+
rule.meta._responsiveIntents = resolved.responsive;
|
|
11038
|
+
}
|
|
11039
|
+
if (resolved.a11y.length > 0) {
|
|
11040
|
+
rule.meta._a11yRequirements = resolved.a11y;
|
|
11041
|
+
}
|
|
11042
|
+
}
|
|
11043
|
+
return ir;
|
|
11044
|
+
};
|
|
11045
|
+
var intentAPI = {
|
|
11046
|
+
resolve: resolveIntent,
|
|
11047
|
+
list: getAvailableIntents,
|
|
11048
|
+
byCategory: getIntentsByCategory,
|
|
11049
|
+
description: getIntentDescription,
|
|
11050
|
+
catalog: INTENT_CATALOG,
|
|
11051
|
+
pass: intentAPIPass
|
|
11052
|
+
};
|
|
11053
|
+
|
|
11054
|
+
// src/index.ts
|
|
7333
11055
|
init_Chain();
|
|
7334
11056
|
|
|
7335
11057
|
// src/compiler/math-engine.ts
|
|
@@ -7765,6 +11487,76 @@ var convert = math.convert.bind(math);
|
|
|
7765
11487
|
var toPx = math.toPx.bind(math);
|
|
7766
11488
|
var scale = math.scale.bind(math);
|
|
7767
11489
|
|
|
11490
|
+
// src/compiler/css-if-transpiler.ts
|
|
11491
|
+
function detectIfPatterns(styles) {
|
|
11492
|
+
const conditions = [];
|
|
11493
|
+
if (!styles._conditions) return conditions;
|
|
11494
|
+
const condEntries = Object.entries(styles._conditions || {});
|
|
11495
|
+
for (const [variable, branches] of condEntries) {
|
|
11496
|
+
const branch = branches;
|
|
11497
|
+
const trueStyles = branch.true || {};
|
|
11498
|
+
const falseStyles = branch.false || {};
|
|
11499
|
+
const allProps = /* @__PURE__ */ new Set([...Object.keys(trueStyles), ...Object.keys(falseStyles)]);
|
|
11500
|
+
for (const prop of allProps) {
|
|
11501
|
+
if (prop.startsWith("_") || prop === "selectors") continue;
|
|
11502
|
+
const trueVal = trueStyles[prop];
|
|
11503
|
+
const falseVal = falseStyles[prop];
|
|
11504
|
+
if (trueVal !== void 0 && falseVal !== void 0 && trueVal !== falseVal) {
|
|
11505
|
+
conditions.push({
|
|
11506
|
+
property: prop,
|
|
11507
|
+
variable: variable.startsWith("--") ? variable : "--" + variable,
|
|
11508
|
+
conditions: { true: trueVal },
|
|
11509
|
+
defaultValue: falseVal
|
|
11510
|
+
});
|
|
11511
|
+
}
|
|
11512
|
+
}
|
|
11513
|
+
}
|
|
11514
|
+
return conditions;
|
|
11515
|
+
}
|
|
11516
|
+
function emitCSSIf(selector, detectedConditions, baseProperties = {}) {
|
|
11517
|
+
if (detectedConditions.length === 0) return "";
|
|
11518
|
+
let css = "";
|
|
11519
|
+
css += "/* Native CSS if() \u2014 Chrome 137+ */\n";
|
|
11520
|
+
css += selector + " {\n";
|
|
11521
|
+
for (const [prop, value] of Object.entries(baseProperties)) {
|
|
11522
|
+
css += " " + prop + ": " + value + ";\n";
|
|
11523
|
+
}
|
|
11524
|
+
for (const cond of detectedConditions) {
|
|
11525
|
+
const entries = Object.entries(cond.conditions);
|
|
11526
|
+
if (entries.length === 1) {
|
|
11527
|
+
const [condition, val] = entries[0];
|
|
11528
|
+
css += " " + cond.property + ": if(style(" + cond.variable + ": " + condition + "): " + val + " else " + cond.defaultValue + ");\n";
|
|
11529
|
+
} else {
|
|
11530
|
+
let chain3 = "";
|
|
11531
|
+
for (let i = 0; i < entries.length; i++) {
|
|
11532
|
+
const [condition, val] = entries[i];
|
|
11533
|
+
chain3 += i === 0 ? "if(style(" + cond.variable + ": " + condition + "): " + val : " else if(style(" + cond.variable + ": " + condition + "): " + val;
|
|
11534
|
+
}
|
|
11535
|
+
chain3 += " else " + cond.defaultValue + ")".repeat(entries.length);
|
|
11536
|
+
css += " " + cond.property + ": " + chain3 + ";\n";
|
|
11537
|
+
}
|
|
11538
|
+
}
|
|
11539
|
+
css += "}\n\n";
|
|
11540
|
+
css += "/* Fallback for browsers without CSS if() */\n";
|
|
11541
|
+
css += "@supports not (property: if()) {\n";
|
|
11542
|
+
css += " " + selector + " {\n";
|
|
11543
|
+
for (const [prop, value] of Object.entries(baseProperties)) {
|
|
11544
|
+
css += " " + prop + ": " + value + ";\n";
|
|
11545
|
+
}
|
|
11546
|
+
for (const cond of detectedConditions) {
|
|
11547
|
+
css += " " + cond.property + ": " + cond.defaultValue + ";\n";
|
|
11548
|
+
}
|
|
11549
|
+
css += " }\n";
|
|
11550
|
+
for (const cond of detectedConditions) {
|
|
11551
|
+
for (const [condition, val] of Object.entries(cond.conditions)) {
|
|
11552
|
+
const modClass = selector + "--" + cond.variable.replace("--", "") + "-" + condition;
|
|
11553
|
+
css += " " + modClass + " { " + cond.property + ": " + val + "; }\n";
|
|
11554
|
+
}
|
|
11555
|
+
}
|
|
11556
|
+
css += "}\n";
|
|
11557
|
+
return css;
|
|
11558
|
+
}
|
|
11559
|
+
|
|
7768
11560
|
// src/compiler/intent-engine.ts
|
|
7769
11561
|
var SEMANTIC_INTENTS = [
|
|
7770
11562
|
{ pattern: /^flexbox$/i, handler: (v, ctx) => ({ original: v, property: ctx.property || "display", corrected: "flex", defaults: { display: "flex", justifyContent: "center", alignItems: "center" }, confidence: 0.95, intent: "flexbox-centering", explanation: '"flexbox" mapped to display: flex with centering defaults.' }), description: "flexbox -> flex + centering" },
|
|
@@ -7774,6 +11566,222 @@ var SEMANTIC_INTENTS = [
|
|
|
7774
11566
|
{ pattern: /^(full|fullscreen|full-screen)$/i, handler: (v, ctx) => ({ original: v, property: ctx.property || "size", corrected: "100%", defaults: { width: "100%", height: "100%" }, confidence: 0.85, intent: "full-size", explanation: '"full/fullscreen" -> width/height: 100%' }), description: "full -> 100%" },
|
|
7775
11567
|
{ pattern: /^(rounded|round)$/i, handler: (v, ctx) => ({ original: v, property: ctx.property || "border-radius", corrected: "9999px", defaults: { borderRadius: "9999px" }, confidence: 0.8, intent: "rounded-pill", explanation: '"rounded" -> border-radius: 9999px (pill)' }), description: "rounded -> pill" }
|
|
7776
11568
|
];
|
|
11569
|
+
var LAYOUT_MACROS = {
|
|
11570
|
+
stickyHeader: {
|
|
11571
|
+
name: "stickyHeader",
|
|
11572
|
+
description: "Sticky header with scroll shadow and entrance animation",
|
|
11573
|
+
properties: {
|
|
11574
|
+
position: "sticky",
|
|
11575
|
+
top: "0",
|
|
11576
|
+
zIndex: "50",
|
|
11577
|
+
backgroundColor: "var(--header-bg, white)",
|
|
11578
|
+
backdropFilter: "blur(8px)",
|
|
11579
|
+
borderBottom: "1px solid transparent"
|
|
11580
|
+
},
|
|
11581
|
+
defaults: {
|
|
11582
|
+
"--header-bg": "white",
|
|
11583
|
+
"--header-shadow": "0 4px 12px rgba(0,0,0,0.1)"
|
|
11584
|
+
},
|
|
11585
|
+
mediaQueries: {
|
|
11586
|
+
"(max-width: 768px)": {
|
|
11587
|
+
padding: "12px 16px"
|
|
11588
|
+
},
|
|
11589
|
+
"(min-width: 769px)": {
|
|
11590
|
+
padding: "16px 32px"
|
|
11591
|
+
}
|
|
11592
|
+
}
|
|
11593
|
+
},
|
|
11594
|
+
card: {
|
|
11595
|
+
name: "card",
|
|
11596
|
+
description: "Standard card container with hover lift effect",
|
|
11597
|
+
properties: {
|
|
11598
|
+
display: "flex",
|
|
11599
|
+
flexDirection: "column",
|
|
11600
|
+
borderRadius: "12px",
|
|
11601
|
+
backgroundColor: "var(--card-bg, white)",
|
|
11602
|
+
boxShadow: "0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.08)",
|
|
11603
|
+
transition: "box-shadow 0.2s ease, transform 0.2s ease",
|
|
11604
|
+
overflow: "hidden"
|
|
11605
|
+
},
|
|
11606
|
+
defaults: {
|
|
11607
|
+
"--card-bg": "white",
|
|
11608
|
+
"--card-hover-shadow": "0 10px 30px rgba(0,0,0,0.15)"
|
|
11609
|
+
},
|
|
11610
|
+
mediaQueries: {
|
|
11611
|
+
"(hover: hover)": {
|
|
11612
|
+
"&:hover": {
|
|
11613
|
+
boxShadow: "var(--card-hover-shadow)",
|
|
11614
|
+
transform: "translateY(-2px)"
|
|
11615
|
+
}
|
|
11616
|
+
}
|
|
11617
|
+
}
|
|
11618
|
+
},
|
|
11619
|
+
hero: {
|
|
11620
|
+
name: "hero",
|
|
11621
|
+
description: "Full-width hero section with centered content",
|
|
11622
|
+
properties: {
|
|
11623
|
+
display: "flex",
|
|
11624
|
+
flexDirection: "column",
|
|
11625
|
+
justifyContent: "center",
|
|
11626
|
+
alignItems: "center",
|
|
11627
|
+
width: "100%",
|
|
11628
|
+
minHeight: "60vh",
|
|
11629
|
+
padding: "48px 24px",
|
|
11630
|
+
textAlign: "center"
|
|
11631
|
+
},
|
|
11632
|
+
defaults: {},
|
|
11633
|
+
mediaQueries: {
|
|
11634
|
+
"(max-width: 768px)": {
|
|
11635
|
+
minHeight: "40vh",
|
|
11636
|
+
padding: "32px 16px"
|
|
11637
|
+
}
|
|
11638
|
+
}
|
|
11639
|
+
},
|
|
11640
|
+
container: {
|
|
11641
|
+
name: "container",
|
|
11642
|
+
description: "Responsive centered container with max-width",
|
|
11643
|
+
properties: {
|
|
11644
|
+
width: "100%",
|
|
11645
|
+
maxWidth: "1200px",
|
|
11646
|
+
marginLeft: "auto",
|
|
11647
|
+
marginRight: "auto",
|
|
11648
|
+
paddingLeft: "16px",
|
|
11649
|
+
paddingRight: "16px"
|
|
11650
|
+
},
|
|
11651
|
+
defaults: {},
|
|
11652
|
+
mediaQueries: {
|
|
11653
|
+
"(min-width: 768px)": {
|
|
11654
|
+
paddingLeft: "24px",
|
|
11655
|
+
paddingRight: "24px"
|
|
11656
|
+
},
|
|
11657
|
+
"(min-width: 1024px)": {
|
|
11658
|
+
paddingLeft: "32px",
|
|
11659
|
+
paddingRight: "32px"
|
|
11660
|
+
}
|
|
11661
|
+
}
|
|
11662
|
+
},
|
|
11663
|
+
center: {
|
|
11664
|
+
name: "center",
|
|
11665
|
+
description: "Absolute centering using flexbox",
|
|
11666
|
+
properties: {
|
|
11667
|
+
display: "flex",
|
|
11668
|
+
justifyContent: "center",
|
|
11669
|
+
alignItems: "center"
|
|
11670
|
+
},
|
|
11671
|
+
defaults: {}
|
|
11672
|
+
},
|
|
11673
|
+
gridList: {
|
|
11674
|
+
name: "gridList",
|
|
11675
|
+
description: "Responsive grid list with auto-fit columns",
|
|
11676
|
+
properties: {
|
|
11677
|
+
display: "grid",
|
|
11678
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))",
|
|
11679
|
+
gap: "24px"
|
|
11680
|
+
},
|
|
11681
|
+
defaults: {},
|
|
11682
|
+
mediaQueries: {
|
|
11683
|
+
"(max-width: 640px)": {
|
|
11684
|
+
gridTemplateColumns: "1fr",
|
|
11685
|
+
gap: "16px"
|
|
11686
|
+
}
|
|
11687
|
+
}
|
|
11688
|
+
},
|
|
11689
|
+
sidebar: {
|
|
11690
|
+
name: "sidebar",
|
|
11691
|
+
description: "Two-column layout: sidebar + main content",
|
|
11692
|
+
properties: {
|
|
11693
|
+
display: "grid",
|
|
11694
|
+
gridTemplateColumns: "280px 1fr",
|
|
11695
|
+
gap: "32px",
|
|
11696
|
+
minHeight: "100vh"
|
|
11697
|
+
},
|
|
11698
|
+
defaults: {},
|
|
11699
|
+
mediaQueries: {
|
|
11700
|
+
"(max-width: 1024px)": {
|
|
11701
|
+
gridTemplateColumns: "1fr",
|
|
11702
|
+
gap: "24px"
|
|
11703
|
+
}
|
|
11704
|
+
}
|
|
11705
|
+
},
|
|
11706
|
+
pill: {
|
|
11707
|
+
name: "pill",
|
|
11708
|
+
description: "Pill-shaped element (fully rounded)",
|
|
11709
|
+
properties: {
|
|
11710
|
+
borderRadius: "9999px",
|
|
11711
|
+
padding: "8px 20px",
|
|
11712
|
+
display: "inline-flex",
|
|
11713
|
+
alignItems: "center",
|
|
11714
|
+
justifyContent: "center"
|
|
11715
|
+
},
|
|
11716
|
+
defaults: {}
|
|
11717
|
+
},
|
|
11718
|
+
glass: {
|
|
11719
|
+
name: "glass",
|
|
11720
|
+
description: "Frosted glass morphism effect",
|
|
11721
|
+
properties: {
|
|
11722
|
+
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
|
11723
|
+
backdropFilter: "blur(16px)",
|
|
11724
|
+
border: "1px solid rgba(255, 255, 255, 0.2)",
|
|
11725
|
+
borderRadius: "16px"
|
|
11726
|
+
},
|
|
11727
|
+
defaults: {}
|
|
11728
|
+
},
|
|
11729
|
+
truncate: {
|
|
11730
|
+
name: "truncate",
|
|
11731
|
+
description: "Single-line text truncation with ellipsis",
|
|
11732
|
+
properties: {
|
|
11733
|
+
overflow: "hidden",
|
|
11734
|
+
textOverflow: "ellipsis",
|
|
11735
|
+
whiteSpace: "nowrap"
|
|
11736
|
+
},
|
|
11737
|
+
defaults: {}
|
|
11738
|
+
},
|
|
11739
|
+
srOnly: {
|
|
11740
|
+
name: "srOnly",
|
|
11741
|
+
description: "Screen-reader only (visually hidden but accessible)",
|
|
11742
|
+
properties: {
|
|
11743
|
+
position: "absolute",
|
|
11744
|
+
width: "1px",
|
|
11745
|
+
height: "1px",
|
|
11746
|
+
padding: "0",
|
|
11747
|
+
margin: "-1px",
|
|
11748
|
+
overflow: "hidden",
|
|
11749
|
+
clip: "rect(0, 0, 0, 0)",
|
|
11750
|
+
whiteSpace: "nowrap",
|
|
11751
|
+
borderWidth: "0"
|
|
11752
|
+
},
|
|
11753
|
+
defaults: {}
|
|
11754
|
+
}
|
|
11755
|
+
};
|
|
11756
|
+
function resolveLayoutMacro(name) {
|
|
11757
|
+
return LAYOUT_MACROS[name] || null;
|
|
11758
|
+
}
|
|
11759
|
+
function expandLayoutMacro(name) {
|
|
11760
|
+
const macro2 = resolveLayoutMacro(name);
|
|
11761
|
+
if (!macro2) return null;
|
|
11762
|
+
const result = { ...macro2.properties };
|
|
11763
|
+
if (macro2.defaults) {
|
|
11764
|
+
Object.assign(result, macro2.defaults);
|
|
11765
|
+
}
|
|
11766
|
+
if (macro2.mediaQueries) {
|
|
11767
|
+
result.atRules = result.atRules || [];
|
|
11768
|
+
for (const [query, props] of Object.entries(macro2.mediaQueries)) {
|
|
11769
|
+
result.atRules.push({
|
|
11770
|
+
type: "media",
|
|
11771
|
+
query,
|
|
11772
|
+
styles: props
|
|
11773
|
+
});
|
|
11774
|
+
}
|
|
11775
|
+
}
|
|
11776
|
+
return result;
|
|
11777
|
+
}
|
|
11778
|
+
function getAvailableMacros() {
|
|
11779
|
+
return Object.keys(LAYOUT_MACROS);
|
|
11780
|
+
}
|
|
11781
|
+
function getMacroDescription(name) {
|
|
11782
|
+
const macro2 = resolveLayoutMacro(name);
|
|
11783
|
+
return macro2?.description || null;
|
|
11784
|
+
}
|
|
7777
11785
|
var VALUE_CORRECTIONS = {
|
|
7778
11786
|
"display": [{ wrong: "flexbox", correct: "flex", confidence: 0.95 }, { wrong: "inline-flexbox", correct: "inline-flex", confidence: 0.95 }],
|
|
7779
11787
|
"position": [{ wrong: "abs", correct: "absolute", confidence: 0.9 }, { wrong: "rel", correct: "relative", confidence: 0.9 }],
|
|
@@ -7889,17 +11897,55 @@ var intent = {
|
|
|
7889
11897
|
explain(correction) {
|
|
7890
11898
|
return correction.explanation;
|
|
7891
11899
|
},
|
|
11900
|
+
cssIf: { detect: detectIfPatterns, emit: emitCSSIf },
|
|
11901
|
+
getIntents() {
|
|
11902
|
+
return SEMANTIC_INTENTS.map((r) => ({ pattern: r.pattern.toString(), description: r.description }));
|
|
11903
|
+
},
|
|
7892
11904
|
getKnownProperties() {
|
|
7893
11905
|
return [...KNOWN_PROPERTIES];
|
|
7894
11906
|
},
|
|
7895
|
-
|
|
7896
|
-
|
|
11907
|
+
// Layout Macros
|
|
11908
|
+
macro(name) {
|
|
11909
|
+
return expandLayoutMacro(name);
|
|
11910
|
+
},
|
|
11911
|
+
getMacros() {
|
|
11912
|
+
return getAvailableMacros();
|
|
11913
|
+
},
|
|
11914
|
+
getMacroDescription(name) {
|
|
11915
|
+
return getMacroDescription(name);
|
|
11916
|
+
},
|
|
11917
|
+
hasMacro(name) {
|
|
11918
|
+
return name in LAYOUT_MACROS;
|
|
11919
|
+
},
|
|
11920
|
+
/**
|
|
11921
|
+
* Apply a layout macro to an existing styles object.
|
|
11922
|
+
* Merges macro properties with user overrides.
|
|
11923
|
+
*/
|
|
11924
|
+
applyMacro(name, overrides) {
|
|
11925
|
+
const macro2 = expandLayoutMacro(name);
|
|
11926
|
+
if (!macro2) return null;
|
|
11927
|
+
if (!overrides) return macro2;
|
|
11928
|
+
const merged = { ...macro2 };
|
|
11929
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
11930
|
+
if (key === "atRules" && Array.isArray(value) && Array.isArray(merged.atRules)) {
|
|
11931
|
+
merged.atRules = [...merged.atRules, ...value];
|
|
11932
|
+
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
11933
|
+
merged[key] = { ...merged[key] || {}, ...value };
|
|
11934
|
+
} else {
|
|
11935
|
+
merged[key] = value;
|
|
11936
|
+
}
|
|
11937
|
+
}
|
|
11938
|
+
return merged;
|
|
7897
11939
|
}
|
|
7898
11940
|
};
|
|
7899
11941
|
var correct = intent.correct.bind(intent);
|
|
7900
11942
|
var heal = intent.heal.bind(intent);
|
|
7901
11943
|
var validate = intent.validate.bind(intent);
|
|
7902
11944
|
var getIntent = intent.getIntent.bind(intent);
|
|
11945
|
+
var macro = intent.macro.bind(intent);
|
|
11946
|
+
var applyMacro = intent.applyMacro.bind(intent);
|
|
11947
|
+
var getMacros = intent.getMacros.bind(intent);
|
|
11948
|
+
var hasMacro = intent.hasMacro.bind(intent);
|
|
7903
11949
|
|
|
7904
11950
|
// src/compiler/analyzer.ts
|
|
7905
11951
|
var CONFLICTS = [
|
|
@@ -7982,43 +12028,86 @@ export {
|
|
|
7982
12028
|
CacheManager,
|
|
7983
12029
|
ChainCSSCompiler,
|
|
7984
12030
|
ChainCSSPrefixer,
|
|
12031
|
+
DEFAULT_PIPELINE,
|
|
7985
12032
|
DesignTokens,
|
|
12033
|
+
PassManager,
|
|
7986
12034
|
PersistentCache,
|
|
12035
|
+
SCROLL_PRESETS,
|
|
7987
12036
|
StyleAnalyzer,
|
|
7988
12037
|
StyleGraphCompiler,
|
|
7989
12038
|
Theme,
|
|
7990
12039
|
VERSION2 as VERSION,
|
|
12040
|
+
accessibilityEngine,
|
|
12041
|
+
accessibilityPass,
|
|
7991
12042
|
add,
|
|
12043
|
+
analyzeResponsive,
|
|
7992
12044
|
analyze as analyzeStyle,
|
|
7993
12045
|
animationPresets,
|
|
12046
|
+
applyPass,
|
|
12047
|
+
applyPasses,
|
|
12048
|
+
atomicExtractionPass,
|
|
12049
|
+
auditAccessibility,
|
|
12050
|
+
auditContrast,
|
|
7994
12051
|
autoDetector,
|
|
12052
|
+
autoFixAll,
|
|
12053
|
+
autoFixIssue,
|
|
7995
12054
|
buildChain,
|
|
7996
12055
|
chain,
|
|
7997
12056
|
smartChain as chainV3,
|
|
12057
|
+
checkContrast,
|
|
12058
|
+
checkRule,
|
|
7998
12059
|
clearTimeline,
|
|
7999
12060
|
compileChainCSS,
|
|
8000
12061
|
compileGraph,
|
|
12062
|
+
compileScrollAnimation,
|
|
12063
|
+
compileScrollAnimations,
|
|
12064
|
+
compileViaIR,
|
|
8001
12065
|
configureAtomic,
|
|
12066
|
+
constraintSolver,
|
|
12067
|
+
constraintSolverPass,
|
|
12068
|
+
contrastRatio,
|
|
8002
12069
|
convert,
|
|
8003
12070
|
correct,
|
|
12071
|
+
countNodes,
|
|
8004
12072
|
createAnimation,
|
|
12073
|
+
createContextualToken,
|
|
12074
|
+
createDeclaration,
|
|
8005
12075
|
createTokens as createDesignTokens,
|
|
12076
|
+
createIR,
|
|
12077
|
+
createRule,
|
|
12078
|
+
createScrollAnimation,
|
|
8006
12079
|
createSmartComponent,
|
|
8007
12080
|
createTheme,
|
|
8008
12081
|
createThemeContract,
|
|
8009
12082
|
createTokens2 as createTokens,
|
|
12083
|
+
cssCompressionPass,
|
|
12084
|
+
cssIfTranspilePass,
|
|
12085
|
+
deadEliminationPass,
|
|
12086
|
+
debugIR,
|
|
8010
12087
|
index_default as default,
|
|
12088
|
+
diagnosticsExportPass,
|
|
8011
12089
|
divide,
|
|
8012
12090
|
enableDebug,
|
|
8013
12091
|
enableTimeline,
|
|
8014
12092
|
expandShorthand,
|
|
8015
12093
|
exportTimeline,
|
|
8016
12094
|
fluidType,
|
|
12095
|
+
generateCSS,
|
|
12096
|
+
generateContextualCSS,
|
|
12097
|
+
generateResponsiveReport,
|
|
8017
12098
|
getAnimationPreset,
|
|
8018
12099
|
getAnimationPresetNames,
|
|
12100
|
+
getAvailableIntents,
|
|
8019
12101
|
getAvailableShorthands,
|
|
12102
|
+
getExtractionCandidates,
|
|
8020
12103
|
getIntent,
|
|
12104
|
+
getIntentDescription,
|
|
12105
|
+
getIntentsByCategory,
|
|
12106
|
+
getLayoutPatterns,
|
|
8021
12107
|
getPropertySuggestion,
|
|
12108
|
+
getScrollPresets,
|
|
12109
|
+
getSemanticDescription,
|
|
12110
|
+
getSemanticIntents,
|
|
8022
12111
|
getShorthandSuggestion,
|
|
8023
12112
|
getStyleChanges,
|
|
8024
12113
|
getStyleDiff,
|
|
@@ -8031,21 +12120,56 @@ export {
|
|
|
8031
12120
|
helpers,
|
|
8032
12121
|
injectChainStyles,
|
|
8033
12122
|
intent,
|
|
12123
|
+
intentAPI,
|
|
12124
|
+
intentAPIPass,
|
|
12125
|
+
intentRecoveryPass,
|
|
8034
12126
|
isShorthand,
|
|
12127
|
+
layoutIntelligence,
|
|
12128
|
+
layoutIntelligencePass,
|
|
12129
|
+
learnPatterns,
|
|
8035
12130
|
macros,
|
|
8036
12131
|
math,
|
|
12132
|
+
mediaQueryPackingPass,
|
|
8037
12133
|
multiply,
|
|
12134
|
+
optimizeSource,
|
|
12135
|
+
orchestrator,
|
|
12136
|
+
parseConstraint,
|
|
12137
|
+
parseIR,
|
|
12138
|
+
patternLearner,
|
|
12139
|
+
patternLearningPass,
|
|
8038
12140
|
recipe,
|
|
12141
|
+
recognizeLayout,
|
|
12142
|
+
resetIdCounter,
|
|
12143
|
+
resolveConstraint,
|
|
12144
|
+
resolveContainerQuery,
|
|
12145
|
+
resolveContextual,
|
|
12146
|
+
resolveIntent,
|
|
12147
|
+
resolveSemantic,
|
|
12148
|
+
resolveStickyUntil,
|
|
12149
|
+
responsiveInference,
|
|
12150
|
+
responsiveInferencePass,
|
|
12151
|
+
runDefaultPipeline,
|
|
8039
12152
|
runtimeChain,
|
|
8040
12153
|
scale,
|
|
12154
|
+
scrollTimeline,
|
|
12155
|
+
semanticTokens,
|
|
12156
|
+
semanticTokensPass,
|
|
8041
12157
|
setBreakpoints,
|
|
8042
12158
|
shorthandMap,
|
|
8043
12159
|
smartChain,
|
|
12160
|
+
sourceOptimizer,
|
|
12161
|
+
sourceOptimizerPass,
|
|
12162
|
+
specificitySortPass,
|
|
12163
|
+
styleIR,
|
|
8044
12164
|
subtract,
|
|
12165
|
+
suggestMacro,
|
|
8045
12166
|
toPx,
|
|
8046
12167
|
tokens2 as tokens,
|
|
12168
|
+
unitResolutionPass,
|
|
8047
12169
|
useSmartStyles,
|
|
8048
12170
|
validateTheme,
|
|
12171
|
+
validateTokenRelationships,
|
|
8049
12172
|
validate as validateValue,
|
|
12173
|
+
validationPass,
|
|
8050
12174
|
withSmartStyles
|
|
8051
12175
|
};
|