rainbowindex 0.4.0 → 0.5.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.
@@ -19,12 +19,12 @@ import {
19
19
  renderCSS,
20
20
  scanCSSForTokenUsage,
21
21
  withTimeout
22
- } from "./chunk-6DAHUFNU.mjs";
22
+ } from "./chunk-ZI5ZYNSU.mjs";
23
23
  import {
24
24
  checkPaletteContrast,
25
25
  generateAllColorVariables,
26
26
  generateThemeOverrides
27
- } from "./chunk-5Y7EXXLS.mjs";
27
+ } from "./chunk-KRZL4IDK.mjs";
28
28
 
29
29
  // src/integrations/font-providers/google/state.ts
30
30
  var googleFontInternals = {
@@ -318,8 +318,13 @@ async function fetchGoogleFontList() {
318
318
  googleFontInternals.googleFontListPromise = localPromise;
319
319
  return localPromise;
320
320
  }
321
+ var refreshMemo = /* @__PURE__ */ new WeakMap();
321
322
  function refreshFontWeightDefaults(fonts) {
322
- return fonts.map((slot) => {
323
+ const state = googleFontInternals.googleFontState;
324
+ const memo = refreshMemo.get(fonts);
325
+ if (memo && memo.state === state) return memo.result;
326
+ let anyChanged = false;
327
+ const refreshed = fonts.map((slot) => {
323
328
  if (slot.kind !== "google") return slot;
324
329
  const meta = googleFontInternals.googleFontState.cache.get(slot.family);
325
330
  if (!meta) return slot;
@@ -339,8 +344,12 @@ function refreshFontWeightDefaults(fonts) {
339
344
  if (next !== f) changed = true;
340
345
  return next;
341
346
  });
347
+ if (changed) anyChanged = true;
342
348
  return changed ? { ...slot, faces } : slot;
343
349
  });
350
+ const result = anyChanged ? refreshed : fonts;
351
+ refreshMemo.set(fonts, { state, result });
352
+ return result;
344
353
  }
345
354
  var FONT_FETCH_TIMEOUT_MS = 1e4;
346
355
  async function resolveGoogleFonts(fonts) {
@@ -359,6 +368,145 @@ async function resolveGoogleFonts(fonts) {
359
368
  return refreshFontWeightDefaults(fonts);
360
369
  }
361
370
 
371
+ // src/integrations/font-providers/metrics-data.ts
372
+ var FONT_METRICS_TABLE = {
373
+ abel: [2006, -604, 0, 2048, 783, "sans-serif"],
374
+ alegreya: [1016, -345, 0, 1e3, 410, "serif"],
375
+ "anonymous pro": [1675, -373, 0, 2048, 1118, "monospace"],
376
+ archivo: [878, -210, 0, 1e3, 440, "sans-serif"],
377
+ arial: [1854, -434, 67, 2048, 913, "sans-serif"],
378
+ asap: [934, -212, 0, 1e3, 442, "sans-serif"],
379
+ barlow: [1e3, -200, 0, 1e3, 431, "sans-serif"],
380
+ bitter: [935, -265, 0, 1e3, 465, "serif"],
381
+ "bricolage grotesque": [930, -270, 0, 1e3, 470, "sans-serif"],
382
+ cabin: [1930, -500, 0, 2e3, 844, "sans-serif"],
383
+ catamaran: [1100, -540, 0, 1e3, 411, "sans-serif"],
384
+ chivo: [940, -250, 0, 1e3, 478, "sans-serif"],
385
+ "cormorant garamond": [924, -287, 0, 1e3, 394, "serif"],
386
+ "courier new": [1705, -615, 0, 2048, 1229, "monospace"],
387
+ "crimson pro": [918, -220, 0, 1024, 410, "serif"],
388
+ "crimson text": [972, -359, 0, 1024, 405, "serif"],
389
+ "dm sans": [992, -310, 0, 1e3, 466, "sans-serif"],
390
+ "dm serif display": [1036, -335, 0, 1e3, 446, "serif"],
391
+ dosis: [1027, -237, 0, 1e3, 377, "sans-serif"],
392
+ "eb garamond": [1007, -298, 0, 1e3, 385, "serif"],
393
+ epilogue: [1580, -470, 0, 2e3, 990, "sans-serif"],
394
+ "exo 2": [999, -201, 0, 1e3, 455, "sans-serif"],
395
+ figtree: [950, -250, 0, 1e3, 449, "sans-serif"],
396
+ "fira code": [1980, -644, 0, 2e3, 1200, "monospace"],
397
+ "fira sans": [935, -265, 0, 1e3, 458, "sans-serif"],
398
+ fraunces: [1956, -510, 0, 2e3, 938, "serif"],
399
+ gabarito: [940, -260, 0, 1e3, 442, "display"],
400
+ "geist mono": [1005, -295, 0, 1e3, 600, "monospace"],
401
+ geist: [1005, -295, 0, 1e3, 467, "sans-serif"],
402
+ georgia: [1878, -449, 0, 2048, 913, "serif"],
403
+ heebo: [2146, -862, 0, 2048, 912, "sans-serif"],
404
+ "helvetica neue": [952, -213, 28, 1e3, 450, "sans-serif"],
405
+ helvetica: [1577, -471, 0, 2048, 913, "sans-serif"],
406
+ hind: [1055, -546, 0, 1e3, 429, "sans-serif"],
407
+ "ibm plex mono": [1025, -275, 0, 1e3, 600, "monospace"],
408
+ "ibm plex sans": [1025, -275, 0, 1e3, 451, "sans-serif"],
409
+ "ibm plex serif": [1025, -275, 0, 1e3, 473, "serif"],
410
+ inconsolata: [859, -190, 0, 1e3, 500, "monospace"],
411
+ "instrument sans": [970, -250, 0, 1e3, 458, "sans-serif"],
412
+ "instrument serif": [990, -310, 0, 1e3, 341, "serif"],
413
+ inter: [1984, -494, 0, 2048, 978, "sans-serif"],
414
+ "jetbrains mono": [1020, -300, 0, 1e3, 600, "monospace"],
415
+ "josefin sans": [750, -250, 0, 1e3, 456, "sans-serif"],
416
+ kanit: [1100, -395, 0, 1e3, 452, "sans-serif"],
417
+ karla: [1834, -504, 0, 2e3, 913, "sans-serif"],
418
+ lato: [1974, -426, 0, 2e3, 871, "sans-serif"],
419
+ lexend: [1e3, -250, 0, 1e3, 490, "sans-serif"],
420
+ "libre baskerville": [970, -270, 0, 1e3, 517, "serif"],
421
+ "libre franklin": [966, -246, 0, 1e3, 465, "sans-serif"],
422
+ lora: [1006, -274, 0, 1e3, 468, "serif"],
423
+ manrope: [2132, -600, 0, 2e3, 920, "sans-serif"],
424
+ "maven pro": [965, -210, 0, 1e3, 462, "sans-serif"],
425
+ merriweather: [1968, -546, 0, 2e3, 970, "serif"],
426
+ montserrat: [968, -251, 0, 1e3, 503, "sans-serif"],
427
+ mulish: [1005, -250, 0, 1e3, 464, "sans-serif"],
428
+ newsreader: [1470, -530, 0, 2e3, 857, "serif"],
429
+ "noto sans": [1069, -293, 0, 1e3, 474, "sans-serif"],
430
+ "noto serif": [1069, -293, 0, 1e3, 481, "serif"],
431
+ "nunito sans": [1011, -353, 0, 1e3, 452, "sans-serif"],
432
+ nunito: [1011, -353, 0, 1e3, 452, "sans-serif"],
433
+ onest: [970, -305, 0, 1e3, 469, "sans-serif"],
434
+ "open sans": [2189, -600, 0, 2048, 960, "sans-serif"],
435
+ oswald: [1193, -289, 0, 1e3, 363, "sans-serif"],
436
+ outfit: [1e3, -260, 0, 1e3, 445, "sans-serif"],
437
+ overpass: [1766, -766, 0, 2e3, 898, "sans-serif"],
438
+ oxygen: [2103, -483, 0, 2048, 923, "sans-serif"],
439
+ "playfair display": [1082, -251, 0, 1e3, 452, "serif"],
440
+ "plus jakarta sans": [1038, -222, 0, 1e3, 468, "sans-serif"],
441
+ poppins: [1050, -350, 100, 1e3, 500, "sans-serif"],
442
+ "pt sans": [1018, -276, 0, 1e3, 431, "sans-serif"],
443
+ "pt serif": [1039, -286, 0, 1e3, 448, "serif"],
444
+ "public sans": [1900, -450, 0, 2e3, 935, "sans-serif"],
445
+ quicksand: [1e3, -250, 0, 1e3, 465, "sans-serif"],
446
+ raleway: [940, -234, 0, 1e3, 463, "sans-serif"],
447
+ "red hat display": [1018, -305, 0, 1e3, 442, "sans-serif"],
448
+ "red hat text": [1018, -305, 0, 1e3, 447, "sans-serif"],
449
+ "roboto condensed": [1900, -500, 0, 2048, 811, "sans-serif"],
450
+ "roboto flex": [1900, -500, 0, 2048, 908, "sans-serif"],
451
+ "roboto mono": [2146, -555, 0, 2048, 1229, "monospace"],
452
+ "roboto slab": [2146, -555, 0, 2048, 972, "serif"],
453
+ roboto: [1900, -500, 0, 2048, 911, "sans-serif"],
454
+ rubik: [935, -250, 0, 1e3, 468, "sans-serif"],
455
+ "schibsted grotesk": [2e3, -528, 0, 2048, 954, "sans-serif"],
456
+ "segoe ui": [2210, -514, 0, 2048, 908, "sans-serif"],
457
+ sora: [970, -290, 0, 1e3, 507, "sans-serif"],
458
+ "source code pro": [984, -273, 0, 1e3, 600, "monospace"],
459
+ "source sans 3": [1024, -400, 0, 1e3, 418, "sans-serif"],
460
+ "source serif 4": [1036, -335, 0, 1e3, 479, "serif"],
461
+ "space grotesk": [984, -292, 0, 1e3, 489, "sans-serif"],
462
+ "space mono": [1120, -361, 0, 1e3, 612, "monospace"],
463
+ spectral: [1059, -463, 0, 1e3, 446, "serif"],
464
+ tahoma: [2049, -423, 0, 2048, 917, "sans-serif"],
465
+ "times new roman": [1825, -443, 87, 2048, 832, "serif"],
466
+ "titillium web": [1133, -388, 0, 1e3, 421, "sans-serif"],
467
+ "trebuchet ms": [1923, -455, 0, 2048, 934, "sans-serif"],
468
+ "ubuntu mono": [830, -170, 0, 1e3, 500, "monospace"],
469
+ ubuntu: [932, -189, 28, 1e3, 455, "sans-serif"],
470
+ urbanist: [1900, -500, 0, 2e3, 883, "sans-serif"],
471
+ "varela round": [918, -286, 0, 1e3, 478, "sans-serif"],
472
+ verdana: [2059, -430, 0, 2048, 1049, "sans-serif"],
473
+ "victor mono": [1100, -250, 0, 1e3, 600, "monospace"],
474
+ vollkorn: [952, -441, 0, 1e3, 438, "serif"],
475
+ "work sans": [930, -243, 0, 1e3, 499, "sans-serif"],
476
+ "zilla slab": [944, -256, 0, 1e3, 434, "serif"]
477
+ };
478
+
479
+ // src/integrations/font-providers/metrics.ts
480
+ var CATEGORY_FALLBACK = {
481
+ "sans-serif": "Arial",
482
+ serif: "Times New Roman",
483
+ monospace: "Courier New"
484
+ };
485
+ function lookupFontMetrics(family) {
486
+ return FONT_METRICS_TABLE[family.trim().toLowerCase()];
487
+ }
488
+ var round4 = (n) => Math.round(n * 1e4) / 1e4;
489
+ function computeFallbackMetrics(fallbackName, font, fallbackFont) {
490
+ const [ascent, descent, lineGap, unitsPerEm, xWidthAvg] = font;
491
+ const [, , , fbUnitsPerEm, fbXWidthAvg] = fallbackFont;
492
+ const sizeAdjust = xWidthAvg / unitsPerEm / (fbXWidthAvg / fbUnitsPerEm);
493
+ return {
494
+ fallback: fallbackName,
495
+ sizeAdjust: round4(sizeAdjust * 100),
496
+ ascent: round4(ascent / unitsPerEm / sizeAdjust * 100),
497
+ descent: round4(Math.abs(descent) / unitsPerEm / sizeAdjust * 100),
498
+ lineGap: round4(lineGap / unitsPerEm / sizeAdjust * 100)
499
+ };
500
+ }
501
+ function resolveAutoMetrics(family, fallbackStack, explicitFallback) {
502
+ const font = lookupFontMetrics(family);
503
+ if (!font) return null;
504
+ const fallbackName = explicitFallback ?? fallbackStack.find((f) => lookupFontMetrics(f)) ?? CATEGORY_FALLBACK[font[5]] ?? "Arial";
505
+ const fallbackFont = lookupFontMetrics(fallbackName);
506
+ if (!fallbackFont) return null;
507
+ return computeFallbackMetrics(fallbackName, font, fallbackFont);
508
+ }
509
+
362
510
  // src/integrations/font-providers/index.ts
363
511
  var SYSTEM_STACKS = Object.freeze({
364
512
  sans: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
@@ -390,8 +538,7 @@ function googleFontsUrl(family, face) {
390
538
  axisParam = face.style.includes("italic") ? `ital,wght@0,${w};1,${w}` : `wght@${w}`;
391
539
  }
392
540
  const display = face.display || "swap";
393
- const subset = face.subset && face.subset !== "latin" ? `&subset=${encodeURIComponent(face.subset)}` : "";
394
- return `https://fonts.googleapis.com/css2?family=${encodedFamily}:${axisParam}&display=${display}${subset}`;
541
+ return `https://fonts.googleapis.com/css2?family=${encodedFamily}:${axisParam}&display=${display}`;
395
542
  }
396
543
  function escapeFontFamily(name) {
397
544
  return name.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\0/g, "\\0").replace(/\n/g, "\\a ").replace(/\r/g, "\\d ");
@@ -400,7 +547,7 @@ function generateFallbackFontFace(family, metrics) {
400
547
  const safeFamily = escapeFontFamily(family);
401
548
  return `@font-face {
402
549
  font-family: "${safeFamily} Fallback";
403
- src: local("${metrics.fallback}");
550
+ src: local("${escapeFontFamily(metrics.fallback)}");
404
551
  size-adjust: ${metrics.sizeAdjust}%;
405
552
  ascent-override: ${metrics.ascent}%;
406
553
  descent-override: ${metrics.descent}%;
@@ -425,7 +572,7 @@ function generateWebFontFace(family, face) {
425
572
  if (face.provider === "google") {
426
573
  return { type: "import", css: `@import url("${googleFontsUrl(family, face)}");` };
427
574
  }
428
- const safeProvider = face.provider.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
575
+ const safeProvider = escapeFontFamily(face.provider);
429
576
  const format = inferFontFormat(face.provider);
430
577
  const src = `url("${safeProvider}") format("${format}")`;
431
578
  const declarations = [` font-family: "${escapeFontFamily(family)}";`, ` src: ${src};`];
@@ -447,6 +594,27 @@ function normalizeLocalStyle(style, family, warnings) {
447
594
  }
448
595
  return style;
449
596
  }
597
+ function resolveSlotMetrics(slot, warnings) {
598
+ const cfg = slot.metrics;
599
+ if (cfg === null) return null;
600
+ if (cfg?.sizeAdjust !== void 0) {
601
+ return {
602
+ fallback: cfg.fallback || slot.fallback[0] || "Arial",
603
+ sizeAdjust: cfg.sizeAdjust,
604
+ ascent: cfg.ascent,
605
+ descent: cfg.descent,
606
+ lineGap: cfg.lineGap
607
+ };
608
+ }
609
+ const resolved = resolveAutoMetrics(slot.family, slot.fallback, cfg?.fallback);
610
+ if (!resolved && cfg?.fallback) {
611
+ const missing = lookupFontMetrics(slot.family) ? cfg.fallback : slot.family;
612
+ warnings.push(
613
+ `[RI-1220] @font slot "${slot.slot}" requests metrics matching against "${cfg.fallback}", but "${missing}" is not in the built-in metrics table \u2014 no fallback @font-face was generated. Provide the four percentages explicitly (metrics: "${cfg.fallback}" <size-adjust> <ascent> <descent> <line-gap>) or use \`metrics: none\`.`
614
+ );
615
+ }
616
+ return resolved;
617
+ }
450
618
  function generateFontCSS(slot) {
451
619
  const imports = [];
452
620
  const fontFaces = [];
@@ -461,25 +629,14 @@ function generateFontCSS(slot) {
461
629
  return { imports, fontFaces, variables, warnings };
462
630
  }
463
631
  if (slot.kind === "manual") {
464
- const stack = [`"${escapeFontFamily(slot.family)}"`, ...slot.fallback].join(", ");
632
+ const fallback = slot.fallback.length > 0 ? slot.fallback.join(", ") : getFallbackStack(slot.slot);
633
+ const stack = [`"${escapeFontFamily(slot.family)}"`, fallback].join(", ");
465
634
  variables.push(`--font-${slot.slot}: ${stack};`);
466
635
  pushFeatureVars();
467
636
  return { imports, fontFaces, variables, warnings };
468
637
  }
469
- const { sizeAdjust, ascent, descent, lineGap } = slot;
470
- const hasMetrics = sizeAdjust !== void 0 && ascent !== void 0 && descent !== void 0 && lineGap !== void 0;
471
- if (hasMetrics) {
472
- const metricsFallbackFont = slot.metricsFallback || slot.fallback[0] || "Arial";
473
- fontFaces.push(
474
- generateFallbackFontFace(slot.family, {
475
- fallback: metricsFallbackFont,
476
- sizeAdjust,
477
- ascent,
478
- descent,
479
- lineGap
480
- })
481
- );
482
- }
638
+ const metrics = resolveSlotMetrics(slot, warnings);
639
+ if (metrics) fontFaces.push(generateFallbackFontFace(slot.family, metrics));
483
640
  for (const face of slot.faces) {
484
641
  if (face.provider !== "google" && !face.provider.startsWith("/") && !face.provider.startsWith("http") && !face.provider.startsWith(".")) {
485
642
  warnings.push(
@@ -496,7 +653,7 @@ function generateFontCSS(slot) {
496
653
  const fallbackStack = slot.fallback.length > 0 ? slot.fallback.join(", ") : getFallbackStack(slot.slot);
497
654
  const safeFamily = escapeFontFamily(slot.family);
498
655
  const stackParts = [`"${safeFamily}"`];
499
- if (hasMetrics) stackParts.push(`"${safeFamily} Fallback"`);
656
+ if (metrics) stackParts.push(`"${safeFamily} Fallback"`);
500
657
  stackParts.push(fallbackStack);
501
658
  variables.push(`--font-${slot.slot}: ${stackParts.join(", ")};`);
502
659
  pushFeatureVars();
@@ -507,8 +664,7 @@ function getFontPreloadLinks(slots) {
507
664
  const seen = /* @__PURE__ */ new Set();
508
665
  for (const slot of slots) {
509
666
  for (const face of slot.faces) {
510
- const preload = face.preload ?? slot.preload;
511
- if (!preload) continue;
667
+ if (!face.preload) continue;
512
668
  if (face.provider === "system" || face.provider === "google" || !face.provider) continue;
513
669
  if (seen.has(face.provider)) continue;
514
670
  seen.add(face.provider);
@@ -658,7 +814,7 @@ function errMessage(err) {
658
814
 
659
815
  // src/scanner/sources.ts
660
816
  var DEFAULT_PATTERNS = Object.freeze([
661
- "index.html",
817
+ "*.html",
662
818
  "src/**/*.{html,js,jsx,ts,tsx,mdx,vue,svelte}"
663
819
  ]);
664
820
  var DEFAULT_EXCLUDES = Object.freeze([
@@ -775,31 +931,38 @@ function collectInlineClasses(sources) {
775
931
  return { classes, warnings };
776
932
  }
777
933
  var FILE_IO_TIMEOUT_MS = 1e4;
934
+ var SCAN_CACHE_MAX_ENTRIES = 2e4;
935
+ var scanCache = /* @__PURE__ */ new Map();
778
936
  async function scanOneFile(file) {
779
937
  const warnings = [];
780
938
  try {
781
- const fileSize = await withTimeout(
782
- stat(file).then((s) => s.size),
783
- FILE_IO_TIMEOUT_MS,
784
- "stat() timed out"
785
- );
786
- if (fileSize > MAX_FILE_SIZE) {
787
- return {
939
+ const stats = await withTimeout(stat(file), FILE_IO_TIMEOUT_MS, "stat() timed out");
940
+ const cached = scanCache.get(file);
941
+ if (cached && cached.mtimeMs === stats.mtimeMs && cached.size === stats.size) {
942
+ return cached.result;
943
+ }
944
+ let result;
945
+ if (stats.size > MAX_FILE_SIZE) {
946
+ result = {
788
947
  classes: null,
789
948
  warnings,
790
- failure: `[RI-1405] Skipping source file "${file}" (${fileSize} bytes) \u2014 exceeds ${MAX_FILE_SIZE} byte limit.`
949
+ failure: `[RI-1405] Skipping source file "${file}" (${stats.size} bytes) \u2014 exceeds ${MAX_FILE_SIZE} byte limit.`
950
+ };
951
+ } else {
952
+ const content = await withTimeout(
953
+ readFile(file, "utf-8"),
954
+ FILE_IO_TIMEOUT_MS,
955
+ "readFile() timed out"
956
+ );
957
+ result = {
958
+ classes: extractClassesFromSource({ path: file, content }, warnings),
959
+ warnings,
960
+ failure: null
791
961
  };
792
962
  }
793
- const content = await withTimeout(
794
- readFile(file, "utf-8"),
795
- FILE_IO_TIMEOUT_MS,
796
- "readFile() timed out"
797
- );
798
- return {
799
- classes: extractClassesFromSource({ path: file, content }, warnings),
800
- warnings,
801
- failure: null
802
- };
963
+ if (scanCache.size >= SCAN_CACHE_MAX_ENTRIES) scanCache.clear();
964
+ scanCache.set(file, { mtimeMs: stats.mtimeMs, size: stats.size, result });
965
+ return result;
803
966
  } catch (err) {
804
967
  return {
805
968
  classes: null,
@@ -1047,7 +1210,7 @@ var modules = [
1047
1210
  {
1048
1211
  name: "margins",
1049
1212
  category: "core",
1050
- css: `body, h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd, pre {
1213
+ css: `body, h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd, pre, ol, ul, menu {
1051
1214
  margin: 0;
1052
1215
  }`
1053
1216
  },
@@ -1064,9 +1227,6 @@ var modules = [
1064
1227
  name: "root-defaults",
1065
1228
  category: "core",
1066
1229
  css: `:root {
1067
- --sans-fallback: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
1068
- --serif-fallback: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
1069
- --mono-fallback: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
1070
1230
  line-height: 1.5;
1071
1231
  -webkit-text-size-adjust: 100%;
1072
1232
  tab-size: 4;
@@ -1175,7 +1335,7 @@ button {
1175
1335
  category: "forms",
1176
1336
  css: `input::placeholder, textarea::placeholder {
1177
1337
  opacity: 1;
1178
- color: oklch(0.556 0 0);
1338
+ color: color-mix(in oklab, currentColor 48%, transparent);
1179
1339
  }
1180
1340
  input:where([type="button"], [type="reset"], [type="submit"]) {
1181
1341
  -webkit-appearance: button;
@@ -1190,16 +1350,14 @@ input:where([type="button"], [type="reset"], [type="submit"]) {
1190
1350
  }`
1191
1351
  },
1192
1352
  {
1193
- name: "select-reset",
1353
+ name: "fieldset-reset",
1194
1354
  category: "forms",
1195
- css: `select {
1196
- -webkit-appearance: none;
1197
- appearance: none;
1198
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='currentColor'%3e%3cpath fill-rule='evenodd' d='M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06z' clip-rule='evenodd'/%3e%3c/svg%3e");
1199
- background-position: right 0.5rem center;
1200
- background-repeat: no-repeat;
1201
- background-size: 1.5em 1.5em;
1202
- padding-inline-end: 2.5rem;
1355
+ css: `fieldset {
1356
+ margin: 0;
1357
+ padding: 0;
1358
+ }
1359
+ legend {
1360
+ padding: 0;
1203
1361
  }`
1204
1362
  },
1205
1363
  // ── Interactive ──────────────────────────────────────────
@@ -1207,13 +1365,10 @@ input:where([type="button"], [type="reset"], [type="submit"]) {
1207
1365
  name: "focus-visible",
1208
1366
  category: "interactive",
1209
1367
  css: `:focus-visible {
1210
- outline-width: var(--spacing);
1368
+ outline-width: 2px;
1211
1369
  outline-style: solid;
1212
- outline-offset: calc(var(--spacing) * 0.5);
1370
+ outline-offset: 2px;
1213
1371
  outline-color: currentColor;
1214
- }
1215
- :focus:not(:focus-visible) {
1216
- outline: none;
1217
1372
  }`
1218
1373
  },
1219
1374
  {
@@ -1575,13 +1730,20 @@ function collectApplyClassNames(css, warnings) {
1575
1730
  }
1576
1731
  return classes;
1577
1732
  }
1733
+ var effectiveThemeMemo = /* @__PURE__ */ new WeakMap();
1578
1734
  async function finalizeProjectCompilation(options) {
1579
1735
  const { analysis } = options;
1580
1736
  let effectiveTheme = analysis.theme;
1581
1737
  if (options.resolveFonts) {
1582
1738
  const resolvedFonts = await options.resolveFonts(analysis.theme.fonts);
1583
1739
  if (resolvedFonts !== analysis.theme.fonts) {
1584
- effectiveTheme = { ...analysis.theme, fonts: [...resolvedFonts] };
1740
+ const memo = effectiveThemeMemo.get(analysis.theme);
1741
+ if (memo && memo.fonts === resolvedFonts) {
1742
+ effectiveTheme = memo.theme;
1743
+ } else {
1744
+ effectiveTheme = { ...analysis.theme, fonts: [...resolvedFonts] };
1745
+ effectiveThemeMemo.set(analysis.theme, { fonts: resolvedFonts, theme: effectiveTheme });
1746
+ }
1585
1747
  }
1586
1748
  }
1587
1749
  const expansionWarnings = [];
@@ -1625,8 +1787,21 @@ async function finalizeProjectCompilation(options) {
1625
1787
  }
1626
1788
 
1627
1789
  // src/project/scan.ts
1790
+ var lastAnalysis = null;
1791
+ function analyzeProjectCSSMemo(css) {
1792
+ if (lastAnalysis === null || lastAnalysis.css !== css) {
1793
+ lastAnalysis = { css, analysis: analyzeProjectCSS(css) };
1794
+ }
1795
+ const cached = lastAnalysis.analysis;
1796
+ return {
1797
+ ...cached,
1798
+ warnings: [...cached.warnings],
1799
+ warningSeen: new Set(cached.warningSeen),
1800
+ diagnostics: [...cached.diagnostics]
1801
+ };
1802
+ }
1628
1803
  async function compileScannedProject(options) {
1629
- const analysis = analyzeProjectCSS(options.css);
1804
+ const analysis = analyzeProjectCSSMemo(options.css);
1630
1805
  const resolveFonts = options.resolveFonts ?? resolveGoogleFonts;
1631
1806
  const fontsReady = Promise.resolve(resolveFonts(analysis.theme.fonts));
1632
1807
  fontsReady.catch(() => {
@@ -2101,6 +2101,7 @@ var FONT_FAMILY_PROPS = Object.freeze([
2101
2101
  "font-variation-settings"
2102
2102
  ]);
2103
2103
  var MASK_RADIAL_SIZE_PROPS = Object.freeze(["--ri-mask-radial-size"]);
2104
+ var STROKE_WIDTH_PROPS = Object.freeze(["stroke-width"]);
2104
2105
  var BG_IMAGE_PROPS = Object.freeze(["background-image"]);
2105
2106
  var BORDER_COLOR_PROPS = Object.freeze(["border-color"]);
2106
2107
  var OUTLINE_COLOR_PROPS = Object.freeze(["outline-color"]);
@@ -2108,6 +2109,7 @@ var OUTLINE_STYLE_PROPS = Object.freeze(["outline-style"]);
2108
2109
  var DECORATION_COLOR_PROPS = Object.freeze(["text-decoration-color"]);
2109
2110
  var RE_SIGNED_INT = /^-?\d+$/;
2110
2111
  var RE_UNSIGNED_INT = /^\d+$/;
2112
+ var RE_DECIMAL = /^\d+(?:[._]\d+)?$/;
2111
2113
  function colorOrDefault(prefix, colorProps) {
2112
2114
  const defaultProps = PREFIX_PROPS[prefix];
2113
2115
  return (value, _textSizes2, _fontFamilies2, colorNames) => isColorValue(value, void 0, colorNames) ? colorProps : defaultProps;
@@ -2134,6 +2136,14 @@ var DUAL_MODE_PREFIXES = {
2134
2136
  return PREFIX_PROPS.outline;
2135
2137
  return OUTLINE_STYLE_PROPS;
2136
2138
  },
2139
+ // Width-vs-color, mirroring svg.ts's generate-side dispatch: decimals
2140
+ // (stroke-2, stroke-1.5) and non-color arbitraries (stroke-[3px]) emit
2141
+ // stroke-width; every color-shaped value emits the `stroke` paint property.
2142
+ stroke: (value, _textSizes2, _fontFamilies2, colorNames) => {
2143
+ if (RE_DECIMAL.test(value) || value.startsWith("[") && !isColorValue(value, void 0, colorNames))
2144
+ return STROKE_WIDTH_PROPS;
2145
+ return PREFIX_PROPS.stroke;
2146
+ },
2137
2147
  decoration: (value, _textSizes2, _fontFamilies2, colorNames) => {
2138
2148
  if (value.startsWith("(length:") || value.startsWith("[length:"))
2139
2149
  return PREFIX_PROPS.decoration;
@@ -2410,16 +2420,19 @@ function mergeClasses(classes, resolve, cache) {
2410
2420
  return output;
2411
2421
  }
2412
2422
  function mergeFrom(inputs, resolve, cache) {
2413
- let allStrings = inputs.length > 0;
2423
+ let fastPath = true;
2424
+ let rawKey = "";
2414
2425
  for (let i = 0; i < inputs.length; i++) {
2415
2426
  const input = inputs[i];
2416
- if (typeof input !== "string" || input === "") {
2417
- allStrings = false;
2427
+ if (typeof input === "string") {
2428
+ if (input !== "") rawKey = rawKey === "" ? input : `${rawKey}\0${input}`;
2429
+ } else if (input) {
2430
+ fastPath = false;
2418
2431
  break;
2419
2432
  }
2420
2433
  }
2421
- if (allStrings) {
2422
- const rawKey = inputs.join("\0");
2434
+ if (fastPath) {
2435
+ if (rawKey === "") return "";
2423
2436
  if (rawKey.length <= RI_CACHE_KEY_MAX_LEN) {
2424
2437
  const cached = lruGet(cache, rawKey);
2425
2438
  if (cached !== void 0) return cached;
@@ -15,10 +15,10 @@ import {
15
15
  codepointCompare,
16
16
  parseUtility,
17
17
  resolveUtilityDeclarations
18
- } from "./chunk-6DAHUFNU.mjs";
18
+ } from "./chunk-ZI5ZYNSU.mjs";
19
19
  import {
20
20
  SPECIAL_COLORS
21
- } from "./chunk-5Y7EXXLS.mjs";
21
+ } from "./chunk-KRZL4IDK.mjs";
22
22
 
23
23
  // src/project/css-entry.ts
24
24
  var CSS_ENTRY_CANDIDATES = Object.freeze([