@lettercamp/cli 0.1.7 → 0.1.9

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 (60) hide show
  1. package/dist/account.js +20 -8
  2. package/dist/build.js +22 -5
  3. package/dist/cli.js +15 -6
  4. package/dist/code-block.js +50 -0
  5. package/dist/code-detection.js +24 -0
  6. package/dist/code-highlight.js +54 -0
  7. package/dist/component-blocks.js +32 -0
  8. package/dist/custom-layout.js +32 -0
  9. package/dist/deploy.js +131 -0
  10. package/dist/dev.js +28 -10
  11. package/dist/domain.js +190 -0
  12. package/dist/hosting.js +132 -0
  13. package/dist/lettercamp.js +1 -4
  14. package/dist/media-dimensions.js +154 -0
  15. package/dist/new.js +1 -1
  16. package/dist/preview.js +1 -1
  17. package/dist/prompts.js +31 -0
  18. package/dist/publication-links.js +23 -0
  19. package/dist/react.js +15 -0
  20. package/dist/upload.js +40 -10
  21. package/dist/workspace.js +5 -3
  22. package/package.json +11 -2
  23. package/renderer/src/components/AuthorByline.astro +20 -11
  24. package/renderer/src/components/BlogHome.astro +14 -57
  25. package/renderer/src/components/Footer.astro +12 -9
  26. package/renderer/src/components/Header.astro +38 -0
  27. package/renderer/src/components/MobileMenuIcons.astro +7 -0
  28. package/renderer/src/components/PostCard.astro +12 -5
  29. package/renderer/src/components/SocialLink.astro +4 -4
  30. package/renderer/src/layouts/SiteLayout.astro +70 -51
  31. package/renderer/src/lib/analytics.ts +21 -0
  32. package/renderer/src/lib/author-dialog.ts +102 -0
  33. package/renderer/src/lib/authors.js +18 -0
  34. package/renderer/src/lib/block-widths.ts +35 -0
  35. package/renderer/src/lib/branding.js +15 -0
  36. package/renderer/src/lib/button.js +10 -0
  37. package/renderer/src/lib/category-routes.js +19 -0
  38. package/renderer/src/lib/code-block.ts +46 -0
  39. package/renderer/src/lib/font-fallbacks.js +196 -0
  40. package/renderer/src/lib/fonts.js +48 -0
  41. package/renderer/src/lib/home-layout.ts +5 -0
  42. package/renderer/src/lib/image-radius.ts +5 -0
  43. package/renderer/src/lib/image-rounding.ts +28 -0
  44. package/renderer/src/lib/image-widths.ts +13 -0
  45. package/renderer/src/lib/metadata.js +47 -0
  46. package/renderer/src/lib/mobile-navigation.ts +76 -0
  47. package/renderer/src/lib/post-layout.ts +6 -0
  48. package/renderer/src/lib/post-share.ts +264 -0
  49. package/renderer/src/lib/post-toc.ts +69 -0
  50. package/renderer/src/lib/posts.js +14 -6
  51. package/renderer/src/lib/publication-url.js +14 -0
  52. package/renderer/src/lib/table-styles.ts +12 -0
  53. package/renderer/src/lib/urls.js +3 -0
  54. package/renderer/src/pages/[...slug].astro +21 -11
  55. package/renderer/src/pages/category/[tag].astro +16 -0
  56. package/renderer/src/pages/category/index.astro +4 -0
  57. package/renderer/src/pages/index.astro +1 -1
  58. package/renderer/src/styles/post-share.css +57 -0
  59. package/renderer/src/styles/site.css +261 -91
  60. package/dist/public-assets.js +0 -29
@@ -0,0 +1,18 @@
1
+ // Generated from lettercamp/lib/authors.ts.
2
+ export function readAuthors(value) {
3
+ if (!Array.isArray(value))
4
+ return [];
5
+ return value.flatMap(item => item && typeof item.name === "string"
6
+ ? [{ name: item.name, ...(typeof item.image === "string" ? { image: item.image } : {}) }] : []);
7
+ }
8
+ // Keep blank names while editing; only bylines omit unfinished author rows.
9
+ export function resolveAuthors(source) {
10
+ return readAuthors(source.authors);
11
+ }
12
+ // Names alone are not identities: two contributors can share a name.
13
+ export function authorKey(author) {
14
+ return JSON.stringify([author.name.trim().toLowerCase(), author.image || ""]);
15
+ }
16
+ export function uniqueAuthors(authors) {
17
+ return [...new Map(authors.filter(author => author.name.trim()).map(author => [authorKey(author), author])).values()];
18
+ }
@@ -0,0 +1,35 @@
1
+ // Generated from lettercamp/lib/block-widths.ts.
2
+ // One sizing contract for editor surfaces and published body blocks.
3
+ const blocks = ':is(.publication-post .prose > table, [data-lettercamp-table], [data-lettercamp-code][data-code-width], [data-block-width], .publication-post .prose :is(img, video)[data-image-width]:not([width]), .publication-post .prose :is(img, video):is([data-image-width="wide"], [data-image-width="full"]))';
4
+ const wide = ':is([data-table-width="wide"], [data-code-width="wide"], [data-block-width="wide"], [data-image-width="wide"])';
5
+ const full = ':is([data-table-width="full"], [data-code-width="full"], [data-block-width="full"], [data-image-width="full"])';
6
+
7
+ export const blockWidthStyles = `
8
+ .publication-post { container-type: inline-size; }
9
+ .publication-post .prose [data-lettercamp-component] { display: grid; grid-template-columns: minmax(0, 1fr); }
10
+ .publication-post .prose [data-lettercamp-component] > :not(style, script),
11
+ .publication-post .prose [data-lettercamp-component] > astro-island > :not(style, script) {
12
+ box-sizing: border-box; width: 100%; max-width: 100%; min-width: 0;
13
+ }
14
+ ${blocks} {
15
+ --block-normal-width: 100%;
16
+ --block-display-width: var(--block-normal-width);
17
+ box-sizing: border-box; width: var(--block-display-width); max-width: var(--block-normal-width); margin-inline: auto;
18
+ }
19
+ ${blocks}${wide} {
20
+ --block-display-width: max(var(--block-normal-width), min(calc(var(--block-normal-width) + 128px), var(--block-available-width, 100cqw)));
21
+ max-width: none; margin-inline: calc((100% - var(--block-display-width)) / 2);
22
+ margin-left: calc((100% - var(--block-display-width)) / 2 + min(0px, (var(--block-available-width, 100cqw) - var(--block-display-width)) / 2 - var(--block-column-offset, 0px)));
23
+ }
24
+ ${blocks}${full} {
25
+ --block-display-width: max(var(--block-normal-width), var(--block-available-width, 100cqw));
26
+ max-width: none; margin-inline: calc((100% - var(--block-display-width)) / 2);
27
+ margin-left: calc((100% - var(--block-display-width)) / 2 - var(--block-column-offset, 0px));
28
+ }
29
+ /* The two-column TOC layout shifts the reading column 106px from center. */
30
+ @media (min-width: 761px) and (max-width: 1199px) {
31
+ .publication-post[data-post-hero="split"][data-post-toc="visible"] .article-body-with-toc .prose {
32
+ --block-column-offset: 106px;
33
+ }
34
+ }
35
+ `;
@@ -0,0 +1,15 @@
1
+ export const defaultSiteBranding = {
2
+ name: 'Lettercamp Blog',
3
+ logo: 'https://lettercamp.com/lettercamp-logo.png?v=7',
4
+ favicon: 'https://lettercamp.com/favicon-96x96.png?v=7',
5
+ };
6
+
7
+ export function withDefaultBranding(source) {
8
+ const branding = Object.fromEntries(Object.entries(defaultSiteBranding).map(([key, fallback]) =>
9
+ [key, typeof source[key] === 'string' && source[key].trim() ? source[key] : fallback]));
10
+ return {
11
+ ...source,
12
+ ...branding,
13
+ home: { ...source.home, title: source.home?.title ?? (branding.name === defaultSiteBranding.name ? branding.name : `${branding.name} Blog`) },
14
+ };
15
+ }
@@ -0,0 +1,10 @@
1
+ // Keep the color fallback and contrast calculation aligned with the editor preview.
2
+ export function buttonColors(value) {
3
+ const background = typeof value === 'string' && /^#[0-9a-f]{6}$/i.test(value) ? value : '#111111';
4
+ const channels = [1, 3, 5].map(offset => {
5
+ const channel = parseInt(background.slice(offset, offset + 2), 16) / 255;
6
+ return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
7
+ });
8
+ const luminance = channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722;
9
+ return { background, text: luminance > 0.179 ? '#000000' : '#ffffff' };
10
+ }
@@ -0,0 +1,19 @@
1
+ // Generated from lettercamp/lib/category-routes.ts.
2
+ export function categoryTag(category) {
3
+ return category.trim().normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "") || "category";
4
+ }
5
+ export function categoryRoutes(categories) {
6
+ const names = [...new Set(categories.map(name => name.trim()).filter(Boolean))];
7
+ const used = new Set();
8
+ const tags = new Map();
9
+ // Assign unique tags independently of the posts' date order.
10
+ for (const name of [...names].sort()) {
11
+ const base = categoryTag(name);
12
+ let tag = base;
13
+ for (let suffix = 2; used.has(tag); suffix++)
14
+ tag = `${base}-${suffix}`;
15
+ used.add(tag);
16
+ tags.set(name, tag);
17
+ }
18
+ return names.map(name => ({ name, tag: tags.get(name), href: `/category/${encodeURIComponent(tags.get(name))}/` }));
19
+ }
@@ -0,0 +1,46 @@
1
+ // Generated from lettercamp/lib/code-block.ts.
2
+ export const codeThemes = { classic: "Classic", ocean: "Ocean", forest: "Forest", mono: "Monochrome" } as const;
3
+ export type CodeBlockSettings = { width: "regular" | "wide" | "full"; wrap: boolean; theme: keyof typeof codeThemes; caption: string };
4
+ const settingPattern = /(?:^|\s)lc-(width|wrap|theme|caption)=([^\s]*)/g;
5
+
6
+ export function readCodeSettings(meta = ""): CodeBlockSettings {
7
+ const settings: CodeBlockSettings = { width: "regular", wrap: true, theme: "classic", caption: "" };
8
+ for (const match of meta.matchAll(settingPattern)) {
9
+ const [, key, value] = match;
10
+ if (key === "width" && (value === "regular" || value === "wide" || value === "full")) settings.width = value;
11
+ if (key === "theme" && Object.hasOwn(codeThemes, value)) settings.theme = value as CodeBlockSettings["theme"];
12
+ if (key === "wrap") settings.wrap = value !== "false";
13
+ if (key === "caption") { try { settings.caption = decodeURIComponent(value); } catch { /* Keep malformed source editable. */ } }
14
+ }
15
+ return settings;
16
+ }
17
+
18
+ export function writeCodeSettings(meta: string, patch: Partial<CodeBlockSettings>) {
19
+ const settings = { ...readCodeSettings(meta), ...patch };
20
+ return [meta.replace(settingPattern, "").trim(), `lc-width=${settings.width}`, `lc-wrap=${settings.wrap}`, `lc-theme=${settings.theme}`,
21
+ ...(settings.caption ? [`lc-caption=${encodeURIComponent(settings.caption)}`] : [])].filter(Boolean).join(" ");
22
+ }
23
+
24
+ // The editor and both publication renderers use the same token classes and palette.
25
+ export const codeBlockStyles = `
26
+ [data-lettercamp-code] { --code-keyword: #8250a0; --code-string: #3d7757; --code-number: #a15c32; --code-name: #326b99; --code-comment: #93939b; color: #414146; }
27
+ [data-lettercamp-code][data-code-width] { margin-block: 24px; }
28
+ [data-lettercamp-code][data-code-theme="ocean"] { --code-keyword: #3d62a8; --code-string: #277c85; --code-number: #98623e; --code-name: #6961a4; }
29
+ [data-lettercamp-code][data-code-theme="forest"] { --code-keyword: #47724a; --code-string: #827030; --code-number: #a25d47; --code-name: #3c7b79; }
30
+ [data-lettercamp-code][data-code-theme="mono"] { --code-keyword: #45454a; --code-string: #62626a; --code-number: #62626a; --code-name: #45454a; }
31
+ [data-lettercamp-code] .tok-keyword, [data-lettercamp-code] .tok-operator { color: var(--code-keyword); }
32
+ [data-lettercamp-code] .tok-string, [data-lettercamp-code] .tok-string2, [data-lettercamp-code] .tok-inserted { color: var(--code-string); }
33
+ [data-lettercamp-code] .tok-number, [data-lettercamp-code] .tok-bool, [data-lettercamp-code] .tok-atom, [data-lettercamp-code] .tok-literal { color: var(--code-number); }
34
+ [data-lettercamp-code] .tok-typeName, [data-lettercamp-code] .tok-className, [data-lettercamp-code] .tok-propertyName, [data-lettercamp-code] .tok-definition, [data-lettercamp-code] .tok-variableName2, [data-lettercamp-code] .tok-labelName { color: var(--code-name); }
35
+ [data-lettercamp-code] .tok-comment, [data-lettercamp-code] .tok-meta { color: var(--code-comment); }
36
+ [data-lettercamp-code] .tok-invalid, [data-lettercamp-code] .tok-deleted { color: #aa4747; }
37
+ [data-lettercamp-code] .tok-strong { font-weight: 600; }
38
+ [data-lettercamp-code] .tok-emphasis { font-style: italic; }
39
+ figure[data-lettercamp-code] > pre { box-sizing: border-box; margin: 0; padding: 16px 0; overflow-x: auto; border-radius: 8px; background: #fafafa; color: #414146; font: 13px/1.7 ui-monospace, SFMono-Regular, Menlo, monospace; tab-size: 4; }
40
+ figure[data-lettercamp-code] > pre > code { display: block; width: max-content; min-width: 100%; padding: 0; background: transparent; color: inherit; font: inherit; white-space: normal; counter-reset: code-line; }
41
+ [data-lettercamp-code] .code-line { position: relative; display: block; box-sizing: border-box; min-height: 1.7em; padding: 0 18px 0 calc(var(--code-gutter, 3ch) + 24px); white-space: pre; }
42
+ [data-lettercamp-code] .code-line::before { position: absolute; left: 8px; width: var(--code-gutter, 3ch); text-align: right; content: counter(code-line); counter-increment: code-line; color: #b4b4bc; user-select: none; font-variant-numeric: tabular-nums; }
43
+ [data-lettercamp-code][data-code-wrap="true"] > pre > code { width: 100%; }
44
+ [data-lettercamp-code][data-code-wrap="true"] .code-line { white-space: pre-wrap; overflow-wrap: anywhere; }
45
+ figure[data-lettercamp-code] > figcaption { margin: 8px 0 0; color: #888; font: 13px/1.5 var(--font-family, inherit); text-align: center; overflow-wrap: anywhere; }
46
+ `;
@@ -0,0 +1,196 @@
1
+ // Generated from lettercamp/lib/font-fallbacks.ts.
2
+ export const fontFallbacks = {
3
+ "Inter": { generic: "sans-serif", faces: [
4
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "107.1194%", "ascentOverride": "90.4365%", "descentOverride": "22.5180%", "lineGapOverride": "0.0000%" },
5
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "108.2147%", "ascentOverride": "89.5211%", "descentOverride": "22.2900%", "lineGapOverride": "0.0000%" },
6
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "101.5259%", "ascentOverride": "95.4190%", "descentOverride": "23.7586%", "lineGapOverride": "0.0000%" },
7
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "102.5432%", "ascentOverride": "94.4723%", "descentOverride": "23.5229%", "lineGapOverride": "0.0000%" },
8
+ ] },
9
+ "Geist": { generic: "sans-serif", faces: [
10
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "104.7553%", "ascentOverride": "95.9379%", "descentOverride": "28.1609%", "lineGapOverride": "0.0000%" },
11
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "107.2228%", "ascentOverride": "93.7301%", "descentOverride": "27.5128%", "lineGapOverride": "0.0000%" },
12
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "101.8791%", "ascentOverride": "98.6463%", "descentOverride": "28.9559%", "lineGapOverride": "0.0000%" },
13
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "103.9626%", "ascentOverride": "96.6694%", "descentOverride": "28.3756%", "lineGapOverride": "0.0000%" },
14
+ ] },
15
+ "Lora": { generic: "serif", faces: [
16
+ { "weight": 400, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "115.2000%", "ascentOverride": "87.3264%", "descentOverride": "23.7847%", "lineGapOverride": "0.0000%" },
17
+ { "weight": 500, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "116.4308%", "ascentOverride": "86.4033%", "descentOverride": "23.5333%", "lineGapOverride": "0.0000%" },
18
+ { "weight": 600, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "110.4903%", "ascentOverride": "91.0487%", "descentOverride": "24.7986%", "lineGapOverride": "0.0000%" },
19
+ { "weight": 700, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "111.4149%", "ascentOverride": "90.2931%", "descentOverride": "24.5928%", "lineGapOverride": "0.0000%" },
20
+ ] },
21
+ "Merriweather": { generic: "serif", faces: [
22
+ { "weight": 400, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "119.3846%", "ascentOverride": "82.4227%", "descentOverride": "22.8673%", "lineGapOverride": "0.0000%" },
23
+ { "weight": 500, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "119.8769%", "ascentOverride": "82.0842%", "descentOverride": "22.7734%", "lineGapOverride": "0.0000%" },
24
+ { "weight": 600, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "113.3797%", "ascentOverride": "86.7880%", "descentOverride": "24.0784%", "lineGapOverride": "0.0000%" },
25
+ { "weight": 700, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "115.3445%", "ascentOverride": "85.3097%", "descentOverride": "23.6682%", "lineGapOverride": "0.0000%" },
26
+ ] },
27
+ "Open Sans": { generic: "sans-serif", faces: [
28
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "105.1479%", "ascentOverride": "101.6519%", "descentOverride": "27.8625%", "lineGapOverride": "0.0000%" },
29
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "107.1194%", "ascentOverride": "99.7810%", "descentOverride": "27.3497%", "lineGapOverride": "0.0000%" },
30
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "101.2208%", "ascentOverride": "105.5957%", "descentOverride": "28.9435%", "lineGapOverride": "0.0000%" },
31
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "104.7813%", "ascentOverride": "102.0075%", "descentOverride": "27.9600%", "lineGapOverride": "0.0000%" },
32
+ ] },
33
+ "Playfair Display": { generic: "serif", faces: [
34
+ { "weight": 400, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "111.2615%", "ascentOverride": "97.2483%", "descentOverride": "22.5595%", "lineGapOverride": "0.0000%" },
35
+ { "weight": 500, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "112.2462%", "ascentOverride": "96.3953%", "descentOverride": "22.3616%", "lineGapOverride": "0.0000%" },
36
+ { "weight": 600, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "106.0984%", "ascentOverride": "101.9808%", "descentOverride": "23.6573%", "lineGapOverride": "0.0000%" },
37
+ { "weight": 700, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "107.0230%", "ascentOverride": "101.0997%", "descentOverride": "23.4529%", "lineGapOverride": "0.0000%" },
38
+ ] },
39
+ "Roboto": { generic: "sans-serif", faces: [
40
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "99.7809%", "ascentOverride": "92.9771%", "descentOverride": "24.4677%", "lineGapOverride": "0.0000%" },
41
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "100.6572%", "ascentOverride": "92.1677%", "descentOverride": "24.2547%", "lineGapOverride": "0.0000%" },
42
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "93.8962%", "ascentOverride": "98.8042%", "descentOverride": "26.0011%", "lineGapOverride": "0.0000%" },
43
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "94.2014%", "ascentOverride": "98.4841%", "descentOverride": "25.9169%", "lineGapOverride": "0.0000%" },
44
+ ] },
45
+ "Libre Baskerville": { generic: "serif", faces: [
46
+ { "weight": 400, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "127.2615%", "ascentOverride": "76.2210%", "descentOverride": "21.2162%", "lineGapOverride": "0.0000%" },
47
+ { "weight": 500, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "128.7385%", "ascentOverride": "75.3466%", "descentOverride": "20.9728%", "lineGapOverride": "0.0000%" },
48
+ { "weight": 600, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "121.5856%", "ascentOverride": "79.7792%", "descentOverride": "22.2066%", "lineGapOverride": "0.0000%" },
49
+ { "weight": 700, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "122.5102%", "ascentOverride": "79.1771%", "descentOverride": "22.0390%", "lineGapOverride": "0.0000%" },
50
+ ] },
51
+ "Lato": { generic: "sans-serif", faces: [
52
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "97.6894%", "ascentOverride": "101.0345%", "descentOverride": "21.8038%", "lineGapOverride": "0.0000%" },
53
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "92.8163%", "ascentOverride": "106.3391%", "descentOverride": "22.9486%", "lineGapOverride": "0.0000%" },
54
+ ] },
55
+ "Source Serif 4": { generic: "serif", faces: [
56
+ { "weight": 400, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "117.9077%", "ascentOverride": "87.8653%", "descentOverride": "28.4121%", "lineGapOverride": "0.0000%" },
57
+ { "weight": 500, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "118.6462%", "ascentOverride": "87.3185%", "descentOverride": "28.2352%", "lineGapOverride": "0.0000%" },
58
+ { "weight": 600, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "112.3395%", "ascentOverride": "92.2205%", "descentOverride": "29.8203%", "lineGapOverride": "0.0000%" },
59
+ { "weight": 700, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "114.1887%", "ascentOverride": "90.7270%", "descentOverride": "29.3374%", "lineGapOverride": "0.0000%" },
60
+ ] },
61
+ "Source Sans 3": { generic: "sans-serif", faces: [
62
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "93.7639%", "ascentOverride": "109.2105%", "descentOverride": "42.6604%", "lineGapOverride": "0.0000%" },
63
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "95.1097%", "ascentOverride": "107.6651%", "descentOverride": "42.0567%", "lineGapOverride": "0.0000%" },
64
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "89.7953%", "ascentOverride": "114.0371%", "descentOverride": "44.5458%", "lineGapOverride": "0.0000%" },
65
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "92.2954%", "ascentOverride": "110.9481%", "descentOverride": "43.3391%", "lineGapOverride": "0.0000%" },
66
+ ] },
67
+ "Montserrat": { generic: "sans-serif", faces: [
68
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "112.8307%", "ascentOverride": "85.7923%", "descentOverride": "22.2457%", "lineGapOverride": "0.0000%" },
69
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "114.6252%", "ascentOverride": "84.4491%", "descentOverride": "21.8975%", "lineGapOverride": "0.0000%" },
70
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "108.3377%", "ascentOverride": "89.3502%", "descentOverride": "23.1683%", "lineGapOverride": "0.0000%" },
71
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "110.4212%", "ascentOverride": "87.6644%", "descentOverride": "22.7312%", "lineGapOverride": "0.0000%" },
72
+ ] },
73
+ "Poppins": { generic: "sans-serif", faces: [
74
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "112.1577%", "ascentOverride": "93.6182%", "descentOverride": "31.2061%", "lineGapOverride": "8.9160%" },
75
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "113.5036%", "ascentOverride": "92.5081%", "descentOverride": "30.8360%", "lineGapOverride": "8.8103%" },
76
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "106.2543%", "ascentOverride": "98.8195%", "descentOverride": "32.9398%", "lineGapOverride": "9.4114%" },
77
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "107.2960%", "ascentOverride": "97.8601%", "descentOverride": "32.6200%", "lineGapOverride": "9.3200%" },
78
+ ] },
79
+ "Nunito Sans": { generic: "sans-serif", faces: [
80
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "101.3906%", "ascentOverride": "99.7134%", "descentOverride": "34.8159%", "lineGapOverride": "0.0000%" },
81
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "102.2878%", "ascentOverride": "98.8387%", "descentOverride": "34.5105%", "lineGapOverride": "0.0000%" },
82
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "95.8372%", "ascentOverride": "105.4914%", "descentOverride": "36.8333%", "lineGapOverride": "0.0000%" },
83
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "97.9207%", "ascentOverride": "103.2469%", "descentOverride": "36.0496%", "lineGapOverride": "0.0000%" },
84
+ ] },
85
+ "Work Sans": { generic: "sans-serif", faces: [
86
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "111.9334%", "ascentOverride": "83.0851%", "descentOverride": "21.7093%", "lineGapOverride": "0.0000%" },
87
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "112.1577%", "ascentOverride": "82.9189%", "descentOverride": "21.6659%", "lineGapOverride": "0.0000%" },
88
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "104.3792%", "ascentOverride": "89.0982%", "descentOverride": "23.2805%", "lineGapOverride": "0.0000%" },
89
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "104.5876%", "ascentOverride": "88.9207%", "descentOverride": "23.2341%", "lineGapOverride": "0.0000%" },
90
+ ] },
91
+ "DM Sans": { generic: "sans-serif", faces: [
92
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "104.5310%", "ascentOverride": "94.9001%", "descentOverride": "29.6563%", "lineGapOverride": "0.0000%" },
93
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "106.3255%", "ascentOverride": "93.2984%", "descentOverride": "29.1557%", "lineGapOverride": "0.0000%" },
94
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "101.0458%", "ascentOverride": "98.1733%", "descentOverride": "30.6792%", "lineGapOverride": "0.0000%" },
95
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "102.7125%", "ascentOverride": "96.5802%", "descentOverride": "30.1813%", "lineGapOverride": "0.0000%" },
96
+ ] },
97
+ "Manrope": { generic: "sans-serif", faces: [
98
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "103.1851%", "ascentOverride": "103.3095%", "descentOverride": "29.0740%", "lineGapOverride": "0.0000%" },
99
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "105.0918%", "ascentOverride": "101.4351%", "descentOverride": "28.5465%", "lineGapOverride": "0.0000%" },
100
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "99.3790%", "ascentOverride": "107.2661%", "descentOverride": "30.1875%", "lineGapOverride": "0.0000%" },
101
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "101.1499%", "ascentOverride": "105.3881%", "descentOverride": "29.6589%", "lineGapOverride": "0.0000%" },
102
+ ] },
103
+ "Figtree": { generic: "sans-serif", faces: [
104
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "100.7176%", "ascentOverride": "94.3231%", "descentOverride": "24.8219%", "lineGapOverride": "0.0000%" },
105
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "101.6149%", "ascentOverride": "93.4902%", "descentOverride": "24.6027%", "lineGapOverride": "0.0000%" },
106
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "95.4205%", "ascentOverride": "99.5593%", "descentOverride": "26.1998%", "lineGapOverride": "0.0000%" },
107
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "96.4623%", "ascentOverride": "98.4841%", "descentOverride": "25.9169%", "lineGapOverride": "0.0000%" },
108
+ ] },
109
+ "Outfit": { generic: "sans-serif", faces: [
110
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "99.8204%", "ascentOverride": "100.1800%", "descentOverride": "26.0468%", "lineGapOverride": "0.0000%" },
111
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "100.0447%", "ascentOverride": "99.9553%", "descentOverride": "25.9884%", "lineGapOverride": "0.0000%" },
112
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "94.5872%", "ascentOverride": "105.7226%", "descentOverride": "27.4879%", "lineGapOverride": "0.0000%" },
113
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "96.0456%", "ascentOverride": "104.1172%", "descentOverride": "27.0705%", "lineGapOverride": "0.0000%" },
114
+ ] },
115
+ "Plus Jakarta Sans": { generic: "sans-serif", faces: [
116
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "104.9796%", "ascentOverride": "98.8763%", "descentOverride": "21.1470%", "lineGapOverride": "0.0000%" },
117
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "106.3255%", "ascentOverride": "97.6247%", "descentOverride": "20.8793%", "lineGapOverride": "0.0000%" },
118
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "99.7957%", "ascentOverride": "104.0125%", "descentOverride": "22.2454%", "lineGapOverride": "0.0000%" },
119
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "100.8374%", "ascentOverride": "102.9380%", "descentOverride": "22.0156%", "lineGapOverride": "0.0000%" },
120
+ ] },
121
+ "Raleway": { generic: "sans-serif", faces: [
122
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "103.8581%", "ascentOverride": "90.5081%", "descentOverride": "22.5308%", "lineGapOverride": "0.0000%" },
123
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "105.4283%", "ascentOverride": "89.1602%", "descentOverride": "22.1952%", "lineGapOverride": "0.0000%" },
124
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "98.9624%", "ascentOverride": "94.9856%", "descentOverride": "23.6454%", "lineGapOverride": "0.0000%" },
125
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "100.2124%", "ascentOverride": "93.8008%", "descentOverride": "23.3504%", "lineGapOverride": "0.0000%" },
126
+ ] },
127
+ "IBM Plex Sans": { generic: "sans-serif", faces: [
128
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "101.1663%", "ascentOverride": "101.3184%", "descentOverride": "27.1830%", "lineGapOverride": "0.0000%" },
129
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "103.4094%", "ascentOverride": "99.1206%", "descentOverride": "26.5933%", "lineGapOverride": "0.0000%" },
130
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "97.2956%", "ascentOverride": "105.3490%", "descentOverride": "28.2644%", "lineGapOverride": "0.0000%" },
131
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "98.5457%", "ascentOverride": "104.0127%", "descentOverride": "27.9058%", "lineGapOverride": "0.0000%" },
132
+ ] },
133
+ "Noto Sans": { generic: "sans-serif", faces: [
134
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "106.3255%", "ascentOverride": "100.5403%", "descentOverride": "27.5569%", "lineGapOverride": "0.0000%" },
135
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "108.1200%", "ascentOverride": "98.8716%", "descentOverride": "27.0995%", "lineGapOverride": "0.0000%" },
136
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "102.5042%", "ascentOverride": "104.2884%", "descentOverride": "28.5842%", "lineGapOverride": "0.0000%" },
137
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "104.7959%", "ascentOverride": "102.0078%", "descentOverride": "27.9591%", "lineGapOverride": "0.0000%" },
138
+ ] },
139
+ "Crimson Pro": { generic: "serif", faces: [
140
+ { "weight": 400, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "98.5577%", "ascentOverride": "90.9604%", "descentOverride": "21.7988%", "lineGapOverride": "0.0000%" },
141
+ { "weight": 500, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "100.4808%", "ascentOverride": "89.2195%", "descentOverride": "21.3816%", "lineGapOverride": "0.0000%" },
142
+ { "weight": 600, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "96.3883%", "ascentOverride": "93.0076%", "descentOverride": "22.2894%", "lineGapOverride": "0.0000%" },
143
+ { "weight": 700, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "98.6456%", "ascentOverride": "90.8793%", "descentOverride": "21.7794%", "lineGapOverride": "0.0000%" },
144
+ ] },
145
+ "Literata": { generic: "serif", faces: [
146
+ { "weight": 400, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "118.1538%", "ascentOverride": "99.6159%", "descentOverride": "26.0677%", "lineGapOverride": "0.0000%" },
147
+ { "weight": 500, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "120.8615%", "ascentOverride": "97.3842%", "descentOverride": "25.4837%", "lineGapOverride": "0.0000%" },
148
+ { "weight": 600, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "115.5756%", "ascentOverride": "101.8381%", "descentOverride": "26.6492%", "lineGapOverride": "0.0000%" },
149
+ { "weight": 700, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "118.3494%", "ascentOverride": "99.4513%", "descentOverride": "26.0246%", "lineGapOverride": "0.0000%" },
150
+ ] },
151
+ "DM Serif Display": { generic: "serif", faces: [
152
+ { "weight": 400, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "109.7846%", "ascentOverride": "94.3666%", "descentOverride": "30.5143%", "lineGapOverride": "0.0000%" },
153
+ ] },
154
+ "Bitter": { generic: "serif", faces: [
155
+ { "weight": 400, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "114.4615%", "ascentOverride": "81.6868%", "descentOverride": "23.1519%", "lineGapOverride": "0.0000%" },
156
+ { "weight": 500, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "115.2000%", "ascentOverride": "81.1632%", "descentOverride": "23.0035%", "lineGapOverride": "0.0000%" },
157
+ { "weight": 600, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "108.8722%", "ascentOverride": "85.8805%", "descentOverride": "24.3405%", "lineGapOverride": "0.0000%" },
158
+ { "weight": 700, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "109.5657%", "ascentOverride": "85.3369%", "descentOverride": "24.1864%", "lineGapOverride": "0.0000%" },
159
+ ] },
160
+ "Noto Serif": { generic: "serif", faces: [
161
+ { "weight": 400, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "118.4000%", "ascentOverride": "90.2872%", "descentOverride": "24.7466%", "lineGapOverride": "0.0000%" },
162
+ { "weight": 500, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "120.6154%", "ascentOverride": "88.6288%", "descentOverride": "24.2921%", "lineGapOverride": "0.0000%" },
163
+ { "weight": 600, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "115.5756%", "ascentOverride": "92.4936%", "descentOverride": "25.3514%", "lineGapOverride": "0.0000%" },
164
+ { "weight": 700, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "118.3494%", "ascentOverride": "90.3257%", "descentOverride": "24.7572%", "lineGapOverride": "0.0000%" },
165
+ ] },
166
+ "IBM Plex Serif": { generic: "serif", faces: [
167
+ { "weight": 400, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "116.4308%", "ascentOverride": "88.0351%", "descentOverride": "23.6192%", "lineGapOverride": "0.0000%" },
168
+ { "weight": 500, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "118.6462%", "ascentOverride": "86.3913%", "descentOverride": "23.1782%", "lineGapOverride": "0.0000%" },
169
+ { "weight": 600, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "112.8018%", "ascentOverride": "90.8673%", "descentOverride": "24.3790%", "lineGapOverride": "0.0000%" },
170
+ { "weight": 700, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "114.6510%", "ascentOverride": "89.4017%", "descentOverride": "23.9858%", "lineGapOverride": "0.0000%" },
171
+ ] },
172
+ "Roboto Slab": { generic: "serif", faces: [
173
+ { "weight": 400, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "116.8269%", "ascentOverride": "89.6926%", "descentOverride": "23.1964%", "lineGapOverride": "0.0000%" },
174
+ { "weight": 500, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "117.6683%", "ascentOverride": "89.0513%", "descentOverride": "23.0305%", "lineGapOverride": "0.0000%" },
175
+ { "weight": 600, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "110.7223%", "ascentOverride": "94.6378%", "descentOverride": "24.4753%", "lineGapOverride": "0.0000%" },
176
+ { "weight": 700, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "111.0609%", "ascentOverride": "94.3492%", "descentOverride": "24.4007%", "lineGapOverride": "0.0000%" },
177
+ ] },
178
+ "Newsreader": { generic: "serif", faces: [
179
+ { "weight": 400, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "105.4769%", "ascentOverride": "69.6835%", "descentOverride": "25.1240%", "lineGapOverride": "0.0000%" },
180
+ { "weight": 500, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "107.9385%", "ascentOverride": "68.0944%", "descentOverride": "24.5510%", "lineGapOverride": "0.0000%" },
181
+ { "weight": 600, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "104.0181%", "ascentOverride": "70.6608%", "descentOverride": "25.4763%", "lineGapOverride": "0.0000%" },
182
+ { "weight": 700, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "106.6763%", "ascentOverride": "68.9000%", "descentOverride": "24.8415%", "lineGapOverride": "0.0000%" },
183
+ ] },
184
+ "EB Garamond": { generic: "serif", faces: [
185
+ { "weight": 400, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "94.7692%", "ascentOverride": "106.2581%", "descentOverride": "31.4448%", "lineGapOverride": "0.0000%" },
186
+ { "weight": 500, "local": ["Times New Roman", "TimesNewRomanPSMT"], "sizeAdjust": "98.2154%", "ascentOverride": "102.5298%", "descentOverride": "30.3415%", "lineGapOverride": "0.0000%" },
187
+ { "weight": 600, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "95.0032%", "ascentOverride": "105.9965%", "descentOverride": "31.3674%", "lineGapOverride": "0.0000%" },
188
+ { "weight": 700, "local": ["Times New Roman Bold", "TimesNewRomanPS-BoldMT"], "sizeAdjust": "98.0081%", "ascentOverride": "102.7466%", "descentOverride": "30.4056%", "lineGapOverride": "0.0000%" },
189
+ ] },
190
+ "Atkinson Hyperlegible Next": { generic: "sans-serif", faces: [
191
+ { "weight": 400, "local": ["Arial", "ArialMT"], "sizeAdjust": "99.5961%", "ascentOverride": "98.7991%", "descentOverride": "31.7282%", "lineGapOverride": "0.0000%" },
192
+ { "weight": 500, "local": ["Arial", "ArialMT"], "sizeAdjust": "102.5122%", "ascentOverride": "95.9886%", "descentOverride": "30.8256%", "lineGapOverride": "0.0000%" },
193
+ { "weight": 600, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "97.2956%", "ascentOverride": "101.1351%", "descentOverride": "32.4783%", "lineGapOverride": "0.0000%" },
194
+ { "weight": 700, "local": ["Arial Bold", "Arial-BoldMT"], "sizeAdjust": "99.5874%", "ascentOverride": "98.8077%", "descentOverride": "31.7309%", "lineGapOverride": "0.0000%" },
195
+ ] },
196
+ };
@@ -0,0 +1,48 @@
1
+ // Generated from lettercamp/lib/font-resources.ts.
2
+ import { fontFallbacks } from "./font-fallbacks.js";
3
+ export function fontResources(typography = {}) {
4
+ const quote = (value) => '"' + Array.from(String(value), char => /[a-zA-Z0-9 -]/.test(char) ? char : '\\' + char.codePointAt(0).toString(16) + ' ').join('') + '"';
5
+ const google = new Set();
6
+ const custom = new Map();
7
+ const faces = [];
8
+ const resolve = (input) => {
9
+ const value = input && typeof input === "object" ? input : undefined;
10
+ if (value && typeof value.url === 'string' && !(typeof value.google === 'string' && value.google.trim())) {
11
+ try {
12
+ const url = new URL(value.url);
13
+ if (url.protocol !== 'https:' || url.username || url.password)
14
+ throw new Error();
15
+ const weight = typeof value.weight === 'string' && /^(?:[1-9]\d{0,2}|1000)(?: (?:[1-9]\d{0,2}|1000))?$/.test(value.weight) ? value.weight : '400';
16
+ const key = url.href + ' ' + weight;
17
+ if (!custom.has(key)) {
18
+ const family = 'Lettercamp Custom ' + custom.size;
19
+ custom.set(key, family);
20
+ faces.push('@font-face{font-family:' + quote(family) + ';src:url(' + quote(url.href) + ');font-weight:' + weight + ';font-style:normal;font-display:swap;}');
21
+ }
22
+ return quote(custom.get(key)) + ', Arial, Helvetica, sans-serif';
23
+ }
24
+ catch { /* Invalid custom URLs use the default family. */ }
25
+ }
26
+ const family = value && typeof value.google === 'string' && value.google.trim() ? value.google.trim() : 'Inter';
27
+ const fallback = Object.hasOwn(fontFallbacks, family) ? fontFallbacks[family] : undefined;
28
+ if (fallback) {
29
+ const alias = family + ' Fallback';
30
+ if (!google.has(family))
31
+ for (const face of fallback.faces) {
32
+ faces.push('@font-face{font-family:' + quote(alias) + ';src:' + face.local.map(name => 'local(' + quote(name) + ')').join(',')
33
+ + ';font-weight:' + face.weight + ';font-style:normal;size-adjust:' + face.sizeAdjust
34
+ + ';ascent-override:' + face.ascentOverride + ';descent-override:' + face.descentOverride + ';line-gap-override:' + face.lineGapOverride + ';}');
35
+ }
36
+ google.add(family);
37
+ return quote(family) + ', ' + quote(alias) + ', ' + fallback.generic;
38
+ }
39
+ google.add(family);
40
+ return quote(family) + ', Arial, Helvetica, sans-serif';
41
+ };
42
+ const headingFamily = resolve(typography.headingFont);
43
+ const bodyFamily = resolve(typography.bodyFont);
44
+ return {
45
+ headingFamily, bodyFamily, css: faces.join('\n'),
46
+ googleUrl: google.size ? 'https://fonts.googleapis.com/css2?' + [...google].map(family => 'family=' + encodeURIComponent(family) + ':wght@' + (Object.hasOwn(fontFallbacks, family) ? fontFallbacks[family].faces.map(face => face.weight).join(';') : '400;500;600;700')).join('&') + '&display=swap' : null,
47
+ };
48
+ }
@@ -0,0 +1,5 @@
1
+ // Generated from lettercamp/lib/home-layout.ts.
2
+ export function readHomeWidthPx(layout: { contentWidthPx?: unknown } = {}) {
3
+ const width = layout.contentWidthPx;
4
+ return typeof width === "number" && Number.isFinite(width) && width > 0 ? width : 1280;
5
+ }
@@ -0,0 +1,5 @@
1
+ // Generated from lettercamp/lib/image-rounding.ts.
2
+ export const defaultImageRadius = 2;
3
+ export function readImageRadius(value: unknown): number {
4
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 20 ? value : defaultImageRadius;
5
+ }
@@ -0,0 +1,28 @@
1
+ export { readImageRadius } from "./image-radius";
2
+
3
+ export const siteImageSelector = ".post-card-cover, .cover img, .prose img, .article-hero-image img";
4
+ // Observe published and preview content, including live editor replacements.
5
+ export const imageRoundingScript = `(
6
+ function observeImageSizes(root, selector) {
7
+ const observed = new Set();
8
+ const resize = new ResizeObserver(entries => {
9
+ for (const entry of entries) {
10
+ const box = entry.borderBoxSize[0];
11
+ const size = box ? Math.min(box.inlineSize, box.blockSize) : Math.min(entry.contentRect.width, entry.contentRect.height);
12
+ entry.target.style.setProperty("--image-short-side", size + "px");
13
+ }
14
+ });
15
+ const sync = () => {
16
+ for (const target of observed) {
17
+ if (!root.contains(target)) { resize.unobserve(target); observed.delete(target); }
18
+ }
19
+ for (const target of root.querySelectorAll(selector)) {
20
+ if (!observed.has(target)) { observed.add(target); resize.observe(target); }
21
+ }
22
+ };
23
+ sync();
24
+ const mutations = new MutationObserver(sync);
25
+ mutations.observe(root, { childList: true, subtree: true });
26
+ return () => { resize.disconnect(); mutations.disconnect(); };
27
+ }
28
+ )(document, ${JSON.stringify(siteImageSelector)});`;
@@ -0,0 +1,13 @@
1
+ export const imageWidthStyles = `
2
+ .publication-post { container-type: inline-size; }
3
+ .publication-post .prose video[data-image-width] { width: 100%; height: auto; }
4
+ .publication-post .prose span:has(> img[data-image-width]),
5
+ .publication-post .prose span:has(> a > img[data-image-width]) { display: block; }
6
+ .publication-post .article-hero-image { width: min(100%, var(--post-content-width)); max-width: none; margin-inline: auto; }
7
+ .publication-post .article-hero-image:not([data-image-width="regular"]):not([data-image-width="full"]) { width: min(100%, calc(var(--post-content-width) + 128px)); max-width: none; }
8
+ .publication-post .article-hero-image[data-image-width="full"] { width: 100%; max-width: none; }
9
+ .publication-post .article-hero-image[data-image-width="regular"] { width: min(100%, var(--cover-image-width, var(--post-content-width)), var(--post-content-width)); max-width: none; }
10
+ .publication-post .article-hero:has(.article-hero-image:not([data-image-width="regular"])) { display: block; }
11
+ .publication-post .article-hero:has(.article-hero-image:not([data-image-width="regular"])) .article-intro { max-width: var(--post-content-width); margin-inline: auto; }
12
+ .publication-post .article-hero:has(.article-hero-image:not([data-image-width="regular"])) .article-hero-image { margin-top: 32px; }
13
+ `;
@@ -0,0 +1,47 @@
1
+ // Generated from lettercamp/lib/page-metadata.ts.
2
+ export function pageMetadata(site, page, url, article = false, basePath = "") {
3
+ const seo = site.seo && typeof site.seo === "object" ? site.seo : {};
4
+ const text = (value) => typeof value === "string" ? value.trim() : "";
5
+ const title = text(page.title) || text(site.name);
6
+ const pageTitle = title === site.name ? title : `${title} — ${text(site.name)}`;
7
+ const homepage = !article && new URL(url).pathname.replace(/\/+$/, "") === basePath.replace(/\/+$/, "");
8
+ const documentTitle = text(page.metaTitle) || (homepage && text(seo.metaTitle)) || pageTitle;
9
+ const description = text(page.metaDescription) || text(page.description) || text(site.description);
10
+ const socialTitle = text(page.ogTitle) || (homepage && (text(seo.ogTitle) || text(seo.metaTitle))) || title;
11
+ const socialDescription = text(page.ogDescription) || text(page.description) || text(seo.ogDescription) || description;
12
+ const canonical = new URL(url);
13
+ canonical.search = "";
14
+ canonical.hash = "";
15
+ const resolveUrl = (value) => {
16
+ try {
17
+ const resolved = text(value) && new URL(text(value), canonical);
18
+ return resolved && ["https:", "http:"].includes(resolved.protocol) ? resolved.href : "";
19
+ }
20
+ catch {
21
+ return "";
22
+ }
23
+ };
24
+ const imageUrl = resolveUrl(page.ogImage || page.image || seo.ogImage);
25
+ const imageAlt = text(page.ogImageAlt) || (page.ogImage || page.image ? text(page.imageAlt) : text(seo.ogImageAlt));
26
+ const twitterImageUrl = resolveUrl(page.twitterImage) || imageUrl;
27
+ const language = text(page.language) || text(site.language) || "en";
28
+ let locale = "";
29
+ try {
30
+ const parsed = new Intl.Locale(language).maximize();
31
+ locale = `${parsed.language}_${parsed.region}`;
32
+ }
33
+ catch { }
34
+ const keywords = page.keywords || seo.keywords || "";
35
+ return {
36
+ documentTitle, description, socialTitle, socialDescription, imageUrl, imageAlt, language, locale,
37
+ canonical: resolveUrl(page.canonicalUrl) || canonical.href,
38
+ keywords: Array.isArray(keywords) ? keywords.filter(value => typeof value === "string").join(", ") : text(keywords),
39
+ robots: page.noindex === true ? "noindex, follow" : "index, follow",
40
+ videoUrl: resolveUrl(page.ogVideo), videoType: text(page.ogVideoType),
41
+ twitterTitle: text(page.twitterTitle) || socialTitle,
42
+ twitterDescription: text(page.twitterDescription) || socialDescription,
43
+ twitterImageUrl, twitterImageAlt: text(page.twitterImageAlt) || (page.twitterImage ? "" : imageAlt),
44
+ twitterCard: ["summary", "summary_large_image"].includes(text(page.twitterCard)) ? text(page.twitterCard) : twitterImageUrl ? "summary_large_image" : "summary",
45
+ twitterSite: text(page.twitterSite), twitterCreator: text(page.twitterCreator),
46
+ };
47
+ }