@emailens/engine 0.10.2 → 0.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,9 +7,10 @@
7
7
 
8
8
  **The rendering linter for email**
9
9
 
10
+ [![CI](https://github.com/emailens/engine/actions/workflows/ci.yml/badge.svg)](https://github.com/emailens/engine/actions/workflows/ci.yml)
10
11
  [![npm](https://img.shields.io/npm/v/@emailens/engine)](https://www.npmjs.com/package/@emailens/engine)
11
12
  [![license](https://img.shields.io/npm/l/@emailens/engine)](./LICENSE)
12
- [![tests](https://img.shields.io/badge/tests-719%20passing-brightgreen)]()
13
+ [![tests](https://img.shields.io/badge/tests-910%20passing-brightgreen)]()
13
14
  [![node](https://img.shields.io/node/v/@emailens/engine)](https://nodejs.org/)
14
15
  [![MCP](https://img.shields.io/badge/MCP-Server-blue)](https://github.com/emailens/mcp)
15
16
  [![GitHub stars](https://img.shields.io/github/stars/emailens/engine?style=flat)](https://github.com/emailens/engine/stargazers)
@@ -20,9 +21,11 @@
20
21
 
21
22
  **Your email looks perfect in Apple Mail. Gmail strips half the CSS. Outlook renders it in Word.**
22
23
 
23
- `@emailens/engine` analyzes your HTML against 250+ CSS properties across 21 email clients, scores compatibility, and shows you exactly what to fix — before you hit send.
24
+ `@emailens/engine` analyzes your email against 255 CSS and HTML features across 21 email clients, scores compatibility, and shows you exactly what to fix — before you hit send.
24
25
 
25
- Our data says you need this: across the 255 CSS and HTML features we track, **only 6 are fully supported in every major email client**. See [The State of Email CSS](https://emailens.dev/email-css/report).
26
+ Write it however you like. **HTML, MJML, Maizzle and React Email** all go in the front: the engine compiles the template with your own compiler and lints the HTML that actually gets sent, which is the only version your reader sees.
27
+
28
+ Our data says you need this: of those 255 features, **only 6 are fully supported in every major email client**. See [The State of Email CSS](https://emailens.dev/email-css/report).
26
29
 
27
30
  ![emailens lint output showing errors and warnings across email clients](./docs/lint-demo.png)
28
31
 
@@ -105,7 +108,7 @@ const { code } = await generateAiFix({
105
108
 
106
109
  10 analysis engines, one `auditEmail()` call.
107
110
 
108
- - **CSS compatibility** — 250+ properties tested across 21 email clients, with fix snippets and AI-powered auto-fix
111
+ - **CSS compatibility** — 255 CSS and HTML features tested across 21 email clients, with fix snippets and AI-powered auto-fix
109
112
  - **Content overflow** — fixed widths wider than the email frame and unbreakable strings that force horizontal scrolling
110
113
  - **Visual bugs** — gradients/background images with no color fallback (invisible content in Outlook) and fonts with no web-safe fallback, each with a concrete fix
111
114
  - **Spam scoring** — 45+ signals modeled after SpamAssassin, CAN-SPAM, and GDPR
package/dist/index.cjs CHANGED
@@ -68,6 +68,7 @@ __export(index_exports, {
68
68
  CompileError: () => CompileError,
69
69
  EMAIL_CLIENTS: () => EMAIL_CLIENTS,
70
70
  EMPTY_DELIVERABILITY: () => EMPTY_DELIVERABILITY,
71
+ FEATURE_URLS: () => FEATURE_URLS,
71
72
  GENERIC_LINK_TEXT: () => GENERIC_LINK_TEXT,
72
73
  HTML_ELEMENT_FEATURES: () => HTML_ELEMENT_FEATURES,
73
74
  MAX_HTML_SIZE: () => MAX_HTML_SIZE,
@@ -92,6 +93,7 @@ __export(index_exports, {
92
93
  errorWarnings: () => errorWarnings,
93
94
  estimateAiFixTokens: () => estimateAiFixTokens,
94
95
  extractInboxPreview: () => extractInboxPreview,
96
+ featureUrl: () => featureUrl,
95
97
  formatRgb: () => formatRgb,
96
98
  generateAiFix: () => generateAiFix,
97
99
  generateCompatibilityScore: () => generateCompatibilityScore,
@@ -11021,6 +11023,54 @@ function locOfAttr(el, attr) {
11021
11023
  const attrLoc = (_d = (_a = raw.attrs) == null ? void 0 : _a[attr]) != null ? _d : (_c = (_b = raw.startTag) == null ? void 0 : _b.attrs) == null ? void 0 : _c[attr];
11022
11024
  return attrLoc ? toLoc(attrLoc) : locOfElement(el);
11023
11025
  }
11026
+ function locInAttr(attrLoc, source, property, occurrence = 0) {
11027
+ if (!attrLoc || !source) return void 0;
11028
+ const raw = source.slice(attrLoc.offset, attrLoc.offset + attrLoc.length);
11029
+ const open = raw.search(/["']/);
11030
+ const close = raw.lastIndexOf(raw[open]);
11031
+ if (open === -1 || close <= open) return void 0;
11032
+ const found = declarationsIn(raw.slice(open + 1, close), property);
11033
+ const hit = found[occurrence];
11034
+ if (!hit) return void 0;
11035
+ const start = attrLoc.offset + open + 1 + hit.start;
11036
+ const end = attrLoc.offset + open + 1 + hit.end;
11037
+ const from = positionOf(source, start);
11038
+ const to = positionOf(source, end);
11039
+ return {
11040
+ line: from.line,
11041
+ column: from.column,
11042
+ endLine: to.line,
11043
+ endColumn: to.column,
11044
+ offset: start,
11045
+ length: end - start
11046
+ };
11047
+ }
11048
+ function declarationsIn(value, property) {
11049
+ const wanted = property.toLowerCase();
11050
+ const found = [];
11051
+ let depth = 0;
11052
+ let start = 0;
11053
+ const consider = (from, to) => {
11054
+ const text = value.slice(from, to);
11055
+ const colon = text.indexOf(":");
11056
+ if (colon === -1) return;
11057
+ if (text.slice(0, colon).trim().toLowerCase() !== wanted) return;
11058
+ const lead = text.length - text.trimStart().length;
11059
+ const trail = text.length - text.trimEnd().length;
11060
+ if (from + lead < to - trail) found.push({ start: from + lead, end: to - trail });
11061
+ };
11062
+ for (let i = 0; i < value.length; i++) {
11063
+ const c = value[i];
11064
+ if (c === "(") depth++;
11065
+ else if (c === ")") depth = Math.max(0, depth - 1);
11066
+ else if (c === ";" && depth === 0) {
11067
+ consider(start, i);
11068
+ start = i + 1;
11069
+ }
11070
+ }
11071
+ consider(start, value.length);
11072
+ return found;
11073
+ }
11024
11074
  function locOfFirst($, selector) {
11025
11075
  const el = $(selector).first()[0];
11026
11076
  return el ? locOfElement(el) : void 0;
@@ -11286,7 +11336,7 @@ function describeSelector($, el) {
11286
11336
  if (cls) return `${tag}.${cls.split(/\s+/)[0]}`;
11287
11337
  return tag;
11288
11338
  }
11289
- function checkDarkModeFromDom($) {
11339
+ function checkDarkModeFromDom($, source) {
11290
11340
  var _a, _b;
11291
11341
  const darkBlock = collectDarkBlocks($);
11292
11342
  if (!darkBlock) return [];
@@ -11313,7 +11363,7 @@ function checkDarkModeFromDom($) {
11313
11363
  const coveredByAny = matchedElements($, darkBlock.any);
11314
11364
  let uncovered = 0;
11315
11365
  $("[bgcolor], [style]").each((_, el) => {
11316
- var _a2;
11366
+ var _a2, _b2;
11317
11367
  if (uncovered >= MAX_UNCOVERED_ELEMENTS) return false;
11318
11368
  const $el = $(el);
11319
11369
  const style = parseInlineStyle($el.attr("style") || "");
@@ -11323,7 +11373,8 @@ function checkDarkModeFromDom($) {
11323
11373
  if (inline ? coveredByImportant.has(el) : coveredByAny.has(el)) return;
11324
11374
  uncovered++;
11325
11375
  const selector = describeSelector($, el);
11326
- const loc = locOfAttr(el, inline ? "style" : "bgcolor");
11376
+ const attrLoc = locOfAttr(el, inline ? "style" : "bgcolor");
11377
+ const loc = inline ? (_b2 = locInAttr(attrLoc, source, style.get("background-color") !== void 0 ? "background-color" : "background")) != null ? _b2 : attrLoc : attrLoc;
11327
11378
  for (const clientId of PREFERS_COLOR_SCHEME_CLIENTS) {
11328
11379
  warnings.push(__spreadValues({
11329
11380
  severity: "warning",
@@ -11588,18 +11639,37 @@ function analyzeEmailFromDom($, framework, source) {
11588
11639
  const style = $(el).attr("style") || "";
11589
11640
  const props = parseStyleProperties(style);
11590
11641
  const selector = describeSelector2(el);
11591
- const locs = elementLocs(locOfAttr(el, "style"));
11642
+ const attrLoc = locOfAttr(el, "style");
11643
+ const declarationLocs = (prop, occurrence = 0) => {
11644
+ var _a;
11645
+ return (_a = elementLocs(locInAttr(attrLoc, source, prop, occurrence))) != null ? _a : elementLocs(attrLoc);
11646
+ };
11647
+ const locs = elementLocs(attrLoc);
11592
11648
  for (const prop of props) {
11593
11649
  for (const det of COMPOUND_DETECTORS) {
11594
11650
  if (prop === det.property) {
11595
11651
  const value2 = getStyleValue(style, prop);
11596
11652
  if (value2 == null ? void 0 : value2.toLowerCase().includes(det.valueIncludes)) {
11597
- checkPropertySupport(det.key, addWarning, framework, selector, void 0, void 0, locs);
11653
+ checkPropertySupport(
11654
+ det.key,
11655
+ addWarning,
11656
+ framework,
11657
+ selector,
11658
+ void 0,
11659
+ void 0,
11660
+ declarationLocs(prop)
11661
+ );
11598
11662
  }
11599
11663
  }
11600
11664
  }
11601
11665
  if (cssPropertiesToCheck.includes(prop)) {
11602
11666
  const declared = getStyleValues(style, prop);
11667
+ const placed = [];
11668
+ declared.forEach((value2, i) => {
11669
+ const at = locInAttr(attrLoc, source, prop, i);
11670
+ if (at) placed.push({ value: value2, loc: at });
11671
+ });
11672
+ const occurrences = placed.length === declared.length && placed.length > 0 ? { locs: placed.map((p) => p.loc), values: placed.map((p) => p.value) } : locs;
11603
11673
  checkPropertySupport(
11604
11674
  prop,
11605
11675
  addWarning,
@@ -11607,14 +11677,22 @@ function analyzeEmailFromDom($, framework, source) {
11607
11677
  selector,
11608
11678
  void 0,
11609
11679
  declared.length ? declared : void 0,
11610
- locs
11680
+ occurrences
11611
11681
  );
11612
11682
  }
11613
11683
  const value = getStyleValue(style, prop);
11614
11684
  if (value) {
11615
11685
  for (const fn of CSS_FUNCTION_DETECTORS) {
11616
11686
  if (value.includes(fn.pattern)) {
11617
- checkPropertySupport(fn.key, addWarning, framework, selector, void 0, void 0, locs);
11687
+ checkPropertySupport(
11688
+ fn.key,
11689
+ addWarning,
11690
+ framework,
11691
+ selector,
11692
+ void 0,
11693
+ void 0,
11694
+ declarationLocs(prop)
11695
+ );
11618
11696
  }
11619
11697
  }
11620
11698
  }
@@ -11653,7 +11731,7 @@ function analyzeEmailFromDom($, framework, source) {
11653
11731
  for (const fn of detectedCssFunctions) {
11654
11732
  checkPropertySupport(fn, addWarning, framework, void 0, propertyLines.get(fn), void 0, propertyLocs.get(fn));
11655
11733
  }
11656
- for (const w of checkDarkModeFromDom($)) addWarning(w);
11734
+ for (const w of checkDarkModeFromDom($, source)) addWarning(w);
11657
11735
  const severityOrder = { error: 0, warning: 1, info: 2 };
11658
11736
  warnings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
11659
11737
  return warnings;
@@ -12083,6 +12161,263 @@ function extractCode(response) {
12083
12161
  return response.trim();
12084
12162
  }
12085
12163
 
12164
+ // src/rules/feature-urls.ts
12165
+ var FEATURE_URLS = {
12166
+ "!important": "https://www.caniemail.com/features/css-important/",
12167
+ "::after": "https://www.caniemail.com/features/css-pseudo-element-after/",
12168
+ "::before": "https://www.caniemail.com/features/css-pseudo-element-before/",
12169
+ "::first-letter": "https://www.caniemail.com/features/css-pseudo-element-first-letter/",
12170
+ "::first-line": "https://www.caniemail.com/features/css-pseudo-element-first-line/",
12171
+ "::marker": "https://www.caniemail.com/features/css-pseudo-element-marker/",
12172
+ "::placeholder": "https://www.caniemail.com/features/css-pseudo-element-placeholder/",
12173
+ ":adjacent-sibling": "https://www.caniemail.com/features/css-selector-adjacent-sibling/",
12174
+ ":attribute-selector": "https://www.caniemail.com/features/css-selector-attribute/",
12175
+ ":chaining": "https://www.caniemail.com/features/css-selector-chaining/",
12176
+ ":checked": "https://www.caniemail.com/features/css-pseudo-class-checked/",
12177
+ ":child-combinator": "https://www.caniemail.com/features/css-selector-child/",
12178
+ ":class-selector": "https://www.caniemail.com/features/css-selector-class/",
12179
+ ":descendant-combinator": "https://www.caniemail.com/features/css-selector-descendant/",
12180
+ ":first-child": "https://www.caniemail.com/features/css-pseudo-class-first-child/",
12181
+ ":first-of-type": "https://www.caniemail.com/features/css-pseudo-class-first-of-type/",
12182
+ ":focus": "https://www.caniemail.com/features/css-pseudo-class-focus/",
12183
+ ":focus-visible": "https://www.caniemail.com/features/css-pseudo-class-focus-visible/",
12184
+ ":focus-within": "https://www.caniemail.com/features/css-pseudo-class-focus-within/",
12185
+ ":general-sibling": "https://www.caniemail.com/features/css-selector-general-sibling/",
12186
+ ":grouping": "https://www.caniemail.com/features/css-selector-grouping/",
12187
+ ":has": "https://www.caniemail.com/features/css-pseudo-class-has/",
12188
+ ":hover": "https://www.caniemail.com/features/css-pseudo-class-hover/",
12189
+ ":id-selector": "https://www.caniemail.com/features/css-selector-id/",
12190
+ ":lang": "https://www.caniemail.com/features/css-pseudo-class-lang/",
12191
+ ":last-child": "https://www.caniemail.com/features/css-pseudo-class-last-child/",
12192
+ ":last-of-type": "https://www.caniemail.com/features/css-pseudo-class-last-of-type/",
12193
+ ":link": "https://www.caniemail.com/features/css-pseudo-class-link/",
12194
+ ":not": "https://www.caniemail.com/features/css-pseudo-class-not/",
12195
+ ":nth-child": "https://www.caniemail.com/features/css-pseudo-class-nth-child/",
12196
+ ":nth-last-child": "https://www.caniemail.com/features/css-pseudo-class-nth-last-child/",
12197
+ ":nth-of-type": "https://www.caniemail.com/features/css-pseudo-class-nth-of-type/",
12198
+ ":only-child": "https://www.caniemail.com/features/css-pseudo-class-only-child/",
12199
+ ":only-of-type": "https://www.caniemail.com/features/css-pseudo-class-only-of-type/",
12200
+ ":target": "https://www.caniemail.com/features/css-pseudo-class-target/",
12201
+ ":type-selector": "https://www.caniemail.com/features/css-selector-type/",
12202
+ ":universal-selector": "https://www.caniemail.com/features/css-selector-universal/",
12203
+ ":visited": "https://www.caniemail.com/features/css-pseudo-class-visited/",
12204
+ "<abbr>": "https://www.caniemail.com/features/html-abbr/",
12205
+ "<acronym>": "https://www.caniemail.com/features/html-acronym/",
12206
+ "<address>": "https://www.caniemail.com/features/html-address/",
12207
+ "<audio>": "https://www.caniemail.com/features/html-audio/",
12208
+ "<base>": "https://www.caniemail.com/features/html-base/",
12209
+ "<bdi>": "https://www.caniemail.com/features/html-bdi/",
12210
+ "<blockquote>": "https://www.caniemail.com/features/html-blockquote/",
12211
+ "<body>": "https://www.caniemail.com/features/html-body/",
12212
+ "<code>": "https://www.caniemail.com/features/html-code/",
12213
+ "<del>": "https://www.caniemail.com/features/html-del/",
12214
+ "<dfn>": "https://www.caniemail.com/features/html-dfn/",
12215
+ "<dialog>": "https://www.caniemail.com/features/html-dialog/",
12216
+ "<div>": "https://www.caniemail.com/features/html-div/",
12217
+ "<form>": "https://www.caniemail.com/features/html-form/",
12218
+ "<h1>": "https://www.caniemail.com/features/html-h1-h6/",
12219
+ "<hr>": "https://www.caniemail.com/features/html-hr/",
12220
+ "<img>": "https://www.caniemail.com/features/html-img/",
12221
+ "<link>": "https://www.caniemail.com/features/html-link/",
12222
+ "<marquee>": "https://www.caniemail.com/features/html-marquee/",
12223
+ "<meter>": "https://www.caniemail.com/features/html-meter/",
12224
+ "<object>": "https://www.caniemail.com/features/html-object/",
12225
+ "<p>": "https://www.caniemail.com/features/html-p/",
12226
+ "<picture>": "https://www.caniemail.com/features/html-picture/",
12227
+ "<pre>": "https://www.caniemail.com/features/html-pre/",
12228
+ "<progress>": "https://www.caniemail.com/features/html-progress/",
12229
+ "<rp>": "https://www.caniemail.com/features/html-rp/",
12230
+ "<rt>": "https://www.caniemail.com/features/html-rt/",
12231
+ "<ruby>": "https://www.caniemail.com/features/html-ruby/",
12232
+ "<select>": "https://www.caniemail.com/features/html-select/",
12233
+ "<small>": "https://www.caniemail.com/features/html-small/",
12234
+ "<span>": "https://www.caniemail.com/features/html-span/",
12235
+ "<strike>": "https://www.caniemail.com/features/html-strike/",
12236
+ "<strong>": "https://www.caniemail.com/features/html-strong/",
12237
+ "<style>": "https://www.caniemail.com/features/html-style/",
12238
+ "<svg>": "https://www.caniemail.com/features/html-svg/",
12239
+ "<table>": "https://www.caniemail.com/features/html-table/",
12240
+ "<textarea>": "https://www.caniemail.com/features/html-textarea/",
12241
+ "<ul>": "https://www.caniemail.com/features/html-lists/",
12242
+ "<video>": "https://www.caniemail.com/features/html-video/",
12243
+ "<wbr>": "https://www.caniemail.com/features/html-wbr/",
12244
+ "@font-face": "https://www.caniemail.com/features/css-at-font-face/",
12245
+ "@import": "https://www.caniemail.com/features/css-at-import/",
12246
+ "@keyframes": "https://www.caniemail.com/features/css-at-keyframes/",
12247
+ "@media": "https://www.caniemail.com/features/css-at-media/",
12248
+ "@supports": "https://www.caniemail.com/features/css-at-supports/",
12249
+ "accent-color": "https://www.caniemail.com/features/css-accent-color/",
12250
+ "align-items": "https://www.caniemail.com/features/css-align-items/",
12251
+ "animation": "https://www.caniemail.com/features/css-animation/",
12252
+ "aspect-ratio": "https://www.caniemail.com/features/css-aspect-ratio/",
12253
+ "backdrop-filter": "https://www.caniemail.com/features/css-backdrop-filter/",
12254
+ "background": "https://www.caniemail.com/features/css-background/",
12255
+ "background-blend-mode": "https://www.caniemail.com/features/css-background-blend-mode/",
12256
+ "background-clip": "https://www.caniemail.com/features/css-background-clip/",
12257
+ "background-color": "https://www.caniemail.com/features/css-background-color/",
12258
+ "background-image": "https://www.caniemail.com/features/css-background-image/",
12259
+ "background-origin": "https://www.caniemail.com/features/css-background-origin/",
12260
+ "background-position": "https://www.caniemail.com/features/css-background-position/",
12261
+ "background-repeat": "https://www.caniemail.com/features/css-background-repeat/",
12262
+ "background-size": "https://www.caniemail.com/features/css-background-size/",
12263
+ "bimi": "https://www.caniemail.com/features/bimi/",
12264
+ "block-size": "https://www.caniemail.com/features/css-block-inline-size/",
12265
+ "border": "https://www.caniemail.com/features/css-border/",
12266
+ "border-collapse": "https://www.caniemail.com/features/css-border-collapse/",
12267
+ "border-image": "https://www.caniemail.com/features/css-border-image/",
12268
+ "border-inline": "https://www.caniemail.com/features/css-border-inline-block/",
12269
+ "border-inline-individual": "https://www.caniemail.com/features/css-border-inline-block-individual/",
12270
+ "border-inline-longhand": "https://www.caniemail.com/features/css-border-inline-block-longhand/",
12271
+ "border-radius": "https://www.caniemail.com/features/css-border-radius/",
12272
+ "border-radius-logical": "https://www.caniemail.com/features/css-border-radius-logical/",
12273
+ "border-spacing": "https://www.caniemail.com/features/css-border-spacing/",
12274
+ "box-shadow": "https://www.caniemail.com/features/css-box-shadow/",
12275
+ "box-sizing": "https://www.caniemail.com/features/css-box-sizing/",
12276
+ "calc": "https://www.caniemail.com/features/css-unit-calc/",
12277
+ "caption-side": "https://www.caniemail.com/features/css-caption-side/",
12278
+ "clamp": "https://www.caniemail.com/features/css-function-clamp/",
12279
+ "clear": "https://www.caniemail.com/features/css-clear/",
12280
+ "clip-path": "https://www.caniemail.com/features/css-clip-path/",
12281
+ "color-scheme": "https://www.caniemail.com/features/css-color-scheme/",
12282
+ "column-count": "https://www.caniemail.com/features/css-column-count/",
12283
+ "columns": "https://www.caniemail.com/features/css-column-layout-properties/",
12284
+ "conic-gradient": "https://www.caniemail.com/features/css-conic-gradient/",
12285
+ "css-comments": "https://www.caniemail.com/features/css-comments/",
12286
+ "cursor": "https://www.caniemail.com/features/css-cursor/",
12287
+ "custom-properties": "https://www.caniemail.com/features/css-variables/",
12288
+ "direction": "https://www.caniemail.com/features/css-direction/",
12289
+ "display": "https://www.caniemail.com/features/css-display/",
12290
+ "display:flex": "https://www.caniemail.com/features/css-display-flex/",
12291
+ "display:grid": "https://www.caniemail.com/features/css-display-grid/",
12292
+ "display:none": "https://www.caniemail.com/features/css-display-none/",
12293
+ "empty-cells": "https://www.caniemail.com/features/css-empty-cells/",
12294
+ "filter": "https://www.caniemail.com/features/css-filter/",
12295
+ "fit-content": "https://www.caniemail.com/features/css-intrinsic-size/",
12296
+ "flex-direction": "https://www.caniemail.com/features/css-flex-direction/",
12297
+ "flex-wrap": "https://www.caniemail.com/features/css-flex-wrap/",
12298
+ "float": "https://www.caniemail.com/features/css-float/",
12299
+ "font": "https://www.caniemail.com/features/css-font/",
12300
+ "font-kerning": "https://www.caniemail.com/features/css-font-kerning/",
12301
+ "font-size": "https://www.caniemail.com/features/css-font-size/",
12302
+ "font-size-adjust": "https://www.caniemail.com/features/css-font-size-adjust/",
12303
+ "font-stretch": "https://www.caniemail.com/features/css-font-stretch/",
12304
+ "font-weight": "https://www.caniemail.com/features/css-font-weight/",
12305
+ "gap": "https://www.caniemail.com/features/css-gap/",
12306
+ "grid-template-columns": "https://www.caniemail.com/features/css-grid-template/",
12307
+ "height": "https://www.caniemail.com/features/css-height/",
12308
+ "hyphenate-character": "https://www.caniemail.com/features/css-hyphenate-character/",
12309
+ "hyphenate-limit-chars": "https://www.caniemail.com/features/css-hyphenate-limit-chars/",
12310
+ "hyphens": "https://www.caniemail.com/features/css-hyphens/",
12311
+ "inert": "https://www.caniemail.com/features/css-inert-attribute/",
12312
+ "inline-size": "https://www.caniemail.com/features/css-inline-size/",
12313
+ "inset": "https://www.caniemail.com/features/css-inset/",
12314
+ "justify-content": "https://www.caniemail.com/features/css-justify-content/",
12315
+ "left": "https://www.caniemail.com/features/css-left-right-top-bottom/",
12316
+ "letter-spacing": "https://www.caniemail.com/features/css-letter-spacing/",
12317
+ "light-dark": "https://www.caniemail.com/features/css-function-light-dark/",
12318
+ "line-height": "https://www.caniemail.com/features/css-line-height/",
12319
+ "linear-gradient": "https://www.caniemail.com/features/css-linear-gradient/",
12320
+ "list-style": "https://www.caniemail.com/features/css-list-style/",
12321
+ "list-style-image": "https://www.caniemail.com/features/css-list-style-image/",
12322
+ "list-style-position": "https://www.caniemail.com/features/css-list-style-position/",
12323
+ "list-style-type": "https://www.caniemail.com/features/css-list-style-type/",
12324
+ "margin": "https://www.caniemail.com/features/css-margin/",
12325
+ "margin-block-start-end": "https://www.caniemail.com/features/css-margin-block-start-end/",
12326
+ "margin-inline-block": "https://www.caniemail.com/features/css-margin-inline-block/",
12327
+ "margin-inline-start-end": "https://www.caniemail.com/features/css-margin-inline-start-end/",
12328
+ "mask-image": "https://www.caniemail.com/features/css-mask-image/",
12329
+ "max": "https://www.caniemail.com/features/css-function-max/",
12330
+ "max-block-size": "https://www.caniemail.com/features/css-max-block-size/",
12331
+ "max-height": "https://www.caniemail.com/features/css-max-height/",
12332
+ "max-inline-size": "https://www.caniemail.com/features/css-max-inline-size/",
12333
+ "max-width": "https://www.caniemail.com/features/css-max-width/",
12334
+ "min": "https://www.caniemail.com/features/css-function-min/",
12335
+ "min-block-size": "https://www.caniemail.com/features/css-min-block-size/",
12336
+ "min-height": "https://www.caniemail.com/features/css-min-height/",
12337
+ "min-inline-size": "https://www.caniemail.com/features/css-min-inline-size/",
12338
+ "min-width": "https://www.caniemail.com/features/css-min-width/",
12339
+ "mix-blend-mode": "https://www.caniemail.com/features/css-mix-blend-mode/",
12340
+ "modern-color": "https://www.caniemail.com/features/css-modern-color/",
12341
+ "nesting": "https://www.caniemail.com/features/css-nesting/",
12342
+ "object-fit": "https://www.caniemail.com/features/css-object-fit/",
12343
+ "object-position": "https://www.caniemail.com/features/css-object-position/",
12344
+ "opacity": "https://www.caniemail.com/features/css-opacity/",
12345
+ "orphans": "https://www.caniemail.com/features/css-orphans/",
12346
+ "outline": "https://www.caniemail.com/features/css-outline/",
12347
+ "outline-offset": "https://www.caniemail.com/features/css-outline-offset/",
12348
+ "overflow": "https://www.caniemail.com/features/css-overflow/",
12349
+ "overflow-wrap": "https://www.caniemail.com/features/css-word-wrap/",
12350
+ "padding": "https://www.caniemail.com/features/css-padding/",
12351
+ "padding-block-start-end": "https://www.caniemail.com/features/css-padding-block-start-end/",
12352
+ "padding-inline-block": "https://www.caniemail.com/features/css-padding-inline-block/",
12353
+ "padding-inline-start-end": "https://www.caniemail.com/features/css-padding-inline-start-end/",
12354
+ "position": "https://www.caniemail.com/features/css-position/",
12355
+ "pseudo-class-active": "https://www.caniemail.com/features/css-pseudo-class-active/",
12356
+ "pseudo-class-default": "https://www.caniemail.com/features/css-pseudo-class-default/",
12357
+ "pseudo-class-nth-last-of-type": "https://www.caniemail.com/features/css-pseudo-class-nth-last-of-type/",
12358
+ "radial-gradient": "https://www.caniemail.com/features/css-radial-gradient/",
12359
+ "resize": "https://www.caniemail.com/features/css-resize/",
12360
+ "rgb": "https://www.caniemail.com/features/css-rgb/",
12361
+ "rgba": "https://www.caniemail.com/features/css-rgba/",
12362
+ "scroll-snap": "https://www.caniemail.com/features/css-scroll-snap/",
12363
+ "shape-margin": "https://www.caniemail.com/features/css-shape-margin/",
12364
+ "shape-outside": "https://www.caniemail.com/features/css-shape-outside/",
12365
+ "system-ui": "https://www.caniemail.com/features/css-sytem-ui/",
12366
+ "tab-size": "https://www.caniemail.com/features/css-tab-size/",
12367
+ "table-layout": "https://www.caniemail.com/features/css-table-layout/",
12368
+ "text-align": "https://www.caniemail.com/features/css-text-align/",
12369
+ "text-align-last": "https://www.caniemail.com/features/css-text-align-last/",
12370
+ "text-decoration": "https://www.caniemail.com/features/css-text-decoration/",
12371
+ "text-decoration-color": "https://www.caniemail.com/features/css-text-decoration-color/",
12372
+ "text-decoration-line": "https://www.caniemail.com/features/css-text-decoration-line/",
12373
+ "text-decoration-skip-ink": "https://www.caniemail.com/features/css-text-decoration-skip-ink/",
12374
+ "text-decoration-style": "https://www.caniemail.com/features/css-text-decoration-style/",
12375
+ "text-decoration-thickness": "https://www.caniemail.com/features/css-text-decoration-thickness/",
12376
+ "text-emphasis": "https://www.caniemail.com/features/css-text-emphasis/",
12377
+ "text-emphasis-position": "https://www.caniemail.com/features/css-text-emphasis-position/",
12378
+ "text-indent": "https://www.caniemail.com/features/css-text-indent/",
12379
+ "text-justify": "https://www.caniemail.com/features/css-text-justify/",
12380
+ "text-orientation": "https://www.caniemail.com/features/css-text-orientation/",
12381
+ "text-overflow": "https://www.caniemail.com/features/css-text-overflow/",
12382
+ "text-shadow": "https://www.caniemail.com/features/css-text-shadow/",
12383
+ "text-transform": "https://www.caniemail.com/features/css-text-transform/",
12384
+ "text-underline-offset": "https://www.caniemail.com/features/css-text-underline-offset/",
12385
+ "text-underline-position": "https://www.caniemail.com/features/css-text-underline-position/",
12386
+ "text-wrap": "https://www.caniemail.com/features/css-text-wrap/",
12387
+ "transform": "https://www.caniemail.com/features/css-transform/",
12388
+ "transition": "https://www.caniemail.com/features/css-transition/",
12389
+ "unit-ch": "https://www.caniemail.com/features/css-unit-ch/",
12390
+ "unit-cm": "https://www.caniemail.com/features/css-unit-cm/",
12391
+ "unit-em": "https://www.caniemail.com/features/css-unit-em/",
12392
+ "unit-ex": "https://www.caniemail.com/features/css-unit-ex/",
12393
+ "unit-in": "https://www.caniemail.com/features/css-unit-in/",
12394
+ "unit-initial": "https://www.caniemail.com/features/css-unit-initial/",
12395
+ "unit-mm": "https://www.caniemail.com/features/css-unit-mm/",
12396
+ "unit-pc": "https://www.caniemail.com/features/css-unit-pc/",
12397
+ "unit-percent": "https://www.caniemail.com/features/css-unit-percent/",
12398
+ "unit-pt": "https://www.caniemail.com/features/css-unit-pt/",
12399
+ "unit-px": "https://www.caniemail.com/features/css-unit-px/",
12400
+ "unit-rem": "https://www.caniemail.com/features/css-unit-rem/",
12401
+ "unit-vh": "https://www.caniemail.com/features/css-unit-vh/",
12402
+ "unit-vmax": "https://www.caniemail.com/features/css-unit-vmax/",
12403
+ "unit-vmin": "https://www.caniemail.com/features/css-unit-vmin/",
12404
+ "unit-vw": "https://www.caniemail.com/features/css-unit-vw/",
12405
+ "user-select": "https://www.caniemail.com/features/css-user-select/",
12406
+ "vertical-align": "https://www.caniemail.com/features/css-vertical-align/",
12407
+ "visibility": "https://www.caniemail.com/features/css-visibility/",
12408
+ "white-space": "https://www.caniemail.com/features/css-white-space/",
12409
+ "white-space-collapse": "https://www.caniemail.com/features/css-white-space-collapse/",
12410
+ "widows": "https://www.caniemail.com/features/css-widows/",
12411
+ "width": "https://www.caniemail.com/features/css-width/",
12412
+ "word-break": "https://www.caniemail.com/features/css-word-break/",
12413
+ "word-spacing": "https://www.caniemail.com/features/css-word-spacing/",
12414
+ "writing-mode": "https://www.caniemail.com/features/css-writing-mode/",
12415
+ "z-index": "https://www.caniemail.com/features/css-z-index/"
12416
+ };
12417
+ function featureUrl(code) {
12418
+ return FEATURE_URLS[code];
12419
+ }
12420
+
12086
12421
  // src/spam-scorer.ts
12087
12422
  var SPAM_TRIGGER_PHRASES = [
12088
12423
  "act now",
@@ -14058,6 +14393,7 @@ var CompileError = class extends Error {
14058
14393
  CompileError,
14059
14394
  EMAIL_CLIENTS,
14060
14395
  EMPTY_DELIVERABILITY,
14396
+ FEATURE_URLS,
14061
14397
  GENERIC_LINK_TEXT,
14062
14398
  HTML_ELEMENT_FEATURES,
14063
14399
  MAX_HTML_SIZE,
@@ -14082,6 +14418,7 @@ var CompileError = class extends Error {
14082
14418
  errorWarnings,
14083
14419
  estimateAiFixTokens,
14084
14420
  extractInboxPreview,
14421
+ featureUrl,
14085
14422
  formatRgb,
14086
14423
  generateAiFix,
14087
14424
  generateCompatibilityScore,