@drawnagency/primitives 0.1.90 → 0.1.92

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 (43) hide show
  1. package/dist/{chunk-ZLG7S54V.js → chunk-7QEJDYTG.js} +6 -6
  2. package/dist/{chunk-XUIJHYKI.js → chunk-A2V7BZPD.js} +3 -3
  3. package/dist/{chunk-NV4R6VHN.js → chunk-CI624HRS.js} +1 -1
  4. package/dist/{chunk-SIYQ37QY.js → chunk-IZLXF3CR.js} +1 -1
  5. package/dist/{chunk-XKRQMTE4.js → chunk-LQQY2RW6.js} +2 -2
  6. package/dist/{chunk-KMQZYORS.js → chunk-RUKO2N3F.js} +5 -1
  7. package/dist/{chunk-CZ3YXGK5.js → chunk-SOILPZ7U.js} +27 -3
  8. package/dist/{chunk-AGWE2LJG.js → chunk-WOYGQK2D.js} +1 -1
  9. package/dist/{chunk-KBQNJZUX.js → chunk-YI2HUFE3.js} +1 -1
  10. package/dist/{chunk-MK7YB2A4.js → chunk-Z66TBL4Q.js} +1 -1
  11. package/dist/components/chat/ChatPanel.d.ts +14 -6
  12. package/dist/components/chat/ChatPanel.d.ts.map +1 -1
  13. package/dist/components/chat/ChatWidget.d.ts.map +1 -1
  14. package/dist/components/editor/index.js +4 -4
  15. package/dist/components/primitives/index.js +5 -5
  16. package/dist/components/sections/brand-guide.js +5 -5
  17. package/dist/components/sections/register-schemas.js +8 -8
  18. package/dist/components/sections/register.js +8 -8
  19. package/dist/components/shared/Fab.d.ts +37 -0
  20. package/dist/components/shared/Fab.d.ts.map +1 -0
  21. package/dist/components/shell/BugReportFAB.d.ts.map +1 -1
  22. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  23. package/dist/components/shell/ViewerLoginForm.d.ts +1 -0
  24. package/dist/components/shell/ViewerLoginForm.d.ts.map +1 -1
  25. package/dist/index.js +4 -4
  26. package/dist/lib/index.js +2 -2
  27. package/dist/media/compose-preview.d.ts +9 -0
  28. package/dist/media/compose-preview.d.ts.map +1 -1
  29. package/dist/media/index.js +2 -2
  30. package/dist/media/preview-inputs.d.ts.map +1 -1
  31. package/dist/schemas/index.js +2 -2
  32. package/dist/schemas/site-config.d.ts +1 -0
  33. package/dist/schemas/site-config.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/components/chat/ChatPanel.tsx +51 -19
  36. package/src/components/chat/ChatWidget.tsx +113 -51
  37. package/src/components/shared/Fab.tsx +57 -0
  38. package/src/components/shell/BugReportFAB.tsx +3 -7
  39. package/src/components/shell/EditorShell.tsx +38 -19
  40. package/src/components/shell/ViewerLoginForm.tsx +19 -8
  41. package/src/media/compose-preview.ts +56 -4
  42. package/src/media/preview-inputs.ts +3 -1
  43. package/src/schemas/site-config.ts +4 -0
@@ -10,6 +10,9 @@ export interface PreviewImageInputs {
10
10
  invertLogo: boolean;
11
11
  siteName: string;
12
12
  headingFont: string;
13
+ /** The site's Google Fonts CSS URL (or null for system-ui), used to load the
14
+ * heading font before drawing so text mode never falls back to system-ui. */
15
+ googleFontsUrl: string | null;
13
16
  /** Computed contrasting text color (text mode). */
14
17
  textColor: string;
15
18
  }
@@ -17,6 +20,53 @@ export interface PreviewImageInputs {
17
20
  const WIDTH = 1200;
18
21
  const HEIGHT = 630;
19
22
 
23
+ /**
24
+ * Bump when the composition logic changes so already-composed rasters (stamped
25
+ * with the old version, or 0 for pre-versioning ones) regenerate on next editor
26
+ * open. v2 = load the Google heading font before drawing + draw at weight 700.
27
+ */
28
+ export const PREVIEW_COMPOSE_VERSION = 2;
29
+
30
+ // The raster draws the site name at this weight; the loader ensures this exact
31
+ // weight is available so canvas doesn't silently fall back to a nearest weight.
32
+ const HEADING_WEIGHT = 700;
33
+
34
+ /**
35
+ * Ensure the heading font is actually loaded before canvas draws with it.
36
+ * `document.fonts.load` only fetches a family that already has a matching
37
+ * @font-face rule registered; in the editor that rule comes from a <link> that
38
+ * may not be parsed yet when composition runs, so an ambient load() can resolve
39
+ * empty and the draw silently falls back to system-ui. If the ambient load
40
+ * yields nothing, fetch the Google Fonts CSS and register the weight-700
41
+ * face(s) explicitly via the CSS Font Loading API.
42
+ */
43
+ async function loadHeadingFont(family: string, googleFontsUrl: string | null): Promise<void> {
44
+ if (!family || family === "system-ui" || !googleFontsUrl || typeof FontFace === "undefined") return;
45
+ const spec = `${HEADING_WEIGHT} 84px "${family}"`;
46
+ try {
47
+ const loaded = await document.fonts.load(spec);
48
+ if (loaded.length > 0) return;
49
+ } catch {
50
+ // fall through to explicit registration
51
+ }
52
+ try {
53
+ const css = await (await fetch(googleFontsUrl)).text();
54
+ const blocks = css.match(/@font-face\s*\{[^}]*\}/g) ?? [];
55
+ const wanted = blocks.filter((b) => new RegExp(`font-weight:\\s*${HEADING_WEIGHT}\\b`).test(b));
56
+ await Promise.all(
57
+ (wanted.length ? wanted : blocks).map(async (block) => {
58
+ const url = block.match(/url\(([^)]+)\)/)?.[1];
59
+ if (!url) return;
60
+ const face = new FontFace(family, `url(${url})`, { weight: String(HEADING_WEIGHT) });
61
+ await face.load();
62
+ document.fonts.add(face);
63
+ }),
64
+ );
65
+ } catch {
66
+ // give up — the draw below falls back to system-ui
67
+ }
68
+ }
69
+
20
70
  /**
21
71
  * Composes the 1200×630 social-preview raster in the browser (Canvas API only,
22
72
  * no dependency). Returns a WebP blob. Must run client-side (uses document/Image
@@ -60,13 +110,15 @@ export async function composePreviewImage(inputs: PreviewImageInputs): Promise<B
60
110
 
61
111
  if (!drewLogo) {
62
112
  if (document.fonts?.ready) await document.fonts.ready;
63
- // fillText doesn't itself trigger a font load — explicitly load the exact
64
- // weight/family used below so it's available before we draw.
65
- await document.fonts.load(`600 84px ${inputs.headingFont}`).catch(() => {});
113
+ // Load the heading font (exact weight) before drawing — fillText doesn't
114
+ // itself trigger a webfont load, so without this canvas falls back silently.
115
+ await loadHeadingFont(inputs.headingFont, inputs.googleFontsUrl);
66
116
  ctx.fillStyle = inputs.textColor;
67
117
  ctx.textAlign = "center";
68
118
  ctx.textBaseline = "middle";
69
- ctx.font = `600 84px ${inputs.headingFont}, system-ui, sans-serif`;
119
+ // Quote the family: multi-word names ("Playfair Display") break the CSS font
120
+ // shorthand parse when unquoted, which would drop the whole declaration.
121
+ ctx.font = `${HEADING_WEIGHT} 84px "${inputs.headingFont}", system-ui, sans-serif`;
70
122
  ctx.fillText(inputs.siteName, WIDTH / 2, HEIGHT / 2, WIDTH * 0.86);
71
123
  }
72
124
 
@@ -19,7 +19,7 @@ export function derivePreviewInputs(
19
19
  siteConfig: SiteConfig,
20
20
  resolveLogoUrl: (imageId: string) => string | null,
21
21
  ): { inputs: PreviewImageInputs; signature: string } {
22
- const { logo, darkMode, primaryColor, siteName, headingFont } = siteConfig;
22
+ const { logo, darkMode, primaryColor, siteName, headingFont, googleFontsUrl } = siteConfig;
23
23
  const dark = darkMode === "dark";
24
24
 
25
25
  let mode: "logo" | "text" = "text";
@@ -43,6 +43,7 @@ export function derivePreviewInputs(
43
43
  invertLogo,
44
44
  siteName,
45
45
  headingFont,
46
+ googleFontsUrl,
46
47
  textColor: contrastColor(primaryColor),
47
48
  };
48
49
 
@@ -53,6 +54,7 @@ export function derivePreviewInputs(
53
54
  invertLogo: inputs.invertLogo,
54
55
  siteName: inputs.siteName,
55
56
  headingFont: inputs.headingFont,
57
+ googleFontsUrl: inputs.googleFontsUrl,
56
58
  textColor: inputs.textColor,
57
59
  });
58
60
 
@@ -245,6 +245,10 @@ export const SiteConfigSchema = z.object({
245
245
  imageId: z.string(),
246
246
  width: z.number().int().positive(),
247
247
  height: z.number().int().positive(),
248
+ // Compose-logic version that produced this raster. Pre-existing rasters
249
+ // (and manifests written before this field) default to 0, which is below
250
+ // any current PREVIEW_COMPOSE_VERSION, so the editor regenerates them.
251
+ version: z.number().int().nonnegative().default(0),
248
252
  })
249
253
  .nullable()
250
254
  .default(null),