@style-capture/core 0.0.1

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/index.js ADDED
@@ -0,0 +1,1308 @@
1
+ //#region src/claude-export.ts
2
+ const CAPTURE_NODE_ATTRIBUTE = "data-lc";
3
+ const MAX_TAILWIND_SUGGESTIONS = 6;
4
+ const MAX_OPEN_QUESTION_ITEMS = 8;
5
+ const CLAUDE_CAPTURE_INSTRUCTION = "Recreate or refactor this UI faithfully. html_capture + css_capture are ground truth. Preserve structure unless simplifying is clearly better. Tailwind hints are hints. Use the smallest codebase-ready change and state ambiguities instead of inventing details.";
6
+ const compactInlineText = (value) => value.replaceAll(/\s+/g, " ").trim();
7
+ const escapeXmlAttribute = (value) => value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
8
+ const compactSelector = (selector) => selector.replaceAll(/\s*([>+~])\s*/g, "$1").replaceAll(/,\s+/g, ",").replaceAll(/\s+/g, " ").trim();
9
+ const buildCompactRefs = (order) => {
10
+ const refs = {};
11
+ for (const [index, elementId] of order.entries()) refs[elementId] = index.toString(36);
12
+ return refs;
13
+ };
14
+ const buildCompactSelector = (ref) => `[${CAPTURE_NODE_ATTRIBUTE}="${ref}"]`;
15
+ const buildFallbackSelectors = (capture) => {
16
+ const selectors = {};
17
+ for (const elementId of capture.order) {
18
+ const snapshot = capture.elements[elementId];
19
+ if (!snapshot) continue;
20
+ selectors[elementId] = compactSelector(snapshot.selector);
21
+ }
22
+ return selectors;
23
+ };
24
+ const stripCommentNodes = (root) => {
25
+ const walker = root.ownerDocument.createTreeWalker(root, NodeFilter.SHOW_COMMENT);
26
+ const comments = [];
27
+ while (walker.nextNode()) if (walker.currentNode instanceof Comment) comments.push(walker.currentNode);
28
+ for (const comment of comments) comment.remove();
29
+ };
30
+ const minifyHtmlString = (html) => html.replaceAll(/<!--[\s\S]*?-->/g, "").replaceAll(/>\s+</g, "><").trim();
31
+ const elementMatchesSnapshot = (element, snapshot) => {
32
+ if (element.tagName.toLowerCase() !== snapshot.tagName) return false;
33
+ for (const className of snapshot.classList) if (!element.classList.contains(className)) return false;
34
+ for (const [name, value] of Object.entries(snapshot.attributes)) {
35
+ if (name === "class") continue;
36
+ if (element.getAttribute(name) !== value) return false;
37
+ }
38
+ return true;
39
+ };
40
+ const findMatchingElementIndex = (candidates, snapshot, startIndex) => {
41
+ for (let index = startIndex; index < candidates.length; index += 1) if (elementMatchesSnapshot(candidates[index], snapshot)) return index;
42
+ return -1;
43
+ };
44
+ const annotateCaptureHtml = (capture) => {
45
+ const refs = buildCompactRefs(capture.order);
46
+ if (!capture.rootOuterHtml.trim()) return {
47
+ html: "",
48
+ refs,
49
+ selectors: buildFallbackSelectors(capture)
50
+ };
51
+ if (typeof DOMParser === "undefined") return {
52
+ html: minifyHtmlString(capture.rootOuterHtml),
53
+ refs,
54
+ selectors: buildFallbackSelectors(capture)
55
+ };
56
+ const root = new DOMParser().parseFromString(capture.rootOuterHtml, "text/html").body.firstElementChild;
57
+ if (!root) return {
58
+ html: minifyHtmlString(capture.rootOuterHtml),
59
+ refs,
60
+ selectors: buildFallbackSelectors(capture)
61
+ };
62
+ stripCommentNodes(root);
63
+ const candidates = [root, ...root.querySelectorAll("*")];
64
+ const selectors = buildFallbackSelectors(capture);
65
+ let searchStartIndex = 0;
66
+ for (const elementId of capture.order) {
67
+ const snapshot = capture.elements[elementId];
68
+ const ref = refs[elementId];
69
+ if (!(snapshot && ref)) continue;
70
+ const matchIndex = findMatchingElementIndex(candidates, snapshot, searchStartIndex);
71
+ if (matchIndex === -1) continue;
72
+ candidates[matchIndex].setAttribute(CAPTURE_NODE_ATTRIBUTE, ref);
73
+ selectors[elementId] = buildCompactSelector(ref);
74
+ searchStartIndex = matchIndex + 1;
75
+ }
76
+ return {
77
+ html: minifyHtmlString(root.outerHTML),
78
+ refs,
79
+ selectors
80
+ };
81
+ };
82
+ const formatCssPropertyName = (property) => {
83
+ if (property.includes("-")) return property;
84
+ return property.replaceAll(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
85
+ };
86
+ const formatDeclarationBlock = (styles) => Object.entries(styles).filter(([, value]) => value.trim().length > 0).toSorted(([left], [right]) => left.localeCompare(right)).map(([property, value]) => `${formatCssPropertyName(property)}:${compactInlineText(value)}`).join(";");
87
+ const formatPseudoBlock = (selector, pseudo) => {
88
+ if (!pseudo) return "";
89
+ const declarationBlock = formatDeclarationBlock(pseudo.styles);
90
+ if (!declarationBlock) return "";
91
+ return `${selector}::${pseudo.kind}{${declarationBlock}}`;
92
+ };
93
+ const formatElementCssBlock = (snapshot, selector) => {
94
+ const parts = [];
95
+ const declarationBlock = formatDeclarationBlock(snapshot.styles);
96
+ const beforeBlock = formatPseudoBlock(selector, snapshot.pseudo.before);
97
+ const afterBlock = formatPseudoBlock(selector, snapshot.pseudo.after);
98
+ if (declarationBlock) parts.push(`${selector}{${declarationBlock}}`);
99
+ if (beforeBlock) parts.push(beforeBlock);
100
+ if (afterBlock) parts.push(afterBlock);
101
+ return parts.join("");
102
+ };
103
+ const formatCaptureCss = (capture, selectors) => capture.order.map((elementId) => {
104
+ const snapshot = capture.elements[elementId];
105
+ if (!snapshot) return "";
106
+ return formatElementCssBlock(snapshot, selectors[elementId] ?? snapshot.selector);
107
+ }).filter(Boolean).join("");
108
+ const buildTailwindHintEntries = (capture, mapping, refs) => {
109
+ if (!mapping) return [];
110
+ const entries = [];
111
+ const rootMapping = mapping.elements[capture.rootElementId];
112
+ if (rootMapping?.suggestedClassName || rootMapping?.className) entries.push({
113
+ key: "root",
114
+ value: rootMapping.suggestedClassName || rootMapping.className
115
+ });
116
+ const topSuggestions = capture.order.filter((elementId) => elementId !== capture.rootElementId).map((elementId) => mapping.elements[elementId]).filter((element) => Boolean(element?.suggestedClassName || element?.className)).slice(0, MAX_TAILWIND_SUGGESTIONS);
117
+ for (const suggestion of topSuggestions) entries.push({
118
+ key: refs[suggestion.elementId] ?? suggestion.elementId,
119
+ value: suggestion.suggestedClassName || suggestion.className
120
+ });
121
+ return entries;
122
+ };
123
+ const buildOpenQuestionEntries = (mapping, refs) => {
124
+ if (!mapping || mapping.reviewQueue.length === 0) return [];
125
+ return mapping.reviewQueue.slice(0, MAX_OPEN_QUESTION_ITEMS).map((item) => ({
126
+ key: refs[item.elementId] ?? item.elementId,
127
+ value: item.reasons.join("; ")
128
+ }));
129
+ };
130
+ const buildClaudeCapturePrompt = (capture, mapping) => {
131
+ const annotation = annotateCaptureHtml(capture);
132
+ const rootElement = capture.elements[capture.rootElementId];
133
+ return {
134
+ cssCapture: formatCaptureCss(capture, annotation.selectors),
135
+ htmlCapture: annotation.html,
136
+ instruction: CLAUDE_CAPTURE_INSTRUCTION,
137
+ metadata: {
138
+ elementCount: capture.summary.elementCount,
139
+ mode: capture.settings.captureMode,
140
+ pseudoCount: capture.summary.pseudoElementCount,
141
+ rootRef: annotation.refs[capture.rootElementId] ?? capture.rootElementId,
142
+ rootSelector: rootElement?.selector ?? "Unavailable",
143
+ url: capture.metadata.url
144
+ },
145
+ openQuestions: buildOpenQuestionEntries(mapping, annotation.refs),
146
+ tailwindHints: buildTailwindHintEntries(capture, mapping, annotation.refs)
147
+ };
148
+ };
149
+ const formatCaptureForClaudeMarkdown = (capture, mapping) => {
150
+ const prompt = buildClaudeCapturePrompt(capture, mapping);
151
+ const sections = [
152
+ `<style_capture url="${escapeXmlAttribute(prompt.metadata.url)}" mode="${prompt.metadata.mode}" root_ref="${prompt.metadata.rootRef}" root_selector="${escapeXmlAttribute(prompt.metadata.rootSelector)}" elements="${prompt.metadata.elementCount}" pseudos="${prompt.metadata.pseudoCount}">`,
153
+ prompt.instruction,
154
+ `<html_capture>${prompt.htmlCapture}</html_capture>`,
155
+ `<css_capture>${prompt.cssCapture}</css_capture>`
156
+ ];
157
+ if (prompt.tailwindHints.length > 0) sections.push("<tailwind_hints>", ...prompt.tailwindHints.map((entry) => `${entry.key}=${compactInlineText(entry.value)}`), "</tailwind_hints>");
158
+ if (prompt.openQuestions.length > 0) sections.push("<open_questions>", ...prompt.openQuestions.map((entry) => `${entry.key}:${compactInlineText(entry.value)}`), "</open_questions>");
159
+ sections.push("</style_capture>");
160
+ return sections.join("\n").trim();
161
+ };
162
+ //#endregion
163
+ //#region src/messages.ts
164
+ const MESSAGE_TYPE_CAPTURE_COMPLETED = "capture/completed";
165
+ const MESSAGE_TYPE_CAPTURE_CANCELLED = "capture/cancelled";
166
+ const MESSAGE_TYPE_CAPTURE_FAILED = "capture/failed";
167
+ const createDefaultSettings = () => ({
168
+ captureMode: "curated",
169
+ includeHiddenElements: false,
170
+ includePseudoElements: true
171
+ });
172
+ //#endregion
173
+ //#region src/tailwind-mapper.ts
174
+ const HIGH_CONFIDENCE = .92;
175
+ const MEDIUM_CONFIDENCE = .72;
176
+ const LOW_CONFIDENCE = .45;
177
+ const PASSIVE_CONFIDENCE = .84;
178
+ const LENGTH_PATTERN = /^(-?\d*\.?\d+)(px|rem|em|%|vh|vw)?$/;
179
+ const RGB_PATTERN = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([0-9.]+))?\)$/;
180
+ const WHITESPACE_SPLIT_PATTERN = /\s+/;
181
+ const SPACING_SCALE = new Map([
182
+ [0, "0"],
183
+ [1, "px"],
184
+ [2, "0.5"],
185
+ [4, "1"],
186
+ [6, "1.5"],
187
+ [8, "2"],
188
+ [10, "2.5"],
189
+ [12, "3"],
190
+ [14, "3.5"],
191
+ [16, "4"],
192
+ [20, "5"],
193
+ [24, "6"],
194
+ [28, "7"],
195
+ [32, "8"],
196
+ [36, "9"],
197
+ [40, "10"],
198
+ [44, "11"],
199
+ [48, "12"],
200
+ [56, "14"],
201
+ [64, "16"],
202
+ [80, "20"],
203
+ [96, "24"],
204
+ [112, "28"],
205
+ [128, "32"],
206
+ [144, "36"],
207
+ [160, "40"],
208
+ [176, "44"],
209
+ [192, "48"],
210
+ [224, "56"],
211
+ [256, "64"],
212
+ [288, "72"],
213
+ [320, "80"],
214
+ [384, "96"]
215
+ ]);
216
+ const FONT_SIZE_SCALE = new Map([
217
+ [12, "xs"],
218
+ [14, "sm"],
219
+ [16, "base"],
220
+ [18, "lg"],
221
+ [20, "xl"],
222
+ [24, "2xl"],
223
+ [30, "3xl"],
224
+ [36, "4xl"],
225
+ [48, "5xl"],
226
+ [60, "6xl"],
227
+ [72, "7xl"],
228
+ [96, "8xl"],
229
+ [128, "9xl"]
230
+ ]);
231
+ const LINE_HEIGHT_SCALE = new Map([
232
+ [12, "3"],
233
+ [16, "4"],
234
+ [20, "5"],
235
+ [24, "6"],
236
+ [28, "7"],
237
+ [32, "8"],
238
+ [36, "9"],
239
+ [40, "10"]
240
+ ]);
241
+ const RADIUS_SCALE = new Map([
242
+ [0, "none"],
243
+ [2, "sm"],
244
+ [4, ""],
245
+ [6, "md"],
246
+ [8, "lg"],
247
+ [12, "xl"],
248
+ [16, "2xl"],
249
+ [24, "3xl"]
250
+ ]);
251
+ const BORDER_WIDTH_SCALE = new Map([
252
+ [1, ""],
253
+ [2, "2"],
254
+ [4, "4"],
255
+ [8, "8"]
256
+ ]);
257
+ const Z_INDEX_SCALE = new Set([
258
+ 0,
259
+ 10,
260
+ 20,
261
+ 30,
262
+ 40,
263
+ 50
264
+ ]);
265
+ const OPACITY_SCALE = new Set([
266
+ 0,
267
+ 5,
268
+ 10,
269
+ 15,
270
+ 20,
271
+ 25,
272
+ 30,
273
+ 35,
274
+ 40,
275
+ 45,
276
+ 50,
277
+ 55,
278
+ 60,
279
+ 65,
280
+ 70,
281
+ 75,
282
+ 80,
283
+ 85,
284
+ 90,
285
+ 95,
286
+ 100
287
+ ]);
288
+ const CLEAN_ARBITRARY_SOURCE_PROPERTIES = new Set([
289
+ "background-color",
290
+ "border-color",
291
+ "color",
292
+ "text-decoration-color"
293
+ ]);
294
+ const REVIEW_ONLY_SOURCE_PROPERTIES = new Set([
295
+ "background-image",
296
+ "bottom",
297
+ "font-family",
298
+ "grid-template-columns",
299
+ "grid-template-rows",
300
+ "height",
301
+ "left",
302
+ "pseudo-elements",
303
+ "right",
304
+ "top",
305
+ "transform",
306
+ "transform-origin",
307
+ "width"
308
+ ]);
309
+ const DISPLAY_MAP = {
310
+ contents: "contents",
311
+ flex: "flex",
312
+ "flow-root": "flow-root",
313
+ grid: "grid",
314
+ inline: "inline",
315
+ "inline-block": "inline-block",
316
+ "inline-flex": "inline-flex",
317
+ "inline-grid": "inline-grid",
318
+ none: "hidden"
319
+ };
320
+ const POSITION_MAP = {
321
+ absolute: "absolute",
322
+ fixed: "fixed",
323
+ relative: "relative",
324
+ sticky: "sticky"
325
+ };
326
+ const FLEX_DIRECTION_MAP = {
327
+ column: "flex-col",
328
+ "column-reverse": "flex-col-reverse",
329
+ "row-reverse": "flex-row-reverse"
330
+ };
331
+ const FLEX_WRAP_MAP = {
332
+ wrap: "flex-wrap",
333
+ "wrap-reverse": "flex-wrap-reverse"
334
+ };
335
+ const ALIGN_ITEMS_MAP = {
336
+ baseline: "items-baseline",
337
+ center: "items-center",
338
+ end: "items-end",
339
+ "flex-end": "items-end",
340
+ "flex-start": "items-start",
341
+ start: "items-start"
342
+ };
343
+ const JUSTIFY_CONTENT_MAP = {
344
+ center: "justify-center",
345
+ end: "justify-end",
346
+ "flex-end": "justify-end",
347
+ "flex-start": "justify-start",
348
+ left: "justify-start",
349
+ right: "justify-end",
350
+ "space-around": "justify-around",
351
+ "space-between": "justify-between",
352
+ "space-evenly": "justify-evenly",
353
+ start: "justify-start"
354
+ };
355
+ const GRID_AUTO_FLOW_MAP = {
356
+ column: "grid-flow-col",
357
+ "column dense": "grid-flow-col-dense",
358
+ dense: "grid-flow-dense",
359
+ "row dense": "grid-flow-row-dense"
360
+ };
361
+ const TEXT_ALIGN_MAP = {
362
+ center: "text-center",
363
+ end: "text-end",
364
+ justify: "text-justify",
365
+ right: "text-right"
366
+ };
367
+ const TEXT_TRANSFORM_MAP = {
368
+ capitalize: "capitalize",
369
+ lowercase: "lowercase",
370
+ uppercase: "uppercase"
371
+ };
372
+ const WHITE_SPACE_MAP = {
373
+ "break-spaces": "whitespace-break-spaces",
374
+ nowrap: "whitespace-nowrap",
375
+ pre: "whitespace-pre",
376
+ "pre-line": "whitespace-pre-line",
377
+ "pre-wrap": "whitespace-pre-wrap"
378
+ };
379
+ const LIST_STYLE_MAP = {
380
+ decimal: "list-decimal",
381
+ none: "list-none"
382
+ };
383
+ const OBJECT_FIT_MAP = {
384
+ contain: "object-contain",
385
+ cover: "object-cover",
386
+ none: "object-none",
387
+ "scale-down": "object-scale-down"
388
+ };
389
+ const OVERFLOW_MAP = {
390
+ auto: "auto",
391
+ clip: "clip",
392
+ hidden: "hidden",
393
+ scroll: "scroll",
394
+ visible: "visible"
395
+ };
396
+ const BORDER_STYLE_MAP = {
397
+ dashed: "border-dashed",
398
+ dotted: "border-dotted",
399
+ double: "border-double"
400
+ };
401
+ const FONT_WEIGHT_MAP = {
402
+ "100": "thin",
403
+ "200": "extralight",
404
+ "300": "light",
405
+ "500": "medium",
406
+ "600": "semibold",
407
+ "700": "bold",
408
+ "800": "extrabold",
409
+ "900": "black"
410
+ };
411
+ const POSITION_KEYWORD_MAP = {
412
+ "0% 0%": "top-left",
413
+ "0% 100%": "bottom-left",
414
+ "0% 50%": "left",
415
+ "100% 0%": "top-right",
416
+ "100% 100%": "bottom-right",
417
+ "100% 50%": "right",
418
+ "50% 0%": "top",
419
+ "50% 100%": "bottom"
420
+ };
421
+ const DIMENSION_KEYWORD_MAP = {
422
+ "100%": "full",
423
+ "100vh": "screen",
424
+ "100vw": "screen",
425
+ "fit-content": "fit",
426
+ "max-content": "max",
427
+ "min-content": "min"
428
+ };
429
+ const FONT_FAMILY_MAP = [
430
+ {
431
+ keyword: "monospace",
432
+ utility: "font-mono"
433
+ },
434
+ {
435
+ keyword: "sans-serif",
436
+ utility: "font-sans"
437
+ },
438
+ {
439
+ keyword: "system-ui",
440
+ utility: "font-sans"
441
+ },
442
+ {
443
+ keyword: "serif",
444
+ utility: "font-serif"
445
+ }
446
+ ];
447
+ const roundToTwo = (value) => Math.round(value * 100) / 100;
448
+ const dedupe = (values) => [...new Set((values ?? []).filter(Boolean))];
449
+ const allEqual = (values) => values.every((value) => value === values[0]);
450
+ const normalizeWhitespace = (value) => value.replaceAll(/\s+/g, " ").trim();
451
+ const parseLength = (value) => {
452
+ const match = value.trim().match(LENGTH_PATTERN);
453
+ if (!match) return null;
454
+ return {
455
+ unit: match[2] ?? "px",
456
+ value: Number(match[1])
457
+ };
458
+ };
459
+ const isZeroLength = (value) => {
460
+ const length = parseLength(value);
461
+ return Boolean(length && Math.abs(length.value) <= .01);
462
+ };
463
+ const isTransparentColor = (value) => {
464
+ const normalized = value.trim().replaceAll(/\s+/g, "").toLowerCase();
465
+ return normalized === "rgba(0,0,0,0)" || normalized === "transparent";
466
+ };
467
+ const normalizeColor = (value) => {
468
+ const trimmed = value.trim().toLowerCase();
469
+ if (trimmed === "transparent" || isTransparentColor(trimmed)) return "transparent";
470
+ const rgbMatch = trimmed.match(RGB_PATTERN);
471
+ if (!rgbMatch) return trimmed;
472
+ const { 1: redRaw, 2: greenRaw, 3: blueRaw, 4: alpha } = rgbMatch;
473
+ const red = Number(redRaw).toString(16).padStart(2, "0");
474
+ const green = Number(greenRaw).toString(16).padStart(2, "0");
475
+ const blue = Number(blueRaw).toString(16).padStart(2, "0");
476
+ if (!alpha || alpha === "1") return `#${red}${green}${blue}`;
477
+ return trimmed.replaceAll(/\s+/g, "");
478
+ };
479
+ const sanitizeArbitraryValue = (value) => value.trim().replaceAll(/\s*,\s*/g, ",").replaceAll(/\s*\/\s*/g, "/").replaceAll(/\s+/g, "_");
480
+ const createArbitraryUtility = (prefix, value) => `${prefix}-[${sanitizeArbitraryValue(value)}]`;
481
+ const createArbitraryPropertyClass = (property, value) => `[${property}:${sanitizeArbitraryValue(value)}]`;
482
+ const lookupMappedUtility = (map, value) => map[value] ?? null;
483
+ const labelFromConfidence = (confidence) => {
484
+ if (confidence >= .85) return "high";
485
+ if (confidence >= .62) return "medium";
486
+ return "low";
487
+ };
488
+ const addMatch = (accumulator, match) => {
489
+ const utility = match.utility.trim();
490
+ if (!utility) return;
491
+ const nextMatch = {
492
+ ...match,
493
+ label: labelFromConfidence(match.confidence),
494
+ notes: dedupe(match.notes),
495
+ sourceProperties: dedupe(match.sourceProperties),
496
+ sourceValues: dedupe(match.sourceValues),
497
+ utility
498
+ };
499
+ const existing = accumulator.matches.find((entry) => entry.utility === utility);
500
+ if (existing) {
501
+ existing.confidence = Math.max(existing.confidence, nextMatch.confidence);
502
+ existing.label = labelFromConfidence(existing.confidence);
503
+ existing.notes = dedupe([...existing.notes, ...nextMatch.notes]);
504
+ existing.sourceProperties = dedupe([...existing.sourceProperties, ...nextMatch.sourceProperties]);
505
+ existing.sourceValues = dedupe([...existing.sourceValues, ...nextMatch.sourceValues]);
506
+ } else accumulator.matches.push(nextMatch);
507
+ if (accumulator.classSet.has(utility)) return;
508
+ accumulator.classSet.add(utility);
509
+ accumulator.classes.push(utility);
510
+ };
511
+ const addUnsupported = (accumulator, property, value, reason) => {
512
+ const key = `${property}:${value}:${reason}`;
513
+ if (accumulator.unsupported.some((entry) => `${entry.property}:${entry.value}:${entry.reason}` === key)) return;
514
+ accumulator.unsupported.push({
515
+ property,
516
+ reason,
517
+ value
518
+ });
519
+ };
520
+ const addCandidateMatch = (accumulator, property, value, candidate) => {
521
+ if (!(value && candidate)) return;
522
+ addMatch(accumulator, {
523
+ confidence: candidate.confidence,
524
+ notes: candidate.notes ?? [],
525
+ sourceProperties: property.includes("|") ? property.split("|") : [property],
526
+ sourceValues: [value],
527
+ strategy: candidate.strategy,
528
+ utility: candidate.utility
529
+ });
530
+ };
531
+ const addMappedUtility = (accumulator, property, value, map, shouldAdd = true) => {
532
+ if (!(shouldAdd && value)) return;
533
+ const utility = lookupMappedUtility(map, value);
534
+ if (!utility) {
535
+ addUnsupported(accumulator, property, value, `${property} needs manual review.`);
536
+ return;
537
+ }
538
+ addMatch(accumulator, {
539
+ confidence: HIGH_CONFIDENCE,
540
+ sourceProperties: [property],
541
+ sourceValues: [value],
542
+ strategy: "semantic",
543
+ utility
544
+ });
545
+ };
546
+ const addArbitraryPropertyMatch = (accumulator, property, value, note) => {
547
+ if (!value || value === "none") return;
548
+ addMatch(accumulator, {
549
+ confidence: LOW_CONFIDENCE,
550
+ notes: [note],
551
+ sourceProperties: [property],
552
+ sourceValues: [value],
553
+ strategy: "arbitrary",
554
+ utility: createArbitraryPropertyClass(property, value)
555
+ });
556
+ };
557
+ const shouldEmitInheritedValue = (property, value, parentValue) => {
558
+ if (!value) return false;
559
+ if (!parentValue) return true;
560
+ if (property === "color") return normalizeColor(value) !== normalizeColor(parentValue);
561
+ return value !== parentValue;
562
+ };
563
+ const shouldMapDimension = (property, value) => {
564
+ if (!value) return false;
565
+ if (property.startsWith("min-")) return value !== "0px" && value !== "auto";
566
+ if (property.startsWith("max-")) return value !== "none";
567
+ return value !== "auto";
568
+ };
569
+ const buildSpacingCandidate = (prefix, value, allowNegative) => {
570
+ if (isZeroLength(value)) return null;
571
+ if (value === "auto" && prefix.startsWith("m")) return {
572
+ confidence: HIGH_CONFIDENCE,
573
+ strategy: "semantic",
574
+ utility: `${prefix}-auto`
575
+ };
576
+ const length = parseLength(value);
577
+ if (!length) return {
578
+ confidence: MEDIUM_CONFIDENCE,
579
+ notes: ["Spacing required an arbitrary value."],
580
+ strategy: "arbitrary",
581
+ utility: createArbitraryUtility(prefix, value)
582
+ };
583
+ if (length.value < 0 && !allowNegative) return null;
584
+ const token = length.unit === "px" ? SPACING_SCALE.get(Math.abs(length.value)) : null;
585
+ if (!token) return {
586
+ confidence: MEDIUM_CONFIDENCE,
587
+ notes: ["Spacing required an arbitrary value."],
588
+ strategy: "arbitrary",
589
+ utility: createArbitraryUtility(prefix, value)
590
+ };
591
+ const baseUtility = token === "px" ? `${prefix}-px` : `${prefix}-${token}`;
592
+ return {
593
+ confidence: HIGH_CONFIDENCE,
594
+ strategy: "scale",
595
+ utility: length.value < 0 ? `-${baseUtility}` : baseUtility
596
+ };
597
+ };
598
+ const buildDimensionCandidate = (prefix, value, options) => {
599
+ if (value === "auto") return {
600
+ confidence: HIGH_CONFIDENCE,
601
+ strategy: "semantic",
602
+ utility: `${prefix}-auto`
603
+ };
604
+ const keywordSuffix = DIMENSION_KEYWORD_MAP[value];
605
+ if (keywordSuffix) return {
606
+ confidence: HIGH_CONFIDENCE,
607
+ strategy: "semantic",
608
+ utility: `${prefix}-${keywordSuffix}`
609
+ };
610
+ const length = parseLength(value);
611
+ const token = length && length.unit === "px" ? SPACING_SCALE.get(Math.abs(length.value)) : null;
612
+ if (token) return {
613
+ confidence: options.confidence,
614
+ notes: options.note ? [options.note] : [],
615
+ strategy: "scale",
616
+ utility: token === "px" ? `${prefix}-px` : `${prefix}-${token}`
617
+ };
618
+ return {
619
+ confidence: options.confidence,
620
+ notes: options.note ? [options.note] : ["Length required an arbitrary value."],
621
+ strategy: "arbitrary",
622
+ utility: createArbitraryUtility(prefix, value)
623
+ };
624
+ };
625
+ const buildScaleCandidate = (prefix, value, scale, note) => {
626
+ const length = parseLength(value);
627
+ const token = length && length.unit === "px" ? scale.get(length.value) : null;
628
+ if (token) return {
629
+ confidence: HIGH_CONFIDENCE,
630
+ strategy: "scale",
631
+ utility: `${prefix}-${token}`
632
+ };
633
+ return {
634
+ confidence: MEDIUM_CONFIDENCE,
635
+ notes: [note],
636
+ strategy: "arbitrary",
637
+ utility: createArbitraryUtility(prefix, value)
638
+ };
639
+ };
640
+ const buildColorCandidate = (prefix, value) => {
641
+ const normalized = normalizeColor(value);
642
+ let semanticUtility = null;
643
+ if (normalized === "transparent") semanticUtility = `${prefix}-transparent`;
644
+ else if (normalized === "#000000") semanticUtility = `${prefix}-black`;
645
+ else if (normalized === "#ffffff") semanticUtility = `${prefix}-white`;
646
+ if (semanticUtility) return {
647
+ confidence: HIGH_CONFIDENCE,
648
+ strategy: "semantic",
649
+ utility: semanticUtility
650
+ };
651
+ return {
652
+ confidence: MEDIUM_CONFIDENCE,
653
+ strategy: "arbitrary",
654
+ utility: createArbitraryUtility(prefix, normalized)
655
+ };
656
+ };
657
+ const buildFontFamilyCandidate = (value) => {
658
+ const normalized = value.toLowerCase();
659
+ for (const { keyword, utility } of FONT_FAMILY_MAP) if (normalized.includes(keyword)) return {
660
+ confidence: MEDIUM_CONFIDENCE,
661
+ notes: ["Mapped to the nearest generic Tailwind family."],
662
+ strategy: "heuristic",
663
+ utility
664
+ };
665
+ return {
666
+ confidence: LOW_CONFIDENCE,
667
+ notes: ["Font family required an arbitrary property utility."],
668
+ strategy: "arbitrary",
669
+ utility: createArbitraryPropertyClass("font-family", value)
670
+ };
671
+ };
672
+ const buildBorderWidthCandidate = (prefix, value) => {
673
+ const length = parseLength(value);
674
+ const token = length && length.unit === "px" ? BORDER_WIDTH_SCALE.get(length.value) : null;
675
+ if (token !== void 0) return {
676
+ confidence: HIGH_CONFIDENCE,
677
+ strategy: "scale",
678
+ utility: token ? `${prefix}-${token}` : prefix
679
+ };
680
+ return {
681
+ confidence: MEDIUM_CONFIDENCE,
682
+ notes: ["Border width required an arbitrary value."],
683
+ strategy: "arbitrary",
684
+ utility: createArbitraryUtility(prefix, value)
685
+ };
686
+ };
687
+ const buildBorderColorCandidate = (value) => buildColorCandidate("border", value);
688
+ const buildRadiusCandidate = (prefix, value) => {
689
+ const length = parseLength(value);
690
+ if (length && length.value >= 9999) return {
691
+ confidence: HIGH_CONFIDENCE,
692
+ strategy: "semantic",
693
+ utility: `${prefix}-full`
694
+ };
695
+ const token = length && length.unit === "px" ? RADIUS_SCALE.get(length.value) : null;
696
+ if (token !== void 0) return {
697
+ confidence: HIGH_CONFIDENCE,
698
+ strategy: "scale",
699
+ utility: token ? `${prefix}-${token}` : prefix
700
+ };
701
+ return {
702
+ confidence: MEDIUM_CONFIDENCE,
703
+ notes: ["Border radius required an arbitrary value."],
704
+ strategy: "arbitrary",
705
+ utility: createArbitraryUtility(prefix, value)
706
+ };
707
+ };
708
+ const buildZIndexCandidate = (value) => {
709
+ const numericValue = Number(value);
710
+ if (Number.isFinite(numericValue) && Z_INDEX_SCALE.has(numericValue)) return {
711
+ confidence: HIGH_CONFIDENCE,
712
+ strategy: "scale",
713
+ utility: `z-${numericValue}`
714
+ };
715
+ return {
716
+ confidence: MEDIUM_CONFIDENCE,
717
+ notes: ["Resolved z-index required an arbitrary value."],
718
+ strategy: "arbitrary",
719
+ utility: `z-[${sanitizeArbitraryValue(value)}]`
720
+ };
721
+ };
722
+ const addGapMatches = (accumulator, gap, rowGap, columnGap) => {
723
+ if (gap && gap !== "normal" && !isZeroLength(gap)) {
724
+ addCandidateMatch(accumulator, "gap", gap, buildSpacingCandidate("gap", gap, false));
725
+ return;
726
+ }
727
+ addCandidateMatch(accumulator, "row-gap", rowGap, rowGap ? buildSpacingCandidate("gap-y", rowGap, false) : null);
728
+ addCandidateMatch(accumulator, "column-gap", columnGap, columnGap ? buildSpacingCandidate("gap-x", columnGap, false) : null);
729
+ };
730
+ const addFlexNumberMatch = (accumulator, property, value, prefix) => {
731
+ if (!value) return;
732
+ let utility = createArbitraryUtility(prefix, value);
733
+ if (value === "1") utility = prefix;
734
+ else if (value === "0") utility = `${prefix}-0`;
735
+ const confidence = value === "0" || value === "1" ? HIGH_CONFIDENCE : LOW_CONFIDENCE;
736
+ const strategy = value === "0" || value === "1" ? "semantic" : "arbitrary";
737
+ addMatch(accumulator, {
738
+ confidence,
739
+ notes: strategy === "semantic" ? [] : [`${property} required an arbitrary value.`],
740
+ sourceProperties: [property],
741
+ sourceValues: [value],
742
+ strategy,
743
+ utility
744
+ });
745
+ };
746
+ const addAxisSpacingMatch = (accumulator, prefix, first, second, allowNegative) => {
747
+ if (!(first[1] && second[1]) || first[1] !== second[1]) return;
748
+ addCandidateMatch(accumulator, `${first[0]}|${second[0]}`, first[1], buildSpacingCandidate(prefix, first[1], allowNegative));
749
+ };
750
+ const addEdgeSpacingMatch = (accumulator, prefix, entry, allowNegative) => {
751
+ if (!entry[1] || isZeroLength(entry[1])) return;
752
+ addCandidateMatch(accumulator, entry[0], entry[1], buildSpacingCandidate(prefix, entry[1], allowNegative));
753
+ };
754
+ const addBoxSpacingMatches = (accumulator, prefix, entries) => {
755
+ const values = entries.map((entry) => entry[1]);
756
+ if (values.some((value) => !value)) return;
757
+ const normalizedValues = values;
758
+ if (normalizedValues.every((value) => isZeroLength(value))) return;
759
+ if (allEqual(normalizedValues)) {
760
+ addCandidateMatch(accumulator, entries[0][0], normalizedValues[0], buildSpacingCandidate(prefix, normalizedValues[0], prefix === "m"));
761
+ return;
762
+ }
763
+ addAxisSpacingMatch(accumulator, `${prefix}y`, entries[0], entries[2], prefix === "m");
764
+ addAxisSpacingMatch(accumulator, `${prefix}x`, entries[1], entries[3], prefix === "m");
765
+ addEdgeSpacingMatch(accumulator, `${prefix}t`, entries[0], prefix === "m");
766
+ addEdgeSpacingMatch(accumulator, `${prefix}r`, entries[1], prefix === "m");
767
+ addEdgeSpacingMatch(accumulator, `${prefix}b`, entries[2], prefix === "m");
768
+ addEdgeSpacingMatch(accumulator, `${prefix}l`, entries[3], prefix === "m");
769
+ };
770
+ const addColorMatch = (accumulator, prefix, property, value, shouldAdd) => {
771
+ if (!(shouldAdd && value)) return;
772
+ addCandidateMatch(accumulator, property, value, buildColorCandidate(prefix, value));
773
+ };
774
+ const addFontFamilyMatch = (accumulator, value, shouldAdd) => {
775
+ if (!(shouldAdd && value)) return;
776
+ addCandidateMatch(accumulator, "font-family", value, buildFontFamilyCandidate(value));
777
+ };
778
+ const addScaleMatch = (accumulator, property, value, shouldAdd, scale, prefix, note) => {
779
+ if (!(shouldAdd && value)) return;
780
+ addCandidateMatch(accumulator, property, value, buildScaleCandidate(prefix, value, scale, note));
781
+ };
782
+ const addFontWeightMatch = (accumulator, value, shouldAdd) => {
783
+ if (!(shouldAdd && value) || value === "400") return;
784
+ const utility = lookupMappedUtility(FONT_WEIGHT_MAP, value);
785
+ addMatch(accumulator, {
786
+ confidence: utility ? HIGH_CONFIDENCE : LOW_CONFIDENCE,
787
+ notes: utility ? [] : ["Font weight required an arbitrary value."],
788
+ sourceProperties: ["font-weight"],
789
+ sourceValues: [value],
790
+ strategy: utility ? "semantic" : "arbitrary",
791
+ utility: utility ? `font-${utility}` : createArbitraryUtility("font", value)
792
+ });
793
+ };
794
+ const addFontStyleMatch = (accumulator, value, shouldAdd) => {
795
+ if (!(shouldAdd && value) || value === "normal") return;
796
+ addMatch(accumulator, {
797
+ confidence: HIGH_CONFIDENCE,
798
+ sourceProperties: ["font-style"],
799
+ sourceValues: [value],
800
+ strategy: "semantic",
801
+ utility: value === "italic" ? "italic" : "not-italic"
802
+ });
803
+ };
804
+ const addTrackingMatch = (accumulator, value, shouldAdd) => {
805
+ if (!(shouldAdd && value) || value === "normal" || isZeroLength(value)) return;
806
+ addMatch(accumulator, {
807
+ confidence: LOW_CONFIDENCE,
808
+ notes: ["Letter spacing required an arbitrary value."],
809
+ sourceProperties: ["letter-spacing"],
810
+ sourceValues: [value],
811
+ strategy: "arbitrary",
812
+ utility: createArbitraryUtility("tracking", value)
813
+ });
814
+ };
815
+ const addDecorationLineMatches = (accumulator, value) => {
816
+ if (!value || value === "none") return;
817
+ for (const part of value.split(WHITESPACE_SPLIT_PATTERN)) {
818
+ const utility = {
819
+ "line-through": "line-through",
820
+ overline: "overline",
821
+ underline: "underline"
822
+ }[part] ?? null;
823
+ if (!utility) continue;
824
+ addMatch(accumulator, {
825
+ confidence: HIGH_CONFIDENCE,
826
+ sourceProperties: ["text-decoration-line"],
827
+ sourceValues: [value],
828
+ strategy: "semantic",
829
+ utility
830
+ });
831
+ }
832
+ };
833
+ const addUniformBorderMatch = (element, accumulator) => {
834
+ const widths = [
835
+ element.styles["border-top-width"],
836
+ element.styles["border-right-width"],
837
+ element.styles["border-bottom-width"],
838
+ element.styles["border-left-width"]
839
+ ];
840
+ const styles = [
841
+ element.styles["border-top-style"],
842
+ element.styles["border-right-style"],
843
+ element.styles["border-bottom-style"],
844
+ element.styles["border-left-style"]
845
+ ];
846
+ const colors = [
847
+ element.styles["border-top-color"],
848
+ element.styles["border-right-color"],
849
+ element.styles["border-bottom-color"],
850
+ element.styles["border-left-color"]
851
+ ];
852
+ if (widths.some((value) => !value || isZeroLength(value)) || styles.some((value) => !value || value === "none")) return;
853
+ if (!(allEqual(widths) && allEqual(styles) && allEqual(colors))) {
854
+ addUnsupported(accumulator, "border", "mixed sides", "Per-side border variations need manual review.");
855
+ return;
856
+ }
857
+ const width = widths[0];
858
+ const borderStyle = styles[0];
859
+ const color = colors[0];
860
+ addCandidateMatch(accumulator, "border-width", width, buildBorderWidthCandidate("border", width));
861
+ if (borderStyle !== "solid") {
862
+ const utility = lookupMappedUtility(BORDER_STYLE_MAP, borderStyle);
863
+ if (utility) addMatch(accumulator, {
864
+ confidence: HIGH_CONFIDENCE,
865
+ sourceProperties: ["border-style"],
866
+ sourceValues: [borderStyle],
867
+ strategy: "semantic",
868
+ utility
869
+ });
870
+ else addUnsupported(accumulator, "border-style", borderStyle, "Border style needs manual review.");
871
+ }
872
+ addCandidateMatch(accumulator, "border-color", color, buildBorderColorCandidate(color));
873
+ };
874
+ const addRadiusMatches = (element, accumulator) => {
875
+ const values = [
876
+ element.styles["border-top-left-radius"],
877
+ element.styles["border-top-right-radius"],
878
+ element.styles["border-bottom-right-radius"],
879
+ element.styles["border-bottom-left-radius"]
880
+ ];
881
+ if (values.some((value) => !value) || values.every((value) => isZeroLength(value))) return;
882
+ const radiusValues = values;
883
+ if (allEqual(radiusValues)) {
884
+ addCandidateMatch(accumulator, "border-radius", radiusValues[0], buildRadiusCandidate("rounded", radiusValues[0]));
885
+ return;
886
+ }
887
+ addUnsupported(accumulator, "border-radius", radiusValues.join(", "), "Mixed corner radii need manual review.");
888
+ };
889
+ const addShadowMatch = (accumulator, value) => {
890
+ if (!value || value === "none") return;
891
+ addMatch(accumulator, {
892
+ confidence: MEDIUM_CONFIDENCE,
893
+ sourceProperties: ["box-shadow"],
894
+ sourceValues: [value],
895
+ strategy: "arbitrary",
896
+ utility: createArbitraryUtility("shadow", value)
897
+ });
898
+ };
899
+ const addOpacityMatch = (accumulator, value) => {
900
+ if (!value || value === "1") return;
901
+ const numericValue = Number(value);
902
+ if (!Number.isFinite(numericValue)) return;
903
+ const percent = Math.round(numericValue * 100);
904
+ const semantic = OPACITY_SCALE.has(percent);
905
+ addMatch(accumulator, {
906
+ confidence: semantic ? MEDIUM_CONFIDENCE : LOW_CONFIDENCE,
907
+ notes: semantic ? [] : ["Opacity required an arbitrary value."],
908
+ sourceProperties: ["opacity"],
909
+ sourceValues: [value],
910
+ strategy: semantic ? "scale" : "arbitrary",
911
+ utility: semantic ? `opacity-${percent}` : createArbitraryUtility("opacity", value)
912
+ });
913
+ };
914
+ const addPositionMatch = (accumulator, prefix, property, value) => {
915
+ if (!value || value === "50% 50%") return;
916
+ const keyword = POSITION_KEYWORD_MAP[normalizeWhitespace(value)];
917
+ const named = keyword ? `${prefix}-${keyword}` : null;
918
+ addMatch(accumulator, {
919
+ confidence: named ? HIGH_CONFIDENCE : MEDIUM_CONFIDENCE,
920
+ notes: named ? [] : [`${property} required an arbitrary value.`],
921
+ sourceProperties: [property],
922
+ sourceValues: [value],
923
+ strategy: named ? "semantic" : "arbitrary",
924
+ utility: named ?? createArbitraryUtility(prefix, value)
925
+ });
926
+ };
927
+ const addOverflowAxisMatch = (accumulator, property, value) => {
928
+ if (value === "visible") return;
929
+ const suffix = lookupMappedUtility(OVERFLOW_MAP, value);
930
+ if (!suffix) {
931
+ addUnsupported(accumulator, property, value, "Overflow needs manual review.");
932
+ return;
933
+ }
934
+ addMatch(accumulator, {
935
+ confidence: HIGH_CONFIDENCE,
936
+ sourceProperties: [property],
937
+ sourceValues: [value],
938
+ strategy: "semantic",
939
+ utility: `${property}-${suffix}`
940
+ });
941
+ };
942
+ const addObjectPositionMatch = (accumulator, value) => {
943
+ addPositionMatch(accumulator, "object", "object-position", value);
944
+ };
945
+ const mapDisplay = (context, accumulator) => {
946
+ const { display } = context.element.styles;
947
+ if (!display || display === "block") return;
948
+ const utility = lookupMappedUtility(DISPLAY_MAP, display);
949
+ if (utility) {
950
+ addMatch(accumulator, {
951
+ confidence: HIGH_CONFIDENCE,
952
+ sourceProperties: ["display"],
953
+ sourceValues: [display],
954
+ strategy: "semantic",
955
+ utility
956
+ });
957
+ return;
958
+ }
959
+ addUnsupported(accumulator, "display", display, "Display needs manual review.");
960
+ };
961
+ const mapPositioning = (context, accumulator) => {
962
+ const { styles } = context.element;
963
+ const { position } = styles;
964
+ if (position && position !== "static") {
965
+ const utility = lookupMappedUtility(POSITION_MAP, position);
966
+ if (utility) addMatch(accumulator, {
967
+ confidence: HIGH_CONFIDENCE,
968
+ sourceProperties: ["position"],
969
+ sourceValues: [position],
970
+ strategy: "semantic",
971
+ utility
972
+ });
973
+ }
974
+ if (!position || position === "static") return;
975
+ for (const property of [
976
+ "top",
977
+ "right",
978
+ "bottom",
979
+ "left"
980
+ ]) {
981
+ const value = styles[property];
982
+ if (!value || value === "auto") continue;
983
+ addCandidateMatch(accumulator, property, value, buildDimensionCandidate(property, value, {
984
+ confidence: LOW_CONFIDENCE,
985
+ note: "Computed insets are layout-derived and need review."
986
+ }));
987
+ }
988
+ const zIndex = styles["z-index"];
989
+ if (!zIndex || zIndex === "auto") return;
990
+ addCandidateMatch(accumulator, "z-index", zIndex, buildZIndexCandidate(zIndex));
991
+ };
992
+ const mapFlexLayout = (context, accumulator) => {
993
+ const { styles } = context.element;
994
+ const { display } = styles;
995
+ if (display !== "flex" && display !== "inline-flex") return;
996
+ addMappedUtility(accumulator, "flex-direction", styles["flex-direction"], FLEX_DIRECTION_MAP);
997
+ addMappedUtility(accumulator, "flex-wrap", styles["flex-wrap"], FLEX_WRAP_MAP);
998
+ addMappedUtility(accumulator, "justify-content", styles["justify-content"], JUSTIFY_CONTENT_MAP);
999
+ addMappedUtility(accumulator, "align-items", styles["align-items"], ALIGN_ITEMS_MAP);
1000
+ addGapMatches(accumulator, styles.gap, styles["row-gap"], styles["column-gap"]);
1001
+ addFlexNumberMatch(accumulator, "flex-grow", styles["flex-grow"], "grow");
1002
+ addFlexNumberMatch(accumulator, "flex-shrink", styles["flex-shrink"], "shrink");
1003
+ const basis = styles["flex-basis"];
1004
+ if (!basis || basis === "auto") return;
1005
+ addCandidateMatch(accumulator, "flex-basis", basis, buildDimensionCandidate("basis", basis, { confidence: MEDIUM_CONFIDENCE }));
1006
+ };
1007
+ const mapGridLayout = (context, accumulator) => {
1008
+ const { styles } = context.element;
1009
+ const { display } = styles;
1010
+ if (display !== "grid" && display !== "inline-grid") return;
1011
+ addGapMatches(accumulator, styles.gap, styles["row-gap"], styles["column-gap"]);
1012
+ addMappedUtility(accumulator, "grid-auto-flow", styles["grid-auto-flow"], GRID_AUTO_FLOW_MAP);
1013
+ for (const [property, prefix] of [
1014
+ ["grid-column-start", "col-start"],
1015
+ ["grid-column-end", "col-end"],
1016
+ ["grid-row-start", "row-start"],
1017
+ ["grid-row-end", "row-end"]
1018
+ ]) {
1019
+ const value = styles[property];
1020
+ if (!value || value === "auto") continue;
1021
+ addMatch(accumulator, {
1022
+ confidence: MEDIUM_CONFIDENCE,
1023
+ sourceProperties: [property],
1024
+ sourceValues: [value],
1025
+ strategy: "arbitrary",
1026
+ utility: createArbitraryUtility(prefix, value)
1027
+ });
1028
+ }
1029
+ addArbitraryPropertyMatch(accumulator, "grid-template-columns", styles["grid-template-columns"], "Computed grid tracks lose authored repeat syntax.");
1030
+ addArbitraryPropertyMatch(accumulator, "grid-template-rows", styles["grid-template-rows"], "Computed grid tracks lose authored repeat syntax.");
1031
+ };
1032
+ const mapSpacing = (context, accumulator) => {
1033
+ addBoxSpacingMatches(accumulator, "p", [
1034
+ ["padding-top", context.element.styles["padding-top"]],
1035
+ ["padding-right", context.element.styles["padding-right"]],
1036
+ ["padding-bottom", context.element.styles["padding-bottom"]],
1037
+ ["padding-left", context.element.styles["padding-left"]]
1038
+ ]);
1039
+ addBoxSpacingMatches(accumulator, "m", [
1040
+ ["margin-top", context.element.styles["margin-top"]],
1041
+ ["margin-right", context.element.styles["margin-right"]],
1042
+ ["margin-bottom", context.element.styles["margin-bottom"]],
1043
+ ["margin-left", context.element.styles["margin-left"]]
1044
+ ]);
1045
+ };
1046
+ const mapSizing = (context, accumulator) => {
1047
+ const { styles } = context.element;
1048
+ for (const [property, prefix, confidence] of [
1049
+ [
1050
+ "min-width",
1051
+ "min-w",
1052
+ MEDIUM_CONFIDENCE
1053
+ ],
1054
+ [
1055
+ "min-height",
1056
+ "min-h",
1057
+ MEDIUM_CONFIDENCE
1058
+ ],
1059
+ [
1060
+ "max-width",
1061
+ "max-w",
1062
+ MEDIUM_CONFIDENCE
1063
+ ],
1064
+ [
1065
+ "max-height",
1066
+ "max-h",
1067
+ MEDIUM_CONFIDENCE
1068
+ ],
1069
+ [
1070
+ "width",
1071
+ "w",
1072
+ LOW_CONFIDENCE
1073
+ ],
1074
+ [
1075
+ "height",
1076
+ "h",
1077
+ LOW_CONFIDENCE
1078
+ ]
1079
+ ]) {
1080
+ const value = styles[property];
1081
+ if (!shouldMapDimension(property, value)) continue;
1082
+ addCandidateMatch(accumulator, property, value, buildDimensionCandidate(prefix, value, {
1083
+ confidence,
1084
+ note: property === "width" || property === "height" ? "Computed size values are often layout-dependent." : void 0
1085
+ }));
1086
+ }
1087
+ };
1088
+ const mapTypographyBasics = (context, accumulator) => {
1089
+ const { element, parent } = context;
1090
+ addColorMatch(accumulator, "text", "color", element.styles.color, shouldEmitInheritedValue("color", element.styles.color, parent?.styles.color));
1091
+ addFontFamilyMatch(accumulator, element.styles["font-family"], shouldEmitInheritedValue("font-family", element.styles["font-family"], parent?.styles["font-family"]));
1092
+ addScaleMatch(accumulator, "font-size", element.styles["font-size"], shouldEmitInheritedValue("font-size", element.styles["font-size"], parent?.styles["font-size"]), FONT_SIZE_SCALE, "text", "Font size required an arbitrary value.");
1093
+ addFontWeightMatch(accumulator, element.styles["font-weight"], shouldEmitInheritedValue("font-weight", element.styles["font-weight"], parent?.styles["font-weight"]));
1094
+ addFontStyleMatch(accumulator, element.styles["font-style"], shouldEmitInheritedValue("font-style", element.styles["font-style"], parent?.styles["font-style"]));
1095
+ addScaleMatch(accumulator, "line-height", element.styles["line-height"], shouldEmitInheritedValue("line-height", element.styles["line-height"], parent?.styles["line-height"]) && element.styles["line-height"] !== "normal", LINE_HEIGHT_SCALE, "leading", "Line height required an arbitrary value.");
1096
+ addTrackingMatch(accumulator, element.styles["letter-spacing"], shouldEmitInheritedValue("letter-spacing", element.styles["letter-spacing"], parent?.styles["letter-spacing"]));
1097
+ };
1098
+ const mapTypographyPresentation = (context, accumulator) => {
1099
+ const { element, parent } = context;
1100
+ addMappedUtility(accumulator, "text-align", element.styles["text-align"], TEXT_ALIGN_MAP, shouldEmitInheritedValue("text-align", element.styles["text-align"], parent?.styles["text-align"]) && !["left", "start"].includes(element.styles["text-align"] ?? ""));
1101
+ addMappedUtility(accumulator, "text-transform", element.styles["text-transform"], TEXT_TRANSFORM_MAP, shouldEmitInheritedValue("text-transform", element.styles["text-transform"], parent?.styles["text-transform"]) && element.styles["text-transform"] !== "none");
1102
+ addMappedUtility(accumulator, "white-space", element.styles["white-space"], WHITE_SPACE_MAP, shouldEmitInheritedValue("white-space", element.styles["white-space"], parent?.styles["white-space"]) && element.styles["white-space"] !== "normal");
1103
+ addMappedUtility(accumulator, "list-style-type", element.styles["list-style-type"], LIST_STYLE_MAP, shouldEmitInheritedValue("list-style-type", element.styles["list-style-type"], parent?.styles["list-style-type"]) && element.styles["list-style-type"] !== "disc");
1104
+ addDecorationLineMatches(accumulator, element.styles["text-decoration-line"]);
1105
+ addColorMatch(accumulator, "decoration", "text-decoration-color", element.styles["text-decoration-color"], element.styles["text-decoration-line"] !== "none" && Boolean(element.styles["text-decoration-color"]));
1106
+ };
1107
+ const mapBackground = (context, accumulator) => {
1108
+ addColorMatch(accumulator, "bg", "background-color", context.element.styles["background-color"], !isTransparentColor(context.element.styles["background-color"] ?? ""));
1109
+ addArbitraryPropertyMatch(accumulator, "background-image", context.element.styles["background-image"], "Background images are emitted as arbitrary properties.");
1110
+ };
1111
+ const mapBorder = (context, accumulator) => {
1112
+ addUniformBorderMatch(context.element, accumulator);
1113
+ addRadiusMatches(context.element, accumulator);
1114
+ };
1115
+ const mapEffects = (context, accumulator) => {
1116
+ const { styles } = context.element;
1117
+ addShadowMatch(accumulator, styles["box-shadow"]);
1118
+ addOpacityMatch(accumulator, styles.opacity);
1119
+ addArbitraryPropertyMatch(accumulator, "transform", styles.transform, "Computed transforms are emitted as raw properties.");
1120
+ addPositionMatch(accumulator, "origin", "transform-origin", styles["transform-origin"]);
1121
+ if (styles.visibility === "hidden") addMatch(accumulator, {
1122
+ confidence: HIGH_CONFIDENCE,
1123
+ sourceProperties: ["visibility"],
1124
+ sourceValues: ["hidden"],
1125
+ strategy: "semantic",
1126
+ utility: "invisible"
1127
+ });
1128
+ };
1129
+ const mapOverflow = (context, accumulator) => {
1130
+ const overflowX = context.element.styles["overflow-x"];
1131
+ const overflowY = context.element.styles["overflow-y"];
1132
+ if (!(overflowX && overflowY)) return;
1133
+ if (overflowX === overflowY && overflowX !== "visible") {
1134
+ const suffix = lookupMappedUtility(OVERFLOW_MAP, overflowX);
1135
+ if (suffix) addMatch(accumulator, {
1136
+ confidence: HIGH_CONFIDENCE,
1137
+ sourceProperties: ["overflow-x", "overflow-y"],
1138
+ sourceValues: [overflowX, overflowY],
1139
+ strategy: "semantic",
1140
+ utility: `overflow-${suffix}`
1141
+ });
1142
+ return;
1143
+ }
1144
+ addOverflowAxisMatch(accumulator, "overflow-x", overflowX);
1145
+ addOverflowAxisMatch(accumulator, "overflow-y", overflowY);
1146
+ };
1147
+ const mapObjectLayout = (context, accumulator) => {
1148
+ const objectFit = context.element.styles["object-fit"];
1149
+ if (objectFit && objectFit !== "fill") {
1150
+ const utility = lookupMappedUtility(OBJECT_FIT_MAP, objectFit);
1151
+ if (utility) addMatch(accumulator, {
1152
+ confidence: HIGH_CONFIDENCE,
1153
+ sourceProperties: ["object-fit"],
1154
+ sourceValues: [objectFit],
1155
+ strategy: "semantic",
1156
+ utility
1157
+ });
1158
+ }
1159
+ addObjectPositionMatch(accumulator, context.element.styles["object-position"]);
1160
+ };
1161
+ const mapPseudoElements = (context, accumulator) => {
1162
+ const pseudoKinds = Object.keys(context.element.pseudo);
1163
+ if (pseudoKinds.length === 0) return;
1164
+ addUnsupported(accumulator, "pseudo-elements", pseudoKinds.join(", "), "Pseudo-elements were captured, but Tailwind output still needs manual content utilities.");
1165
+ };
1166
+ const isReviewOnlyNote = (note) => {
1167
+ const normalizedNote = note.toLowerCase();
1168
+ return normalizedNote.includes("manual review") || normalizedNote.includes("layout-dependent") || normalizedNote.includes("layout-derived") || normalizedNote.includes("arbitrary property utility") || normalizedNote.includes("required an arbitrary value") || normalizedNote.includes("computed transforms") || normalizedNote.includes("lose authored repeat syntax");
1169
+ };
1170
+ const shouldMoveMatchToReview = (match) => {
1171
+ if (match.label === "low" || match.utility.startsWith("[")) return true;
1172
+ if (match.sourceProperties.some((property) => REVIEW_ONLY_SOURCE_PROPERTIES.has(property))) return true;
1173
+ if (match.strategy === "arbitrary" && !match.sourceProperties.every((property) => CLEAN_ARBITRARY_SOURCE_PROPERTIES.has(property))) return true;
1174
+ return match.notes.some((note) => isReviewOnlyNote(note));
1175
+ };
1176
+ const splitMatchesForSuggestion = (matches) => {
1177
+ const reviewMatches = [];
1178
+ const suggestedMatches = [];
1179
+ for (const match of matches) if (shouldMoveMatchToReview(match)) reviewMatches.push(match);
1180
+ else suggestedMatches.push(match);
1181
+ return {
1182
+ reviewMatches,
1183
+ suggestedMatches
1184
+ };
1185
+ };
1186
+ const calculateElementConfidence = (accumulator) => {
1187
+ if (accumulator.matches.length === 0) return accumulator.unsupported.length > 0 ? LOW_CONFIDENCE : PASSIVE_CONFIDENCE;
1188
+ const average = accumulator.matches.reduce((sum, match) => sum + match.confidence, 0) / accumulator.matches.length;
1189
+ if (accumulator.unsupported.length === 0) return roundToTwo(average);
1190
+ return roundToTwo(Math.max(LOW_CONFIDENCE, average - .12));
1191
+ };
1192
+ const buildReviewFallbackNote = (match) => {
1193
+ if (match.label === "low") return "Low-confidence utility needs manual review.";
1194
+ if (match.sourceProperties.some((property) => REVIEW_ONLY_SOURCE_PROPERTIES.has(property))) return "Computed layout or custom CSS was kept out of the clean suggestion.";
1195
+ if (match.strategy === "arbitrary") return "Arbitrary utility was kept out of the clean suggestion.";
1196
+ return "Utility was kept out of the clean suggestion for review.";
1197
+ };
1198
+ const buildReviewItem = (mapping) => {
1199
+ const reasons = [...mapping.unsupported.map((entry) => `${entry.property}: ${entry.reason}`), ...mapping.matches.filter((match) => shouldMoveMatchToReview(match)).flatMap((match) => {
1200
+ return (match.notes.length ? match.notes : [buildReviewFallbackNote(match)]).map((note) => `${match.utility}: ${note}`);
1201
+ })];
1202
+ if (reasons.length === 0) return null;
1203
+ return {
1204
+ confidence: mapping.confidence,
1205
+ confidenceLabel: mapping.confidenceLabel,
1206
+ elementId: mapping.elementId,
1207
+ reasons: dedupe(reasons).slice(0, 6),
1208
+ selector: mapping.selector,
1209
+ unsupportedCount: mapping.unsupported.length
1210
+ };
1211
+ };
1212
+ const createAccumulator = () => ({
1213
+ classSet: /* @__PURE__ */ new Set(),
1214
+ classes: [],
1215
+ matches: [],
1216
+ unsupported: []
1217
+ });
1218
+ const MAPPING_STEPS = [
1219
+ mapDisplay,
1220
+ mapPositioning,
1221
+ mapFlexLayout,
1222
+ mapGridLayout,
1223
+ mapSpacing,
1224
+ mapSizing,
1225
+ mapTypographyBasics,
1226
+ mapTypographyPresentation,
1227
+ mapBackground,
1228
+ mapBorder,
1229
+ mapEffects,
1230
+ mapOverflow,
1231
+ mapObjectLayout,
1232
+ mapPseudoElements
1233
+ ];
1234
+ const mapElementSnapshot = (context) => {
1235
+ const accumulator = createAccumulator();
1236
+ for (const step of MAPPING_STEPS) step(context, accumulator);
1237
+ const confidence = calculateElementConfidence(accumulator);
1238
+ const { reviewMatches, suggestedMatches } = splitMatchesForSuggestion(accumulator.matches);
1239
+ return {
1240
+ className: accumulator.classes.join(" "),
1241
+ confidence,
1242
+ confidenceLabel: labelFromConfidence(confidence),
1243
+ elementId: context.element.id,
1244
+ matchCount: accumulator.matches.length,
1245
+ matches: accumulator.matches,
1246
+ reviewClassName: reviewMatches.map((match) => match.utility).join(" "),
1247
+ reviewMatchCount: reviewMatches.length,
1248
+ selector: context.element.selector,
1249
+ suggestedClassName: suggestedMatches.map((match) => match.utility).join(" "),
1250
+ suggestedMatchCount: suggestedMatches.length,
1251
+ tagName: context.element.tagName,
1252
+ unsupported: accumulator.unsupported
1253
+ };
1254
+ };
1255
+ const mapCaptureToTailwind = (capture) => {
1256
+ const elements = {};
1257
+ const reviewQueue = [];
1258
+ let cleanUtilityCount = 0;
1259
+ let confidenceSum = 0;
1260
+ let lowConfidenceElementCount = 0;
1261
+ let mappedElementCount = 0;
1262
+ let reviewUtilityCount = 0;
1263
+ let unsupportedPropertyCount = 0;
1264
+ let utilityCount = 0;
1265
+ for (const elementId of capture.order) {
1266
+ const element = capture.elements[elementId];
1267
+ if (!element) continue;
1268
+ const mapping = mapElementSnapshot({
1269
+ element,
1270
+ parent: element.parentId === null ? null : capture.elements[element.parentId] ?? null
1271
+ });
1272
+ elements[elementId] = mapping;
1273
+ confidenceSum += mapping.confidence;
1274
+ if (mapping.confidenceLabel === "low") lowConfidenceElementCount += 1;
1275
+ if (mapping.matchCount > 0) mappedElementCount += 1;
1276
+ unsupportedPropertyCount += mapping.unsupported.length;
1277
+ cleanUtilityCount += mapping.suggestedMatchCount;
1278
+ reviewUtilityCount += mapping.reviewMatchCount;
1279
+ utilityCount += mapping.matches.length;
1280
+ const reviewItem = buildReviewItem(mapping);
1281
+ if (reviewItem) reviewQueue.push(reviewItem);
1282
+ }
1283
+ reviewQueue.sort((left, right) => {
1284
+ if (left.confidence !== right.confidence) return left.confidence - right.confidence;
1285
+ return right.unsupportedCount - left.unsupportedCount;
1286
+ });
1287
+ const elementCount = capture.order.length;
1288
+ return {
1289
+ elements,
1290
+ order: capture.order,
1291
+ reviewQueue,
1292
+ summary: {
1293
+ averageConfidence: elementCount ? roundToTwo(confidenceSum / elementCount) : 0,
1294
+ cleanUtilityCount,
1295
+ elementCount,
1296
+ lowConfidenceElementCount,
1297
+ mappedElementCount,
1298
+ reviewCount: reviewQueue.length,
1299
+ reviewUtilityCount,
1300
+ unsupportedPropertyCount,
1301
+ utilityCount
1302
+ }
1303
+ };
1304
+ };
1305
+ //#endregion
1306
+ export { MESSAGE_TYPE_CAPTURE_CANCELLED, MESSAGE_TYPE_CAPTURE_COMPLETED, MESSAGE_TYPE_CAPTURE_FAILED, buildClaudeCapturePrompt, createDefaultSettings, formatCaptureForClaudeMarkdown, mapCaptureToTailwind };
1307
+
1308
+ //# sourceMappingURL=index.js.map