@motion-proto/live-tokens 0.55.0 → 0.56.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.
Files changed (38) hide show
  1. package/.claude/skills/{live-tokens-adjust-shape-space → live-tokens-adjust-geometry}/SKILL.md +5 -5
  2. package/.claude/skills/live-tokens-build-page/SKILL.md +2 -0
  3. package/.claude/skills/live-tokens-create-component/SKILL.md +20 -357
  4. package/.claude/skills/live-tokens-create-component/references/fixed-overlays.md +3 -0
  5. package/.claude/skills/live-tokens-create-component/references/intrinsics.md +58 -0
  6. package/.claude/skills/live-tokens-create-component/references/linked-siblings.md +70 -0
  7. package/.claude/skills/live-tokens-generate-theme/SKILL.md +79 -98
  8. package/.claude/skills/live-tokens-generate-theme/references/named-themes.md +18 -0
  9. package/.claude/skills/live-tokens-pair-fonts/SKILL.md +88 -0
  10. package/.claude/skills/live-tokens-pick-component/SKILL.md +4 -1
  11. package/CHANGELOG.md +108 -0
  12. package/README.md +20 -7
  13. package/bin/cli.mjs +41 -3
  14. package/bin/set-fonts.mjs +280 -0
  15. package/dist-plugin/adjust/index.d.cts +2 -2
  16. package/dist-plugin/adjust/index.d.ts +2 -2
  17. package/dist-plugin/fontPairing/index.cjs +411 -0
  18. package/dist-plugin/fontPairing/index.d.cts +109 -0
  19. package/dist-plugin/fontPairing/index.d.ts +109 -0
  20. package/dist-plugin/fontPairing/index.js +320 -0
  21. package/dist-plugin/generateColorsAndType/index.d.cts +2 -2
  22. package/dist-plugin/generateColorsAndType/index.d.ts +2 -2
  23. package/dist-plugin/index-4N-Orzzi.d.cts +3 -0
  24. package/dist-plugin/index-4N-Orzzi.d.ts +3 -0
  25. package/dist-plugin/{index-DpTIRZ2H.d.cts → themeTypes-BAqtv4XO.d.cts} +1 -3
  26. package/dist-plugin/{index-DpTIRZ2H.d.ts → themeTypes-BAqtv4XO.d.ts} +1 -3
  27. package/package.json +3 -2
  28. package/src/editor/core/fonts/applyFontPairing.ts +157 -0
  29. package/src/editor/core/fonts/fontPairing.ts +13 -6
  30. package/src/editor/core/fonts/googleFontsUrl.ts +123 -0
  31. package/src/editor/core/fonts/weightCoverage.ts +92 -0
  32. package/src/editor/docs/content/themes-workflow.md +39 -0
  33. package/src/editor/docs/content.generated.ts +1 -1
  34. package/src/editor/ui/ColorEditPanel.svelte +16 -1
  35. package/src/editor/ui/PaletteEditor.svelte +124 -31
  36. package/src/editor/ui/ProjectFontsSection.svelte +24 -36
  37. package/src/editor/ui/palette/OverridesPanel.svelte +5 -2
  38. package/src/editor/ui/palette/PaletteBase.svelte +13 -0
@@ -0,0 +1,411 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // vite-plugin/fontPairing/index.ts
31
+ var fontPairing_exports = {};
32
+ __export(fontPairing_exports, {
33
+ SLOT_ORDER: () => SLOT_ORDER,
34
+ SLOT_VARIABLES: () => SLOT_VARIABLES,
35
+ applyFontPairing: () => applyFontPairing,
36
+ discoveryUrl: () => discoveryUrl,
37
+ fontPairingLabel: () => fontPairingLabel,
38
+ persistUrlFor: () => persistUrlFor,
39
+ readLiveTokensConfig: () => readLiveTokensConfig,
40
+ requiredWeights: () => requiredWeights,
41
+ resolveDataDirs: () => resolveDataDirs,
42
+ resolveGoogleFont: () => resolveGoogleFont,
43
+ slugifyFamily: () => slugifyFamily,
44
+ weightCoverage: () => weightCoverage
45
+ });
46
+ module.exports = __toCommonJS(fontPairing_exports);
47
+
48
+ // src/editor/core/fonts/fontParse.ts
49
+ function parseGoogleFontsUrl(url2) {
50
+ let u;
51
+ try {
52
+ u = new URL(url2);
53
+ } catch {
54
+ return null;
55
+ }
56
+ if (!u.hostname.includes("fonts.googleapis.com")) return null;
57
+ const familyParams = u.searchParams.getAll("family");
58
+ if (familyParams.length === 0) return null;
59
+ return familyParams.map((raw) => {
60
+ const [rawName, spec] = raw.split(":");
61
+ const name = decodeURIComponent(rawName).replace(/\+/g, " ");
62
+ const parsed = { name };
63
+ if (spec) {
64
+ const axisMatch = spec.match(/(?:ital,)?wght@([^&]+)/);
65
+ const italics = spec.startsWith("ital,");
66
+ if (italics) parsed.italics = true;
67
+ if (axisMatch) {
68
+ const tokens = axisMatch[1].split(";");
69
+ const weights = /* @__PURE__ */ new Set();
70
+ for (const tok of tokens) {
71
+ const parts = tok.split(",");
72
+ const wghtStr = parts.length === 2 ? parts[1] : parts[0];
73
+ if (wghtStr.includes("..")) {
74
+ const [lo, hi] = wghtStr.split("..").map((n) => parseInt(n, 10));
75
+ if (!isNaN(lo) && !isNaN(hi)) {
76
+ for (let w = lo; w <= hi; w += 100) weights.add(w);
77
+ }
78
+ } else {
79
+ const w = parseInt(wghtStr, 10);
80
+ if (!isNaN(w)) weights.add(w);
81
+ }
82
+ }
83
+ if (weights.size > 0) parsed.weights = [...weights].sort((a, b) => a - b);
84
+ }
85
+ }
86
+ return parsed;
87
+ });
88
+ }
89
+ function parseFontFaceText(css) {
90
+ const rules = [...css.matchAll(/@font-face\s*\{([^}]*)\}/g)];
91
+ const byName = /* @__PURE__ */ new Map();
92
+ for (const [, body] of rules) {
93
+ const nameMatch = body.match(/font-family\s*:\s*([^;]+);/i);
94
+ if (!nameMatch) continue;
95
+ const name = nameMatch[1].trim().replace(/^['"]|['"]$/g, "");
96
+ const weightMatch = body.match(/font-weight\s*:\s*([^;]+);/i);
97
+ const styleMatch = body.match(/font-style\s*:\s*([^;]+);/i);
98
+ const entry = byName.get(name) ?? { weights: /* @__PURE__ */ new Set(), italics: false };
99
+ if (weightMatch) {
100
+ const val = weightMatch[1].trim();
101
+ const rangeParts = val.split(/\s+/).map((s) => parseInt(s, 10)).filter((n) => !isNaN(n));
102
+ if (rangeParts.length === 2) {
103
+ const [lo, hi] = rangeParts;
104
+ for (let w = lo; w <= hi; w += 100) entry.weights.add(w);
105
+ } else if (rangeParts.length === 1) {
106
+ entry.weights.add(rangeParts[0]);
107
+ }
108
+ }
109
+ if (styleMatch && /italic|oblique/i.test(styleMatch[1])) entry.italics = true;
110
+ byName.set(name, entry);
111
+ }
112
+ return [...byName.entries()].map(([name, e]) => ({
113
+ name,
114
+ ...e.weights.size > 0 ? { weights: [...e.weights].sort((a, b) => a - b) } : {},
115
+ ...e.italics ? { italics: true } : {}
116
+ }));
117
+ }
118
+
119
+ // src/editor/core/fonts/fontPairing.ts
120
+ var PAIRING = ["--font-display", "--font-sans"];
121
+ function familyIndex(sources) {
122
+ const familyById = /* @__PURE__ */ new Map();
123
+ for (const source of sources) {
124
+ for (const family of source.families) familyById.set(family.id, family);
125
+ }
126
+ return familyById;
127
+ }
128
+ function resolvedFaceName(stack, familyById) {
129
+ for (const slot of stack?.slots ?? []) {
130
+ if (slot.kind === "project") {
131
+ const family = familyById.get(slot.familyId);
132
+ if (family) return family.name;
133
+ continue;
134
+ }
135
+ if (slot.kind === "system") return "System";
136
+ return slot.value;
137
+ }
138
+ return null;
139
+ }
140
+ function fontPairingLabel(stacks, sources) {
141
+ const familyById = familyIndex(sources);
142
+ return PAIRING.map((variable) => resolvedFaceName(stacks.find((s) => s.variable === variable), familyById)).filter((label) => label !== null).join(" / ");
143
+ }
144
+
145
+ // src/editor/core/fonts/applyFontPairing.ts
146
+ var SLOT_ORDER = ["display", "body", "serif", "mono"];
147
+ var SLOT_VARIABLES = {
148
+ display: "--font-display",
149
+ body: "--font-sans",
150
+ serif: "--font-serif",
151
+ mono: "--font-mono"
152
+ };
153
+ function slugifyFamily(name) {
154
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
155
+ }
156
+ function weightsFor(face) {
157
+ if (face.weights) {
158
+ return { weights: face.weights, ...face.italics ? { italics: true } : {} };
159
+ }
160
+ const parsed = parseGoogleFontsUrl(face.url)?.[0];
161
+ return {
162
+ ...parsed?.weights ? { weights: parsed.weights } : {},
163
+ ...face.italics || parsed?.italics ? { italics: true } : {}
164
+ };
165
+ }
166
+ function sourceFor(face, idPrefix) {
167
+ const id = `${idPrefix}${slugifyFamily(face.name)}`;
168
+ return {
169
+ id,
170
+ kind: "google",
171
+ url: face.url,
172
+ label: "Google Fonts",
173
+ families: [
174
+ {
175
+ id: `${id}:${slugifyFamily(face.name)}`,
176
+ name: face.name,
177
+ cssName: `"${face.name}"`,
178
+ ...weightsFor(face)
179
+ }
180
+ ]
181
+ };
182
+ }
183
+ function rewriteStack(stacks, variable, familyId) {
184
+ const stack = stacks.find((s) => s.variable === variable);
185
+ if (!stack) throw new Error(`colors and type have no ${variable} stack to rewrite`);
186
+ return stacks.map(
187
+ (s) => s === stack ? { ...s, slots: [{ kind: "project", familyId }, ...s.slots.filter((slot) => slot.kind !== "project")] } : s
188
+ );
189
+ }
190
+ function applyFontPairing(colorsAndType, pairing, { idPrefix = "src_google_" } = {}) {
191
+ const slots = SLOT_ORDER.filter((slot) => pairing[slot]);
192
+ if (slots.length === 0) throw new Error("pairing names no slot to change");
193
+ const before = colorsAndType.fontStacks ?? [];
194
+ const beforeSources = colorsAndType.fontSources ?? [];
195
+ const beforeIndex = familyIndex(beforeSources);
196
+ const stamped = slots.map((slot) => ({ slot, source: sourceFor(pairing[slot], idPrefix) }));
197
+ const stampedIds = new Set(stamped.map((s) => s.source.id));
198
+ let stacks = before;
199
+ const changes = [];
200
+ for (const { slot, source } of stamped) {
201
+ const variable = SLOT_VARIABLES[slot];
202
+ const from = resolvedFaceName(before.find((s) => s.variable === variable), beforeIndex);
203
+ stacks = rewriteStack(stacks, variable, source.families[0].id);
204
+ changes.push({ slot, variable, from, to: source.families[0].name });
205
+ }
206
+ const referenced = new Set(
207
+ stacks.flatMap((s) => s.slots).filter((s) => s.kind === "project").map((s) => s.familyId)
208
+ );
209
+ const kept = [...beforeSources.filter((s) => !stampedIds.has(s.id)), ...stamped.map((s) => s.source)];
210
+ const sources = kept.filter((s) => s.families.some((f) => referenced.has(f.id)));
211
+ const dropped = kept.filter((s) => !sources.includes(s)).map((s) => ({ id: s.id, names: s.families.map((f) => f.name) }));
212
+ const next = { ...colorsAndType, fontSources: sources, fontStacks: stacks };
213
+ const changed = JSON.stringify([beforeSources, before]) !== JSON.stringify([sources, stacks]);
214
+ return {
215
+ colorsAndType: next,
216
+ report: { changes: changes.filter((c) => c.from !== c.to), dropped, changed }
217
+ };
218
+ }
219
+
220
+ // src/editor/core/fonts/googleFontsUrl.ts
221
+ var LADDER = [100, 200, 300, 400, 500, 600, 700, 800, 900];
222
+ function familyParam(name) {
223
+ return name.trim().replace(/\s+/g, "+");
224
+ }
225
+ function url(name, spec) {
226
+ const family = spec ? `${familyParam(name)}:${spec}` : familyParam(name);
227
+ return `https://fonts.googleapis.com/css2?family=${family}&display=swap`;
228
+ }
229
+ function discoveryUrl(name) {
230
+ const pairs = [
231
+ ...LADDER.map((w) => `0,${w}`),
232
+ ...LADDER.map((w) => `1,${w}`)
233
+ ];
234
+ return url(name, `ital,wght@${pairs.join(";")}`);
235
+ }
236
+ function isContiguous(weights) {
237
+ return weights.length > 1 && weights.every((w, i) => i === 0 || w - weights[i - 1] === 100);
238
+ }
239
+ function persistUrlFor(name, weights, italics) {
240
+ if (weights.length === 0 || weights.length === 1 && weights[0] === 400 && !italics) {
241
+ return url(name);
242
+ }
243
+ if (isContiguous(weights)) {
244
+ const range = `${weights[0]}..${weights[weights.length - 1]}`;
245
+ return url(name, italics ? `ital,wght@0,${range};1,${range}` : `wght@${range}`);
246
+ }
247
+ const spec = italics ? `ital,wght@${[...weights.map((w) => `0,${w}`), ...weights.map((w) => `1,${w}`)].join(";")}` : `wght@${weights.join(";")}`;
248
+ return url(name, spec);
249
+ }
250
+ function censusFrom(css, requested) {
251
+ const families = parseFontFaceText(css);
252
+ if (families.length === 0) {
253
+ throw new Error(`Google Fonts returned no @font-face rules for "${requested}".`);
254
+ }
255
+ const family = families[0];
256
+ return {
257
+ name: family.name,
258
+ weights: family.weights ?? [],
259
+ italics: family.italics === true
260
+ };
261
+ }
262
+ async function resolveGoogleFont(name, fetcher) {
263
+ const probeUrl = discoveryUrl(name);
264
+ const probe = await fetcher(probeUrl);
265
+ if (!probe.ok) {
266
+ throw new Error(
267
+ `"${name}" is not on Google Fonts (the API answered ${probe.status}). Check the spelling against fonts.google.com.`
268
+ );
269
+ }
270
+ const census = censusFrom(await probe.text(), name);
271
+ const candidates = [persistUrlFor(census.name, census.weights, census.italics)];
272
+ const enumerated = census.weights.length > 0 ? url(
273
+ census.name,
274
+ census.italics ? `ital,wght@${[...census.weights.map((w) => `0,${w}`), ...census.weights.map((w) => `1,${w}`)].join(";")}` : `wght@${census.weights.join(";")}`
275
+ ) : null;
276
+ if (enumerated && !candidates.includes(enumerated)) candidates.push(enumerated);
277
+ const bare = url(census.name);
278
+ if (!candidates.includes(bare)) candidates.push(bare);
279
+ for (const candidate of candidates) {
280
+ const res = await fetcher(candidate);
281
+ if (!res.ok) continue;
282
+ const served = censusFrom(await res.text(), census.name);
283
+ return {
284
+ name: census.name,
285
+ url: candidate,
286
+ weights: served.weights,
287
+ italics: served.italics,
288
+ probeUrl
289
+ };
290
+ }
291
+ throw new Error(`Could not build a working Google Fonts URL for "${census.name}".`);
292
+ }
293
+
294
+ // src/editor/core/fonts/fontWeightAvailability.ts
295
+ function inferFontFamilyVariable(weightVariable) {
296
+ return weightVariable.endsWith("-font-weight") ? `${weightVariable.slice(0, -"-font-weight".length)}-font-family` : null;
297
+ }
298
+
299
+ // src/editor/core/fonts/weightCoverage.ts
300
+ var STACK_REF = /var\(\s*(--font-(?:display|sans|serif|mono))\s*\)/;
301
+ var VAR_REF = /var\(\s*(--[a-z0-9-]+)/i;
302
+ function declarations(css) {
303
+ const decls = /* @__PURE__ */ new Map();
304
+ for (const [, name, value] of css.matchAll(/(--[a-z0-9-]+)\s*:\s*([^;{}]+);/gi)) {
305
+ decls.set(name, value.trim());
306
+ }
307
+ return decls;
308
+ }
309
+ function requiredWeights(tokensCss, overrides = {}) {
310
+ const decls = declarations(tokensCss);
311
+ for (const [name, value] of Object.entries(overrides)) decls.set(name, String(value).trim());
312
+ const scale = /* @__PURE__ */ new Map();
313
+ for (const [name, value] of decls) {
314
+ if (name.startsWith("--font-weight-") && /^\d+$/.test(value)) scale.set(name, Number(value));
315
+ }
316
+ const seen = /* @__PURE__ */ new Set();
317
+ const out = [];
318
+ for (const [name, value] of decls) {
319
+ const familyVariable = inferFontFamilyVariable(name);
320
+ if (!familyVariable) continue;
321
+ const stack = decls.get(familyVariable)?.match(STACK_REF)?.[1];
322
+ if (!stack) continue;
323
+ const weight = /^\d+$/.test(value) ? Number(value) : scale.get(value.match(VAR_REF)?.[1] ?? "");
324
+ if (weight === void 0) continue;
325
+ const key = `${stack}:${weight}`;
326
+ if (seen.has(key)) continue;
327
+ seen.add(key);
328
+ out.push({ variable: name, stack, weight });
329
+ }
330
+ return out;
331
+ }
332
+ function weightCoverage(requirements, faces) {
333
+ const out = [];
334
+ for (const [stack, face] of Object.entries(faces)) {
335
+ const required = [...new Set(requirements.filter((r) => r.stack === stack).map((r) => r.weight))].sort(
336
+ (a, b) => a - b
337
+ );
338
+ out.push({
339
+ stack,
340
+ family: face.name,
341
+ required,
342
+ available: face.weights,
343
+ missing: required.filter((w) => !face.weights.includes(w)),
344
+ italics: face.italics
345
+ });
346
+ }
347
+ return out;
348
+ }
349
+
350
+ // vite-plugin/files/dataPaths.ts
351
+ var import_fs = __toESM(require("fs"), 1);
352
+ var import_path = __toESM(require("path"), 1);
353
+ var DEFAULT_DATA_DIR = "src/live-tokens/data";
354
+ var KNOWN_CONFIG_KEYS = /* @__PURE__ */ new Set([
355
+ "dataDir",
356
+ "colorsAndTypeDir",
357
+ "componentConfigsDir",
358
+ "themesDir",
359
+ "manifestsDir",
360
+ "tokensCssPath"
361
+ ]);
362
+ var cached = null;
363
+ function readLiveTokensConfig() {
364
+ if (cached) return cached;
365
+ try {
366
+ const configPath = import_path.default.resolve("live-tokens.config.json");
367
+ if (!import_fs.default.existsSync(configPath)) return cached = {};
368
+ const parsed = JSON.parse(import_fs.default.readFileSync(configPath, "utf-8"));
369
+ if (!parsed || typeof parsed !== "object") return cached = {};
370
+ const unknown = Object.keys(parsed).filter(
371
+ (k) => k !== "$schema" && !KNOWN_CONFIG_KEYS.has(k)
372
+ );
373
+ if (unknown.length > 0) {
374
+ console.warn(
375
+ `[live-tokens] Unknown key(s) in live-tokens.config.json: ${unknown.join(", ")}. Known keys: ${Array.from(KNOWN_CONFIG_KEYS).join(", ")}.`
376
+ );
377
+ }
378
+ cached = parsed;
379
+ return cached;
380
+ } catch {
381
+ return cached = {};
382
+ }
383
+ }
384
+ function resolveDataDirs(opts = {}) {
385
+ const fileConfig = readLiveTokensConfig();
386
+ const dataDirRaw = opts.dataDir ?? fileConfig.dataDir ?? DEFAULT_DATA_DIR;
387
+ const dataDir = import_path.default.resolve(dataDirRaw);
388
+ const sub = (name) => import_path.default.resolve(dataDir, name);
389
+ return {
390
+ dataDir,
391
+ colorsAndTypeDir: opts.colorsAndTypeDir ? import_path.default.resolve(opts.colorsAndTypeDir) : fileConfig.colorsAndTypeDir ? import_path.default.resolve(fileConfig.colorsAndTypeDir) : sub("colors-and-type"),
392
+ componentConfigsDir: opts.componentConfigsDir ? import_path.default.resolve(opts.componentConfigsDir) : fileConfig.componentConfigsDir ? import_path.default.resolve(fileConfig.componentConfigsDir) : sub("component-configs"),
393
+ themesDir: opts.themesDir ? import_path.default.resolve(opts.themesDir) : fileConfig.themesDir ? import_path.default.resolve(fileConfig.themesDir) : sub("themes"),
394
+ legacyManifestsDir: fileConfig.manifestsDir ? import_path.default.resolve(fileConfig.manifestsDir) : void 0
395
+ };
396
+ }
397
+ // Annotate the CommonJS export names for ESM import in node:
398
+ 0 && (module.exports = {
399
+ SLOT_ORDER,
400
+ SLOT_VARIABLES,
401
+ applyFontPairing,
402
+ discoveryUrl,
403
+ fontPairingLabel,
404
+ persistUrlFor,
405
+ readLiveTokensConfig,
406
+ requiredWeights,
407
+ resolveDataDirs,
408
+ resolveGoogleFont,
409
+ slugifyFamily,
410
+ weightCoverage
411
+ });
@@ -0,0 +1,109 @@
1
+ import { c as FontStackVariable, C as ColorsAndType, b as FontStack, F as FontSource } from '../themeTypes-BAqtv4XO.cjs';
2
+ export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-DBN0RPuT.cjs';
3
+
4
+ type PairingSlot = 'display' | 'body' | 'serif' | 'mono';
5
+ interface PairingFace {
6
+ name: string;
7
+ url: string;
8
+ weights?: number[];
9
+ italics?: boolean;
10
+ }
11
+ type FontPairing = Partial<Record<PairingSlot, PairingFace>>;
12
+ interface PairingChange {
13
+ slot: PairingSlot;
14
+ variable: FontStackVariable;
15
+ from: string | null;
16
+ to: string;
17
+ }
18
+ interface DroppedSource {
19
+ id: string;
20
+ names: string[];
21
+ }
22
+ interface FontPairingReport {
23
+ changes: PairingChange[];
24
+ dropped: DroppedSource[];
25
+ changed: boolean;
26
+ }
27
+ interface FontPairingResult {
28
+ colorsAndType: ColorsAndType;
29
+ report: FontPairingReport;
30
+ }
31
+ /** Slot order is also the order stamped sources land in `fontSources`;
32
+ * `check:preset-themes` reads the preset pair back out positionally. */
33
+ declare const SLOT_ORDER: PairingSlot[];
34
+ declare const SLOT_VARIABLES: Record<PairingSlot, FontStackVariable>;
35
+ declare function slugifyFamily(name: string): string;
36
+ interface ApplyFontPairingOptions {
37
+ /** `src_preset_` for the shipped presets, so `check:preset-themes` can still
38
+ * tell a stamped source from one the user added. */
39
+ idPrefix?: string;
40
+ }
41
+ /**
42
+ * Bind each named slot to a Google Fonts family: stamp the source, put that
43
+ * family at the head of the slot's stack, and keep every fallback behind it.
44
+ * Slots the pairing omits are left exactly as they are.
45
+ */
46
+ declare function applyFontPairing(colorsAndType: ColorsAndType, pairing: FontPairing, { idPrefix }?: ApplyFontPairingOptions): FontPairingResult;
47
+
48
+ interface CssResponse {
49
+ ok: boolean;
50
+ status: number;
51
+ text: () => Promise<string>;
52
+ }
53
+ type CssFetcher = (url: string) => Promise<CssResponse>;
54
+ interface ResolvedGoogleFont {
55
+ /** As Google spells it in the returned CSS, not as the caller typed it. */
56
+ name: string;
57
+ url: string;
58
+ weights: number[];
59
+ italics: boolean;
60
+ probeUrl: string;
61
+ }
62
+ /**
63
+ * Ask for every weight in both styles. Google silently drops the ones a family
64
+ * does not have and answers 200 anyway, so the returned CSS is a census of the
65
+ * family rather than a yes/no on the request. A 400 here means the family
66
+ * itself is not on Google Fonts.
67
+ */
68
+ declare function discoveryUrl(name: string): string;
69
+ /** The narrowest URL that still delivers everything the family has. A range
70
+ * serves one variable file; an enumeration serves one file per weight. */
71
+ declare function persistUrlFor(name: string, weights: number[], italics: boolean): string;
72
+ /**
73
+ * Verify a family exists on Google Fonts and settle on the URL to persist.
74
+ * Two requests in the common case: one to take the census, one to confirm the
75
+ * narrower URL built from it. A range that the family's axis cannot serve
76
+ * answers 400, so the enumerated form is the fallback rather than the guess.
77
+ */
78
+ declare function resolveGoogleFont(name: string, fetcher: CssFetcher): Promise<ResolvedGoogleFont>;
79
+
80
+ interface WeightRequirement {
81
+ variable: string;
82
+ stack: FontStackVariable;
83
+ weight: number;
84
+ }
85
+ interface FaceCoverage {
86
+ stack: FontStackVariable;
87
+ family: string;
88
+ required: number[];
89
+ available: number[];
90
+ missing: number[];
91
+ italics: boolean;
92
+ }
93
+ /**
94
+ * Every weight the token contract asks of each font stack, read off the
95
+ * `-font-family` / `-font-weight` pairing that typography tokens follow.
96
+ * `overrides` takes a theme's own `cssVariables`, so component tokens count
97
+ * the same as the shipped text styles.
98
+ */
99
+ declare function requiredWeights(tokensCss: string, overrides?: Record<string, string>): WeightRequirement[];
100
+ interface CoveredFace {
101
+ name: string;
102
+ weights: number[];
103
+ italics: boolean;
104
+ }
105
+ declare function weightCoverage(requirements: WeightRequirement[], faces: Partial<Record<FontStackVariable, CoveredFace>>): FaceCoverage[];
106
+
107
+ declare function fontPairingLabel(stacks: FontStack[], sources: FontSource[]): string;
108
+
109
+ export { type ApplyFontPairingOptions, type CoveredFace, type CssFetcher, type CssResponse, type DroppedSource, type FaceCoverage, type FontPairing, type FontPairingReport, type FontPairingResult, type PairingChange, type PairingFace, type PairingSlot, type ResolvedGoogleFont, SLOT_ORDER, SLOT_VARIABLES, type WeightRequirement, applyFontPairing, discoveryUrl, fontPairingLabel, persistUrlFor, requiredWeights, resolveGoogleFont, slugifyFamily, weightCoverage };
@@ -0,0 +1,109 @@
1
+ import { c as FontStackVariable, C as ColorsAndType, b as FontStack, F as FontSource } from '../themeTypes-BAqtv4XO.js';
2
+ export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-DBN0RPuT.js';
3
+
4
+ type PairingSlot = 'display' | 'body' | 'serif' | 'mono';
5
+ interface PairingFace {
6
+ name: string;
7
+ url: string;
8
+ weights?: number[];
9
+ italics?: boolean;
10
+ }
11
+ type FontPairing = Partial<Record<PairingSlot, PairingFace>>;
12
+ interface PairingChange {
13
+ slot: PairingSlot;
14
+ variable: FontStackVariable;
15
+ from: string | null;
16
+ to: string;
17
+ }
18
+ interface DroppedSource {
19
+ id: string;
20
+ names: string[];
21
+ }
22
+ interface FontPairingReport {
23
+ changes: PairingChange[];
24
+ dropped: DroppedSource[];
25
+ changed: boolean;
26
+ }
27
+ interface FontPairingResult {
28
+ colorsAndType: ColorsAndType;
29
+ report: FontPairingReport;
30
+ }
31
+ /** Slot order is also the order stamped sources land in `fontSources`;
32
+ * `check:preset-themes` reads the preset pair back out positionally. */
33
+ declare const SLOT_ORDER: PairingSlot[];
34
+ declare const SLOT_VARIABLES: Record<PairingSlot, FontStackVariable>;
35
+ declare function slugifyFamily(name: string): string;
36
+ interface ApplyFontPairingOptions {
37
+ /** `src_preset_` for the shipped presets, so `check:preset-themes` can still
38
+ * tell a stamped source from one the user added. */
39
+ idPrefix?: string;
40
+ }
41
+ /**
42
+ * Bind each named slot to a Google Fonts family: stamp the source, put that
43
+ * family at the head of the slot's stack, and keep every fallback behind it.
44
+ * Slots the pairing omits are left exactly as they are.
45
+ */
46
+ declare function applyFontPairing(colorsAndType: ColorsAndType, pairing: FontPairing, { idPrefix }?: ApplyFontPairingOptions): FontPairingResult;
47
+
48
+ interface CssResponse {
49
+ ok: boolean;
50
+ status: number;
51
+ text: () => Promise<string>;
52
+ }
53
+ type CssFetcher = (url: string) => Promise<CssResponse>;
54
+ interface ResolvedGoogleFont {
55
+ /** As Google spells it in the returned CSS, not as the caller typed it. */
56
+ name: string;
57
+ url: string;
58
+ weights: number[];
59
+ italics: boolean;
60
+ probeUrl: string;
61
+ }
62
+ /**
63
+ * Ask for every weight in both styles. Google silently drops the ones a family
64
+ * does not have and answers 200 anyway, so the returned CSS is a census of the
65
+ * family rather than a yes/no on the request. A 400 here means the family
66
+ * itself is not on Google Fonts.
67
+ */
68
+ declare function discoveryUrl(name: string): string;
69
+ /** The narrowest URL that still delivers everything the family has. A range
70
+ * serves one variable file; an enumeration serves one file per weight. */
71
+ declare function persistUrlFor(name: string, weights: number[], italics: boolean): string;
72
+ /**
73
+ * Verify a family exists on Google Fonts and settle on the URL to persist.
74
+ * Two requests in the common case: one to take the census, one to confirm the
75
+ * narrower URL built from it. A range that the family's axis cannot serve
76
+ * answers 400, so the enumerated form is the fallback rather than the guess.
77
+ */
78
+ declare function resolveGoogleFont(name: string, fetcher: CssFetcher): Promise<ResolvedGoogleFont>;
79
+
80
+ interface WeightRequirement {
81
+ variable: string;
82
+ stack: FontStackVariable;
83
+ weight: number;
84
+ }
85
+ interface FaceCoverage {
86
+ stack: FontStackVariable;
87
+ family: string;
88
+ required: number[];
89
+ available: number[];
90
+ missing: number[];
91
+ italics: boolean;
92
+ }
93
+ /**
94
+ * Every weight the token contract asks of each font stack, read off the
95
+ * `-font-family` / `-font-weight` pairing that typography tokens follow.
96
+ * `overrides` takes a theme's own `cssVariables`, so component tokens count
97
+ * the same as the shipped text styles.
98
+ */
99
+ declare function requiredWeights(tokensCss: string, overrides?: Record<string, string>): WeightRequirement[];
100
+ interface CoveredFace {
101
+ name: string;
102
+ weights: number[];
103
+ italics: boolean;
104
+ }
105
+ declare function weightCoverage(requirements: WeightRequirement[], faces: Partial<Record<FontStackVariable, CoveredFace>>): FaceCoverage[];
106
+
107
+ declare function fontPairingLabel(stacks: FontStack[], sources: FontSource[]): string;
108
+
109
+ export { type ApplyFontPairingOptions, type CoveredFace, type CssFetcher, type CssResponse, type DroppedSource, type FaceCoverage, type FontPairing, type FontPairingReport, type FontPairingResult, type PairingChange, type PairingFace, type PairingSlot, type ResolvedGoogleFont, SLOT_ORDER, SLOT_VARIABLES, type WeightRequirement, applyFontPairing, discoveryUrl, fontPairingLabel, persistUrlFor, requiredWeights, resolveGoogleFont, slugifyFamily, weightCoverage };