@liiift-studio/sanity-font-manager 2.5.0 → 2.5.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.
@@ -4488,14 +4488,14 @@ var extractFontMetadata = (font, title, weightKeywordList, italicKeywordList, pr
4488
4488
  weightName = expandAbbreviations(weightName);
4489
4489
  }
4490
4490
  const fullName = getNameString(font, 4) || (ttfFallbackMeta == null ? void 0 : ttfFallbackMeta.fullName) || "";
4491
- if ((weightName === "" || weightName.toLowerCase() === "roman") && fullName) {
4491
+ const axes = getVariationAxes(font);
4492
+ const variableFont = axes !== null;
4493
+ if (!variableFont && (weightName === "" || weightName.toLowerCase() === "roman") && fullName) {
4492
4494
  weightName = extractWeightFromFullName(font, title, ttfFallbackMeta);
4493
4495
  if (!preserveShortenedNames) {
4494
4496
  weightName = expandAbbreviations(weightName);
4495
4497
  }
4496
4498
  }
4497
- const axes = getVariationAxes(font);
4498
- const variableFont = axes !== null;
4499
4499
  const trimmedTitle = title.trim();
4500
4500
  const nameId4Remainder = fullName ? fullName.replace(trimmedTitle, "").trim() : "";
4501
4501
  const nameId1 = getNameString(font, 1) || (ttfFallbackMeta == null ? void 0 : ttfFallbackMeta.familyName) || "";
@@ -4779,6 +4779,7 @@ async function buildUploadPlan({
4779
4779
  onProgress
4780
4780
  }) {
4781
4781
  const plan = createEmptyPlan(settings);
4782
+ plan.settings.typefaceTitle = typefaceTitle;
4782
4783
  plan.phase = PLAN_PHASE.PROCESSING;
4783
4784
  plan.processingProgress.total = files.length;
4784
4785
  for (let i = 0; i < files.length; i++) {
@@ -5302,10 +5303,11 @@ function ExistingDocumentResolver({ decision, tempId, dispatch }) {
5302
5303
  // src/components/FontReviewCard.jsx
5303
5304
  var STANDARD_TYPES = ["ttf", "otf", "woff", "woff2"];
5304
5305
  var EXTENDED_TYPES = ["eot", "svg", "css", "woff2_subset", "woff2_web"];
5305
- var FontReviewCard = _react.memo.call(void 0, function FontReviewCard2({ entry, dispatch, allExpanded }) {
5306
+ var FontReviewCard = _react.memo.call(void 0, function FontReviewCard2({ entry, dispatch, allExpanded, typefaceTitle, price }) {
5306
5307
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
5307
5308
  const [expanded, setExpanded] = _react.useState.call(void 0, false);
5308
5309
  const [showAllFileTypes, setShowAllFileTypes] = _react.useState.call(void 0, false);
5310
+ const [showDocPreview, setShowDocPreview] = _react.useState.call(void 0, false);
5309
5311
  _react.useEffect.call(void 0, () => {
5310
5312
  setExpanded(allExpanded);
5311
5313
  }, [allExpanded]);
@@ -5513,7 +5515,32 @@ var FontReviewCard = _react.memo.call(void 0, function FontReviewCard2({ entry,
5513
5515
  tempId: entry.tempId,
5514
5516
  dispatch
5515
5517
  }
5516
- ), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { justify: "flex-end", gap: 2 }, hasUserOverrides && /* @__PURE__ */ _react2.default.createElement(
5518
+ ), /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(
5519
+ _ui.Button,
5520
+ {
5521
+ mode: "bleed",
5522
+ fontSize: 0,
5523
+ padding: 1,
5524
+ text: showDocPreview ? "Hide document preview" : "Show document preview",
5525
+ onClick: () => setShowDocPreview((v) => !v),
5526
+ style: { cursor: "pointer", alignSelf: "flex-start" }
5527
+ }
5528
+ ), showDocPreview && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { border: true, padding: 3, radius: 1, style: { fontFamily: "monospace", fontSize: 12 } }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, [
5529
+ ["_id", entry.documentId],
5530
+ ["_type", "font"],
5531
+ ["title", entry.title],
5532
+ ["slug", entry.documentId],
5533
+ ["typefaceName", typefaceTitle || "\u2014"],
5534
+ ["weightName", entry.weightName || "\u2014"],
5535
+ ["weight", entry.weight],
5536
+ ["style", entry.style],
5537
+ ["subfamily", entry.subfamily || "\u2014"],
5538
+ ["variableFont", String(entry.variableFont)],
5539
+ ["price", _nullishCoalesce(price, () => ( "\u2014"))],
5540
+ ["sell", price > 0 ? "true" : "false"],
5541
+ ["normalWeight", "true"],
5542
+ ["files", (entry.files || []).map((f) => f.name).join(", ") || "\u2014"]
5543
+ ].map(([key, value]) => /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { key, gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true, style: { width: 120, flexShrink: 0 } }, key), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, style: { wordBreak: "break-all" } }, String(value))))))), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { justify: "flex-end", gap: 2 }, hasUserOverrides && /* @__PURE__ */ _react2.default.createElement(
5517
5544
  _ui.Button,
5518
5545
  {
5519
5546
  mode: "ghost",
@@ -5922,15 +5949,20 @@ function UploadStep2Review({
5922
5949
  " ",
5923
5950
  sortBy === col.key ? sortDir === "asc" ? "\u2191" : "\u2193" : ""
5924
5951
  ))
5925
- ), Object.entries(groupedEntries).map(([subfamily, entries]) => /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { key: subfamily, space: 1 }, /* @__PURE__ */ _react2.default.createElement(_ui.Card, { padding: 2, radius: 1, style: { background: "var(--card-muted-bg-color)" } }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold" }, subfamily), /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { mode: "outline", fontSize: 0 }, entries.length))), /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 0 }, entries.map((entry) => /* @__PURE__ */ _react2.default.createElement(
5926
- FontReviewCard_default,
5927
- {
5928
- key: entry.tempId,
5929
- entry,
5930
- dispatch,
5931
- allExpanded
5932
- }
5933
- ))))), visibleEntries.length === 0 && fontEntries.length > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { border: true, padding: 4, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, muted: true, align: "center" }, "No fonts match the current filter")), isReviewing && validationErrors.length > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { tone: "caution", border: true, padding: 2, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 1 }, validationErrors.map((err, i) => /* @__PURE__ */ _react2.default.createElement(_ui.Text, { key: i, size: 0, tone: "caution" }, "\u2022 ", err)))), isReviewing && processedCount > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { justify: "flex-end", gap: 2, style: { position: "sticky", bottom: 0, background: "var(--card-bg-color)", paddingTop: 8, paddingBottom: 4 } }, /* @__PURE__ */ _react2.default.createElement(
5952
+ ), Object.entries(groupedEntries).map(([subfamily, entries]) => /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { key: subfamily, space: 1 }, /* @__PURE__ */ _react2.default.createElement(_ui.Card, { padding: 2, radius: 1, style: { background: "var(--card-muted-bg-color)" } }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold" }, subfamily), /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { mode: "outline", fontSize: 0 }, entries.length))), /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 0 }, entries.map((entry) => {
5953
+ var _a2, _b2;
5954
+ return /* @__PURE__ */ _react2.default.createElement(
5955
+ FontReviewCard_default,
5956
+ {
5957
+ key: entry.tempId,
5958
+ entry,
5959
+ dispatch,
5960
+ allExpanded,
5961
+ typefaceTitle: (_a2 = plan.settings) == null ? void 0 : _a2.typefaceTitle,
5962
+ price: (_b2 = plan.settings) == null ? void 0 : _b2.price
5963
+ }
5964
+ );
5965
+ })))), visibleEntries.length === 0 && fontEntries.length > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { border: true, padding: 4, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, muted: true, align: "center" }, "No fonts match the current filter")), isReviewing && validationErrors.length > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { tone: "caution", border: true, padding: 2, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 1 }, validationErrors.map((err, i) => /* @__PURE__ */ _react2.default.createElement(_ui.Text, { key: i, size: 0, tone: "caution" }, "\u2022 ", err)))), isReviewing && processedCount > 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { justify: "flex-end", gap: 2, style: { position: "sticky", bottom: 0, background: "var(--card-bg-color)", paddingTop: 8, paddingBottom: 4 } }, /* @__PURE__ */ _react2.default.createElement(
5934
5966
  _ui.Button,
5935
5967
  {
5936
5968
  mode: "default",
@@ -6168,6 +6200,169 @@ async function generateFontData({ fileInput, url, fontKit, fontId, client, commi
6168
6200
 
6169
6201
  // src/utils/parseVariableFontInstances.js
6170
6202
 
6203
+ var WIDTH_PREFIXES = [
6204
+ "XXXWide",
6205
+ "XXWide",
6206
+ "XWide",
6207
+ "Wide",
6208
+ "XXXNarrow",
6209
+ "XXNarrow",
6210
+ "XNarrow",
6211
+ "Narrow"
6212
+ ];
6213
+ function parseInstanceName(instanceName) {
6214
+ let subfamily = "";
6215
+ let remaining = instanceName.trim();
6216
+ for (const prefix of WIDTH_PREFIXES) {
6217
+ if (remaining.toLowerCase().startsWith(prefix.toLowerCase() + " ") || remaining.toLowerCase() === prefix.toLowerCase()) {
6218
+ subfamily = prefix;
6219
+ remaining = remaining.substring(prefix.length).trim();
6220
+ break;
6221
+ }
6222
+ }
6223
+ let style = "";
6224
+ for (const suffix of ["Backslant", "Slant", "Italic", "Oblique"]) {
6225
+ if (remaining.toLowerCase().endsWith(" " + suffix.toLowerCase()) || remaining.toLowerCase() === suffix.toLowerCase()) {
6226
+ style = suffix;
6227
+ remaining = remaining.substring(0, remaining.length - suffix.length).trim();
6228
+ break;
6229
+ }
6230
+ }
6231
+ return { subfamily, weight: remaining || "Regular", style };
6232
+ }
6233
+ function filterBySubfamily(staticFonts, instanceSubfamily, typefaceName) {
6234
+ if (!instanceSubfamily) {
6235
+ return staticFonts.filter((sf) => {
6236
+ const sub = (sf.subfamily || "").toLowerCase();
6237
+ if (sub === "" || sub === "regular") return true;
6238
+ const afterTypeface = (sf.title || "").replace(typefaceName, "").trim();
6239
+ return !WIDTH_PREFIXES.some((p) => afterTypeface.toLowerCase().startsWith(p.toLowerCase()));
6240
+ });
6241
+ }
6242
+ const lowerSf = instanceSubfamily.toLowerCase();
6243
+ const expanded = (expandAbbreviations(instanceSubfamily) || "").toLowerCase();
6244
+ return staticFonts.filter((sf) => {
6245
+ const sub = (sf.subfamily || "").toLowerCase();
6246
+ if (sub === lowerSf || expanded && sub === expanded) return true;
6247
+ const afterTypeface = (sf.title || "").replace(typefaceName, "").trim().toLowerCase();
6248
+ if (afterTypeface.startsWith(lowerSf)) return true;
6249
+ if (expanded && afterTypeface.startsWith(expanded)) return true;
6250
+ return false;
6251
+ });
6252
+ }
6253
+ var WEIGHT_MAP = [
6254
+ { term: "ultra", weight: 950 },
6255
+ { term: "xxlight", weight: 200 },
6256
+ { term: "xlight", weight: 250 },
6257
+ { term: "extralight", weight: 200 },
6258
+ { term: "extra light", weight: 200 },
6259
+ { term: "thin", weight: 100 },
6260
+ { term: "hairline", weight: 100 },
6261
+ { term: "light", weight: 300 },
6262
+ { term: "regular", weight: 400 },
6263
+ { term: "normal", weight: 400 },
6264
+ { term: "medium", weight: 500 },
6265
+ { term: "semibold", weight: 600 },
6266
+ { term: "semi bold", weight: 600 },
6267
+ { term: "extrabold", weight: 800 },
6268
+ { term: "extra bold", weight: 800 },
6269
+ { term: "xbold", weight: 800 },
6270
+ { term: "bold", weight: 700 },
6271
+ { term: "black", weight: 900 },
6272
+ { term: "heavy", weight: 900 }
6273
+ ];
6274
+ function weightFromName(name) {
6275
+ const lower = name.toLowerCase();
6276
+ for (const { term, weight } of WEIGHT_MAP) {
6277
+ if (lower === term || lower.includes(term)) return weight;
6278
+ }
6279
+ return 400;
6280
+ }
6281
+ var STRATEGIES = [
6282
+ // Pass 1: Exact title match (with typeface prefix)
6283
+ {
6284
+ name: "exact-title",
6285
+ match: (instanceName, parsed, candidates, typefaceName) => {
6286
+ const withPrefix = `${typefaceName} ${instanceName}`;
6287
+ return candidates.find((sf) => sf.title === instanceName || sf.title === withPrefix) || null;
6288
+ }
6289
+ },
6290
+ // Pass 2: Title normalisation — strip typeface name and compare remainder
6291
+ {
6292
+ name: "title-normalised",
6293
+ match: (instanceName, parsed, candidates, typefaceName) => {
6294
+ return candidates.find((sf) => {
6295
+ const sfName = (sf.title || "").replace(typefaceName, "").trim();
6296
+ if (sfName.toLowerCase() === instanceName.toLowerCase()) return true;
6297
+ if (parsed.weight === "Regular" && !parsed.style) {
6298
+ if (sfName.toLowerCase() === parsed.subfamily.toLowerCase()) return true;
6299
+ }
6300
+ return false;
6301
+ }) || null;
6302
+ }
6303
+ },
6304
+ // Pass 3: Abbreviation expansion (XLight → ExtraLight, XBold → ExtraBold)
6305
+ {
6306
+ name: "abbreviation",
6307
+ match: (instanceName, parsed, candidates, typefaceName) => {
6308
+ const expandedFull = instanceName.split(" ").map((w) => expandAbbreviations(w) || w).join(" ");
6309
+ let found = candidates.find((sf) => {
6310
+ const sfName = (sf.title || "").replace(typefaceName, "").trim();
6311
+ return sfName.toLowerCase() === expandedFull.toLowerCase();
6312
+ });
6313
+ if (found) return found;
6314
+ const expandedWeight = expandAbbreviations(parsed.weight) || parsed.weight;
6315
+ const target = [parsed.subfamily, expandedWeight, parsed.style].filter(Boolean).join(" ");
6316
+ return candidates.find((sf) => {
6317
+ const sfName = (sf.title || "").replace(typefaceName, "").trim();
6318
+ return sfName.toLowerCase() === target.toLowerCase();
6319
+ }) || null;
6320
+ }
6321
+ },
6322
+ // Pass 4: fullName metadata comparison
6323
+ {
6324
+ name: "metadata-fullName",
6325
+ match: (instanceName, parsed, candidates, typefaceName) => {
6326
+ return candidates.find((sf) => {
6327
+ var _a;
6328
+ if (!((_a = sf.metaData) == null ? void 0 : _a.fullName)) return false;
6329
+ const typefacePattern = new RegExp(`^${typefaceName}\\s+`, "i");
6330
+ const stylePart = sf.metaData.fullName.replace(typefacePattern, "").trim();
6331
+ return instanceName.toLowerCase() === stylePart.toLowerCase();
6332
+ }) || null;
6333
+ }
6334
+ },
6335
+ // Pass 5: Weight + style matching (numeric, within subfamily)
6336
+ {
6337
+ name: "weight-style",
6338
+ match: (instanceName, parsed, candidates) => {
6339
+ const instanceWeight = weightFromName(parsed.weight);
6340
+ const isBackslant = parsed.style.toLowerCase() === "backslant";
6341
+ const isSlant = parsed.style.toLowerCase() === "slant";
6342
+ const isItalic = parsed.style.toLowerCase() === "italic";
6343
+ return candidates.find((sf) => {
6344
+ var _a, _b;
6345
+ if (Number(sf.weight) !== instanceWeight) return false;
6346
+ if (isBackslant) return sf.style === "Italic" && ((_a = sf.title) == null ? void 0 : _a.toLowerCase().includes("backslant"));
6347
+ if (isSlant) return sf.style === "Italic" && !((_b = sf.title) == null ? void 0 : _b.toLowerCase().includes("backslant"));
6348
+ if (isItalic) return sf.style === "Italic";
6349
+ return sf.style === "Regular";
6350
+ }) || null;
6351
+ }
6352
+ },
6353
+ // Pass 6: weightName string comparison
6354
+ {
6355
+ name: "weightName",
6356
+ match: (instanceName, parsed, candidates) => {
6357
+ const cleanInstance = parsed.weight.toLowerCase().trim();
6358
+ return candidates.find((sf) => {
6359
+ if (!sf.weightName) return false;
6360
+ const cleanWeight = sf.weightName.toLowerCase().replace(/italic|slant|backslant/gi, "").trim();
6361
+ return cleanInstance === cleanWeight;
6362
+ }) || null;
6363
+ }
6364
+ }
6365
+ ];
6171
6366
  var parseVariableFontInstances = async (font, client) => {
6172
6367
  if (!font.variableFont || !font.variableInstances) return [];
6173
6368
  let variableInstances;
@@ -6182,14 +6377,7 @@ var parseVariableFontInstances = async (font, client) => {
6182
6377
  const typeface = await client.fetch(
6183
6378
  `*[_type == 'typeface' && title == $typefaceName][0]{
6184
6379
  'fonts': styles.fonts[]-> {
6185
- _id,
6186
- title,
6187
- subfamily,
6188
- style,
6189
- weight,
6190
- weightName,
6191
- metaData,
6192
- variableFont
6380
+ _id, title, subfamily, style, weight, weightName, metaData, variableFont
6193
6381
  }
6194
6382
  }`,
6195
6383
  { typefaceName: font.typefaceName }
@@ -6201,127 +6389,49 @@ var parseVariableFontInstances = async (font, client) => {
6201
6389
  console.warn("Typeface not found or no fonts in curated list, falling back to all fonts query");
6202
6390
  staticFonts = await client.fetch(
6203
6391
  `*[_type == 'font' && typefaceName == $typefaceName && variableFont != true]{
6204
- _id,
6205
- title,
6206
- subfamily,
6207
- style,
6208
- weight,
6209
- weightName,
6210
- metaData
6392
+ _id, title, subfamily, style, weight, weightName, metaData
6211
6393
  }`,
6212
6394
  { typefaceName: font.typefaceName }
6213
6395
  );
6214
6396
  }
6215
- console.log("Variable font instances:", Object.keys(variableInstances));
6216
- console.log("Available static fonts:", staticFonts.map((sf) => sf.title));
6217
- const instanceMappings = [];
6218
- Object.keys(variableInstances).forEach((instanceName) => {
6219
- let matchingFont = null;
6220
- matchingFont = staticFonts.find((sf) => sf.title === instanceName);
6221
- if (!matchingFont && staticFonts.some((sf) => {
6222
- var _a;
6223
- return (_a = sf.metaData) == null ? void 0 : _a.fullName;
6224
- })) {
6225
- matchingFont = staticFonts.find((sf) => {
6226
- var _a, _b, _c, _d;
6227
- if (!((_a = sf.metaData) == null ? void 0 : _a.fullName)) return false;
6228
- let fullName = sf.metaData.fullName;
6229
- const WORDS_TO_REMOVE = ["VF", "var", "variable", "VAR", "vf"];
6230
- const variableName = (_c = (_b = font.metaData) == null ? void 0 : _b.familyName) == null ? void 0 : _c.replace(new RegExp(`\\b(${WORDS_TO_REMOVE.join("|")})\\b`, "gi"), "").replace(/\s{2,}/g, " ").trim();
6231
- if (variableName && fullName.startsWith(variableName)) {
6232
- fullName = fullName.substring(variableName.length).trim();
6233
- }
6234
- if (variableName) {
6235
- const words = variableName.split(/\s+/).map((w) => w.trim()).filter(Boolean);
6236
- if (words.length > 0) {
6237
- const regex = new RegExp(`\\b(${words.join("|")})\\b`, "gi");
6238
- const stripped = fullName.replace(regex, "").replace(/\s{2,}/g, " ").trim();
6239
- if (stripped !== "") fullName = stripped;
6240
- }
6241
- }
6242
- if (fullName.startsWith(font.typefaceName)) {
6243
- fullName = fullName.substring(font.typefaceName.length).trim();
6244
- }
6245
- if (((_d = sf.style) == null ? void 0 : _d.toLowerCase()) === "italic" && !fullName.toLowerCase().endsWith("italic") && !fullName.toLowerCase().endsWith("slanted")) {
6246
- fullName = fullName + " Italic";
6247
- }
6248
- if (fullName.trim().toLowerCase().endsWith("regular")) {
6249
- if (instanceName.trim().toLowerCase() + " regular" === fullName.trim().toLowerCase()) return true;
6250
- }
6251
- if (fullName.trim().toLowerCase().startsWith("regular")) {
6252
- if ("regular " + instanceName.trim().toLowerCase() === fullName.trim().toLowerCase()) return true;
6253
- }
6254
- if (fullName.trim().toLowerCase().endsWith("italic")) {
6255
- if (instanceName.trim().toLowerCase().endsWith("italic")) {
6256
- const k = instanceName.trim().toLowerCase().slice(0, -6).trim() + " regular italic";
6257
- if (k === fullName.trim().toLowerCase()) return true;
6258
- }
6259
- }
6260
- return fullName.trim().toLowerCase() === instanceName.trim().toLowerCase();
6261
- });
6262
- }
6263
- if (!matchingFont) {
6264
- const expandedName = instanceName.split(" ").map((word) => expandAbbreviations(word)).join(" ");
6265
- matchingFont = staticFonts.find((sf) => {
6266
- const nameWithoutTypeface = sf.title.replace(font.typefaceName, "").trim();
6267
- return nameWithoutTypeface === expandedName;
6268
- });
6269
- }
6270
- if (!matchingFont) {
6271
- const isItalic = instanceName.toLowerCase().includes("italic");
6272
- const weightTerms = [
6273
- { term: "thin", weight: "100" },
6274
- { term: "extralight", weight: "200" },
6275
- { term: "extra light", weight: "200" },
6276
- { term: "light", weight: "300" },
6277
- { term: "regular", weight: "400" },
6278
- { term: "normal", weight: "400" },
6279
- { term: "medium", weight: "500" },
6280
- { term: "semibold", weight: "600" },
6281
- { term: "semi bold", weight: "600" },
6282
- { term: "bold", weight: "700" },
6283
- { term: "extrabold", weight: "800" },
6284
- { term: "extra bold", weight: "800" },
6285
- { term: "black", weight: "900" },
6286
- { term: "heavy", weight: "900" }
6287
- ];
6288
- let instanceWeight = "400";
6289
- for (const { term, weight } of weightTerms) {
6290
- if (instanceName.toLowerCase().includes(term)) {
6291
- instanceWeight = weight;
6292
- break;
6293
- }
6397
+ const instanceNames = Object.keys(variableInstances);
6398
+ console.log("Variable font instances:", instanceNames.length);
6399
+ console.log("Available static fonts:", staticFonts.length);
6400
+ const parsedInstances = instanceNames.map((name) => ({
6401
+ name,
6402
+ parsed: parseInstanceName(name)
6403
+ }));
6404
+ const results = /* @__PURE__ */ new Map();
6405
+ const claimedFontIds = /* @__PURE__ */ new Set();
6406
+ for (const strategy of STRATEGIES) {
6407
+ const unmatched = parsedInstances.filter((inst) => !results.has(inst.name));
6408
+ if (unmatched.length === 0) break;
6409
+ const passMatches = [];
6410
+ for (const inst of unmatched) {
6411
+ const subfamilyCandidates = filterBySubfamily(staticFonts, inst.parsed.subfamily, font.typefaceName).filter((sf) => !claimedFontIds.has(sf._id));
6412
+ const match = strategy.match(inst.name, inst.parsed, subfamilyCandidates, font.typefaceName, font);
6413
+ if (match) {
6414
+ passMatches.push({ instanceName: inst.name, font: match, strategy: strategy.name });
6294
6415
  }
6295
- matchingFont = staticFonts.find(
6296
- (sf) => sf.weight === instanceWeight && (isItalic && sf.style === "Italic" || !isItalic && sf.style === "Regular")
6297
- );
6298
6416
  }
6299
- if (!matchingFont) {
6300
- matchingFont = staticFonts.find((sf) => {
6301
- if (!sf.weightName) return false;
6302
- const cleanInstance = instanceName.toLowerCase().replace(/italic/i, "").trim();
6303
- const cleanWeight = sf.weightName.toLowerCase().replace(/italic/i, "").trim();
6304
- return cleanInstance === cleanWeight;
6305
- });
6306
- }
6307
- if (!matchingFont && staticFonts.some((sf) => {
6308
- var _a;
6309
- return (_a = sf.metaData) == null ? void 0 : _a.fullName;
6310
- })) {
6311
- matchingFont = staticFonts.find((sf) => {
6312
- var _a;
6313
- if (!((_a = sf.metaData) == null ? void 0 : _a.fullName)) return false;
6314
- const typefacePattern = new RegExp(`^${font.typefaceName}\\s+`, "i");
6315
- const stylePart = sf.metaData.fullName.replace(typefacePattern, "").trim();
6316
- return instanceName.toLowerCase() === stylePart.toLowerCase();
6317
- });
6417
+ for (const m of passMatches) {
6418
+ if (!claimedFontIds.has(m.font._id) && !results.has(m.instanceName)) {
6419
+ results.set(m.instanceName, { fontId: m.font._id, strategy: m.strategy });
6420
+ claimedFontIds.add(m.font._id);
6421
+ }
6318
6422
  }
6319
- console.log(`Instance "${instanceName}" matched with:`, matchingFont ? matchingFont.title : "No match found");
6320
- instanceMappings.push({
6423
+ }
6424
+ const matched = [...results.values()].length;
6425
+ console.log(`[parseVariableFontInstances] Matched ${matched}/${instanceNames.length} instances across ${STRATEGIES.length} passes`);
6426
+ const instanceMappings = instanceNames.map((instanceName) => {
6427
+ const result = results.get(instanceName);
6428
+ const matchedFont = result ? staticFonts.find((sf) => sf._id === result.fontId) : null;
6429
+ console.log(`Instance "${instanceName}" \u2192 ${matchedFont ? `${matchedFont.title} (${result.strategy})` : "No match"}`);
6430
+ return {
6321
6431
  key: instanceName,
6322
- value: matchingFont ? { _type: "reference", _ref: matchingFont._id, _weak: true } : null,
6432
+ value: matchedFont ? { _type: "reference", _ref: matchedFont._id, _weak: true } : null,
6323
6433
  _key: _nanoid.nanoid.call(void 0, )
6324
- });
6434
+ };
6325
6435
  });
6326
6436
  return instanceMappings;
6327
6437
  };
@@ -6332,9 +6442,20 @@ var parseVariableFontInstances_default = parseVariableFontInstances;
6332
6442
  var updateTypefaceDocument = async (doc_id, fontRefs, variableRefs, subfamilies, uniqueSubfamilies, subfamiliesArray, preferredStyleRef, newPreferredStyle, stylesObject, client, setStatus, setError) => {
6333
6443
  console.log("Updating typeface document with new fonts:", { fontRefs, variableRefs, subfamilies, uniqueSubfamilies });
6334
6444
  setStatus("Updating typeface references...");
6445
+ const dedupeRefs = (existing, incoming) => {
6446
+ const merged = [...existing || []];
6447
+ const existingRefs = new Set(merged.map((r) => r._ref).filter(Boolean));
6448
+ incoming.forEach((ref) => {
6449
+ if (ref._ref && !existingRefs.has(ref._ref)) {
6450
+ merged.push(ref);
6451
+ existingRefs.add(ref._ref);
6452
+ }
6453
+ });
6454
+ return merged;
6455
+ };
6335
6456
  let patch = {
6336
- "styles.fonts": stylesObject.fonts ? [...stylesObject.fonts, ...fontRefs] : [...fontRefs],
6337
- "styles.variableFont": (stylesObject == null ? void 0 : stylesObject.variableFont) ? [...stylesObject.variableFont, ...variableRefs] : [...variableRefs]
6457
+ "styles.fonts": dedupeRefs(stylesObject.fonts, fontRefs),
6458
+ "styles.variableFont": dedupeRefs(stylesObject == null ? void 0 : stylesObject.variableFont, variableRefs)
6338
6459
  };
6339
6460
  setStatus("Organising font subfamilies...");
6340
6461
  subfamiliesArray = subfamiliesArray || [];
@@ -6539,7 +6660,7 @@ async function executeUploadPlan({
6539
6660
  return result;
6540
6661
  }
6541
6662
  async function executeSingleFont({ entry, plan, client, progress, onProgress }) {
6542
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
6663
+ var _a, _b, _c, _d;
6543
6664
  const fontProgress = progress[entry.tempId];
6544
6665
  fontProgress.status = EXECUTION_STATUS.UPLOADING_ASSETS;
6545
6666
  if (onProgress) {
@@ -6656,7 +6777,7 @@ async function executeSingleFont({ entry, plan, client, progress, onProgress })
6656
6777
  _key: _nanoid.nanoid.call(void 0, ),
6657
6778
  title: entry.title,
6658
6779
  slug: { _type: "slug", current: fontDocId },
6659
- typefaceName: ((_g = (_f = (_e = plan.fonts[entry.tempId]) == null ? void 0 : _e.decisions) == null ? void 0 : _f.title) == null ? void 0 : _g.original) ? entry.title.split(" ").slice(0, -1).join(" ") || entry.title : entry.title,
6780
+ typefaceName: plan.settings.typefaceTitle || entry.title,
6660
6781
  style: entry.style,
6661
6782
  variableFont: entry.variableFont,
6662
6783
  weightName: entry.weightName,
@@ -6667,7 +6788,6 @@ async function executeSingleFont({ entry, plan, client, progress, onProgress })
6667
6788
  normalWeight: true,
6668
6789
  fileInput
6669
6790
  };
6670
- fontDoc.typefaceName = ((_j = (_i = (_h = Object.values(plan.fonts)[0]) == null ? void 0 : _h.decisions) == null ? void 0 : _i.title) == null ? void 0 : _j.original) ? plan.settings.typefaceTitle || fontDoc.typefaceName : fontDoc.typefaceName;
6671
6791
  if (entry.metaData) fontDoc.metaData = entry.metaData;
6672
6792
  if (entry.metrics) fontDoc.metrics = entry.metrics;
6673
6793
  if (entry.variableAxes) fontDoc.variableAxes = entry.variableAxes;
@@ -6900,6 +7020,291 @@ function UploadStep3Execute({
6900
7020
  }))), execState.error && /* @__PURE__ */ _react2.default.createElement(_ui.Card, { tone: "critical", border: true, padding: 3, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1 }, execState.error)));
6901
7021
  }
6902
7022
 
7023
+ // src/components/UploadStep3bInstances.jsx
7024
+
7025
+
7026
+
7027
+
7028
+ function UploadStep3bInstances({
7029
+ plan,
7030
+ executionResult,
7031
+ client,
7032
+ typefaceTitle,
7033
+ onComplete
7034
+ }) {
7035
+ const [loading, setLoading] = _react.useState.call(void 0, true);
7036
+ const [saving, setSaving] = _react.useState.call(void 0, false);
7037
+ const [vfMappings, setVfMappings] = _react.useState.call(void 0, {});
7038
+ const [allStaticFonts, setAllStaticFonts] = _react.useState.call(void 0, []);
7039
+ const [filterUnmatched, setFilterUnmatched] = _react.useState.call(void 0, false);
7040
+ const vfEntries = _react.useMemo.call(void 0,
7041
+ () => Object.values(plan.fonts).filter((f) => f.variableFont && f.status !== "error"),
7042
+ [plan.fonts]
7043
+ );
7044
+ const runMatching = _react.useCallback.call(void 0, async () => {
7045
+ var _a;
7046
+ setLoading(true);
7047
+ const mappings = {};
7048
+ let staticFonts = [];
7049
+ try {
7050
+ const typeface = await client.fetch(
7051
+ `*[_type == 'typeface' && title == $typefaceTitle][0]{
7052
+ 'fonts': styles.fonts[]-> {
7053
+ _id, title, subfamily, style, weight, weightName, metaData, variableFont
7054
+ }
7055
+ }`,
7056
+ { typefaceTitle }
7057
+ );
7058
+ if (((_a = typeface == null ? void 0 : typeface.fonts) == null ? void 0 : _a.length) > 0) {
7059
+ staticFonts = typeface.fonts.filter((f) => !f.variableFont);
7060
+ }
7061
+ if (staticFonts.length === 0) {
7062
+ staticFonts = await client.fetch(
7063
+ `*[_type == 'font' && typefaceName == $typefaceTitle && variableFont != true]{
7064
+ _id, title, subfamily, style, weight, weightName, metaData
7065
+ }`,
7066
+ { typefaceTitle }
7067
+ );
7068
+ }
7069
+ } catch (err) {
7070
+ console.error("[InstanceMapper] Failed to fetch static fonts:", err);
7071
+ }
7072
+ const deduped = /* @__PURE__ */ new Map();
7073
+ staticFonts.forEach((f) => {
7074
+ if (f._id && !deduped.has(f._id)) deduped.set(f._id, f);
7075
+ });
7076
+ staticFonts = [...deduped.values()];
7077
+ console.log(`[InstanceMapper] Found ${staticFonts.length} static fonts for "${typefaceTitle}"`);
7078
+ setAllStaticFonts(staticFonts);
7079
+ for (const vf of vfEntries) {
7080
+ try {
7081
+ const vfDoc = await client.fetch(
7082
+ `*[_id == $id][0]{
7083
+ _id, title, typefaceName, variableFont, variableInstances, metaData
7084
+ }`,
7085
+ { id: vf.documentId }
7086
+ );
7087
+ if (!vfDoc) {
7088
+ console.warn(`[InstanceMapper] VF document not found: ${vf.documentId}`);
7089
+ mappings[vf.tempId] = [];
7090
+ continue;
7091
+ }
7092
+ console.log(`[InstanceMapper] Running matcher for VF: ${vfDoc.title}, variableInstances: ${vfDoc.variableInstances ? "present" : "missing"}`);
7093
+ const instanceMappings = await parseVariableFontInstances(vfDoc, client);
7094
+ console.log(`[InstanceMapper] Matched ${instanceMappings.filter((m) => m.value).length}/${instanceMappings.length} instances for ${vfDoc.title}`);
7095
+ mappings[vf.tempId] = instanceMappings.map((m) => {
7096
+ var _a2;
7097
+ return {
7098
+ instanceName: m.key,
7099
+ matchedFontId: ((_a2 = m.value) == null ? void 0 : _a2._ref) || "",
7100
+ matchedFontTitle: "",
7101
+ _key: m._key || _nanoid.nanoid.call(void 0, )
7102
+ };
7103
+ });
7104
+ } catch (err) {
7105
+ console.error(`[InstanceMapper] Error matching VF ${vf.documentId}:`, err);
7106
+ mappings[vf.tempId] = [];
7107
+ }
7108
+ }
7109
+ const allMatchedIds = /* @__PURE__ */ new Set();
7110
+ Object.values(mappings).forEach((m) => m.forEach((i) => {
7111
+ if (i.matchedFontId) allMatchedIds.add(i.matchedFontId);
7112
+ }));
7113
+ if (allMatchedIds.size > 0) {
7114
+ try {
7115
+ const titles = await client.fetch(`*[_id in $ids]{ _id, title }`, { ids: [...allMatchedIds] });
7116
+ const titleMap = {};
7117
+ titles.forEach((t) => {
7118
+ titleMap[t._id] = t.title;
7119
+ });
7120
+ Object.values(mappings).forEach((m) => {
7121
+ m.forEach((i) => {
7122
+ if (i.matchedFontId) i.matchedFontTitle = titleMap[i.matchedFontId] || i.matchedFontId;
7123
+ });
7124
+ });
7125
+ } catch (err) {
7126
+ console.warn("[InstanceMapper] Failed to resolve font titles:", err);
7127
+ }
7128
+ }
7129
+ setVfMappings(mappings);
7130
+ setLoading(false);
7131
+ }, [vfEntries, client, typefaceTitle]);
7132
+ _react.useEffect.call(void 0, () => {
7133
+ runMatching();
7134
+ }, [runMatching]);
7135
+ const claimedFontIds = _react.useMemo.call(void 0, () => {
7136
+ const claimed = /* @__PURE__ */ new Set();
7137
+ Object.values(vfMappings).forEach((mappings) => {
7138
+ mappings.forEach((m) => {
7139
+ if (m.matchedFontId) claimed.add(m.matchedFontId);
7140
+ });
7141
+ });
7142
+ return claimed;
7143
+ }, [vfMappings]);
7144
+ const handleMappingChange = _react.useCallback.call(void 0, (vfTempId, instanceKey, fontId) => {
7145
+ const font = allStaticFonts.find((sf) => sf._id === fontId);
7146
+ setVfMappings((prev) => ({
7147
+ ...prev,
7148
+ [vfTempId]: prev[vfTempId].map(
7149
+ (m) => m._key === instanceKey ? { ...m, matchedFontId: fontId, matchedFontTitle: (font == null ? void 0 : font.title) || fontId } : m
7150
+ )
7151
+ }));
7152
+ }, [allStaticFonts]);
7153
+ const handleSave = _react.useCallback.call(void 0, async () => {
7154
+ setSaving(true);
7155
+ const errors = [];
7156
+ for (const vf of vfEntries) {
7157
+ const mappings = vfMappings[vf.tempId] || [];
7158
+ const references = mappings.filter((m) => m.matchedFontId).map((m) => ({
7159
+ _key: _nanoid.nanoid.call(void 0, ),
7160
+ _type: "object",
7161
+ key: m.instanceName,
7162
+ value: {
7163
+ _type: "reference",
7164
+ _ref: m.matchedFontId,
7165
+ _weak: true
7166
+ }
7167
+ }));
7168
+ try {
7169
+ await client.patch(vf.documentId).set({
7170
+ variableInstanceReferences: references
7171
+ }).commit();
7172
+ console.log(`Patched VF instance mappings: ${vf.documentId} (${references.length} instances)`);
7173
+ } catch (err) {
7174
+ console.error(`Failed to patch VF ${vf.documentId}:`, err);
7175
+ errors.push({ vfTitle: vf.title, error: err.message });
7176
+ }
7177
+ }
7178
+ setSaving(false);
7179
+ onComplete({ success: errors.length === 0, errors });
7180
+ }, [vfEntries, vfMappings, client, onComplete]);
7181
+ const totalInstances = Object.values(vfMappings).reduce((sum, m) => sum + m.length, 0);
7182
+ const matchedInstances = Object.values(vfMappings).reduce(
7183
+ (sum, m) => sum + m.filter((i) => i.matchedFontId).length,
7184
+ 0
7185
+ );
7186
+ const unmatchedInstances = totalInstances - matchedInstances;
7187
+ const getAutocompleteOptions = _react.useCallback.call(void 0, (currentFontId) => {
7188
+ return allStaticFonts.filter((sf) => !claimedFontIds.has(sf._id) || sf._id === currentFontId).map((sf) => ({
7189
+ value: sf._id,
7190
+ payload: sf
7191
+ }));
7192
+ }, [allStaticFonts, claimedFontIds]);
7193
+ if (loading) {
7194
+ return /* @__PURE__ */ _react2.default.createElement(_ui.Card, { border: true, padding: 4, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 3, justify: "center" }, /* @__PURE__ */ _react2.default.createElement(_ui.Spinner, null), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1 }, "Matching variable font instances to static fonts...")));
7195
+ }
7196
+ if (vfEntries.length === 0) {
7197
+ onComplete({ success: true, errors: [] });
7198
+ return null;
7199
+ }
7200
+ return /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 4 }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 2, weight: "semibold" }, "Map Variable Font Instances"), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, muted: true }, "Review the auto-matched instances below. Each named instance should map to its corresponding static font document.")), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 2, align: "center" }, /* @__PURE__ */ _react2.default.createElement(
7201
+ _ui.Button,
7202
+ {
7203
+ mode: "ghost",
7204
+ tone: "primary",
7205
+ icon: _icons.SearchIcon,
7206
+ text: "Re-run Matching",
7207
+ fontSize: 0,
7208
+ padding: 2,
7209
+ onClick: runMatching,
7210
+ disabled: loading,
7211
+ style: { cursor: "pointer" }
7212
+ }
7213
+ ), /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "positive", fontSize: 0 }, matchedInstances, " matched"), unmatchedInstances > 0 && /* @__PURE__ */ _react2.default.createElement(
7214
+ _ui.Badge,
7215
+ {
7216
+ tone: "critical",
7217
+ fontSize: 0,
7218
+ style: { cursor: "pointer" },
7219
+ onClick: () => setFilterUnmatched((v) => !v)
7220
+ },
7221
+ unmatchedInstances,
7222
+ " unmatched ",
7223
+ filterUnmatched ? "(showing)" : ""
7224
+ ), filterUnmatched && /* @__PURE__ */ _react2.default.createElement(
7225
+ _ui.Badge,
7226
+ {
7227
+ mode: "outline",
7228
+ fontSize: 0,
7229
+ style: { cursor: "pointer" },
7230
+ onClick: () => setFilterUnmatched(false)
7231
+ },
7232
+ "Clear filter"
7233
+ ), /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { mode: "outline", fontSize: 0 }, allStaticFonts.length, " fonts available")), vfEntries.map((vf) => {
7234
+ const mappings = vfMappings[vf.tempId] || [];
7235
+ const displayMappings = filterUnmatched ? mappings.filter((m) => !m.matchedFontId) : mappings;
7236
+ const vfMatched = mappings.filter((m) => m.matchedFontId).length;
7237
+ return /* @__PURE__ */ _react2.default.createElement(_ui.Card, { key: vf.tempId, border: true, padding: 3, radius: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 3 }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { tone: "primary", fontSize: 0 }, "VF"), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, weight: "semibold" }, vf.title), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true, style: { marginLeft: "auto" } }, vfMatched, "/", mappings.length, " matched")), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2, paddingY: 1, style: { borderBottom: "1px solid var(--card-border-color)" } }, /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { width: 20 } }), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, weight: "semibold", muted: true, style: { flex: 1 } }, "Instance"), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, weight: "semibold", muted: true, style: { flex: 2 } }, "Static Font Document")), /* @__PURE__ */ _react2.default.createElement(_ui.Stack, { space: 1 }, displayMappings.map((mapping) => {
7238
+ const isMatched = !!mapping.matchedFontId;
7239
+ const options = getAutocompleteOptions(mapping.matchedFontId);
7240
+ return /* @__PURE__ */ _react2.default.createElement(
7241
+ _ui.Flex,
7242
+ {
7243
+ key: mapping._key,
7244
+ align: "center",
7245
+ gap: 2,
7246
+ paddingY: 2,
7247
+ style: { borderBottom: "1px solid var(--card-border-color)" }
7248
+ },
7249
+ /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { width: 20, flexShrink: 0 } }, isMatched ? /* @__PURE__ */ _react2.default.createElement(_icons.CheckmarkCircleIcon, { style: { color: "#43b649", fontSize: 16 } }) : /* @__PURE__ */ _react2.default.createElement(_icons.CloseCircleIcon, { style: { color: "#f03e2f", fontSize: 16 } })),
7250
+ /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, style: { flex: 1, whiteSpace: "nowrap" } }, mapping.instanceName),
7251
+ /* @__PURE__ */ _react2.default.createElement(_ui.Box, { style: { flex: 2 } }, /* @__PURE__ */ _react2.default.createElement(
7252
+ _ui.Autocomplete,
7253
+ {
7254
+ id: `instance-${mapping._key}`,
7255
+ options,
7256
+ value: mapping.matchedFontId,
7257
+ placeholder: "Search for a font...",
7258
+ icon: _icons.SearchIcon,
7259
+ fontSize: 1,
7260
+ filterOption: (query, option) => {
7261
+ var _a, _b, _c, _d;
7262
+ const sf = option.payload;
7263
+ const q = query.toLowerCase();
7264
+ return ((_a = sf.title) == null ? void 0 : _a.toLowerCase().includes(q)) || ((_b = sf._id) == null ? void 0 : _b.toLowerCase().includes(q)) || ((_c = sf.weightName) == null ? void 0 : _c.toLowerCase().includes(q)) || String(sf.weight).includes(q) || ((_d = sf.subfamily) == null ? void 0 : _d.toLowerCase().includes(q));
7265
+ },
7266
+ renderOption: (option) => {
7267
+ const sf = option.payload;
7268
+ const isClaimed = claimedFontIds.has(sf._id) && sf._id !== mapping.matchedFontId;
7269
+ return /* @__PURE__ */ _react2.default.createElement(_ui.Card, { as: "button", padding: 2, style: { opacity: isClaimed ? 0.4 : 1 } }, /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { align: "center", gap: 2 }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1 }, sf.title), /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 0, muted: true }, sf.weight, " ", sf.style), sf.subfamily && sf.subfamily !== "Regular" && /* @__PURE__ */ _react2.default.createElement(_ui.Badge, { mode: "outline", fontSize: 0 }, sf.subfamily)));
7270
+ },
7271
+ renderValue: (value, option) => {
7272
+ if (option == null ? void 0 : option.payload) return option.payload.title;
7273
+ if (mapping.matchedFontTitle) return mapping.matchedFontTitle;
7274
+ const font = allStaticFonts.find((sf) => sf._id === value);
7275
+ return (font == null ? void 0 : font.title) || value;
7276
+ },
7277
+ onSelect: (value) => handleMappingChange(vf.tempId, mapping._key, value),
7278
+ openButton: true
7279
+ }
7280
+ ))
7281
+ );
7282
+ })), mappings.length === 0 && /* @__PURE__ */ _react2.default.createElement(_ui.Text, { size: 1, muted: true }, "No named instances found in this variable font.")));
7283
+ }), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { justify: "flex-end", gap: 2, style: { position: "sticky", bottom: 0, background: "var(--card-bg-color)", paddingTop: 8, paddingBottom: 4 } }, /* @__PURE__ */ _react2.default.createElement(
7284
+ _ui.Button,
7285
+ {
7286
+ mode: "ghost",
7287
+ text: "Skip \u2014 I'll map instances later",
7288
+ fontSize: 1,
7289
+ padding: 3,
7290
+ onClick: () => onComplete({ success: true, errors: [], skipped: true }),
7291
+ style: { cursor: "pointer" }
7292
+ }
7293
+ ), /* @__PURE__ */ _react2.default.createElement(
7294
+ _ui.Button,
7295
+ {
7296
+ mode: "default",
7297
+ tone: "positive",
7298
+ text: saving ? "Saving..." : `Save Mappings (${matchedInstances}/${totalInstances})`,
7299
+ fontSize: 1,
7300
+ padding: 3,
7301
+ disabled: saving,
7302
+ onClick: handleSave,
7303
+ style: { cursor: "pointer" }
7304
+ }
7305
+ )));
7306
+ }
7307
+
6903
7308
  // src/components/UploadSummary.jsx
6904
7309
 
6905
7310
 
@@ -6995,7 +7400,8 @@ function UploadSummary({
6995
7400
  var STEPS = [
6996
7401
  { key: 1, label: "Upload Files" },
6997
7402
  { key: 2, label: "Review" },
6998
- { key: 3, label: "Upload" }
7403
+ { key: 3, label: "Upload" },
7404
+ { key: 4, label: "Map Instances" }
6999
7405
  ];
7000
7406
  function phaseToStep(phase) {
7001
7407
  switch (phase) {
@@ -7031,10 +7437,17 @@ function UploadModal({
7031
7437
  const [processingCancelled, setProcessingCancelled] = _react.useState.call(void 0, false);
7032
7438
  const [executionResult, setExecutionResult] = _react.useState.call(void 0, null);
7033
7439
  const [retryTempIds, setRetryTempIds] = _react.useState.call(void 0, null);
7440
+ const [instanceMappingPhase, setInstanceMappingPhase] = _react.useState.call(void 0, false);
7441
+ const [instanceMappingResult, setInstanceMappingResult] = _react.useState.call(void 0, null);
7034
7442
  const cancelRef = _react.useRef.call(void 0, false);
7035
7443
  const focusRef = _react.useRef.call(void 0, null);
7036
7444
  const { weightKeywordList, italicKeywordList } = _react.useMemo.call(void 0, () => generateStyleKeywords(), []);
7037
- const currentStep = phaseToStep(plan.phase);
7445
+ const hasVFs = _react.useMemo.call(void 0,
7446
+ () => Object.values(plan.fonts).some((f) => f.variableFont && f.status !== "error"),
7447
+ [plan.fonts]
7448
+ );
7449
+ const baseStep = phaseToStep(plan.phase);
7450
+ const currentStep = instanceMappingPhase ? 4 : plan.phase === PLAN_PHASE.COMPLETE && !instanceMappingResult ? baseStep : baseStep;
7038
7451
  const isExecuting = plan.phase === PLAN_PHASE.EXECUTING;
7039
7452
  _react.useEffect.call(void 0, () => {
7040
7453
  if (!open || !isExecuting) return;
@@ -7061,7 +7474,7 @@ function UploadModal({
7061
7474
  onClose();
7062
7475
  }, [plan, isExecuting, onClose]);
7063
7476
  const handleStartProcessing = _react.useCallback.call(void 0, async (files, settings) => {
7064
- dispatch({ type: "SET_SETTINGS", settings });
7477
+ dispatch({ type: "SET_SETTINGS", settings: { ...settings, typefaceTitle } });
7065
7478
  dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.PROCESSING, totalFiles: files.length });
7066
7479
  cancelRef.current = false;
7067
7480
  setProcessingCancelled(false);
@@ -7104,7 +7517,16 @@ function UploadModal({
7104
7517
  }, []);
7105
7518
  const handleExecutionComplete = _react.useCallback.call(void 0, (result) => {
7106
7519
  setExecutionResult(result);
7107
- dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.COMPLETE });
7520
+ if (hasVFs && result.success !== false) {
7521
+ setInstanceMappingPhase(true);
7522
+ dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.COMPLETE });
7523
+ } else {
7524
+ dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.COMPLETE });
7525
+ }
7526
+ }, [hasVFs]);
7527
+ const handleInstanceMappingComplete = _react.useCallback.call(void 0, (result) => {
7528
+ setInstanceMappingResult(result);
7529
+ setInstanceMappingPhase(false);
7108
7530
  }, []);
7109
7531
  if (!open) return null;
7110
7532
  const handleStepClick = _react.useCallback.call(void 0, (stepKey) => {
@@ -7121,7 +7543,7 @@ function UploadModal({
7121
7543
  _ui.Dialog,
7122
7544
  {
7123
7545
  id: "upload-modal",
7124
- header: /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { direction: "column", gap: 3, style: { width: "100%" } }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { weight: "semibold", size: 2 }, "Upload Fonts"), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 1, style: { width: "100%" } }, STEPS.map((step, i) => {
7546
+ header: /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { direction: "column", gap: 3, style: { width: "100%" } }, /* @__PURE__ */ _react2.default.createElement(_ui.Text, { weight: "semibold", size: 2 }, "Upload Fonts"), /* @__PURE__ */ _react2.default.createElement(_ui.Flex, { gap: 1, style: { width: "100%" } }, STEPS.filter((step) => step.key !== 4 || hasVFs).map((step, i) => {
7125
7547
  const isActive = currentStep === step.key;
7126
7548
  const isCompleted = currentStep > step.key;
7127
7549
  const isClickable = !isExecuting && step.key < currentStep;
@@ -7192,15 +7614,27 @@ function UploadModal({
7192
7614
  handleExecutionComplete(result);
7193
7615
  }
7194
7616
  }
7195
- ), plan.phase === PLAN_PHASE.COMPLETE && /* @__PURE__ */ _react2.default.createElement(
7617
+ ), plan.phase === PLAN_PHASE.COMPLETE && instanceMappingPhase && /* @__PURE__ */ _react2.default.createElement(
7618
+ UploadStep3bInstances,
7619
+ {
7620
+ plan,
7621
+ executionResult,
7622
+ client,
7623
+ typefaceTitle,
7624
+ onComplete: handleInstanceMappingComplete
7625
+ }
7626
+ ), plan.phase === PLAN_PHASE.COMPLETE && !instanceMappingPhase && /* @__PURE__ */ _react2.default.createElement(
7196
7627
  UploadSummary,
7197
7628
  {
7198
7629
  plan,
7199
7630
  result: executionResult,
7631
+ instanceMappingResult,
7200
7632
  onClose: handleClose,
7201
7633
  onRetry: (failedTempIds) => {
7202
7634
  setRetryTempIds(failedTempIds || null);
7203
7635
  setExecutionResult(null);
7636
+ setInstanceMappingPhase(false);
7637
+ setInstanceMappingResult(null);
7204
7638
  dispatch({ type: "SET_PHASE", phase: PLAN_PHASE.EXECUTING });
7205
7639
  },
7206
7640
  client,
@@ -7273,4 +7707,5 @@ function UploadModal({
7273
7707
 
7274
7708
 
7275
7709
 
7276
- exports.parseFont = parseFont; exports.getNameString = getNameString; exports.getAllFeatureTags = getAllFeatureTags; exports.getCharacterSet = getCharacterSet; exports.getVariationAxes = getVariationAxes; exports.getNamedInstances = getNamedInstances; exports.getFontMetrics = getFontMetrics; exports.getFontMetadata = getFontMetadata; exports.getWeightClass = getWeightClass; exports.getFsSelection = getFsSelection; exports.getMacStyle = getMacStyle; exports.getItalicAngle = getItalicAngle; exports.getGlyphCount = getGlyphCount; exports.getFamilyClass = getFamilyClass; exports.escapeCssFontName = escapeCssFontName; exports.reverseSpellingLookup = reverseSpellingLookup; exports.expandAbbreviations = expandAbbreviations; exports.removeWeightNames = removeWeightNames; exports.generateStyleKeywords = generateStyleKeywords; exports.sanitizeForSanityId = sanitizeForSanityId; exports.readFontFile = readFontFile; exports.processFontFiles = processFontFiles; exports.extractFontMetadata = extractFontMetadata; exports.extractWeightName = extractWeightName; exports.extractWeightFromFullName = extractWeightFromFullName; exports.processSubfamilyName = processSubfamilyName; exports.processItalicKeywords = processItalicKeywords; exports.formatFontTitle = formatFontTitle; exports.addItalicToFontTitle = addItalicToFontTitle; exports.createFontObject = createFontObject; exports.determineWeight = determineWeight; exports.sortFontObjects = sortFontObjects; exports.logFontInfo = logFontInfo; exports.generateCssFile = generateCssFile; exports.generateFontData = generateFontData; exports.parseVariableFontInstances = parseVariableFontInstances; exports.parseVariableFontInstances_default = parseVariableFontInstances_default; exports.updateTypefaceDocument = updateTypefaceDocument; exports.PriceInput_default = PriceInput_default; exports.FONT_STATUS = FONT_STATUS; exports.PLAN_PHASE = PLAN_PHASE; exports.RECOMMENDATION = RECOMMENDATION; exports.EXECUTION_STATUS = EXECUTION_STATUS; exports.PLAN_VERSION = PLAN_VERSION; exports.createFontDecisions = createFontDecisions; exports.createEmptyPlan = createEmptyPlan; exports.planReducer = planReducer; exports.resolveExistingFont = resolveExistingFont; exports.buildUploadPlan = buildUploadPlan; exports.UploadStep1Settings = UploadStep1Settings; exports.ExistingDocumentResolver = ExistingDocumentResolver; exports.FontReviewCard_default = FontReviewCard_default; exports.BulkActions = BulkActions; exports.UploadStep2Review = UploadStep2Review; exports.executeUploadPlan = executeUploadPlan; exports.createInitialExecutionState = createInitialExecutionState; exports.executionReducer = executionReducer; exports.UploadStep3Execute = UploadStep3Execute; exports.UploadSummary = UploadSummary; exports.UploadModal = UploadModal;
7710
+
7711
+ exports.parseFont = parseFont; exports.getNameString = getNameString; exports.getAllFeatureTags = getAllFeatureTags; exports.getCharacterSet = getCharacterSet; exports.getVariationAxes = getVariationAxes; exports.getNamedInstances = getNamedInstances; exports.getFontMetrics = getFontMetrics; exports.getFontMetadata = getFontMetadata; exports.getWeightClass = getWeightClass; exports.getFsSelection = getFsSelection; exports.getMacStyle = getMacStyle; exports.getItalicAngle = getItalicAngle; exports.getGlyphCount = getGlyphCount; exports.getFamilyClass = getFamilyClass; exports.escapeCssFontName = escapeCssFontName; exports.reverseSpellingLookup = reverseSpellingLookup; exports.expandAbbreviations = expandAbbreviations; exports.removeWeightNames = removeWeightNames; exports.generateStyleKeywords = generateStyleKeywords; exports.sanitizeForSanityId = sanitizeForSanityId; exports.readFontFile = readFontFile; exports.processFontFiles = processFontFiles; exports.extractFontMetadata = extractFontMetadata; exports.extractWeightName = extractWeightName; exports.extractWeightFromFullName = extractWeightFromFullName; exports.processSubfamilyName = processSubfamilyName; exports.processItalicKeywords = processItalicKeywords; exports.formatFontTitle = formatFontTitle; exports.addItalicToFontTitle = addItalicToFontTitle; exports.createFontObject = createFontObject; exports.determineWeight = determineWeight; exports.sortFontObjects = sortFontObjects; exports.logFontInfo = logFontInfo; exports.generateCssFile = generateCssFile; exports.generateFontData = generateFontData; exports.parseVariableFontInstances = parseVariableFontInstances; exports.parseVariableFontInstances_default = parseVariableFontInstances_default; exports.updateTypefaceDocument = updateTypefaceDocument; exports.PriceInput_default = PriceInput_default; exports.FONT_STATUS = FONT_STATUS; exports.PLAN_PHASE = PLAN_PHASE; exports.RECOMMENDATION = RECOMMENDATION; exports.EXECUTION_STATUS = EXECUTION_STATUS; exports.PLAN_VERSION = PLAN_VERSION; exports.createFontDecisions = createFontDecisions; exports.createEmptyPlan = createEmptyPlan; exports.planReducer = planReducer; exports.resolveExistingFont = resolveExistingFont; exports.buildUploadPlan = buildUploadPlan; exports.UploadStep1Settings = UploadStep1Settings; exports.ExistingDocumentResolver = ExistingDocumentResolver; exports.FontReviewCard_default = FontReviewCard_default; exports.BulkActions = BulkActions; exports.UploadStep2Review = UploadStep2Review; exports.executeUploadPlan = executeUploadPlan; exports.createInitialExecutionState = createInitialExecutionState; exports.executionReducer = executionReducer; exports.UploadStep3Execute = UploadStep3Execute; exports.UploadStep3bInstances = UploadStep3bInstances; exports.UploadSummary = UploadSummary; exports.UploadModal = UploadModal;