hyperframes 0.8.81 → 0.8.82

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.
Files changed (42) hide show
  1. package/dist/backgroundChecksWorker.js +890 -0
  2. package/dist/cli.js +2928 -1809
  3. package/dist/commands/layout-audit.browser.js +55 -25
  4. package/dist/fontLocalizeCli.js +417 -134
  5. package/dist/hyperframe-runtime.js +53 -53
  6. package/dist/hyperframe.manifest.json +1 -1
  7. package/dist/hyperframe.runtime.iife.js +53 -53
  8. package/dist/hyperframes-player.global.js +3 -3
  9. package/dist/registry/localSemantic.js +7 -3
  10. package/dist/renderSetupWorker.js +62 -32
  11. package/dist/sherpaWorker.js +179 -0
  12. package/dist/skills/hyperframes/SKILL.md +2 -0
  13. package/dist/skills/hyperframes/references/plugin-installation.md +43 -0
  14. package/dist/skills/hyperframes/references/skill-lifecycle.md +2 -0
  15. package/dist/skills/hyperframes/scripts/plugin-cli.mjs +88 -0
  16. package/dist/skills/hyperframes/scripts/plugin-cli.test.mjs +117 -0
  17. package/dist/skills/hyperframes-cli/SKILL.md +2 -0
  18. package/dist/skills/hyperframes-cli/references/upgrade-info-misc.md +1 -0
  19. package/dist/skills/media-use/SKILL.md +2 -0
  20. package/dist/skills/media-use/references/operations.md +7 -5
  21. package/dist/skills/media-use/references/setup-providers.md +9 -9
  22. package/dist/skills/media-use/registry/localSemantic.js +7 -3
  23. package/dist/skills/media-use/scripts/lib/ff-binaries.mjs +37 -0
  24. package/dist/skills/media-use/scripts/lib/probe.mjs +3 -1
  25. package/dist/skills/media-use/scripts/lib/probe.test.mjs +118 -1
  26. package/dist/skills/media-use/scripts/lib/tts-local-provider.mjs +5 -3
  27. package/dist/skills/media-use/scripts/resolve-plugin.test.mjs +62 -0
  28. package/dist/skills/media-use/scripts/resolve.mjs +45 -19
  29. package/dist/skills/media-use/scripts/transcribe.mjs +39 -25
  30. package/dist/skills/media-use/scripts/transcribe.test.mjs +112 -0
  31. package/dist/skills/registry/localSemantic.js +7 -3
  32. package/dist/studio/assets/{hyperframes-player-C68UawR3.js → hyperframes-player-zGIbZhLJ.js} +1 -1
  33. package/dist/studio/assets/{index-CMzPGv9P.js → index-BiLCTunu.js} +1 -1
  34. package/dist/studio/assets/{index-hNVM9Pd2.js → index-DSazIt7R.js} +189 -189
  35. package/dist/studio/assets/{index-CS5SImRT.js → index-DStmfnpZ.js} +1 -1
  36. package/dist/studio/index.d.ts +172 -7
  37. package/dist/studio/index.html +1 -1
  38. package/dist/studio/index.js +2414 -2172
  39. package/dist/studio/index.js.map +1 -1
  40. package/dist/templates/_shared/AGENTS.md +5 -1
  41. package/dist/templates/_shared/CLAUDE.md +5 -1
  42. package/package.json +1 -1
@@ -52868,17 +52868,22 @@ function escapeCaseInsensitiveToken(source, token, replacement) {
52868
52868
  function inlineScriptTags(scripts) {
52869
52869
  return scripts.map((source) => `<script>${escapeInlineScriptSource(source)}</script>`).join("\n");
52870
52870
  }
52871
- function findDocumentTag(html, tag) {
52872
- const lowered = lowerAscii(html);
52871
+ function* markupStarts(lowered) {
52873
52872
  const unclosedRawText = /* @__PURE__ */ new Set();
52874
52873
  let cursor = 0;
52875
52874
  while (cursor !== -1) {
52876
52875
  const open = lowered.indexOf("<", cursor);
52877
52876
  if (open === -1)
52878
- return -1;
52877
+ return;
52878
+ yield open;
52879
+ cursor = skipMarkup(lowered, open, unclosedRawText);
52880
+ }
52881
+ }
52882
+ function findDocumentTag(html, tag) {
52883
+ const lowered = lowerAscii(html);
52884
+ for (const open of markupStarts(lowered)) {
52879
52885
  if (isTagAt(lowered, open, tag))
52880
52886
  return open;
52881
- cursor = skipMarkup(lowered, open, unclosedRawText);
52882
52887
  }
52883
52888
  return -1;
52884
52889
  }
@@ -53069,7 +53074,7 @@ function parseApplicationExtension(bytes, offset3) {
53069
53074
  }
53070
53075
  return null;
53071
53076
  }
53072
- function parseAnimatedGifMetadata(bytes) {
53077
+ function parseAnimatedGifMetadata(bytes, onFrame) {
53073
53078
  if (bytes.length < 13)
53074
53079
  return null;
53075
53080
  const signature = readAscii(bytes, 0, 6);
@@ -53085,6 +53090,7 @@ function parseAnimatedGifMetadata(bytes) {
53085
53090
  pos += colorTableByteLength(packed);
53086
53091
  }
53087
53092
  let frameCount = 0;
53093
+ let controlPackedOffset = null;
53088
53094
  const delaysCentiseconds = [];
53089
53095
  let loopCount = null;
53090
53096
  while (pos < bytes.length) {
@@ -53107,6 +53113,7 @@ function parseAnimatedGifMetadata(bytes) {
53107
53113
  if (delay == null)
53108
53114
  return null;
53109
53115
  delaysCentiseconds.push(normalizeDelayCentiseconds(delay));
53116
+ controlPackedOffset = pos + 1;
53110
53117
  pos += 1 + blockSize;
53111
53118
  if (bytes[pos] !== 0)
53112
53119
  return null;
@@ -53122,6 +53129,8 @@ function parseAnimatedGifMetadata(bytes) {
53122
53129
  pos = parsed.nextOffset;
53123
53130
  continue;
53124
53131
  }
53132
+ if (label === PLAIN_TEXT_LABEL)
53133
+ controlPackedOffset = null;
53125
53134
  const next = skipSubBlocks(bytes, pos);
53126
53135
  if (next == null)
53127
53136
  return null;
@@ -53132,8 +53141,11 @@ function parseAnimatedGifMetadata(bytes) {
53132
53141
  if (pos + 9 > bytes.length)
53133
53142
  return null;
53134
53143
  const imagePacked = bytes[pos + 8] ?? 0;
53144
+ const localColorTable = (imagePacked & 128) !== 0;
53145
+ onFrame?.({ controlPackedOffset, localColorTable });
53146
+ controlPackedOffset = null;
53135
53147
  pos += 9;
53136
- if ((imagePacked & 128) !== 0) {
53148
+ if (localColorTable) {
53137
53149
  pos += colorTableByteLength(imagePacked);
53138
53150
  }
53139
53151
  if (pos >= bytes.length)
@@ -53159,11 +53171,59 @@ function parseAnimatedGifMetadata(bytes) {
53159
53171
  animated: frameCount > 1
53160
53172
  };
53161
53173
  }
53162
- var BROWSER_MIN_DELAY_CENTISECONDS;
53174
+ function frameLayouts(bytes) {
53175
+ const frames = [];
53176
+ return parseAnimatedGifMetadata(bytes, (frame) => frames.push(frame)) === null ? null : frames;
53177
+ }
53178
+ function disposalOf(bytes, frame) {
53179
+ return frame.controlPackedOffset === null ? null : (bytes[frame.controlPackedOffset] ?? 0) & DISPOSAL_BITS;
53180
+ }
53181
+ function gifClearsAfterLeavingFrameInPlace(bytes) {
53182
+ const frames = frameLayouts(bytes);
53183
+ if (frames === null)
53184
+ return null;
53185
+ let sawOpaque = false;
53186
+ for (const frame of frames) {
53187
+ const disposal = disposalOf(bytes, frame);
53188
+ if (disposal === LEAVE_IN_PLACE)
53189
+ sawOpaque = true;
53190
+ else if (disposal === DISPOSE_TO_BACKGROUND && sawOpaque)
53191
+ return true;
53192
+ }
53193
+ return false;
53194
+ }
53195
+ function clearGifFramesBeforeNext(bytes) {
53196
+ const frames = frameLayouts(bytes);
53197
+ if (frames === null)
53198
+ return false;
53199
+ const controls = frames.flatMap((f) => f.controlPackedOffset === null ? [] : [f.controlPackedOffset]);
53200
+ const shared = frames.flatMap((f) => f.controlPackedOffset === null || f.localColorTable ? [] : [f.controlPackedOffset]);
53201
+ const hasIndex = (at2) => ((bytes[at2] ?? 0) & HAS_TRANSPARENT_INDEX) !== 0;
53202
+ const source = shared.find(hasIndex);
53203
+ for (const at2 of controls) {
53204
+ bytes[at2] = (bytes[at2] ?? 0) & ~DISPOSAL_BITS | DISPOSE_TO_BACKGROUND;
53205
+ }
53206
+ if (source === void 0)
53207
+ return true;
53208
+ for (const at2 of shared) {
53209
+ if (hasIndex(at2))
53210
+ continue;
53211
+ bytes[at2] = (bytes[at2] ?? 0) | HAS_TRANSPARENT_INDEX;
53212
+ bytes[at2 + TRANSPARENT_INDEX_AFTER_PACKED] = bytes[source + TRANSPARENT_INDEX_AFTER_PACKED] ?? 0;
53213
+ }
53214
+ return true;
53215
+ }
53216
+ var BROWSER_MIN_DELAY_CENTISECONDS, PLAIN_TEXT_LABEL, DISPOSAL_BITS, LEAVE_IN_PLACE, DISPOSE_TO_BACKGROUND, HAS_TRANSPARENT_INDEX, TRANSPARENT_INDEX_AFTER_PACKED;
53163
53217
  var init_gif = __esm({
53164
53218
  "../core/dist/media/gif.js"() {
53165
53219
  "use strict";
53166
53220
  BROWSER_MIN_DELAY_CENTISECONDS = 10;
53221
+ PLAIN_TEXT_LABEL = 1;
53222
+ DISPOSAL_BITS = 28;
53223
+ LEAVE_IN_PLACE = 1 << 2;
53224
+ DISPOSE_TO_BACKGROUND = 2 << 2;
53225
+ HAS_TRANSPARENT_INDEX = 1;
53226
+ TRANSPARENT_INDEX_AFTER_PACKED = 3;
53167
53227
  }
53168
53228
  });
53169
53229
 
@@ -54212,6 +54272,13 @@ var init_safePath = __esm({
54212
54272
  }
54213
54273
  });
54214
54274
 
54275
+ // ../core/dist/projectRule.js
54276
+ var init_projectRule = __esm({
54277
+ "../core/dist/projectRule.js"() {
54278
+ "use strict";
54279
+ }
54280
+ });
54281
+
54215
54282
  // ../core/dist/adapters/gsap.js
54216
54283
  var init_gsap = __esm({
54217
54284
  "../core/dist/adapters/gsap.js"() {
@@ -55334,6 +55401,7 @@ var init_dist2 = __esm({
55334
55401
  init_parityContract();
55335
55402
  init_telemetryRedaction();
55336
55403
  init_safePath();
55404
+ init_projectRule();
55337
55405
  init_gsap();
55338
55406
  init_text2();
55339
55407
  init_renderOutputTimestamp();
@@ -70914,10 +70982,11 @@ function resolveCompositionDuration(input) {
70914
70982
  reason: pendingClips > 0 ? "every clip's length is still pending" : "no authored duration and no clip with a length"
70915
70983
  };
70916
70984
  }
70917
- var isPositiveFinite;
70985
+ var TIMING_TOLERANCE_SECONDS, isPositiveFinite;
70918
70986
  var init_compositionDuration = __esm({
70919
70987
  "../parsers/dist/compositionDuration.js"() {
70920
70988
  "use strict";
70989
+ TIMING_TOLERANCE_SECONDS = 0.05;
70921
70990
  isPositiveFinite = (value) => value !== null && Number.isFinite(value) && value > 0;
70922
70991
  }
70923
70992
  });
@@ -75597,6 +75666,26 @@ function captionFindings(rows, severity) {
75597
75666
  }
75598
75667
  return findings;
75599
75668
  }
75669
+ function clipsPastRootFindings(root, rows) {
75670
+ if (root.attrs["data-composition-id"] === void 0) return [];
75671
+ const rootDuration = readDataDurationSeconds((name) => root.attrs[name]);
75672
+ if (rootDuration === null) return [];
75673
+ const limit = hundredths(rootDuration);
75674
+ return rows.flatMap((row) => {
75675
+ const { start, end } = readClipTiming({ getAttribute: (name) => row.attrs[name] ?? null });
75676
+ if (start === null || end === null || end <= rootDuration + TIMING_TOLERANCE_SECONDS) return [];
75677
+ return [
75678
+ {
75679
+ code: "clip_ends_past_root_duration",
75680
+ severity: "warning",
75681
+ message: `${describe(row)} runs from ${hundredths(start)}s to ${hundredths(end)}s, past the root composition's data-duration of ${limit}s, so it is cut off in previews, posters and renders.`,
75682
+ elementId: row.attrs.id,
75683
+ fixHint: `Extend the root data-duration to ${hundredths(end)}, or make ${describe(row)} end at or before ${limit}s.`,
75684
+ snippet: truncateSnippet(row.open.raw)
75685
+ }
75686
+ ];
75687
+ });
75688
+ }
75600
75689
  function dedupeKeyFor(finding) {
75601
75690
  return [
75602
75691
  finding.code,
@@ -75665,7 +75754,7 @@ async function lintHyperframeHtml(html, options = {}) {
75665
75754
  timings: { totalMs: performance.now() - startedAt, ...timings }
75666
75755
  };
75667
75756
  }
75668
- var import_postcss_selector_parser, COMPOSITION_ID_IN_CSS_PATTERN, TIMELINE_REGISTRY_INIT_PATTERN, TIMELINE_REGISTRY_OBJECT_LITERAL_PATTERN, TIMELINE_REGISTRY_ASSIGN_PATTERN, WINDOW_TIMELINE_ASSIGN_PATTERN, INVALID_SCRIPT_CLOSE_PATTERN, TIMELINE_REGISTRY_KEY_PATTERN, TIMELINE_REGISTRY_OBJECT_OPEN_PATTERN, TIMELINE_REGISTRY_OBJECT_ENTRY_PATTERN, REGEX_ALLOWED_BEFORE, REGEX_ALLOWED_KEYWORDS, WORD_CHAR, CodeContext, VISIBLE_MARKUP_COMMENT_PATTERN, VISIBLE_MARKUP_COMMENT_PROTECTED_BLOCK_PATTERN, coreRules, IMAGE_SRC_EXT, VIDEO_SRC_EXT, AUDIO_SRC_EXT, SRC_KIND_NOUN, mediaRules, AUDIO_GROUP_TIMING_ATTRS, callIndexes, INSPECTION_VIEWPORT_CAP, MAX_COMPOSITION_LINES, MAX_TIMED_ELEMENTS_PER_TRACK, TRACK_DENSITY_EXEMPT_TAGS, CAPTION_CUE_TOKEN, HEAVY_OVERLAY_ELEMENT_COUNT_WARN, HEAVY_OVERLAY_EXEMPT_TAGS, HEAVY_OVERLAY_CSS_PATTERN, INLINE_STYLE_DISPLAY_NONE_PATTERN, ZOOM_DECLARATION, NEGATIVE_Z_INDEX, compositionRules, CLIP_MEDIA_TAGS, SCENE_BOUNDARY_EPSILON_SECONDS, UNRESOLVED_TARGET, gsapWindowsCache, CONFLICTING_TRANSLATE_PROPS, CONFLICTING_SCALE_PROPS, RELATIVE_TWEEN_VALUE, TRANSFORM_SENSITIVE_READ, TRANSFORM_INVARIANT_READ, CALLBACK_MEASUREMENT_PATTERN, NUMBER_METHODS, gsapRules, captionRules, adapterRules, TEXTURE_BASE_CLASS, TEXTURE_CLASS_PREFIX, textureRules, GENERIC_FAMILIES, fontRules, slideshowRules, INLINE_TEXT_TAGS, OPAQUE_TAGS, NON_LAYOUT_TAGS, MEDIA_TAGS2, NODE_ATTRS, describe, structureRules, RULE_GROUPS, LINT_RULE_COUNT, LINT_RULE_GROUP_COUNTS;
75757
+ var import_postcss_selector_parser, COMPOSITION_ID_IN_CSS_PATTERN, TIMELINE_REGISTRY_INIT_PATTERN, TIMELINE_REGISTRY_OBJECT_LITERAL_PATTERN, TIMELINE_REGISTRY_ASSIGN_PATTERN, WINDOW_TIMELINE_ASSIGN_PATTERN, INVALID_SCRIPT_CLOSE_PATTERN, TIMELINE_REGISTRY_KEY_PATTERN, TIMELINE_REGISTRY_OBJECT_OPEN_PATTERN, TIMELINE_REGISTRY_OBJECT_ENTRY_PATTERN, REGEX_ALLOWED_BEFORE, REGEX_ALLOWED_KEYWORDS, WORD_CHAR, CodeContext, VISIBLE_MARKUP_COMMENT_PATTERN, VISIBLE_MARKUP_COMMENT_PROTECTED_BLOCK_PATTERN, coreRules, IMAGE_SRC_EXT, VIDEO_SRC_EXT, AUDIO_SRC_EXT, SRC_KIND_NOUN, mediaRules, AUDIO_GROUP_TIMING_ATTRS, callIndexes, INSPECTION_VIEWPORT_CAP, MAX_COMPOSITION_LINES, MAX_TIMED_ELEMENTS_PER_TRACK, TRACK_DENSITY_EXEMPT_TAGS, CAPTION_CUE_TOKEN, HEAVY_OVERLAY_ELEMENT_COUNT_WARN, HEAVY_OVERLAY_EXEMPT_TAGS, HEAVY_OVERLAY_CSS_PATTERN, INLINE_STYLE_DISPLAY_NONE_PATTERN, ZOOM_DECLARATION, NEGATIVE_Z_INDEX, compositionRules, CLIP_MEDIA_TAGS, SCENE_BOUNDARY_EPSILON_SECONDS, UNRESOLVED_TARGET, gsapWindowsCache, CONFLICTING_TRANSLATE_PROPS, CONFLICTING_SCALE_PROPS, RELATIVE_TWEEN_VALUE, TRANSFORM_SENSITIVE_READ, TRANSFORM_INVARIANT_READ, CALLBACK_MEASUREMENT_PATTERN, NUMBER_METHODS, gsapRules, captionRules, adapterRules, TEXTURE_BASE_CLASS, TEXTURE_CLASS_PREFIX, textureRules, GENERIC_FAMILIES, fontRules, slideshowRules, INLINE_TEXT_TAGS, OPAQUE_TAGS, NON_LAYOUT_TAGS, MEDIA_TAGS2, NODE_ATTRS, describe, hundredths, structureRules, RULE_GROUPS, LINT_RULE_COUNT, LINT_RULE_GROUP_COUNTS;
75669
75758
  var init_dist3 = __esm({
75670
75759
  "../lint/dist/index.js"() {
75671
75760
  "use strict";
@@ -75686,6 +75775,9 @@ var init_dist3 = __esm({
75686
75775
  init_composition();
75687
75776
  init_slideshow();
75688
75777
  init_topLevelElements();
75778
+ init_compositionContract();
75779
+ init_mediaDuration();
75780
+ init_compositionDuration();
75689
75781
  init_esm10();
75690
75782
  init_esm10();
75691
75783
  COMPOSITION_ID_IN_CSS_PATTERN = /\[data-composition-id=["']([^"']+)["']\]/g;
@@ -78902,6 +78994,7 @@ ${other.raw}`)
78902
78994
  "data-composition-src"
78903
78995
  ];
78904
78996
  describe = (node) => `<${node.tag}${node.attrs.id ? ` id="${node.attrs.id}"` : ""}>`;
78997
+ hundredths = (seconds) => Math.round(seconds * 100) / 100;
78905
78998
  structureRules = [
78906
78999
  (ctx) => {
78907
79000
  if (ctx.options.isSubComposition) return [];
@@ -78912,7 +79005,8 @@ ${other.raw}`)
78912
79005
  return [
78913
79006
  ...nestedStructureFindings(rows, severity),
78914
79007
  ...missingDurationFindings(rows, severity),
78915
- ...captionFindings(rows, severity)
79008
+ ...captionFindings(rows, severity),
79009
+ ...clipsPastRootFindings(root, rows)
78916
79010
  ];
78917
79011
  }
78918
79012
  ];
@@ -79820,7 +79914,7 @@ var init_extractionCache = __esm({
79820
79914
  init_extractedFrameIndex();
79821
79915
  COMPLETE_SENTINEL = ".hf-complete";
79822
79916
  GC_MARKER = ".hf-last-gc";
79823
- SCHEMA_PREFIX = "hfcache-v5-";
79917
+ SCHEMA_PREFIX = "hfcache-v6-";
79824
79918
  KEY_HEX_CHARS = 16;
79825
79919
  CACHE_GENERATION_PREFIX = "hfcache-v";
79826
79920
  }
@@ -80219,6 +80313,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
80219
80313
  const outputPattern = join13(videoOutputDir, framePattern);
80220
80314
  const isHdr = isHdrColorSpace(metadata.colorSpace);
80221
80315
  const isMacOS = process.platform === "darwin";
80316
+ const sampleCfrAtOutputFps = !options.finalFrameOnly && !metadata.isVFR;
80222
80317
  const args = [];
80223
80318
  if (isHdr && isMacOS) {
80224
80319
  args.push("-hwaccel", "videotoolbox");
@@ -80229,14 +80324,19 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
80229
80324
  if (options.finalFrameOnly) {
80230
80325
  args.push("-i", videoPath, "-ss", String(startTime), "-frames:v", "1");
80231
80326
  } else {
80327
+ if (sampleCfrAtOutputFps) args.push("-noaccurate_seek");
80232
80328
  args.push("-ss", String(startTime), "-i", videoPath, "-t", String(duration));
80233
80329
  }
80234
80330
  const vfFilters = [];
80235
80331
  if (isHdr && isMacOS) {
80236
80332
  vfFilters.push("format=nv12");
80237
80333
  }
80238
- if (!options.finalFrameOnly && !metadata.isVFR) {
80239
- vfFilters.push(`fps=${ffmpegFps}`);
80334
+ if (sampleCfrAtOutputFps) {
80335
+ vfFilters.push(
80336
+ "settb=intb/2",
80337
+ "setpts=PTS-lte(TB\\,0.001)",
80338
+ `fps=${ffmpegFps}:start_time=0:round=up`
80339
+ );
80240
80340
  }
80241
80341
  if (options.sdrToHdrTransfer) {
80242
80342
  vfFilters.push(SDR_TO_HDR_COLORSPACE_FILTER);
@@ -89007,11 +89107,11 @@ import {
89007
89107
  existsSync as existsSync33,
89008
89108
  mkdirSync as mkdirSync19,
89009
89109
  mkdtempSync as mkdtempSync11,
89010
- readFileSync as readFileSync15,
89110
+ readFileSync as readFileSync16,
89011
89111
  readdirSync as readdirSync12,
89012
89112
  rmSync as rmSync15,
89013
89113
  statSync as statSync12,
89014
- writeFileSync as writeFileSync11,
89114
+ writeFileSync as writeFileSync12,
89015
89115
  copyFileSync as copyFileSync6,
89016
89116
  appendFileSync
89017
89117
  } from "fs";
@@ -94269,7 +94369,80 @@ function dedupeVariants(variants) {
94269
94369
  init_esm10();
94270
94370
  init_fontData_generated();
94271
94371
  init_fontCompression();
94372
+ import postcss5 from "postcss";
94373
+
94374
+ // ../producer/src/services/authoredGoogleFonts.ts
94375
+ init_esm10();
94272
94376
  import postcss4 from "postcss";
94377
+ function screenStylesheet(media) {
94378
+ return !media?.trim() || media.split(",").some((query2) => /^(all|screen)$/i.test(query2.trim()));
94379
+ }
94380
+ function googleStylesheetFamilies(urlText) {
94381
+ let parsed;
94382
+ try {
94383
+ parsed = new URL(urlText);
94384
+ } catch {
94385
+ return [];
94386
+ }
94387
+ if (parsed.protocol !== "https:" || parsed.hostname !== "fonts.googleapis.com") return [];
94388
+ return parsed.searchParams.getAll("family").flatMap((family) => family.split("|")).map((family) => family.split(":")[0]?.trim().toLowerCase() ?? "").filter(Boolean);
94389
+ }
94390
+ function importUrls(css) {
94391
+ let root;
94392
+ try {
94393
+ root = postcss4.parse(css);
94394
+ } catch {
94395
+ return [];
94396
+ }
94397
+ const urls = [];
94398
+ for (const node of root.nodes) {
94399
+ if (node.type !== "atrule" || node.name.toLowerCase() !== "import") continue;
94400
+ const url = unconditionalImportUrl(node.params);
94401
+ if (url) urls.push(url);
94402
+ }
94403
+ return urls;
94404
+ }
94405
+ function unconditionalImportUrl(params) {
94406
+ const match = /^(?:url\(\s*['"]?([^'")\s]+)['"]?\s*\)|['"]([^'"]+)['"])\s*(.*)$/i.exec(params);
94407
+ if (!match || !screenStylesheet(match[3] ?? null)) return void 0;
94408
+ return match[1] ?? match[2];
94409
+ }
94410
+ function activeStylesheet(element) {
94411
+ if (element.hasAttribute("disabled") || !screenStylesheet(element.getAttribute("media")))
94412
+ return false;
94413
+ const type = element.getAttribute("type")?.trim().toLowerCase();
94414
+ return !type || type === "text/css";
94415
+ }
94416
+ function stylesheetUrls(element) {
94417
+ if (!activeStylesheet(element)) return [];
94418
+ if (element.localName !== "link") return importUrls(element.textContent ?? "");
94419
+ const rel = (element.getAttribute("rel") ?? "").toLowerCase().split(/\s+/);
94420
+ if (!rel.includes("stylesheet") || rel.includes("alternate")) return [];
94421
+ const href = element.getAttribute("href");
94422
+ return href ? [href] : [];
94423
+ }
94424
+ function authoredGoogleFontStylesheets(html) {
94425
+ const byFamily = /* @__PURE__ */ new Map();
94426
+ const { document: document2 } = parseHTML(html);
94427
+ for (const element of document2.querySelectorAll("link, style")) {
94428
+ for (const url of stylesheetUrls(element)) {
94429
+ for (const family of googleStylesheetFamilies(url)) {
94430
+ const previous = byFamily.get(family) ?? [];
94431
+ byFamily.set(family, [...previous.filter((existing) => existing !== url), url]);
94432
+ }
94433
+ }
94434
+ }
94435
+ return byFamily;
94436
+ }
94437
+ function withPageText(url, fontText) {
94438
+ const parsed = new URL(url);
94439
+ if (!fontText || parsed.searchParams.has("text")) return url;
94440
+ const withoutFragment = url.split("#")[0] ?? url;
94441
+ const joiner = parsed.search ? "&" : "?";
94442
+ return `${withoutFragment}${joiner}text=${encodeURIComponent(fontText)}${parsed.hash}`;
94443
+ }
94444
+
94445
+ // ../producer/src/services/deterministicFonts.ts
94273
94446
  var GENERIC_FAMILIES2 = /* @__PURE__ */ new Set([
94274
94447
  "sans-serif",
94275
94448
  "serif",
@@ -94286,6 +94459,13 @@ var GENERIC_FAMILIES2 = /* @__PURE__ */ new Set([
94286
94459
  "-apple-system",
94287
94460
  "blinkmacsystemfont"
94288
94461
  ]);
94462
+ var CSS_WIDE_KEYWORDS = /* @__PURE__ */ new Set([
94463
+ "inherit",
94464
+ "initial",
94465
+ "unset",
94466
+ "revert",
94467
+ "revert-layer"
94468
+ ]);
94289
94469
  function parseFontFamilyValue(value) {
94290
94470
  const pieces = [];
94291
94471
  let start = 0;
@@ -94320,7 +94500,17 @@ function parseFontFamilyValue(value) {
94320
94500
  pieces.push(value.slice(start));
94321
94501
  return pieces.map((piece) => piece.trim().replace(/^['"]/, "").replace(/['"]$/, "").trim()).filter((piece) => piece.length > 0);
94322
94502
  }
94323
- function systemPrimaryReplacement(value, deterministicPrimary) {
94503
+ function systemPrimaryReplacement(value, deterministicPrimary, customProperties) {
94504
+ const variable = primaryCssVariable(value);
94505
+ if (variable) {
94506
+ if (customProperties.get(variable.name) || variable.fallback === null) return null;
94507
+ const fallback = systemPrimaryReplacement(
94508
+ variable.fallback,
94509
+ deterministicPrimary,
94510
+ customProperties
94511
+ );
94512
+ return fallback ? `var(${variable.name}, ${fallback})${variable.rest}` : null;
94513
+ }
94324
94514
  const families = parseFontFamilyValue(value);
94325
94515
  if (families.length === 0) return null;
94326
94516
  if (!GENERIC_FAMILIES2.has(normalizeFamilyName(families[0]))) return null;
@@ -94328,7 +94518,7 @@ function systemPrimaryReplacement(value, deterministicPrimary) {
94328
94518
  }
94329
94519
  function parseCssRoot(css) {
94330
94520
  try {
94331
- return postcss4.parse(css);
94521
+ return postcss5.parse(css);
94332
94522
  } catch {
94333
94523
  return null;
94334
94524
  }
@@ -94337,11 +94527,11 @@ function isFontFaceDeclaration(decl) {
94337
94527
  const parent = decl.parent;
94338
94528
  return parent?.type === "atrule" && parent.name.toLowerCase() === "font-face";
94339
94529
  }
94340
- function normalizeCssDeclarations(root, deterministicPrimary) {
94530
+ function normalizeCssDeclarations(root, replacePrimary) {
94341
94531
  let changed = false;
94342
94532
  root.walkDecls((decl) => {
94343
94533
  if (decl.prop.startsWith("--")) {
94344
- const replacement2 = systemPrimaryReplacement(decl.value, deterministicPrimary);
94534
+ const replacement2 = replacePrimary(decl.value);
94345
94535
  if (!replacement2) return;
94346
94536
  decl.value = replacement2;
94347
94537
  changed = true;
@@ -94351,50 +94541,52 @@ function normalizeCssDeclarations(root, deterministicPrimary) {
94351
94541
  if (isFontFaceDeclaration(decl)) {
94352
94542
  return;
94353
94543
  }
94354
- const replacement = systemPrimaryReplacement(decl.value, deterministicPrimary);
94544
+ const replacement = replacePrimary(decl.value);
94355
94545
  if (!replacement) return;
94356
94546
  decl.value = replacement;
94357
94547
  changed = true;
94358
94548
  });
94359
94549
  return changed;
94360
94550
  }
94361
- function normalizeCssFontFamilyDeclarations(css, deterministicPrimary) {
94551
+ function normalizeCssFontFamilyDeclarations(css, replacePrimary) {
94362
94552
  const root = parseCssRoot(css);
94363
94553
  if (!root) return css;
94364
- const changed = normalizeCssDeclarations(root, deterministicPrimary);
94554
+ const changed = normalizeCssDeclarations(root, replacePrimary);
94365
94555
  return changed ? root.toString() : css;
94366
94556
  }
94367
- function normalizeInlineStyleAttribute(style, deterministicPrimary) {
94557
+ function normalizeInlineStyleAttribute(style, replacePrimary) {
94368
94558
  const root = parseCssRoot(`*{${style}}`);
94369
94559
  if (!root) return style;
94370
94560
  const rule = root.first;
94371
94561
  if (rule?.type !== "rule") return style;
94372
94562
  const before2 = rule.toString();
94373
- normalizeCssDeclarations(root, deterministicPrimary);
94563
+ normalizeCssDeclarations(root, replacePrimary);
94374
94564
  if (rule.toString() === before2) return style;
94375
94565
  const serialized = (rule.nodes ?? []).map((node) => node.toString()).join("; ");
94376
94566
  return serialized.endsWith(";") ? serialized : `${serialized};`;
94377
94567
  }
94378
94568
  function normalizeSystemFontPrimaryFamilies(html, deterministicPrimary = "Inter") {
94379
94569
  const { document: document2 } = parseHTML(html);
94570
+ const customProperties = collectFontFamilyCustomProperties(html);
94571
+ const replacePrimary = (value) => systemPrimaryReplacement(value, deterministicPrimary, customProperties);
94380
94572
  let changed = false;
94381
94573
  for (const styleEl of Array.from(document2.querySelectorAll("style"))) {
94382
94574
  const current2 = styleEl.textContent ?? "";
94383
- const next = normalizeCssFontFamilyDeclarations(current2, deterministicPrimary);
94575
+ const next = normalizeCssFontFamilyDeclarations(current2, replacePrimary);
94384
94576
  if (next === current2) continue;
94385
94577
  styleEl.textContent = next;
94386
94578
  changed = true;
94387
94579
  }
94388
94580
  for (const el of Array.from(document2.querySelectorAll("[style]"))) {
94389
94581
  const current2 = el.getAttribute("style") ?? "";
94390
- const next = normalizeInlineStyleAttribute(current2, deterministicPrimary);
94582
+ const next = normalizeInlineStyleAttribute(current2, replacePrimary);
94391
94583
  if (next === current2) continue;
94392
94584
  el.setAttribute("style", next);
94393
94585
  changed = true;
94394
94586
  }
94395
94587
  for (const el of Array.from(document2.querySelectorAll("[data-font-family]"))) {
94396
94588
  const current2 = el.getAttribute("data-font-family") ?? "";
94397
- const next = systemPrimaryReplacement(current2, deterministicPrimary);
94589
+ const next = replacePrimary(current2);
94398
94590
  if (!next) continue;
94399
94591
  el.setAttribute("data-font-family", next);
94400
94592
  changed = true;
@@ -94444,7 +94636,7 @@ function collectFontFamilyCustomProperties(html) {
94444
94636
  }
94445
94637
  return customProperties;
94446
94638
  }
94447
- function primaryCssVariableName(value) {
94639
+ function primaryCssVariable(value) {
94448
94640
  const trimmed = value.trim();
94449
94641
  if (!trimmed.toLowerCase().startsWith("var(")) return null;
94450
94642
  let depth = 0;
@@ -94457,21 +94649,35 @@ function primaryCssVariableName(value) {
94457
94649
  if (char !== ")") continue;
94458
94650
  depth -= 1;
94459
94651
  if (depth !== 0) continue;
94460
- const varExpression = trimmed.slice(0, index + 1);
94461
- const inner = varExpression.slice(4, -1).trim();
94652
+ const inner = trimmed.slice(4, index).trim();
94462
94653
  const commaIndex = inner.indexOf(",");
94463
- const variableName = (commaIndex === -1 ? inner : inner.slice(0, commaIndex)).trim();
94464
- return /^--[A-Za-z0-9_-]+$/.test(variableName) ? variableName : null;
94654
+ const name = (commaIndex === -1 ? inner : inner.slice(0, commaIndex)).trim();
94655
+ if (!/^--[A-Za-z0-9_-]+$/.test(name)) return null;
94656
+ return {
94657
+ name,
94658
+ fallback: commaIndex === -1 ? null : inner.slice(commaIndex + 1),
94659
+ rest: trimmed.slice(index + 1)
94660
+ };
94465
94661
  }
94466
94662
  return null;
94467
94663
  }
94468
- function resolveFontFamilyDeclarationFamilies(declaration, customProperties) {
94664
+ var MAX_CSS_VARIABLE_DEPTH = 8;
94665
+ function resolveDeclaredFamilies(declaration, customProperties, optional = false, depth = 0) {
94666
+ const tag = (families2) => families2.map((family) => ({ family, optional }));
94469
94667
  const families = parseFontFamilyValue(declaration);
94470
- const variableName = primaryCssVariableName(declaration);
94471
- if (!variableName) return families;
94472
- const resolved = customProperties.get(variableName);
94473
- if (!resolved) return families;
94474
- return [...parseFontFamilyValue(resolved), ...families.slice(1)];
94668
+ const variable = depth < MAX_CSS_VARIABLE_DEPTH ? primaryCssVariable(declaration) : null;
94669
+ if (!variable) return tag(families);
94670
+ const resolved = customProperties.get(variable.name);
94671
+ const source = resolved || variable.fallback;
94672
+ if (source === null) return tag(families);
94673
+ const primary = resolveDeclaredFamilies(
94674
+ source,
94675
+ customProperties,
94676
+ !resolved || depth > 0,
94677
+ depth + 1
94678
+ );
94679
+ if (primary.length === 0) return [];
94680
+ return [...primary, ...tag(families.slice(1))];
94475
94681
  }
94476
94682
  function* iterateFontFamilyDeclarations(html) {
94477
94683
  const { document: document2 } = parseHTML(html);
@@ -94596,18 +94802,22 @@ function extractExistingFontFaces(html) {
94596
94802
  }
94597
94803
  return families;
94598
94804
  }
94805
+ function isFetchableFamilyName(normalized2) {
94806
+ if (!normalized2 || normalized2.startsWith("var(")) return false;
94807
+ return !GENERIC_FAMILIES2.has(normalized2) && !CSS_WIDE_KEYWORDS.has(normalized2);
94808
+ }
94599
94809
  function extractRequestedFontFamilies(html) {
94600
94810
  const requested = /* @__PURE__ */ new Map();
94601
94811
  const customProperties = collectFontFamilyCustomProperties(html);
94602
94812
  for (const { declaration } of iterateFontFamilyDeclarations(html)) {
94603
- for (const originalCase of resolveFontFamilyDeclarationFamilies(
94604
- declaration,
94605
- customProperties
94606
- )) {
94607
- const normalized2 = originalCase.toLowerCase();
94608
- if (!normalized2 || GENERIC_FAMILIES2.has(normalized2)) continue;
94609
- if (normalized2.startsWith("var(")) continue;
94610
- if (!requested.has(normalized2)) requested.set(normalized2, originalCase);
94813
+ for (const { family, optional } of resolveDeclaredFamilies(declaration, customProperties)) {
94814
+ const normalized2 = family.toLowerCase();
94815
+ if (!isFetchableFamilyName(normalized2)) continue;
94816
+ const seen = requested.get(normalized2);
94817
+ requested.set(normalized2, {
94818
+ family: seen?.family ?? family,
94819
+ optional: (seen?.optional ?? true) && optional
94820
+ });
94611
94821
  }
94612
94822
  }
94613
94823
  return requested;
@@ -94685,7 +94895,10 @@ function partitionWeightRuns(faces, coveredWeights) {
94685
94895
  async function buildFontFaceCss(requestedFamilies, options, fontText) {
94686
94896
  const rules = [];
94687
94897
  const unresolved = [];
94688
- for (const [normalizedFamily, originalCaseFamily] of requestedFamilies) {
94898
+ const requiredFirst = [...requestedFamilies].sort(
94899
+ ([, a], [, b]) => Number(a.optional) - Number(b.optional)
94900
+ );
94901
+ for (const [normalizedFamily, { family: originalCaseFamily, optional }] of requiredFirst) {
94689
94902
  const canonicalKey = FONT_ALIASES[normalizedFamily];
94690
94903
  if (canonicalKey) {
94691
94904
  const canonical = CANONICAL_FONTS[canonicalKey];
@@ -94707,7 +94920,7 @@ async function buildFontFaceCss(requestedFamilies, options, fontText) {
94707
94920
  coveredWeights.add(coverageKey(face.weight, style));
94708
94921
  }
94709
94922
  const canonicalFamily = resolveAliasDisplayName(normalizedFamily);
94710
- const googleFaces2 = canonicalFamily ? await fetchGoogleFont(canonicalFamily, options, fontText) : [];
94923
+ const googleFaces2 = canonicalFamily ? await fetchFamilyFaces(canonicalFamily, optional, options, fontText) : [];
94711
94924
  const supplementary = googleFaces2.filter(
94712
94925
  (face) => !coveredWeights.has(coverageKey(face.weight, face.style)) || !isBundledSubsetRange(face.unicodeRange)
94713
94926
  );
@@ -94733,7 +94946,7 @@ async function buildFontFaceCss(requestedFamilies, options, fontText) {
94733
94946
  rules.push(...bundledRules);
94734
94947
  continue;
94735
94948
  }
94736
- const googleFaces = await fetchGoogleFont(originalCaseFamily, options, fontText);
94949
+ const googleFaces = await fetchFamilyFaces(originalCaseFamily, optional, options, fontText);
94737
94950
  if (googleFaces.length > 0) {
94738
94951
  for (const face of googleFaces) {
94739
94952
  rules.push(
@@ -94748,7 +94961,7 @@ async function buildFontFaceCss(requestedFamilies, options, fontText) {
94748
94961
  }
94749
94962
  continue;
94750
94963
  }
94751
- if (options.allowSystemFontCapture) {
94964
+ if (options.allowSystemFontCapture && !pageNamedThisFile(originalCaseFamily, options)) {
94752
94965
  const variants = locateSystemFontVariants(originalCaseFamily);
94753
94966
  if (variants.length > 0) {
94754
94967
  let totalBytes = 0;
@@ -94998,7 +95211,8 @@ function raceAgainstAbort(promise, signal) {
94998
95211
  });
94999
95212
  }
95000
95213
  function fetchGoogleFontCss(url, familyName, options) {
95001
- const key2 = `${options.failClosedFontFetch ? "closed" : "open"}:${url}`;
95214
+ const mode = options.failClosedFontFetch ? `closed${options.retryPolicy.maxAttempts}` : "open";
95215
+ const key2 = `${mode}:${url}`;
95002
95216
  let shared = googleFontCssCache.get(key2);
95003
95217
  if (!shared) {
95004
95218
  shared = fetchFontResource(
@@ -95018,35 +95232,66 @@ function fetchGoogleFontCss(url, familyName, options) {
95018
95232
  }
95019
95233
  return raceAgainstAbort(shared, options.abortSignal);
95020
95234
  }
95235
+ function pageNamedThisFile(familyName, options) {
95236
+ return options.authoredStylesheets.has(normalizeFamilyName(familyName.replace(/\+/g, " ")));
95237
+ }
95238
+ function declaredFaceFamily(block) {
95239
+ const found = /font-family:\s*['"]([^'"]+)['"]/i.exec(block);
95240
+ return found?.[1];
95241
+ }
95021
95242
  async function fetchGoogleFont(familyName, options, fontText) {
95022
- const slug = fontSlug(familyName);
95023
95243
  const googleFamilyName = familyName.replace(/\+/g, " ");
95024
95244
  const encodedFamily = encodeURIComponent(googleFamilyName);
95025
95245
  const textParam = fontText ? `&text=${encodeURIComponent(fontText)}` : "";
95026
- const url = `https://fonts.googleapis.com/css2?family=${encodedFamily}:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,400;1,700${textParam}`;
95027
- let cssText;
95246
+ const normalizedFamily = normalizeFamilyName(googleFamilyName);
95247
+ const authoredStylesheet = FONT_ALIASES[normalizedFamily] ? void 0 : options.authoredStylesheets.get(normalizedFamily);
95248
+ const urls = authoredStylesheet ? authoredStylesheet.map((url) => withPageText(url, fontText)) : [
95249
+ `https://fonts.googleapis.com/css2?family=${encodedFamily}:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,400;1,700${textParam}`
95250
+ ];
95251
+ const faces = [];
95252
+ for (const url of urls) {
95253
+ faces.push(...await fetchGoogleFontStylesheet(googleFamilyName, url, options));
95254
+ }
95255
+ return faces;
95256
+ }
95257
+ async function readGoogleFontStylesheet(familyName, url, options) {
95028
95258
  try {
95029
95259
  const cssResult = await fetchGoogleFontCss(url, familyName, options);
95030
95260
  if (!cssResult.ok) {
95031
- return [];
95261
+ return null;
95032
95262
  }
95033
- cssText = cssResult.body;
95263
+ return cssResult.body;
95034
95264
  } catch (err) {
95035
95265
  throwIfCallerAborted(options.abortSignal);
95036
95266
  if (err instanceof FontFetchError) throw err;
95037
95267
  if (options.failClosedFontFetch) {
95038
95268
  throw fontFetchError(familyName, url, "Google Fonts CSS", { error: err });
95039
95269
  }
95040
- return [];
95270
+ return null;
95041
95271
  }
95042
- const faceRegex = /@font-face\s*\{[^}]*font-style:\s*(normal|italic)[^}]*font-weight:\s*(\d+)[^}]*src:\s*url\(([^)]+)\)\s*format\(['"]woff2['"]\)(?:[^}]*?unicode-range:\s*([^;}]+))?[^}]*\}/gi;
95272
+ }
95273
+ function parseGoogleFontSource(match, familyName) {
95274
+ const declared = declaredFaceFamily(match[0]);
95275
+ if (declared && normalizeFamilyName(declared) !== normalizeFamilyName(familyName)) return null;
95276
+ const url = match[3];
95277
+ if (!url) return null;
95278
+ return {
95279
+ style: match[1] || "normal",
95280
+ weight: (match[2] || "400").replace(/\s+/g, " "),
95281
+ url,
95282
+ unicodeRange: match[4]?.trim() || void 0
95283
+ };
95284
+ }
95285
+ async function fetchGoogleFontStylesheet(familyName, url, options) {
95286
+ const slug = fontSlug(familyName);
95287
+ const cssText = await readGoogleFontStylesheet(familyName, url, options);
95288
+ if (!cssText) return [];
95289
+ const faceRegex = /@font-face\s*\{[^}]*font-style:\s*(normal|italic|oblique(?:\s+-?\d+(?:\.\d+)?deg){0,2})\s*;[^}]*font-weight:\s*(\d+(?:\s+\d+)?)[^}]*src:\s*url\(([^)]+)\)\s*format\(['"]woff2['"]\)(?:[^}]*?unicode-range:\s*([^;}]+))?[^}]*\}/gi;
95043
95290
  const faces = [];
95044
95291
  for (const match of cssText.matchAll(faceRegex)) {
95045
- const style = match[1] || "normal";
95046
- const weight = match[2] || "400";
95047
- const woff2Url = match[3] || "";
95048
- const unicodeRange = match[4]?.trim() || void 0;
95049
- if (!woff2Url) continue;
95292
+ const source = parseGoogleFontSource(match, familyName);
95293
+ if (!source) continue;
95294
+ const { weight, style, unicodeRange, url: woff2Url } = source;
95050
95295
  const cachePath = cachedWoff2Path(slug, weight, style, subsetToken(woff2Url));
95051
95296
  const dataUri = await ensureWoff2DataUri(
95052
95297
  cachePath,
@@ -95065,6 +95310,23 @@ async function fetchGoogleFont(familyName, options, fontText) {
95065
95310
  }
95066
95311
  return faces;
95067
95312
  }
95313
+ async function fetchFamilyFaces(familyName, optional, options, fontText) {
95314
+ if (!optional) return fetchGoogleFont(familyName, options, fontText);
95315
+ const maxAttempts = Math.min(options.retryPolicy.maxAttempts, 2);
95316
+ try {
95317
+ return await fetchGoogleFont(
95318
+ familyName,
95319
+ { ...options, retryPolicy: { ...options.retryPolicy, maxAttempts } },
95320
+ fontText
95321
+ );
95322
+ } catch (err) {
95323
+ if (!(err instanceof FontFetchUnavailableError)) throw err;
95324
+ defaultLogger.warn(
95325
+ `[Compiler] Optional font "${familyName}" is unavailable, rendering its fallback: ${err.message}`
95326
+ );
95327
+ return [];
95328
+ }
95329
+ }
95068
95330
  var GOOGLE_FONTS_TEXT_MAX_ENCODED_LENGTH = 1700;
95069
95331
  var SMALL_TO_FULL_KANA = /* @__PURE__ */ new Map([
95070
95332
  ["\u3041", "\u3042"],
@@ -95208,14 +95470,15 @@ async function injectDeterministicFontFaces(html, options = {}) {
95208
95470
  allowSystemFontCapture,
95209
95471
  abortSignal: options.abortSignal,
95210
95472
  retryPolicy,
95211
- retryDeadlineMs: Date.now() + retryPolicy.maxElapsedMs
95473
+ retryDeadlineMs: Date.now() + retryPolicy.maxElapsedMs,
95474
+ authoredStylesheets: authoredGoogleFontStylesheets(html)
95212
95475
  };
95213
95476
  const existingFaces = extractExistingFontFaces(html);
95214
95477
  const requestedFamilies = extractRequestedFontFamilies(html);
95215
95478
  const pendingFamilies = /* @__PURE__ */ new Map();
95216
- for (const [normalizedFamily, originalCaseFamily] of requestedFamilies) {
95479
+ for (const [normalizedFamily, requested] of requestedFamilies) {
95217
95480
  if (!existingFaces.has(normalizedFamily)) {
95218
- pendingFamilies.set(normalizedFamily, originalCaseFamily);
95481
+ pendingFamilies.set(normalizedFamily, requested);
95219
95482
  }
95220
95483
  }
95221
95484
  if (pendingFamilies.size === 0) {
@@ -95226,11 +95489,14 @@ async function injectDeterministicFontFaces(html, options = {}) {
95226
95489
  fetchOptions,
95227
95490
  extractGoogleFontsText(html)
95228
95491
  );
95229
- if (unresolved.length > 0 && options.failClosedFontFetch) {
95492
+ const required = unresolved.filter(
95493
+ (family) => !pendingFamilies.get(family.toLowerCase())?.optional
95494
+ );
95495
+ if (required.length > 0 && options.failClosedFontFetch) {
95230
95496
  throw new FontFetchError(
95231
- unresolved.join(", "),
95497
+ required.join(", "),
95232
95498
  "",
95233
- `[Compiler] Unresolved fonts in fail-closed mode: ${unresolved.join(", ")}. Distributed renders require all fonts to be resolvable.`
95499
+ `[Compiler] Unresolved fonts in fail-closed mode: ${required.join(", ")}. Distributed renders require all fonts to be resolvable.`
95234
95500
  );
95235
95501
  }
95236
95502
  if (!css) {
@@ -100053,7 +100319,17 @@ async function runCaptureHdrStage(input) {
100053
100319
 
100054
100320
  // ../producer/src/services/render/stages/encodeStage.ts
100055
100321
  init_src();
100056
- import { copyFileSync as copyFileSync5, existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync11, rmSync as rmSync13, statSync as statSync11 } from "fs";
100322
+ init_dist2();
100323
+ import {
100324
+ copyFileSync as copyFileSync5,
100325
+ existsSync as existsSync32,
100326
+ mkdirSync as mkdirSync18,
100327
+ readdirSync as readdirSync11,
100328
+ readFileSync as readFileSync15,
100329
+ rmSync as rmSync13,
100330
+ statSync as statSync11,
100331
+ writeFileSync as writeFileSync11
100332
+ } from "fs";
100057
100333
  import { dirname as dirname18, join as join46 } from "path";
100058
100334
 
100059
100335
  // ../producer/src/services/render/stages/gifEncodeArgs.ts
@@ -100061,15 +100337,23 @@ import { join as join45 } from "path";
100061
100337
  function fpsToFfmpegArg2(fps) {
100062
100338
  return fps.den === 1 ? String(fps.num) : `${fps.num}/${fps.den}`;
100063
100339
  }
100340
+ var KEEP_FILTER_GRAPH_WHEN_FRAMES_DROP_ALPHA = ["-reinit_filter", "0"];
100341
+ var WRITE_EVERY_FRAME_WHOLE = ["-gifflags", "0"];
100342
+ function framesInput(input, fpsArg) {
100343
+ return [
100344
+ "-framerate",
100345
+ fpsArg,
100346
+ ...KEEP_FILTER_GRAPH_WHEN_FRAMES_DROP_ALPHA,
100347
+ "-i",
100348
+ join45(input.framesDir, input.framePattern)
100349
+ ];
100350
+ }
100064
100351
  function buildGifPalettegenArgs(input) {
100065
100352
  const fpsArg = fpsToFfmpegArg2(input.fps);
100066
100353
  const transparency = input.preserveAlpha ? ":reserve_transparent=1" : "";
100067
100354
  return [
100068
100355
  "-y",
100069
- "-framerate",
100070
- fpsArg,
100071
- "-i",
100072
- join45(input.framesDir, input.framePattern),
100356
+ ...framesInput(input, fpsArg),
100073
100357
  "-vf",
100074
100358
  `fps=${fpsArg},palettegen=stats_mode=diff${transparency}`,
100075
100359
  input.palettePath
@@ -100080,16 +100364,14 @@ function buildGifPaletteuseArgs(input) {
100080
100364
  const transparency = input.preserveAlpha ? ":alpha_threshold=128" : "";
100081
100365
  return [
100082
100366
  "-y",
100083
- "-framerate",
100084
- fpsArg,
100085
- "-i",
100086
- join45(input.framesDir, input.framePattern),
100367
+ ...framesInput(input, fpsArg),
100087
100368
  "-i",
100088
100369
  input.palettePath,
100089
100370
  "-lavfi",
100090
100371
  `fps=${fpsArg} [x]; [x][1:v] paletteuse=dither=sierra2_4a${transparency}`,
100091
100372
  "-loop",
100092
100373
  String(input.loop),
100374
+ ...input.wholeFrames ? WRITE_EVERY_FRAME_WHOLE : [],
100093
100375
  input.outputPath
100094
100376
  ];
100095
100377
  }
@@ -100103,19 +100385,38 @@ function resolveGifLoop(loop) {
100103
100385
  }
100104
100386
  return resolved;
100105
100387
  }
100388
+ function gifEncodeFailure(startTime, outputPath, error, failureReason) {
100389
+ return {
100390
+ success: false,
100391
+ outputPath,
100392
+ durationMs: Date.now() - startTime,
100393
+ framesEncoded: 0,
100394
+ fileSize: 0,
100395
+ error,
100396
+ failureReason
100397
+ };
100398
+ }
100399
+ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
100400
+ const { outputPath } = argsInput;
100401
+ const result = await runFfmpeg(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
100402
+ if (!result.success) {
100403
+ const error = formatFfmpegError(result.exitCode, result.stderr);
100404
+ return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
100405
+ }
100406
+ const gif = readFileSync15(outputPath);
100407
+ if (!clearGifFramesBeforeNext(gif)) {
100408
+ const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
100409
+ return gifEncodeFailure(startTime, outputPath, error);
100410
+ }
100411
+ writeFileSync11(outputPath, gif);
100412
+ return null;
100413
+ }
100106
100414
  async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
100107
100415
  const startTime = Date.now();
100108
100416
  const files = readdirSync11(framesDir).filter((file) => file.match(/\.(jpg|jpeg|png)$/i));
100109
100417
  const frameCount = files.length;
100110
100418
  if (frameCount === 0) {
100111
- return {
100112
- success: false,
100113
- outputPath,
100114
- durationMs: Date.now() - startTime,
100115
- framesEncoded: 0,
100116
- fileSize: 0,
100117
- error: "[FFmpeg] No frame files found in directory"
100118
- };
100419
+ return gifEncodeFailure(startTime, outputPath, "[FFmpeg] No frame files found in directory");
100119
100420
  }
100120
100421
  const argsInput = {
100121
100422
  framesDir,
@@ -100127,35 +100428,17 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
100127
100428
  preserveAlpha: input.preserveAlpha
100128
100429
  };
100129
100430
  try {
100130
- const paletteResult = await runFfmpeg(buildGifPalettegenArgs(argsInput), {
100131
- signal: input.signal,
100132
- timeout: input.timeout
100133
- });
100134
- if (!paletteResult.success) {
100135
- return {
100136
- success: false,
100137
- outputPath,
100138
- durationMs: Date.now() - startTime,
100139
- framesEncoded: 0,
100140
- fileSize: 0,
100141
- error: formatFfmpegError(paletteResult.exitCode, paletteResult.stderr),
100142
- failureReason: paletteResult.failureReason
100143
- };
100431
+ const run = { signal: input.signal, timeout: input.timeout };
100432
+ for (const args of [buildGifPalettegenArgs(argsInput), buildGifPaletteuseArgs(argsInput)]) {
100433
+ const result = await runFfmpeg(args, run);
100434
+ if (!result.success) {
100435
+ const error = formatFfmpegError(result.exitCode, result.stderr);
100436
+ return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
100437
+ }
100144
100438
  }
100145
- const gifResult = await runFfmpeg(buildGifPaletteuseArgs(argsInput), {
100146
- signal: input.signal,
100147
- timeout: input.timeout
100148
- });
100149
- if (!gifResult.success) {
100150
- return {
100151
- success: false,
100152
- outputPath,
100153
- durationMs: Date.now() - startTime,
100154
- framesEncoded: 0,
100155
- fileSize: 0,
100156
- error: formatFfmpegError(gifResult.exitCode, gifResult.stderr),
100157
- failureReason: gifResult.failureReason
100158
- };
100439
+ if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync15(outputPath)) !== false) {
100440
+ const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
100441
+ if (failure) return failure;
100159
100442
  }
100160
100443
  const fileSize = existsSync32(outputPath) ? statSync11(outputPath).size : 0;
100161
100444
  return {
@@ -101841,7 +102124,7 @@ async function executeRenderPipeline(input) {
101841
102124
  throw new Error(`Entry file not found: ${htmlPath}`);
101842
102125
  }
101843
102126
  assertNotAborted();
101844
- const rawEntry = readFileSync15(htmlPath, "utf-8");
102127
+ const rawEntry = readFileSync16(htmlPath, "utf-8");
101845
102128
  if (entryFile !== "index.html" && rawEntry.trimStart().startsWith("<template")) {
101846
102129
  const wrapperPath = join48(workDir, "standalone-entry.html");
101847
102130
  const projectIndexPath = join48(projectDir, "index.html");
@@ -101851,7 +102134,7 @@ async function executeRenderPipeline(input) {
101851
102134
  );
101852
102135
  }
101853
102136
  const standaloneHtml = extractStandaloneEntryFromIndex(
101854
- readFileSync15(projectIndexPath, "utf-8"),
102137
+ readFileSync16(projectIndexPath, "utf-8"),
101855
102138
  entryFile,
101856
102139
  rawEntry
101857
102140
  );
@@ -101860,7 +102143,7 @@ async function executeRenderPipeline(input) {
101860
102143
  `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
101861
102144
  );
101862
102145
  }
101863
- writeFileSync11(wrapperPath, standaloneHtml, "utf-8");
102146
+ writeFileSync12(wrapperPath, standaloneHtml, "utf-8");
101864
102147
  htmlPath = wrapperPath;
101865
102148
  log.info("Extracted standalone entry from index.html host context", {
101866
102149
  entryFile
@@ -103334,7 +103617,7 @@ async function executeRenderPipeline(input) {
103334
103617
  job.perfSummary = perfSummary;
103335
103618
  if (job.config.debug) {
103336
103619
  try {
103337
- writeFileSync11(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
103620
+ writeFileSync12(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
103338
103621
  } catch (err) {
103339
103622
  log.debug("Failed to write perf summary", {
103340
103623
  perfOutputPath,
@@ -103687,7 +103970,7 @@ import {
103687
103970
  mkdirSync as mkdirSync20,
103688
103971
  statSync as statSync14,
103689
103972
  mkdtempSync as mkdtempSync12,
103690
- writeFileSync as writeFileSync12,
103973
+ writeFileSync as writeFileSync13,
103691
103974
  rmSync as rmSync16,
103692
103975
  createReadStream as createReadStream5
103693
103976
  } from "fs";
@@ -103703,7 +103986,7 @@ import { serve as serve3 } from "@hono/node-server";
103703
103986
  init_dist3();
103704
103987
  import {
103705
103988
  existsSync as existsSync34,
103706
- readFileSync as readFileSync16,
103989
+ readFileSync as readFileSync17,
103707
103990
  statSync as statSync13,
103708
103991
  openSync as openSync8,
103709
103992
  fstatSync as fstatSync5,
@@ -103748,7 +104031,7 @@ function readEntryHtml(filePath) {
103748
104031
  }
103749
104032
  try {
103750
104033
  if (!fstatSync5(fd).isFile()) return null;
103751
- return readFileSync16(fd, "utf-8");
104034
+ return readFileSync17(fd, "utf-8");
103752
104035
  } finally {
103753
104036
  closeSync9(fd);
103754
104037
  }
@@ -104102,7 +104385,7 @@ async function resolveInlineRenderHtml(body) {
104102
104385
  function materializeInlineProject(html, options) {
104103
104386
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir8();
104104
104387
  const tempProjectDir = mkdtempSync12(join51(tempRoot, "producer-project-"));
104105
- writeFileSync12(join51(tempProjectDir, "index.html"), html, "utf-8");
104388
+ writeFileSync13(join51(tempProjectDir, "index.html"), html, "utf-8");
104106
104389
  return {
104107
104390
  prepared: {
104108
104391
  input: { projectDir: tempProjectDir, ...options },
@@ -104552,11 +104835,11 @@ init_src();
104552
104835
  // ../producer/src/services/distributed/plan.ts
104553
104836
  init_dist2();
104554
104837
  init_src();
104555
- import { cpSync as cpSync2, existsSync as existsSync39, mkdirSync as mkdirSync22, renameSync as renameSync8, rmSync as rmSync17, writeFileSync as writeFileSync14 } from "fs";
104838
+ import { cpSync as cpSync2, existsSync as existsSync39, mkdirSync as mkdirSync22, renameSync as renameSync8, rmSync as rmSync17, writeFileSync as writeFileSync15 } from "fs";
104556
104839
  import { join as join55, relative as relative8, sep as sep5 } from "path";
104557
104840
 
104558
104841
  // ../producer/src/services/render/stages/freezePlan.ts
104559
- import { existsSync as existsSync37, mkdirSync as mkdirSync21, readFileSync as readFileSync17, readdirSync as readdirSync13, writeFileSync as writeFileSync13 } from "fs";
104842
+ import { existsSync as existsSync37, mkdirSync as mkdirSync21, readFileSync as readFileSync18, readdirSync as readdirSync13, writeFileSync as writeFileSync14 } from "fs";
104560
104843
  import { join as join52, relative as relative6, resolve as resolve16 } from "path";
104561
104844
 
104562
104845
  // ../producer/src/services/distributed/planProtocol.ts
@@ -104600,7 +104883,7 @@ var FIELD_DELIMITER = Buffer.from([0]);
104600
104883
  init_src();
104601
104884
  import { execFile as execFileCallback } from "child_process";
104602
104885
  import { dirname as dirname23, join as join53 } from "path";
104603
- import { existsSync as existsSync38, readFileSync as readFileSync18 } from "fs";
104886
+ import { existsSync as existsSync38, readFileSync as readFileSync19 } from "fs";
104604
104887
  import { fileURLToPath as fileURLToPath8 } from "url";
104605
104888
  import { promisify as promisify5 } from "util";
104606
104889
  var PLAN_AUDIO_RELATIVE_PATH = MIXED_AUDIO_FILENAME;
@@ -104636,13 +104919,13 @@ import {
104636
104919
  mkdirSync as mkdirSync24,
104637
104920
  mkdtempSync as mkdtempSync14,
104638
104921
  openSync as openSync9,
104639
- readFileSync as readFileSync19,
104922
+ readFileSync as readFileSync20,
104640
104923
  readSync as readSync3,
104641
104924
  readdirSync as readdirSync15,
104642
104925
  renameSync as renameSync10,
104643
104926
  rmSync as rmSync19,
104644
104927
  statSync as statSync16,
104645
- writeFileSync as writeFileSync16
104928
+ writeFileSync as writeFileSync17
104646
104929
  } from "fs";
104647
104930
  import { createHash as createHash14 } from "crypto";
104648
104931
  import { tmpdir as tmpdir9 } from "os";
@@ -104658,7 +104941,7 @@ import {
104658
104941
  renameSync as renameSync9,
104659
104942
  rmSync as rmSync18,
104660
104943
  statSync as statSync15,
104661
- writeFileSync as writeFileSync15
104944
+ writeFileSync as writeFileSync16
104662
104945
  } from "fs";
104663
104946
  import { dirname as dirname24, join as join57 } from "path";
104664
104947
 
@@ -104678,23 +104961,23 @@ import {
104678
104961
  existsSync as existsSync42,
104679
104962
  mkdirSync as mkdirSync25,
104680
104963
  mkdtempSync as mkdtempSync15,
104681
- readFileSync as readFileSync20,
104964
+ readFileSync as readFileSync21,
104682
104965
  readdirSync as readdirSync16,
104683
104966
  rmSync as rmSync20,
104684
104967
  statSync as statSync17,
104685
- writeFileSync as writeFileSync17
104968
+ writeFileSync as writeFileSync18
104686
104969
  } from "fs";
104687
104970
  import { dirname as dirname26, join as join59 } from "path";
104688
104971
 
104689
104972
  // ../producer/src/services/distributed/renderChunk.ts
104690
104973
  init_src();
104691
104974
  import { randomBytes } from "crypto";
104692
- import { existsSync as existsSync43, mkdirSync as mkdirSync26, readFileSync as readFileSync21, readdirSync as readdirSync17, rmSync as rmSync21, writeFileSync as writeFileSync18 } from "fs";
104975
+ import { existsSync as existsSync43, mkdirSync as mkdirSync26, readFileSync as readFileSync22, readdirSync as readdirSync17, rmSync as rmSync21, writeFileSync as writeFileSync19 } from "fs";
104693
104976
  import { extname as extname8, join as join60 } from "path";
104694
104977
  init_src();
104695
104978
 
104696
104979
  // ../producer/src/services/distributed/projectHash.ts
104697
- import { readdirSync as readdirSync18, readFileSync as readFileSync22 } from "fs";
104980
+ import { readdirSync as readdirSync18, readFileSync as readFileSync23 } from "fs";
104698
104981
  import { createHash as createHash15 } from "crypto";
104699
104982
  import { join as join62, relative as relative10 } from "path";
104700
104983
 
@@ -104737,8 +105020,8 @@ async function runFontLocalize(io, localize) {
104737
105020
  }
104738
105021
 
104739
105022
  // src/version.ts
104740
- var VERSION = true ? "0.8.81" : "0.0.0-dev";
104741
- var PRODUCER_VERSION = true ? "0.8.81" : "0.0.0-dev";
105023
+ var VERSION = true ? "0.8.82" : "0.0.0-dev";
105024
+ var PRODUCER_VERSION = true ? "0.8.82" : "0.0.0-dev";
104742
105025
 
104743
105026
  // src/fontLocalizeCli.ts
104744
105027
  async function readStdin() {