@snowcone-app/canvas 0.21.1 → 0.21.3

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 (54) hide show
  1. package/STYLING_CONTRACT.md +14 -0
  2. package/dist/{CanvasRenderer-CwiYq8qg.js → CanvasRenderer-6_HhR4-q.js} +170 -188
  3. package/dist/CanvasRenderer-6_HhR4-q.js.map +1 -0
  4. package/dist/{CanvasStateV1-BFabcAKC.js → CanvasStateV1-DmZ1Qvvn.js} +38 -38
  5. package/dist/{CanvasStateV1-BFabcAKC.js.map → CanvasStateV1-DmZ1Qvvn.js.map} +1 -1
  6. package/dist/{ContextualToolbars--vTtorNg.js → ContextualToolbars-BDEt9ND0.js} +2047 -2324
  7. package/dist/ContextualToolbars-BDEt9ND0.js.map +1 -0
  8. package/dist/{EditorCanvasImpl-2ZgIUVpK.js → EditorCanvasImpl-M7rw1c1k.js} +8 -8
  9. package/dist/{EditorCanvasImpl-2ZgIUVpK.js.map → EditorCanvasImpl-M7rw1c1k.js.map} +1 -1
  10. package/dist/{ExportManager-DK7CdI-G.js → ExportManager-DoU4VK6h.js} +603 -498
  11. package/dist/ExportManager-DoU4VK6h.js.map +1 -0
  12. package/dist/{FontAnalyzer-BpsTYowD.js → FontAnalyzer-CwIpgTGd.js} +309 -299
  13. package/dist/{FontAnalyzer-BpsTYowD.js.map → FontAnalyzer-CwIpgTGd.js.map} +1 -1
  14. package/dist/ImagePanel-BwqFDKvW.js +951 -0
  15. package/dist/ImagePanel-BwqFDKvW.js.map +1 -0
  16. package/dist/{SnowconeCanvas-DqZicdxK.js → SnowconeCanvas-Cx7nXlBI.js} +22 -21
  17. package/dist/{SnowconeCanvas-DqZicdxK.js.map → SnowconeCanvas-Cx7nXlBI.js.map} +1 -1
  18. package/dist/{UnifiedFontService-B1PGVu8O.js → UnifiedFontService-CC3CSmH5.js} +1242 -1188
  19. package/dist/UnifiedFontService-CC3CSmH5.js.map +1 -0
  20. package/dist/advanced.mjs +100 -99
  21. package/dist/advanced.mjs.map +1 -1
  22. package/dist/components/editor/LazyImageBrowser.d.ts +7 -0
  23. package/dist/components/editor/commerceStore.d.ts +13 -0
  24. package/dist/custom-icons-BW6hqmkL.js +236 -0
  25. package/dist/custom-icons-BW6hqmkL.js.map +1 -0
  26. package/dist/{design-builder-CPF60RU2.js → design-builder-CT4DHIPe.js} +2 -2
  27. package/dist/{design-builder-CPF60RU2.js.map → design-builder-CT4DHIPe.js.map} +1 -1
  28. package/dist/{editorErrors-CK1RISFa.js → editorErrors-B2mb1sVN.js} +2 -2
  29. package/dist/{editorErrors-CK1RISFa.js.map → editorErrors-B2mb1sVN.js.map} +1 -1
  30. package/dist/{embed-2tp6Ue90.js → embed-QBZkgWDr.js} +288 -273
  31. package/dist/{embed-2tp6Ue90.js.map → embed-QBZkgWDr.js.map} +1 -1
  32. package/dist/embed.mjs +6 -6
  33. package/dist/guards-8rkFAMBs.js +24 -0
  34. package/dist/guards-8rkFAMBs.js.map +1 -0
  35. package/dist/index.mjs +8 -8
  36. package/dist/internals.mjs +11 -11
  37. package/dist/rendering/element-painters.d.ts +4 -2
  38. package/dist/style.css +1 -1
  39. package/dist/{template-fields-FgImixyv.js → template-fields-C2pSfMBh.js} +59 -41
  40. package/dist/template-fields-C2pSfMBh.js.map +1 -0
  41. package/dist/testing.mjs +4 -4
  42. package/dist/{usePieceGuideBundle-Ctnu5k2D.js → usePieceGuideBundle-CbJJg66S.js} +2 -2
  43. package/dist/{usePieceGuideBundle-Ctnu5k2D.js.map → usePieceGuideBundle-CbJJg66S.js.map} +1 -1
  44. package/dist/validation-C9Fe9ahK.js +8367 -0
  45. package/dist/validation-C9Fe9ahK.js.map +1 -0
  46. package/example/package.json +1 -1
  47. package/package.json +3 -2
  48. package/dist/CanvasRenderer-CwiYq8qg.js.map +0 -1
  49. package/dist/ContextualToolbars--vTtorNg.js.map +0 -1
  50. package/dist/ExportManager-DK7CdI-G.js.map +0 -1
  51. package/dist/UnifiedFontService-B1PGVu8O.js.map +0 -1
  52. package/dist/template-fields-FgImixyv.js.map +0 -1
  53. package/dist/validation-3YEMnKLB.js +0 -9308
  54. package/dist/validation-3YEMnKLB.js.map +0 -1
@@ -0,0 +1,7 @@
1
+ import { default as React } from 'react';
2
+ export declare function LazyImageBrowser({ part, isOpen, onOpenChange, }: {
3
+ /** The part that owns this browser instance — names the error source. */
4
+ part: 'Editor.Add' | 'Editor.Menu';
5
+ isOpen: boolean;
6
+ onOpenChange: (open: boolean) => void;
7
+ }): React.ReactElement | null;
@@ -57,6 +57,19 @@ export declare class CommerceStateStore {
57
57
  /** The variant (gvid) the current selection resolves to. Falls back to the
58
58
  * product's initial `variantId` while nothing has been selected. */
59
59
  resolvedVariantId(): string | undefined;
60
+ /**
61
+ * The variant (gvid) to RENDER with — deliberately looser than
62
+ * {@link resolvedVariantId}. That one is Buy's currency: it returns
63
+ * `undefined` while the selection is incomplete so completeness can gate.
64
+ * But the PRODUCT PHOTO must follow the shopper's picks immediately —
65
+ * "choose the Oak frame" should render Oak even while an unrelated axis
66
+ * (size, pocket) is still unselected. This resolves the best combination
67
+ * matching the CURRENT (possibly partial) selection, falling back to the
68
+ * product's initial variant. (Before this split, switching a color swatch
69
+ * with any axis unselected rendered nothing — the mockup silently stayed
70
+ * on the old color.)
71
+ */
72
+ renderVariantId(): string | undefined;
60
73
  /** Price of the resolved combination, when the catalog carries one. */
61
74
  resolvedPrice(): number | undefined;
62
75
  /** Choices invalid given the rest of the current selection — for hosts to
@@ -0,0 +1,236 @@
1
+ import { jsx as i, jsxs as h } from "react/jsx-runtime";
2
+ import * as c from "react";
3
+ import { n as T, y, N as w } from "./UnifiedFontService-CC3CSmH5.js";
4
+ import * as R from "@radix-ui/react-label";
5
+ import { cva as S } from "class-variance-authority";
6
+ import * as g from "@radix-ui/react-tabs";
7
+ const E = c.forwardRef(
8
+ ({
9
+ className: s,
10
+ type: e,
11
+ isInvalid: a,
12
+ startContent: t,
13
+ endContent: o,
14
+ disabled: f,
15
+ readOnly: b,
16
+ required: r,
17
+ variant: n,
18
+ ...d
19
+ }, u) => {
20
+ const m = T(), l = m.variant === "default" || m.variant === "secondary", p = n ?? (l ? "filled" : "default"), v = /* @__PURE__ */ i(
21
+ "input",
22
+ {
23
+ type: e,
24
+ className: y(
25
+ "sc:flex sc:h-10 sc:w-full sc:rounded-input sc:px-2.5 sc:py-1.5 sc:text-sm sc:appearance-none",
26
+ "sc:text-foreground sc:border-none",
27
+ "sc:file:border-0 sc:file:bg-transparent sc:file:text-sm sc:file:font-medium sc:file:text-foreground",
28
+ "sc:placeholder:text-foreground/50",
29
+ "sc:transition-shadow sc:duration-200 sc:ease-out sc:focus-visible:outline-none sc:focus:ring-2 sc:focus:ring-focus sc:focus:ring-offset-1 sc:focus:ring-offset-background",
30
+ "sc:disabled:cursor-not-allowed sc:disabled:opacity-50",
31
+ // Variant styles - semantic: field on page vs field on surface vs toolbar
32
+ p === "default" && "sc:bg-field sc:shadow-soft sc:dark:shadow-none",
33
+ p === "filled" && "sc:bg-[var(--color-field-on-surface)]",
34
+ p === "subtle" && "sc:bg-default sc:dark:bg-[var(--color-default-on-surface)]",
35
+ a && "sc:ring-2 sc:ring-danger sc:focus-visible:ring-danger",
36
+ t && "sc:pl-10",
37
+ o && "sc:pr-10",
38
+ s
39
+ ),
40
+ ref: u,
41
+ disabled: f,
42
+ readOnly: b,
43
+ required: r,
44
+ "aria-invalid": a,
45
+ ...d
46
+ }
47
+ );
48
+ return t || o ? /* @__PURE__ */ h("div", { className: "sc:relative", children: [
49
+ t && /* @__PURE__ */ i("div", { className: "sc:absolute sc:left-3 sc:top-1/2 sc:-translate-y-1/2 sc:text-foreground sc:opacity-40", children: t }),
50
+ v,
51
+ o && /* @__PURE__ */ i("div", { className: "sc:absolute sc:right-3 sc:top-1/2 sc:-translate-y-1/2 sc:text-foreground sc:opacity-40", children: o })
52
+ ] }) : v;
53
+ }
54
+ );
55
+ E.displayName = "Input";
56
+ const C = S(
57
+ "sc:block sc:text-sm sc:leading-none sc:text-foreground sc:peer-disabled:cursor-not-allowed sc:peer-disabled:opacity-70 sc:font-label"
58
+ ), j = c.forwardRef(({ className: s, ...e }, a) => /* @__PURE__ */ i(R.Root, { ref: a, className: y(C(), s), ...e }));
59
+ j.displayName = R.Root.displayName;
60
+ const L = c.forwardRef(
61
+ ({ value: s, onValueChange: e, ...a }, t) => /* @__PURE__ */ i(g.Root, { ref: t, value: s, onValueChange: e, ...a })
62
+ );
63
+ L.displayName = "Tabs";
64
+ const k = c.forwardRef(({ className: s, children: e, ...a }, t) => {
65
+ const o = c.useRef(null), [f, b] = c.useState({
66
+ opacity: 0
67
+ });
68
+ return c.useEffect(() => {
69
+ const r = o.current;
70
+ if (!r) return;
71
+ const n = () => {
72
+ const l = r.querySelector('[data-state="active"]');
73
+ if (l) {
74
+ const p = r.getBoundingClientRect(), v = l.getBoundingClientRect();
75
+ b({
76
+ width: v.width,
77
+ height: v.height,
78
+ left: v.left - p.left,
79
+ opacity: 1
80
+ });
81
+ }
82
+ };
83
+ n();
84
+ const d = new MutationObserver((l) => {
85
+ for (const p of l)
86
+ if (p.type === "attributes" && p.attributeName === "data-state") {
87
+ n();
88
+ break;
89
+ }
90
+ });
91
+ r.querySelectorAll('[role="tab"]').forEach((l) => {
92
+ d.observe(l, { attributes: !0, attributeFilter: ["data-state"] });
93
+ });
94
+ const m = new ResizeObserver(n);
95
+ return m.observe(r), () => {
96
+ d.disconnect(), m.disconnect();
97
+ };
98
+ }, [e]), /* @__PURE__ */ h(
99
+ g.List,
100
+ {
101
+ ref: (r) => {
102
+ o.current = r, typeof t == "function" ? t(r) : t && (t.current = r);
103
+ },
104
+ className: y(
105
+ "sc:relative sc:inline-flex sc:h-10 sc:items-center sc:justify-center sc:rounded-full sc:px-1 sc:text-muted-foreground",
106
+ // Consistent bg-default matches Button secondary variant for toolbar harmony
107
+ "sc:bg-default sc:dark:bg-[var(--color-default-on-surface)]",
108
+ s
109
+ ),
110
+ ...a,
111
+ children: [
112
+ /* @__PURE__ */ i(
113
+ "span",
114
+ {
115
+ className: "sc:absolute sc:top-1/2 sc:-translate-y-1/2 sc:rounded-full sc:bg-surface sc:dark:bg-popover sc:shadow-md sc:transition-[left,width,opacity]",
116
+ style: {
117
+ ...f,
118
+ transitionDuration: "350ms",
119
+ transitionTimingFunction: "cubic-bezier(0.22, 1, 0.36, 1)"
120
+ },
121
+ "aria-hidden": "true"
122
+ }
123
+ ),
124
+ e
125
+ ]
126
+ }
127
+ );
128
+ });
129
+ k.displayName = g.List.displayName;
130
+ const x = c.forwardRef(
131
+ ({ className: s, id: e, value: a, ...t }, o) => {
132
+ const { variant: f } = T(), b = f === "default" || f === "secondary";
133
+ return /* @__PURE__ */ i(
134
+ g.Trigger,
135
+ {
136
+ ref: o,
137
+ value: e ?? a ?? "",
138
+ className: y(
139
+ "sc:relative sc:z-10 sc:inline-flex sc:h-8 sc:items-center sc:justify-center sc:gap-2 sc:whitespace-nowrap sc:rounded-full sc:px-4 sc:text-sm sc:font-label",
140
+ "sc:transition-[color,opacity]",
141
+ "sc:focus-visible:outline-none sc:focus-visible:ring-2 sc:focus-visible:ring-focus sc:focus-visible:ring-offset-2",
142
+ "sc:disabled:pointer-events-none sc:disabled:opacity-50",
143
+ "sc:data-[state=active]:text-foreground sc:data-[state=active]:opacity-100",
144
+ // Inactive tabs use element opacity (not color opacity) to avoid SVG path overlap issues
145
+ b ? "sc:data-[state=inactive]:text-foreground sc:data-[state=inactive]:opacity-30 sc:data-[state=inactive]:hover:opacity-50" : "sc:data-[state=inactive]:text-foreground sc:data-[state=inactive]:opacity-50 sc:data-[state=inactive]:hover:opacity-70",
146
+ s
147
+ ),
148
+ ...t
149
+ }
150
+ );
151
+ }
152
+ );
153
+ x.displayName = g.Trigger.displayName;
154
+ const N = c.forwardRef(
155
+ ({ className: s, id: e, value: a, ...t }, o) => /* @__PURE__ */ i(
156
+ g.Content,
157
+ {
158
+ ref: o,
159
+ value: e ?? a ?? "",
160
+ className: y(
161
+ "sc:mt-2 sc:ring-offset-background sc:focus-visible:outline-none sc:focus-visible:ring-2 sc:focus-visible:ring-focus sc:focus-visible:ring-offset-2",
162
+ s
163
+ ),
164
+ ...t
165
+ }
166
+ )
167
+ );
168
+ N.displayName = g.Content.displayName;
169
+ const I = c.forwardRef(
170
+ ({ className: s, ...e }, a) => /* @__PURE__ */ i("div", { ref: a, className: s, ...e })
171
+ );
172
+ I.displayName = "TabsListContainer";
173
+ const O = x, B = N, P = () => null, q = L, z = Object.assign(q, {
174
+ List: k,
175
+ ListContainer: I,
176
+ Tab: O,
177
+ Trigger: x,
178
+ Panel: B,
179
+ Content: N,
180
+ Indicator: P
181
+ }), A = c.forwardRef(
182
+ ({ className: s, children: e, isInvalid: a, disabled: t, required: o, ...f }, b) => {
183
+ const r = c.Children.map(e, (n) => {
184
+ if (!c.isValidElement(n)) return n;
185
+ const d = n.type, u = typeof d == "function" ? d.displayName || d.name : typeof d == "string" ? d : "";
186
+ if (u === "Input" || u === "Textarea" || u === "TextArea" || u === "input" || u === "textarea") {
187
+ const l = n.props;
188
+ return c.cloneElement(n, {
189
+ isInvalid: l.isInvalid ?? a,
190
+ disabled: l.disabled ?? t,
191
+ required: l.required ?? o
192
+ });
193
+ }
194
+ return n;
195
+ });
196
+ return /* @__PURE__ */ i("div", { ref: b, className: y("sc:flex sc:flex-col sc:gap-1.5", s), ...f, children: r });
197
+ }
198
+ );
199
+ A.displayName = "TextField";
200
+ const D = ({ className: s, size: e = 24 }) => /* @__PURE__ */ h(
201
+ "svg",
202
+ {
203
+ viewBox: "0 0 24 24",
204
+ fill: "none",
205
+ stroke: "currentColor",
206
+ strokeWidth: "2",
207
+ strokeLinecap: "round",
208
+ strokeLinejoin: "round",
209
+ width: e,
210
+ height: e,
211
+ className: w("sc:inline-block iconify", s),
212
+ children: [
213
+ /* @__PURE__ */ i("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
214
+ /* @__PURE__ */ i("path", { d: "M3 3v5h5" })
215
+ ]
216
+ }
217
+ ), W = ({ className: s, size: e = 24 }) => /* @__PURE__ */ i(
218
+ "svg",
219
+ {
220
+ viewBox: "0 0 28 28",
221
+ fill: "currentColor",
222
+ width: e,
223
+ height: e,
224
+ className: w("sc:inline-block", s),
225
+ children: /* @__PURE__ */ i("path", { d: "M10.06 18.701a1.628 1.628 0 0 0 2.43-.676l.77-2.34a3.82 3.82 0 0 1 2.416-2.418l2.238-.727a1.6 1.6 0 0 0 .786-.595a1.62 1.62 0 0 0-.849-2.489l-2.215-.72a3.82 3.82 0 0 1-2.42-2.414l-.727-2.237a1.62 1.62 0 0 0-.594-.785a1.655 1.655 0 0 0-1.879 0a1.63 1.63 0 0 0-.6.8L8.68 6.365a3.82 3.82 0 0 1-2.359 2.37l-2.24.726a1.626 1.626 0 0 0 .02 3.073l2.216.72a3.86 3.86 0 0 1 1.816 1.286c.266.343.471.728.606 1.14l.728 2.234c.112.318.32.593.595.787m-3.273-6.873l-2.22-.72s-.085-.038-.085-.108a.14.14 0 0 1 .084-.115L6.8 10.16a5.34 5.34 0 0 0 3.32-3.375l.721-2.219s.043-.084.114-.084s.113.084.113.084l.723 2.223a5.32 5.32 0 0 0 3.377 3.372l2.27.735a.13.13 0 0 1 .078.11a.15.15 0 0 1-.083.113l-2.225.722a5.32 5.32 0 0 0-3.374 3.374l-.715 2.2a.11.11 0 0 1-.115.093c-.104 0-.118-.075-.118-.075l-.723-2.22a5.32 5.32 0 0 0-3.376-3.385m13.017 12.954A1.2 1.2 0 0 0 20.5 25a1.2 1.2 0 0 0 .692-.216a1.2 1.2 0 0 0 .446-.6l.372-1.143a1.61 1.61 0 0 1 1.017-1.02l1.166-.378A1.21 1.21 0 0 0 25 20.505a1.2 1.2 0 0 0-.844-1.146l-1.144-.37a1.61 1.61 0 0 1-1.02-1.018l-.38-1.163a1.2 1.2 0 0 0-2.274.016l-.374 1.146a1.61 1.61 0 0 1-.993 1.017l-1.166.378a1.21 1.21 0 0 0-.807 1.139a1.2 1.2 0 0 0 .823 1.138l1.144.372a1.6 1.6 0 0 1 1.02 1.023l.379 1.163a1.2 1.2 0 0 0 .44.582m-1.371-4.194l-.27-.088l.283-.096a3.12 3.12 0 0 0 1.943-1.974l.088-.27l.089.272a3.11 3.11 0 0 0 1.974 1.975l.293.1l-.271.087a3.12 3.12 0 0 0-1.974 1.977l-.088.27l-.088-.272a3.12 3.12 0 0 0-1.98-1.98" })
226
+ }
227
+ );
228
+ export {
229
+ E as I,
230
+ j as L,
231
+ D as R,
232
+ W as S,
233
+ z as T,
234
+ A as a
235
+ };
236
+ //# sourceMappingURL=custom-icons-BW6hqmkL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-icons-BW6hqmkL.js","sources":["../src/vendor/ui/primitives/input.tsx","../src/vendor/ui/primitives/label.tsx","../src/vendor/ui/primitives/tabs.tsx","../src/vendor/ui/primitives/text-field.tsx","../src/components/ui/custom-icons.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '../lib/utils';\nimport { useSurface } from './surface';\n\nexport interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {\n /** Whether the input is in an invalid state */\n isInvalid?: boolean;\n /** Content to display at the start of the input */\n startContent?: React.ReactNode;\n /** Content to display at the end of the input */\n endContent?: React.ReactNode;\n /**\n * Visual variant - auto-detected from Surface context, or can be set explicitly\n * - `default`: Standard field styling for forms (bg-field)\n * - `filled`: Field on elevated surface (bg-field-on-surface)\n * - `subtle`: Blends with buttons/tabs in toolbars (bg-default)\n */\n variant?: 'default' | 'filled' | 'subtle';\n}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n (\n {\n className,\n type,\n isInvalid,\n startContent,\n endContent,\n disabled,\n readOnly,\n required,\n variant,\n ...props\n },\n ref\n ) => {\n // Auto-detect variant from Surface context\n const surface = useSurface();\n const isOnSurface = surface.variant === 'default' || surface.variant === 'secondary';\n const effectiveVariant = variant ?? (isOnSurface ? 'filled' : 'default');\n\n const inputElement = (\n <input\n type={type}\n className={cn(\n 'sc:flex sc:h-10 sc:w-full sc:rounded-input sc:px-2.5 sc:py-1.5 sc:text-sm sc:appearance-none',\n 'sc:text-foreground sc:border-none',\n 'sc:file:border-0 sc:file:bg-transparent sc:file:text-sm sc:file:font-medium sc:file:text-foreground',\n 'sc:placeholder:text-foreground/50',\n 'sc:transition-shadow sc:duration-200 sc:ease-out sc:focus-visible:outline-none sc:focus:ring-2 sc:focus:ring-focus sc:focus:ring-offset-1 sc:focus:ring-offset-background',\n 'sc:disabled:cursor-not-allowed sc:disabled:opacity-50',\n // Variant styles - semantic: field on page vs field on surface vs toolbar\n effectiveVariant === 'default' && 'sc:bg-field sc:shadow-soft sc:dark:shadow-none',\n effectiveVariant === 'filled' && 'sc:bg-[var(--color-field-on-surface)]',\n effectiveVariant === 'subtle' && 'sc:bg-default sc:dark:bg-[var(--color-default-on-surface)]',\n isInvalid && 'sc:ring-2 sc:ring-danger sc:focus-visible:ring-danger',\n startContent && 'sc:pl-10',\n endContent && 'sc:pr-10',\n className\n )}\n ref={ref}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n aria-invalid={isInvalid}\n {...props}\n />\n );\n\n if (startContent || endContent) {\n return (\n <div className=\"sc:relative\">\n {startContent && (\n <div className=\"sc:absolute sc:left-3 sc:top-1/2 sc:-translate-y-1/2 sc:text-foreground sc:opacity-40\">{startContent}</div>\n )}\n {inputElement}\n {endContent && (\n <div className=\"sc:absolute sc:right-3 sc:top-1/2 sc:-translate-y-1/2 sc:text-foreground sc:opacity-40\">{endContent}</div>\n )}\n </div>\n );\n }\n\n return inputElement;\n }\n);\nInput.displayName = 'Input';\n\nexport { Input };\n","'use client';\n\nimport * as React from 'react';\nimport * as LabelPrimitive from '@radix-ui/react-label';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/utils';\n\nconst labelVariants = cva(\n 'sc:block sc:text-sm sc:leading-none sc:text-foreground sc:peer-disabled:cursor-not-allowed sc:peer-disabled:opacity-70 sc:font-label'\n);\n\nconst Label = React.forwardRef<\n React.ElementRef<typeof LabelPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>\n>(({ className, ...props }, ref) => (\n <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />\n));\nLabel.displayName = LabelPrimitive.Root.displayName;\n\nexport { Label };\n","\"use client\";\n\n'use client';\n\nimport * as React from 'react';\nimport * as TabsPrimitive from '@radix-ui/react-tabs';\nimport { cn } from '../lib/utils';\nimport { useSurface } from './surface';\n\ninterface TabsProps\n extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>, 'value' | 'onValueChange'> {\n value?: string;\n onValueChange?: (value: string) => void;\n}\n\nconst TabsRoot = React.forwardRef<React.ElementRef<typeof TabsPrimitive.Root>, TabsProps>(\n ({ value, onValueChange, ...props }, ref) => (\n <TabsPrimitive.Root ref={ref} value={value} onValueChange={onValueChange} {...props} />\n )\n);\nTabsRoot.displayName = 'Tabs';\n\nconst TabsList = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.List>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, children, ...props }, ref) => {\n const listRef = React.useRef<HTMLDivElement>(null);\n const [indicatorStyle, setIndicatorStyle] = React.useState<React.CSSProperties>({\n opacity: 0,\n });\n\n // Update indicator position when active tab changes\n React.useEffect(() => {\n const listElement = listRef.current;\n if (!listElement) return;\n\n const updateIndicator = () => {\n const activeTab = listElement.querySelector('[data-state=\"active\"]') as HTMLElement;\n if (activeTab) {\n const listRect = listElement.getBoundingClientRect();\n const tabRect = activeTab.getBoundingClientRect();\n\n setIndicatorStyle({\n width: tabRect.width,\n height: tabRect.height,\n left: tabRect.left - listRect.left,\n opacity: 1,\n });\n }\n };\n\n // Initial position\n updateIndicator();\n\n // Use MutationObserver to detect when active tab changes\n const observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type === 'attributes' && mutation.attributeName === 'data-state') {\n updateIndicator();\n break;\n }\n }\n });\n\n // Observe all trigger elements for data-state changes\n const triggers = listElement.querySelectorAll('[role=\"tab\"]');\n triggers.forEach((trigger) => {\n observer.observe(trigger, { attributes: true, attributeFilter: ['data-state'] });\n });\n\n // Also update on resize\n const resizeObserver = new ResizeObserver(updateIndicator);\n resizeObserver.observe(listElement);\n\n return () => {\n observer.disconnect();\n resizeObserver.disconnect();\n };\n }, [children]);\n\n return (\n <TabsPrimitive.List\n ref={(node) => {\n // Handle both refs\n (listRef as React.MutableRefObject<HTMLDivElement | null>).current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n }}\n className={cn(\n 'sc:relative sc:inline-flex sc:h-10 sc:items-center sc:justify-center sc:rounded-full sc:px-1 sc:text-muted-foreground',\n // Consistent bg-default matches Button secondary variant for toolbar harmony\n 'sc:bg-default sc:dark:bg-[var(--color-default-on-surface)]',\n className\n )}\n {...props}\n >\n {/* Animated sliding indicator. In dark mode the previous `bg-muted`\n value resolved to the same color as `bg-default` on the container,\n so the active tab was invisible without relying purely on\n shadow-md. `bg-popover` lands ~0.08 lightness above `bg-default`\n in dark mode for a clearly visible elevation. */}\n <span\n className=\"sc:absolute sc:top-1/2 sc:-translate-y-1/2 sc:rounded-full sc:bg-surface sc:dark:bg-popover sc:shadow-md sc:transition-[left,width,opacity]\"\n style={{\n ...indicatorStyle,\n transitionDuration: '350ms',\n transitionTimingFunction: 'cubic-bezier(0.22, 1, 0.36, 1)',\n }}\n aria-hidden=\"true\"\n />\n {children}\n </TabsPrimitive.List>\n );\n});\nTabsList.displayName = TabsPrimitive.List.displayName;\n\ninterface TabsTriggerProps extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>, 'value'> {\n // HeroUI uses 'id' for tab identification, Radix uses 'value'\n // At least one must be provided\n id?: string;\n value?: string;\n}\n\nconst TabsTrigger = React.forwardRef<React.ElementRef<typeof TabsPrimitive.Trigger>, TabsTriggerProps>(\n ({ className, id, value, ...props }, ref) => {\n const { variant: surfaceVariant } = useSurface();\n const isOnSurface = surfaceVariant === 'default' || surfaceVariant === 'secondary';\n\n return (\n <TabsPrimitive.Trigger\n ref={ref}\n value={id ?? value ?? ''}\n className={cn(\n 'sc:relative sc:z-10 sc:inline-flex sc:h-8 sc:items-center sc:justify-center sc:gap-2 sc:whitespace-nowrap sc:rounded-full sc:px-4 sc:text-sm sc:font-label',\n 'sc:transition-[color,opacity]',\n 'sc:focus-visible:outline-none sc:focus-visible:ring-2 sc:focus-visible:ring-focus sc:focus-visible:ring-offset-2',\n 'sc:disabled:pointer-events-none sc:disabled:opacity-50',\n 'sc:data-[state=active]:text-foreground sc:data-[state=active]:opacity-100',\n // Inactive tabs use element opacity (not color opacity) to avoid SVG path overlap issues\n isOnSurface\n ? 'sc:data-[state=inactive]:text-foreground sc:data-[state=inactive]:opacity-30 sc:data-[state=inactive]:hover:opacity-50'\n : 'sc:data-[state=inactive]:text-foreground sc:data-[state=inactive]:opacity-50 sc:data-[state=inactive]:hover:opacity-70',\n className\n )}\n {...props}\n />\n );\n }\n);\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName;\n\ninterface TabsContentProps extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>, 'value'> {\n // HeroUI uses 'id' for panel identification, Radix uses 'value'\n // At least one must be provided\n id?: string;\n value?: string;\n}\n\nconst TabsContent = React.forwardRef<React.ElementRef<typeof TabsPrimitive.Content>, TabsContentProps>(\n ({ className, id, value, ...props }, ref) => (\n <TabsPrimitive.Content\n ref={ref}\n value={id ?? value ?? ''}\n className={cn(\n 'sc:mt-2 sc:ring-offset-background sc:focus-visible:outline-none sc:focus-visible:ring-2 sc:focus-visible:ring-focus sc:focus-visible:ring-offset-2',\n className\n )}\n {...props}\n />\n )\n);\nTabsContent.displayName = TabsPrimitive.Content.displayName;\n\n// HeroUI ListContainer - wrapper for the tabs list (passthrough div)\nconst TabsListContainer = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => <div ref={ref} className={className} {...props} />\n);\nTabsListContainer.displayName = 'TabsListContainer';\n\n// HeroUI Tab - alias for TabsTrigger\nconst TabsTab = TabsTrigger;\n\n// HeroUI Panel - alias for TabsContent\nconst TabsPanel = TabsContent;\n\n// HeroUI Indicator - now rendered automatically by TabsList, this is a no-op\nconst TabsIndicator = () => null;\n\n// Create compound component with all aliases\nconst TabsBase = TabsRoot;\n\n// HeroUI-compatible compound component\nconst Tabs = Object.assign(TabsBase, {\n List: TabsList,\n ListContainer: TabsListContainer,\n Tab: TabsTab,\n Trigger: TabsTrigger,\n Panel: TabsPanel,\n Content: TabsContent,\n Indicator: TabsIndicator,\n});\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent, TabsRoot };\n","'use client';\n\nimport * as React from 'react';\nimport { cn } from '../lib/utils';\n\nexport interface TextFieldProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Accessible label for the input.\n * If not provided via children, use aria-label on the div.\n */\n 'aria-label'?: string;\n /**\n * Error state - applies error styling to children inputs\n */\n isInvalid?: boolean;\n /**\n * Disabled state - disables all children inputs\n */\n disabled?: boolean;\n /**\n * Required state - marks all children inputs as required\n */\n required?: boolean;\n}\n\n/**\n * TextField - A wrapper component that groups Label and Input together.\n *\n * Groups Label and Input with a convenient wrapper that can propagate\n * disabled, required, and invalid states to child inputs.\n *\n * @example\n * ```tsx\n * <TextField>\n * <Label>Email</Label>\n * <Input type=\"email\" placeholder=\"Enter email...\" />\n * </TextField>\n * ```\n */\nconst TextField = React.forwardRef<HTMLDivElement, TextFieldProps>(\n ({ className, children, isInvalid, disabled, required, ...props }, ref) => {\n // Clone children to pass through state props to Input/Textarea children\n const enhancedChildren = React.Children.map(children, (child) => {\n if (!React.isValidElement(child)) return child;\n\n // Get the display name or type name to check if it's an input component\n const childType = child.type;\n const displayName =\n typeof childType === 'function'\n ? (childType as React.ComponentType).displayName || childType.name\n : typeof childType === 'string'\n ? childType\n : '';\n\n // Check if this is an input-like element that should receive state props\n const isInputElement =\n displayName === 'Input' ||\n displayName === 'Textarea' ||\n displayName === 'TextArea' ||\n displayName === 'input' ||\n displayName === 'textarea';\n\n if (isInputElement) {\n const childProps = child.props as Record<string, unknown>;\n return React.cloneElement(child as React.ReactElement<Record<string, unknown>>, {\n isInvalid: (childProps.isInvalid as boolean) ?? isInvalid,\n disabled: (childProps.disabled as boolean) ?? disabled,\n required: (childProps.required as boolean) ?? required,\n });\n }\n\n return child;\n });\n\n return (\n <div ref={ref} className={cn('sc:flex sc:flex-col sc:gap-1.5', className)} {...props}>\n {enhancedChildren}\n </div>\n );\n }\n);\nTextField.displayName = 'TextField';\n\nexport { TextField };\n","/**\n * Custom Icons - Centered versions of icons from various sets\n *\n * These are copies of icons with adjusted viewBox to center properly.\n * Use these instead of the original iconify versions for consistent alignment.\n */\n\nimport React from 'react';\nimport { cn } from '../../lib/utils';\n\ninterface IconProps {\n className?: string;\n size?: number | string;\n}\n\n/**\n * Rotate Left icon (lucide-style stroked version)\n */\nexport const RotateLeftIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n width={size}\n height={size}\n className={cn('sc:inline-block iconify', className)}\n >\n <path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\" />\n <path d=\"M3 3v5h5\" />\n </svg>\n);\n\n/**\n * Rotate Right icon (lucide-style stroked version)\n */\nexport const RotateRightIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n width={size}\n height={size}\n className={cn('sc:inline-block iconify', className)}\n >\n <path d=\"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8\" />\n <path d=\"M21 3v5h-5\" />\n </svg>\n);\n\n/**\n * Sparkles icon (based on gravity-ui:sparkles)\n * Two sparkle shapes - main 4-point star and smaller accent\n */\nexport const SparklesIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\n <svg\n viewBox=\"0 0 28 28\"\n fill=\"currentColor\"\n width={size}\n height={size}\n className={cn('sc:inline-block', className)}\n >\n <path d=\"M10.06 18.701a1.628 1.628 0 0 0 2.43-.676l.77-2.34a3.82 3.82 0 0 1 2.416-2.418l2.238-.727a1.6 1.6 0 0 0 .786-.595a1.62 1.62 0 0 0-.849-2.489l-2.215-.72a3.82 3.82 0 0 1-2.42-2.414l-.727-2.237a1.62 1.62 0 0 0-.594-.785a1.655 1.655 0 0 0-1.879 0a1.63 1.63 0 0 0-.6.8L8.68 6.365a3.82 3.82 0 0 1-2.359 2.37l-2.24.726a1.626 1.626 0 0 0 .02 3.073l2.216.72a3.86 3.86 0 0 1 1.816 1.286c.266.343.471.728.606 1.14l.728 2.234c.112.318.32.593.595.787m-3.273-6.873l-2.22-.72s-.085-.038-.085-.108a.14.14 0 0 1 .084-.115L6.8 10.16a5.34 5.34 0 0 0 3.32-3.375l.721-2.219s.043-.084.114-.084s.113.084.113.084l.723 2.223a5.32 5.32 0 0 0 3.377 3.372l2.27.735a.13.13 0 0 1 .078.11a.15.15 0 0 1-.083.113l-2.225.722a5.32 5.32 0 0 0-3.374 3.374l-.715 2.2a.11.11 0 0 1-.115.093c-.104 0-.118-.075-.118-.075l-.723-2.22a5.32 5.32 0 0 0-3.376-3.385m13.017 12.954A1.2 1.2 0 0 0 20.5 25a1.2 1.2 0 0 0 .692-.216a1.2 1.2 0 0 0 .446-.6l.372-1.143a1.61 1.61 0 0 1 1.017-1.02l1.166-.378A1.21 1.21 0 0 0 25 20.505a1.2 1.2 0 0 0-.844-1.146l-1.144-.37a1.61 1.61 0 0 1-1.02-1.018l-.38-1.163a1.2 1.2 0 0 0-2.274.016l-.374 1.146a1.61 1.61 0 0 1-.993 1.017l-1.166.378a1.21 1.21 0 0 0-.807 1.139a1.2 1.2 0 0 0 .823 1.138l1.144.372a1.6 1.6 0 0 1 1.02 1.023l.379 1.163a1.2 1.2 0 0 0 .44.582m-1.371-4.194l-.27-.088l.283-.096a3.12 3.12 0 0 0 1.943-1.974l.088-.27l.089.272a3.11 3.11 0 0 0 1.974 1.975l.293.1l-.271.087a3.12 3.12 0 0 0-1.974 1.977l-.088.27l-.088-.272a3.12 3.12 0 0 0-1.98-1.98\" />\n </svg>\n);\n\n// Map of icon names to components for easy lookup\nexport const CustomIcons = {\n 'rotate-left': RotateLeftIcon,\n 'rotate-right': RotateRightIcon,\n 'sparkles': SparklesIcon,\n} as const;\n\nexport type CustomIconName = keyof typeof CustomIcons;\n"],"names":["Input","React","className","type","isInvalid","startContent","endContent","disabled","readOnly","required","variant","props","ref","surface","useSurface","isOnSurface","effectiveVariant","inputElement","jsx","cn","jsxs","labelVariants","cva","Label","LabelPrimitive","TabsRoot","value","onValueChange","TabsPrimitive","TabsList","children","listRef","indicatorStyle","setIndicatorStyle","listElement","updateIndicator","activeTab","listRect","tabRect","observer","mutations","mutation","trigger","resizeObserver","node","TabsTrigger","id","surfaceVariant","TabsContent","TabsListContainer","TabsTab","TabsPanel","TabsIndicator","TabsBase","Tabs","TextField","enhancedChildren","child","childType","displayName","childProps","RotateLeftIcon","size","SparklesIcon"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { g } from "./editorErrors-CK1RISFa.js";
1
+ import { g } from "./editorErrors-B2mb1sVN.js";
2
2
  const k = 1;
3
3
  function c(t) {
4
4
  return typeof t == "number" && Number.isFinite(t) ? Math.round(t * 100) / 100 : t;
@@ -364,4 +364,4 @@ export {
364
364
  v as l,
365
365
  x as s
366
366
  };
367
- //# sourceMappingURL=design-builder-CPF60RU2.js.map
367
+ //# sourceMappingURL=design-builder-CT4DHIPe.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"design-builder-CPF60RU2.js","sources":["../src/rendering/serialize-for-server.ts","../src/design/design-builder.ts"],"sourcesContent":["/**\n * Serialize canvas state (from onChange/toJSON) to the format the export worker expects.\n *\n * This bridges the gap between SnowconeCanvas.onChange() output (element configs with\n * transformType and nested transformData) and the export worker's expected format\n * (elements with 'type' field and flattened image properties).\n *\n * Use this when sending canvas state to a server-side renderer that runs the export\n * worker bundle. The client-side serializeForWorkerExport() requires live element\n * instances; this function works on plain JSON from onChange/toJSON.\n */\n\n/**\n * Wire-schema version of the server-render `canvas_state` payload (ADR-0079).\n *\n * This is a PUBLIC contract: third parties send `ServerRenderRequest` (produced\n * by {@link serializeStateForServer}) to Snowcone's realtime renderer. Bump the\n * MAJOR whenever the element/artboard shape changes incompatibly so a renderer\n * can reject states it cannot render instead of silently mis-rendering. Servers\n * treat a MISSING `schemaVersion` as `1` — legacy clients that predate the\n * stamp keep working.\n */\nexport const CANVAS_STATE_SCHEMA_VERSION = 1;\n\n// NOTE: this is a `type`, not an `interface`, ON PURPOSE. The SDK's\n// `RenderSession.renderState(placement, state)` takes a `RenderState` that has a\n// string index signature (`[key: string]: unknown`). A named `interface` does\n// NOT get TypeScript's implicit index signature, so an `interface` value is not\n// assignable to an indexed type — passing `serializeStateForServer()`'s result\n// straight into `renderState()` (the headline realtime flow) fails with TS2345\n// \"Index signature for type 'string' is missing\". A `type` alias DOES get the\n// implicit signature, so the flow type-checks with no cast. Do not convert this\n// back to `interface`. Pinned by @snowcone-app/contracts\n// (src/realtime-getting-started.contract.ts).\nexport type ServerRenderRequest = {\n /** Wire-schema version — see {@link CANVAS_STATE_SCHEMA_VERSION}. */\n schemaVersion: number;\n artboards: Array<{\n id: string;\n name: string;\n x: number;\n y: number;\n width: number;\n height: number;\n backgroundColor: string;\n exportBackground: boolean;\n elements: any[];\n distressTexture?: any;\n imageMask?: any;\n }>;\n};\n\n/**\n * Quantize a geometry value to a stable precision (0.01px).\n *\n * `SnowconeCanvas.onChange` fires on the render loop and reports element\n * geometry with sub-pixel float drift (e.g. `height: 355.9609375`) even when the\n * design is idle. Serialized verbatim, every frame produces a *different* wire\n * state, so a `onChange → renderState` wiring streams renders continuously while\n * the user does nothing — silently burning the realtime render budget. Rounding\n * geometry to a precision well below a pixel collapses that drift so identical\n * idle frames serialize identically (and the SDK's send-dedupe drops them).\n */\nfunction q(n: unknown): unknown {\n return typeof n === 'number' && Number.isFinite(n) ? Math.round(n * 100) / 100 : n;\n}\n\n/** Return a shallow copy with geometry fields quantized (see {@link q}). */\nfunction quantizeGeometry<T extends Record<string, any>>(el: T): T {\n return {\n ...el,\n ...(el.x !== undefined && { x: q(el.x) }),\n ...(el.y !== undefined && { y: q(el.y) }),\n ...(el.width !== undefined && { width: q(el.width) }),\n ...(el.height !== undefined && { height: q(el.height) }),\n ...(el.rotation !== undefined && { rotation: q(el.rotation) }),\n };\n}\n\n/**\n * Convert onChange canvas state to the server render format.\n *\n * Emits EVERY artboard in the state, each holding its own elements: elements\n * are grouped by their `artboard` tag (the artboard NAME `SnowconeCanvas.onChange`\n * stamps on each element). Elements without a tag — legacy saved states — land\n * on the first artboard, which also keeps single-artboard output byte-identical\n * to before. The tag itself is client-side metadata and is stripped from the\n * wire elements, so the per-placement `{schemaVersion, artboards: [...]}` shape\n * the server consumes is unchanged.\n *\n * For a multi-placement product, slice the result per placement:\n *\n * ```ts\n * const wire = serializeStateForServer(state);\n * for (const ab of wire.artboards) {\n * session.renderState(ab.name, { schemaVersion: wire.schemaVersion, artboards: [ab] });\n * }\n * ```\n *\n * @param state - The state from SnowconeCanvas onChange callback\n * @returns ServerRenderRequest ready to send via sendCanvasState()\n */\nexport function serializeStateForServer(state: {\n elements?: any[];\n artboards?: Array<{\n name?: string;\n width?: number;\n height?: number;\n backgroundColor?: string;\n exportBackground?: boolean;\n clipShape?: any;\n distressTexture?: any;\n imageMask?: any;\n /** Pacing role (multi-page templates, ADR-0086) — client/template\n * metadata, stripped from the wire like the element `artboard` tag. */\n role?: string;\n }>;\n activeArtboard?: string;\n}): ServerRenderRequest {\n const srcArtboards = state.artboards?.length\n ? state.artboards\n : [{ name: 'Front', width: 800, height: 800 }];\n\n // Group elements by their artboard tag (artboard NAME, stamped by\n // SnowconeCanvas.onChange). Untagged or unknown-tagged elements fall back to\n // the first artboard — today's behavior for legacy flat states.\n const elementsByArtboard = srcArtboards.map<any[]>(() => []);\n for (const el of state.elements || []) {\n const tag = el?.artboard;\n const idx = typeof tag === 'string'\n ? srcArtboards.findIndex((ab) => (ab.name || 'Front') === tag)\n : -1;\n elementsByArtboard[idx >= 0 ? idx : 0].push(serializeElement(el));\n }\n\n // Wire artboards are built field-by-field: template metadata on the source\n // artboards (the `role` pacing tag, ADR-0086) never reaches the server —\n // same rule as the element `artboard` membership tag below.\n return {\n schemaVersion: CANVAS_STATE_SCHEMA_VERSION,\n artboards: srcArtboards.map((srcArtboard, i) => ({\n id: `artboard-${i + 1}`,\n name: srcArtboard.name || 'Front',\n x: 0,\n y: 0,\n width: q(srcArtboard.width || 800) as number,\n height: q(srcArtboard.height || 800) as number,\n backgroundColor: srcArtboard.backgroundColor || 'transparent',\n // Pass the artboard's flag through — the server worker only paints the\n // background when this is true. Hardcoding `false` here silently dropped\n // explicitly-set artboard backgrounds from every realtime mockup while\n // the editor kept showing them (WYSIWYG drift).\n exportBackground: srcArtboard.exportBackground ?? false,\n elements: elementsByArtboard[i],\n distressTexture: srcArtboard.distressTexture,\n imageMask: srcArtboard.imageMask,\n })),\n };\n}\n\n/**\n * Serialize a single element from toJSON() format to export worker format.\n */\nfunction serializeElement(el: any): any {\n const type = el.type || el.transformType;\n\n if (type === 'image') {\n return serializeImageElement(el);\n }\n\n // Text, shape, group, path elements: map transformType → type, keep everything else\n // Text, shape, group, path elements: pass through as-is with type mapped.\n // Builtin mask URLs (builtin-mask:*) are kept as-is — the server worker\n // generates them procedurally via generateMaskBitmap().\n // Geometry is quantized (see `q`) so idle render-loop float drift doesn't\n // produce a new wire state every frame.\n // The `artboard` membership tag is CLIENT-side metadata (it already decided\n // which wire artboard this element belongs to) — strip it so the server\n // payload shape stays unchanged.\n const { artboard: _artboard, ...rest } = el;\n return quantizeGeometry({\n ...rest,\n type,\n });\n}\n\n/**\n * Serialize an image element, flattening transformData to top-level fields\n * as expected by SerializedImageElement and renderImageElement.\n */\nfunction serializeImageElement(el: any): any {\n const td = el.transformData || {};\n const hasMasks = el.masks && el.masks.length > 0;\n\n // Visible dimensions depend on crop and masks\n const fullWidth = td.width || 0;\n const fullHeight = td.height || 0;\n const visibleWidth = hasMasks ? fullWidth : fullWidth * (td.cropWidth ?? 1);\n const visibleHeight = hasMasks ? fullHeight : fullHeight * (td.cropHeight ?? 1);\n\n const serialized: any = {\n id: el.id,\n type: 'image' as const,\n x: el.x || 0,\n y: el.y || 0,\n width: visibleWidth,\n height: visibleHeight,\n rotation: el.rotation || 0,\n imageUrl: el.imageUrl,\n imageAspectRatio: el.imageAspectRatio,\n };\n\n // Crop (only if actually cropped and no masks)\n const isCropped = !hasMasks && (td.cropX !== 0 || td.cropY !== 0 || td.cropWidth !== 1 || td.cropHeight !== 1);\n if (isCropped) {\n // Note: crop values stay as fractions here — the server's Chrome tab\n // will convert to pixels using the actual bitmap dimensions after fetch.\n // The client doesn't have the bitmap dimensions available in onChange state.\n serialized.cropX = td.cropX;\n serialized.cropY = td.cropY;\n serialized.cropWidth = td.cropWidth;\n serialized.cropHeight = td.cropHeight;\n serialized.needsCropPixelConversion = true; // Flag for server to convert\n }\n\n // Flip and border radius\n if (td.flipHorizontal) serialized.flipHorizontal = td.flipHorizontal;\n if (td.flipVertical) serialized.flipVertical = td.flipVertical;\n if (td.borderRadius) serialized.borderRadius = td.borderRadius;\n if (el.opacity !== undefined) serialized.opacity = el.opacity;\n\n // Effects\n if (el.distressEffect) serialized.distressEffect = el.distressEffect;\n if (el.masks?.length > 0) serialized.masks = el.masks;\n if (el.blendMode) serialized.blendMode = el.blendMode;\n if (el.knockoutParts) serialized.knockoutParts = el.knockoutParts;\n if (el.stroke) serialized.stroke = el.stroke;\n\n // Quantize geometry so idle render-loop float drift doesn't churn the wire state.\n return quantizeGeometry(serialized);\n}\n","/**\n * Declarative design builder — compose a multi-element design by *intent*\n * (full-bleed background, anchored text, anchored shapes) and never by raw\n * coordinates.\n *\n * WHY THIS EXISTS\n * ---------------\n * The canvas/render contract positions every element by its CENTER point\n * (`x, y` is the centre, not the top-left — see CLAUDE.md). That convention is\n * correct for the interactive editor (rotation/scale pivot around centre) but is\n * a foot-gun for anyone hand-constructing a `canvas_state`: the natural\n * \"fill the placement\" attempt (`x:0, y:0, width:full`) centres the element on\n * the artboard's top-left CORNER, so it renders into the top-left quadrant. An\n * expert integrator hit exactly this and mis-diagnosed it as a server bug.\n *\n * This builder makes that class of mistake impossible: you describe *what* you\n * want (a background that fills, a title anchored top-centre, a star bottom-left)\n * and it emits a correct editor-shape canvas state. The centre-coordinate maths\n * lives here, once, instead of in every integrator's head.\n *\n * THREE RENDER TARGETS, ONE DESIGN\n * --------------------------------\n * The builder emits the EDITOR shape (`transformType` + nested `transformData`,\n * centre coords), which is exactly what both consumers already accept:\n * - `design.toJSON()` → `<SnowconeCanvas initialElements=… />` (interactive)\n * - `design.toServerRequest()` → `session.renderState(placement, …)` (realtime)\n * - (single asset on a placement → use `buildMockupUrl` in the SDK instead)\n *\n * So a developer can author a design headlessly, render it live, AND hand the\n * same design to the editor for the end user to drag/scale/rotate from there.\n */\n\nimport type {\n AnyElementConfig,\n ArtboardRole,\n ImageElementConfig,\n CustomElementConfig,\n ShapeElementConfig,\n ShapeTransformData,\n} from '../types/index.js';\nimport { serializeStateForServer, type ServerRenderRequest } from '../rendering/serialize-for-server.js';\nimport { EditorError } from '../errors/editorError.js';\n\n/** A nine-point anchor: where the element sits relative to the artboard. */\nexport type Anchor =\n | 'top-left' | 'top' | 'top-right'\n | 'left' | 'center' | 'right'\n | 'bottom-left' | 'bottom' | 'bottom-right';\n\nexport type ShapeKind =\n | 'rectangle' | 'circle' | 'ellipse' | 'triangle' | 'polygon' | 'star' | 'line';\n\n/** The artboard a design is composed on — one print placement. `width`/`height`\n * are the placement's pixel dimensions (e.g. 1480×2328 for a phone-case Front).\n * `name` MUST match the placement label you render (case-sensitive). */\nexport interface DesignArtboard {\n /** Placement label (case-sensitive — e.g. 'Front'). Defaults to 'Front'. */\n name?: string;\n width: number;\n height: number;\n /** Optional solid background fill behind all elements. Rendered everywhere\n * the design is — editor, exports, and server renders (realtime mockups). */\n backgroundColor?: string;\n /** Pacing role in a multi-page template (ADR-0086 albums). Template\n * metadata only — never sent on the server render wire. */\n role?: ArtboardRole;\n}\n\n/** One artboard of a saved multi-artboard state, as {@link DesignBuilder.fromState}\n * accepts and `toJSON()` emits. */\nexport interface StateArtboard {\n name: string;\n width: number;\n height: number;\n backgroundColor?: string;\n exportBackground?: boolean;\n /** Pacing role in a multi-page template (ADR-0086 albums). */\n role?: ArtboardRole;\n}\n\nexport interface FromStateOptions {\n /**\n * Which artboards to load:\n * - `'active'` (default) — today's behavior: the active artboard becomes the\n * builder's artboard; elements tagged for other artboards are skipped.\n * - `'all'` — multi-page templates (ADR-0086): every artboard and its\n * elements are kept, with `artboard` membership tags preserved, so\n * `toJSON()` / `toServerRequest()` stay multi-artboard. Template fields\n * scope per page via `TemplateField.artboard`.\n */\n artboards?: 'active' | 'all';\n}\n\n/** A stable layer name. Set it so you can bind the layer in an interactive\n * `<SnowconeCanvas/>` editor — `useImageBinding(name)` / `useTextBinding(name)`\n * find the element by `name`. Keep names unique per design. */\ntype NamedOption = { name?: string };\n\nexport interface BackgroundOptions extends NamedOption {\n /** Source image aspect ratio (w/h). When given, the image is COVER-fit (filled\n * + centre-cropped) with no distortion. When omitted, the image fills the\n * artboard box directly (correct when it's already the placement's aspect,\n * e.g. an AI image generated at the placement size). */\n aspectRatio?: number;\n opacity?: number;\n}\n\nexport interface ImageOptions extends NamedOption {\n anchor?: Anchor;\n /** Display width in artboard px. Defaults to a third of the artboard width. */\n width?: number;\n /** Display height in px. Defaults to `width / aspectRatio` (or square). */\n height?: number;\n aspectRatio?: number;\n opacity?: number;\n rotation?: number;\n /** Inset from the artboard edge for non-centre anchors, in px. Default 0. */\n padding?: number;\n}\n\nexport interface TextOptions extends NamedOption {\n anchor?: Anchor;\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n /** Horizontal text alignment. Named `textAlign` to match the canvas editor's\n * element field (not `align`), so the builder and the editor read the same. */\n textAlign?: 'left' | 'center' | 'right';\n bold?: boolean;\n italic?: boolean;\n rotation?: number;\n padding?: number;\n}\n\nexport interface ShapeOptions extends NamedOption {\n anchor?: Anchor;\n /** Square size, or explicit box. Default 200×200. */\n size?: number | { width: number; height: number };\n fill?: string;\n rotation?: number;\n opacity?: number;\n /** star points / polygon sides. */\n points?: number;\n sides?: number;\n padding?: number;\n}\n\nconst DEFAULT_ANCHOR: Anchor = 'center';\n\n// ── Typed layer tokens (ADR-0085 §1 / T1) ────────────────────────────────────\n\n/** The bindable layer kinds. Shapes are not bindable in v1. */\nexport type LayerKind = 'text' | 'image';\n\n/**\n * Any design builder, regardless of its compile-time layer-name unions.\n * CONSUMERS of a builder (props, context) must use this — `DesignBuilder`\n * (= `DesignBuilder<string, string>`) is not a supertype of a statically\n * typed builder because `layer()`'s name constraint narrows with the design.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- variance escape: `any` here means \"accept every name union\", checked at mint/mount instead\nexport type AnyDesign = DesignBuilder<any, any>;\n\ndeclare const LAYER_TOKEN_BRAND: unique symbol;\n/** Runtime marker carried by every minted token (survives serialization\n * boundaries within one realm; `Symbol.for` so duplicated module instances\n * still agree). */\nconst LAYER_TOKEN_MARKER = Symbol.for('snowcone.canvas.layerToken');\n\n/**\n * A validated claim that layer `N` of kind `K` exists in the design that\n * minted it. The ONLY currency `Editor.TextField` (and future `ImageField`)\n * accepts — bare strings are rejected (`E_LAYER_TOKEN_REQUIRED`). Mint with\n * {@link DesignBuilder.layer}; not constructible by hand (branded).\n *\n * Stable since ADR-0085 P4 (the default editor is rebuilt from this public\n * surface, CI-gated); additive-only from here.\n */\nexport interface LayerToken<K extends LayerKind = LayerKind, N extends string = string> {\n readonly kind: K;\n readonly name: N;\n /** @internal */\n readonly [LAYER_TOKEN_BRAND]: true;\n}\n\n/** Runtime check used by parts before trusting a `layer` prop. */\nexport function isLayerToken(value: unknown): value is LayerToken {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[LAYER_TOKEN_MARKER] === true &&\n typeof (value as { name?: unknown }).name === 'string' &&\n ((value as { kind?: unknown }).kind === 'text' || (value as { kind?: unknown }).kind === 'image')\n );\n}\n\nfunction mintLayerToken(kind: LayerKind, name: string): LayerToken {\n return Object.freeze({ kind, name, [LAYER_TOKEN_MARKER]: true }) as unknown as LayerToken;\n}\n\n/** The token kind inferred for name `N` against the builder's accumulated\n * per-kind name unions. A dynamically-loaded design (both unions degraded to\n * `string`) yields the full `'text' | 'image'` union — typing degrades, the\n * runtime validation in `layer()` is the net that still fires (0G#2). */\ntype InferKind<N extends string, TText extends string, TImage extends string> =\n [N] extends [TText] ? ([N] extends [TImage] ? LayerKind : 'text') : 'image';\n\n/** Kind of an element config, for token validation. `null` = not bindable. */\nfunction elementLayerKind(el: AnyElementConfig): LayerKind | null {\n const t = (el as { transformType?: string }).transformType;\n if (t === 'image') return 'image';\n if (t === 'shape' || t === 'group' || t === 'path') return null;\n // Every text transform ('custom', 'circle', 'arch', …) is a text layer.\n return 'text';\n}\n\nfunction hPart(a: Anchor): 'left' | 'center' | 'right' {\n if (a === 'top-left' || a === 'left' || a === 'bottom-left') return 'left';\n if (a === 'top-right' || a === 'right' || a === 'bottom-right') return 'right';\n return 'center';\n}\n\nfunction vPart(a: Anchor): 'top' | 'middle' | 'bottom' {\n if (a === 'top-left' || a === 'top' || a === 'top-right') return 'top';\n if (a === 'bottom-left' || a === 'bottom' || a === 'bottom-right') return 'bottom';\n return 'middle';\n}\n\n/**\n * Resolve an element's CENTRE (x, y) so its bounding box sits at `anchor` on the\n * artboard, inset by `padding`. This is the whole point of the builder: a\n * `center` anchor on a full-bleed element returns the artboard centre, never\n * (0, 0).\n */\nfunction anchorToCenter(\n anchor: Anchor,\n elW: number,\n elH: number,\n artW: number,\n artH: number,\n padding: number,\n): { x: number; y: number } {\n const h = hPart(anchor);\n const v = vPart(anchor);\n const x = h === 'left' ? elW / 2 + padding\n : h === 'right' ? artW - elW / 2 - padding\n : artW / 2;\n const y = v === 'top' ? elH / 2 + padding\n : v === 'bottom' ? artH - elH / 2 - padding\n : artH / 2;\n return { x, y };\n}\n\n/** Compute the centred cover-crop (fractions, 0–1) for a source of aspect `ar`\n * filling an `artW×artH` box, plus the full (uncropped) display size the editor\n * stores in `transformData`. Mirrors the editor's cover behaviour but needs only\n * the aspect ratio, not the pixel dimensions. */\nfunction coverCrop(ar: number, artW: number, artH: number) {\n const artAR = artW / artH;\n if (ar >= artAR) {\n // Source is wider than the box → crop the sides.\n const fullW = artH * ar;\n const cropWidth = artW / fullW; // = artAR / ar\n return {\n width: fullW, height: artH,\n cropX: (1 - cropWidth) / 2, cropY: 0, cropWidth, cropHeight: 1,\n };\n }\n // Source is taller than the box → crop top/bottom.\n const fullH = artW / ar;\n const cropHeight = artH / fullH; // = ar / artAR\n return {\n width: artW, height: fullH,\n cropX: 0, cropY: (1 - cropHeight) / 2, cropWidth: 1, cropHeight,\n };\n}\n\n/**\n * A composed design. Add elements by intent, then emit it for the editor or the\n * render path. Methods are chainable.\n *\n * The two type parameters accumulate the design's NAMED layer literals per\n * kind (ADR-0085 §1): naming a layer in `.text()` / `.image()` /\n * `.background()` widens the corresponding union, so `design.layer(\"naem\")`\n * is a compile error listing the valid names. Designs loaded from JSON\n * ({@link DesignBuilder.fromState}) degrade both unions to `string` — same\n * API, runtime-validated instead (the degradation is deliberate and LOUD in\n * docs; never block the dynamic case).\n */\nexport class DesignBuilder<out TText extends string = string, out TImage extends string = string> {\n private readonly artboard: Required<Pick<DesignArtboard, 'name' | 'width' | 'height'>> & {\n backgroundColor?: string;\n role?: ArtboardRole;\n };\n private readonly els: AnyElementConfig[] = [];\n private idCounter = 0;\n /** Every artboard of a multi-page state (fromState `{ artboards: 'all' }`);\n * unset for single-artboard builders. */\n private allArtboards?: StateArtboard[];\n\n constructor(artboard: DesignArtboard) {\n if (!(artboard.width > 0) || !(artboard.height > 0)) {\n throw new Error('design(): artboard width and height must be positive numbers');\n }\n this.artboard = {\n name: artboard.name || 'Front',\n width: artboard.width,\n height: artboard.height,\n backgroundColor: artboard.backgroundColor,\n role: artboard.role,\n };\n }\n\n /**\n * Seed a builder from a previously saved canvas state — the editor shape\n * `<SnowconeCanvas onChange>` emits (and `toJSON()` produces).\n *\n * This is how a saved editable template re-enters the turnkey embed path:\n * load the template's state JSON, lift it back into a `DesignBuilder`, and\n * hand it to `<RealtimeEmbed design={…}>`. By default the active artboard\n * becomes the builder's artboard; elements tagged for other artboards are\n * skipped (untagged elements are assumed to belong to the active one).\n *\n * Pass `{ artboards: 'all' }` to load a multi-page template (ADR-0086)\n * whole: every artboard (with its pacing `role`) and every element are\n * kept, membership tags preserved, so `toJSON()` / `toServerRequest()`\n * emit all pages and `TemplateField.artboard` can scope fields per page.\n */\n static fromState(\n state: {\n elements: AnyElementConfig[];\n artboards: StateArtboard[];\n activeArtboard: string;\n },\n options: FromStateOptions = {},\n ): DesignBuilder {\n const artboard =\n state.artboards.find((a) => a.name === state.activeArtboard) ?? state.artboards[0];\n if (!artboard) {\n throw new Error('DesignBuilder.fromState(): state has no artboards');\n }\n const builder = new DesignBuilder({\n name: artboard.name,\n width: artboard.width,\n height: artboard.height,\n backgroundColor: artboard.backgroundColor,\n role: artboard.role,\n });\n if (options.artboards === 'all') {\n builder.allArtboards = state.artboards.map((a) => ({ ...a }));\n for (const el of state.elements) {\n // Untagged elements belong to the active artboard (same assumption as\n // the active-only mode) — stamp them so the multi-artboard state keeps\n // them there instead of collapsing onto the first artboard.\n builder.els.push(el.artboard ? { ...el } : { ...el, artboard: artboard.name });\n }\n return builder;\n }\n for (const el of state.elements) {\n if (el.artboard && el.artboard !== artboard.name) continue;\n builder.els.push({ ...el });\n }\n return builder;\n }\n\n private nextId(prefix: string): string {\n return `${prefix}-${++this.idCounter}`;\n }\n\n /** Full-bleed background image (fills the whole artboard). Cover-fit (no\n * distortion) when `aspectRatio` is supplied. Inserted at the bottom of the\n * stack regardless of call order. The default layer name `'Background'`\n * joins the IMAGE-layer token union (auto-injected names are deliberately\n * not one union with text — ADR-0085 §1). */\n background<const N extends string>(\n imageUrl: string,\n opts: BackgroundOptions & { name: N },\n ): DesignBuilder<TText, TImage | N>;\n background(imageUrl: string, opts?: BackgroundOptions): DesignBuilder<TText, TImage | 'Background'>;\n background(imageUrl: string, opts: BackgroundOptions = {}): AnyDesign {\n const { width: artW, height: artH } = this.artboard;\n let td: ImageElementConfig['transformData'];\n if (opts.aspectRatio && opts.aspectRatio > 0) {\n const c = coverCrop(opts.aspectRatio, artW, artH);\n td = {\n type: 'image', width: c.width, height: c.height,\n cropX: c.cropX, cropY: c.cropY, cropWidth: c.cropWidth, cropHeight: c.cropHeight,\n flipHorizontal: false, flipVertical: false, borderRadius: 0,\n };\n } else {\n td = {\n type: 'image', width: artW, height: artH,\n cropX: 0, cropY: 0, cropWidth: 1, cropHeight: 1,\n flipHorizontal: false, flipVertical: false, borderRadius: 0,\n };\n }\n const el: ImageElementConfig = {\n id: this.nextId('bg'),\n transformType: 'image',\n x: artW / 2,\n y: artH / 2,\n rotation: 0,\n opacity: opts.opacity ?? 1,\n imageUrl,\n imageAspectRatio: opts.aspectRatio,\n transformData: td,\n name: opts.name ?? 'Background',\n };\n // Background goes underneath everything else.\n this.els.unshift(el);\n return this;\n }\n\n /** An anchored image element (not full-bleed). */\n image<const N extends string>(\n imageUrl: string,\n opts: ImageOptions & { name: N },\n ): DesignBuilder<TText, TImage | N>;\n image(imageUrl: string, opts?: ImageOptions): DesignBuilder<TText, TImage>;\n image(imageUrl: string, opts: ImageOptions = {}): AnyDesign {\n const { width: artW, height: artH } = this.artboard;\n const w = opts.width ?? Math.round(artW / 3);\n const h = opts.height ?? (opts.aspectRatio ? Math.round(w / opts.aspectRatio) : w);\n const { x, y } = anchorToCenter(opts.anchor ?? DEFAULT_ANCHOR, w, h, artW, artH, opts.padding ?? 0);\n const el: ImageElementConfig = {\n id: this.nextId('img'),\n transformType: 'image',\n x, y,\n rotation: opts.rotation ?? 0,\n opacity: opts.opacity ?? 1,\n imageUrl,\n imageAspectRatio: opts.aspectRatio,\n transformData: {\n type: 'image', width: w, height: h,\n cropX: 0, cropY: 0, cropWidth: 1, cropHeight: 1,\n flipHorizontal: false, flipVertical: false, borderRadius: 0,\n },\n ...(opts.name ? { name: opts.name } : {}),\n };\n this.els.push(el);\n return this;\n }\n\n /** An anchored text layer. Horizontal centring is exact (textAlign); vertical\n * anchoring uses the font size as the line-height estimate. */\n text<const N extends string>(\n text: string,\n opts: TextOptions & { name: N },\n ): DesignBuilder<TText | N, TImage>;\n text(text: string, opts?: TextOptions): DesignBuilder<TText, TImage>;\n text(text: string, opts: TextOptions = {}): AnyDesign {\n const { width: artW, height: artH } = this.artboard;\n const fontSize = opts.fontSize ?? Math.round(this.artboard.height / 12);\n // Text width is metric-dependent (unknown headless); horizontal placement is\n // handled by textAlign, so anchor only drives the vertical centre + the\n // horizontal *reference* point. Use a zero-width box horizontally so the\n // centre sits exactly on the anchor's x line.\n const { x, y } = anchorToCenter(opts.anchor ?? 'top', 0, fontSize, artW, artH, opts.padding ?? Math.round(artH / 20));\n const el: CustomElementConfig = {\n id: this.nextId('text'),\n transformType: 'custom',\n x, y,\n rotation: opts.rotation ?? 0,\n text,\n fontSize,\n fontFamily: opts.fontFamily ?? 'Anton',\n color: opts.color ?? '#ffffff',\n textAlign: opts.textAlign ?? 'center',\n bold: opts.bold ?? false,\n italic: opts.italic ?? false,\n transformData: { type: 'custom' },\n ...(opts.name ? { name: opts.name } : {}),\n };\n this.els.push(el);\n return this;\n }\n\n /**\n * Mint a {@link LayerToken} for a named layer — the only currency\n * `Editor.TextField` accepts (ADR-0085 §1). Compile time: `name` is\n * constrained to this design's declared layer names, kind inferred from\n * which per-kind union it falls in. Runtime: validated EAGERLY against the\n * built elements (nearest the mistake), so CMS/JSON-loaded designs get the\n * same protection with `string` typing.\n *\n * @throws EditorError `E_LAYER_NOT_FOUND` — no layer with this name; lists\n * the valid names per kind.\n * @throws EditorError `E_LAYER_WRONG_KIND` — the name matches a layer that\n * is not bindable (shape/group/path).\n */\n layer<N extends TText | TImage>(name: N): LayerToken<InferKind<N, TText, TImage>, N> {\n const el = this.els.find((e) => (e as { name?: string }).name === name);\n if (!el) {\n const textNames = this.layerNamesOfKind('text');\n const imageNames = this.layerNamesOfKind('image');\n throw new EditorError(\n 'E_LAYER_NOT_FOUND',\n `no layer \"${name}\" in this design. Text layers: ${JSON.stringify(textNames)}; ` +\n `image layers: ${JSON.stringify(imageNames)}. Fix the name, or add the layer ` +\n `first — e.g. design(…).text('…', { name: '${name}' }).`,\n { layerName: name, validNames: [...textNames, ...imageNames], textNames, imageNames, docSlug: 'embed-mode' },\n );\n }\n const kind = elementLayerKind(el);\n if (!kind) {\n throw new EditorError(\n 'E_LAYER_WRONG_KIND',\n `layer \"${name}\" is a ${(el as { transformType?: string }).transformType} element — ` +\n `only text and image layers are bindable. Bindable layers: ` +\n `${JSON.stringify([...this.layerNamesOfKind('text'), ...this.layerNamesOfKind('image')])}.`,\n { layerName: name, actualType: (el as { transformType?: string }).transformType, docSlug: 'embed-mode' },\n );\n }\n return mintLayerToken(kind, name) as LayerToken<InferKind<N, TText, TImage>, N>;\n }\n\n /** Names of bindable layers of one kind, in stack order. @internal */\n layerNamesOfKind(kind: LayerKind): string[] {\n return this.els\n .filter((e) => elementLayerKind(e) === kind)\n .map((e) => (e as { name?: string }).name)\n .filter((n): n is string => Boolean(n));\n }\n\n /** An anchored vector shape. */\n shape(kind: ShapeKind, opts: ShapeOptions = {}): this {\n const { width: artW, height: artH } = this.artboard;\n const size = opts.size ?? Math.round(artW / 6);\n const w = typeof size === 'number' ? size : size.width;\n const h = typeof size === 'number' ? size : size.height;\n const { x, y } = anchorToCenter(opts.anchor ?? DEFAULT_ANCHOR, w, h, artW, artH, opts.padding ?? 0);\n const transformData: ShapeTransformData = {\n type: 'shape',\n shapeType: kind,\n width: w,\n height: h,\n fillColor: opts.fill ?? '#000000',\n fillOpacity: opts.opacity ?? 1,\n ...(opts.points !== undefined ? { points: opts.points } : {}),\n ...(opts.sides !== undefined ? { sides: opts.sides } : {}),\n };\n const el: ShapeElementConfig = {\n id: this.nextId('shape'),\n transformType: 'shape',\n x, y,\n rotation: opts.rotation ?? 0,\n transformData,\n ...(opts.name ? { name: opts.name } : {}),\n };\n this.els.push(el);\n return this;\n }\n\n /** The composed elements, in render order (background first). Editor shape. */\n get elements(): AnyElementConfig[] {\n return this.els.map((e) => ({ ...e }));\n }\n\n /** Editor-shape canvas state for `<SnowconeCanvas initialElements=… />` and\n * `serializeStateForServer`. `activeArtboard` is the artboard name. A\n * builder loaded with `fromState(…, { artboards: 'all' })` emits every\n * page (artboard `role`s and element membership tags included). */\n toJSON(): {\n elements: AnyElementConfig[];\n artboards: StateArtboard[];\n activeArtboard: string;\n } {\n if (this.allArtboards) {\n return {\n // Elements added via builder methods after fromState are untagged —\n // they belong to the builder's (active) artboard; stamp them so the\n // multi-artboard state groups them correctly.\n elements: this.els.map((e) =>\n e.artboard ? { ...e } : { ...e, artboard: this.artboard.name },\n ),\n artboards: this.allArtboards.map((a) => ({ ...a })),\n activeArtboard: this.artboard.name,\n };\n }\n return {\n elements: this.elements,\n artboards: [{\n name: this.artboard.name,\n width: this.artboard.width,\n height: this.artboard.height,\n // A background set on a design is part of the design: flag it\n // renderable so exports and server renders paint it, matching the\n // editor (the export worker skips it without `exportBackground`).\n ...(this.artboard.backgroundColor\n ? {\n backgroundColor: this.artboard.backgroundColor,\n exportBackground: this.artboard.backgroundColor !== 'transparent',\n }\n : {}),\n ...(this.artboard.role ? { role: this.artboard.role } : {}),\n }],\n activeArtboard: this.artboard.name,\n };\n }\n\n /** Server-render payload for `RenderSession.renderState(placement, …)`.\n * Equivalent to `serializeStateForServer(design.toJSON())`. */\n toServerRequest(): ServerRenderRequest {\n return serializeStateForServer(this.toJSON());\n }\n}\n\n/**\n * Start a design on an artboard (one print placement).\n *\n * @example\n * const d = design({ name: 'Front', width: 1480, height: 2328 })\n * .background(aiImageUrl, { aspectRatio: 1480 / 2328, name: 'art' })\n * .text('YOUR NAME', { anchor: 'top', name: 'headline' })\n * .shape('star', { anchor: 'bottom-left', fill: '#000' });\n *\n * session.renderState('Front', d.toServerRequest()); // live\n * // …or hand d.toJSON() to <SnowconeCanvas/> for interactive editing — the\n * // `name`s let you bind layers with useImageBinding('art') / useTextBinding('headline').\n */\nexport function design(artboard: DesignArtboard): DesignBuilder<never, never> {\n return new DesignBuilder<never, never>(artboard);\n}\n\n/**\n * Resolve a layer's kind against a live element/config list — the mount-time\n * revalidation `Editor.TextField` runs (the token is a validated claim; the\n * mounted Root's design state is the authority). @internal\n */\nexport function layerKindOfConfig(el: AnyElementConfig): LayerKind | null {\n return elementLayerKind(el);\n}\n"],"names":["CANVAS_STATE_SCHEMA_VERSION","q","n","quantizeGeometry","el","serializeStateForServer","state","srcArtboards","_a","elementsByArtboard","tag","idx","ab","serializeElement","srcArtboard","type","serializeImageElement","_artboard","rest","td","hasMasks","fullWidth","fullHeight","visibleWidth","visibleHeight","serialized","DEFAULT_ANCHOR","LAYER_TOKEN_MARKER","isLayerToken","value","mintLayerToken","kind","name","elementLayerKind","t","hPart","a","vPart","anchorToCenter","anchor","elW","elH","artW","artH","padding","h","v","x","y","coverCrop","ar","artAR","fullW","cropWidth","fullH","cropHeight","DesignBuilder","artboard","options","builder","prefix","imageUrl","opts","c","w","text","fontSize","e","textNames","imageNames","EditorError","size","transformData","design","layerKindOfConfig"],"mappings":"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"}
1
+ {"version":3,"file":"design-builder-CT4DHIPe.js","sources":["../src/rendering/serialize-for-server.ts","../src/design/design-builder.ts"],"sourcesContent":["/**\n * Serialize canvas state (from onChange/toJSON) to the format the export worker expects.\n *\n * This bridges the gap between SnowconeCanvas.onChange() output (element configs with\n * transformType and nested transformData) and the export worker's expected format\n * (elements with 'type' field and flattened image properties).\n *\n * Use this when sending canvas state to a server-side renderer that runs the export\n * worker bundle. The client-side serializeForWorkerExport() requires live element\n * instances; this function works on plain JSON from onChange/toJSON.\n */\n\n/**\n * Wire-schema version of the server-render `canvas_state` payload (ADR-0079).\n *\n * This is a PUBLIC contract: third parties send `ServerRenderRequest` (produced\n * by {@link serializeStateForServer}) to Snowcone's realtime renderer. Bump the\n * MAJOR whenever the element/artboard shape changes incompatibly so a renderer\n * can reject states it cannot render instead of silently mis-rendering. Servers\n * treat a MISSING `schemaVersion` as `1` — legacy clients that predate the\n * stamp keep working.\n */\nexport const CANVAS_STATE_SCHEMA_VERSION = 1;\n\n// NOTE: this is a `type`, not an `interface`, ON PURPOSE. The SDK's\n// `RenderSession.renderState(placement, state)` takes a `RenderState` that has a\n// string index signature (`[key: string]: unknown`). A named `interface` does\n// NOT get TypeScript's implicit index signature, so an `interface` value is not\n// assignable to an indexed type — passing `serializeStateForServer()`'s result\n// straight into `renderState()` (the headline realtime flow) fails with TS2345\n// \"Index signature for type 'string' is missing\". A `type` alias DOES get the\n// implicit signature, so the flow type-checks with no cast. Do not convert this\n// back to `interface`. Pinned by @snowcone-app/contracts\n// (src/realtime-getting-started.contract.ts).\nexport type ServerRenderRequest = {\n /** Wire-schema version — see {@link CANVAS_STATE_SCHEMA_VERSION}. */\n schemaVersion: number;\n artboards: Array<{\n id: string;\n name: string;\n x: number;\n y: number;\n width: number;\n height: number;\n backgroundColor: string;\n exportBackground: boolean;\n elements: any[];\n distressTexture?: any;\n imageMask?: any;\n }>;\n};\n\n/**\n * Quantize a geometry value to a stable precision (0.01px).\n *\n * `SnowconeCanvas.onChange` fires on the render loop and reports element\n * geometry with sub-pixel float drift (e.g. `height: 355.9609375`) even when the\n * design is idle. Serialized verbatim, every frame produces a *different* wire\n * state, so a `onChange → renderState` wiring streams renders continuously while\n * the user does nothing — silently burning the realtime render budget. Rounding\n * geometry to a precision well below a pixel collapses that drift so identical\n * idle frames serialize identically (and the SDK's send-dedupe drops them).\n */\nfunction q(n: unknown): unknown {\n return typeof n === 'number' && Number.isFinite(n) ? Math.round(n * 100) / 100 : n;\n}\n\n/** Return a shallow copy with geometry fields quantized (see {@link q}). */\nfunction quantizeGeometry<T extends Record<string, any>>(el: T): T {\n return {\n ...el,\n ...(el.x !== undefined && { x: q(el.x) }),\n ...(el.y !== undefined && { y: q(el.y) }),\n ...(el.width !== undefined && { width: q(el.width) }),\n ...(el.height !== undefined && { height: q(el.height) }),\n ...(el.rotation !== undefined && { rotation: q(el.rotation) }),\n };\n}\n\n/**\n * Convert onChange canvas state to the server render format.\n *\n * Emits EVERY artboard in the state, each holding its own elements: elements\n * are grouped by their `artboard` tag (the artboard NAME `SnowconeCanvas.onChange`\n * stamps on each element). Elements without a tag — legacy saved states — land\n * on the first artboard, which also keeps single-artboard output byte-identical\n * to before. The tag itself is client-side metadata and is stripped from the\n * wire elements, so the per-placement `{schemaVersion, artboards: [...]}` shape\n * the server consumes is unchanged.\n *\n * For a multi-placement product, slice the result per placement:\n *\n * ```ts\n * const wire = serializeStateForServer(state);\n * for (const ab of wire.artboards) {\n * session.renderState(ab.name, { schemaVersion: wire.schemaVersion, artboards: [ab] });\n * }\n * ```\n *\n * @param state - The state from SnowconeCanvas onChange callback\n * @returns ServerRenderRequest ready to send via sendCanvasState()\n */\nexport function serializeStateForServer(state: {\n elements?: any[];\n artboards?: Array<{\n name?: string;\n width?: number;\n height?: number;\n backgroundColor?: string;\n exportBackground?: boolean;\n clipShape?: any;\n distressTexture?: any;\n imageMask?: any;\n /** Pacing role (multi-page templates, ADR-0086) — client/template\n * metadata, stripped from the wire like the element `artboard` tag. */\n role?: string;\n }>;\n activeArtboard?: string;\n}): ServerRenderRequest {\n const srcArtboards = state.artboards?.length\n ? state.artboards\n : [{ name: 'Front', width: 800, height: 800 }];\n\n // Group elements by their artboard tag (artboard NAME, stamped by\n // SnowconeCanvas.onChange). Untagged or unknown-tagged elements fall back to\n // the first artboard — today's behavior for legacy flat states.\n const elementsByArtboard = srcArtboards.map<any[]>(() => []);\n for (const el of state.elements || []) {\n const tag = el?.artboard;\n const idx = typeof tag === 'string'\n ? srcArtboards.findIndex((ab) => (ab.name || 'Front') === tag)\n : -1;\n elementsByArtboard[idx >= 0 ? idx : 0].push(serializeElement(el));\n }\n\n // Wire artboards are built field-by-field: template metadata on the source\n // artboards (the `role` pacing tag, ADR-0086) never reaches the server —\n // same rule as the element `artboard` membership tag below.\n return {\n schemaVersion: CANVAS_STATE_SCHEMA_VERSION,\n artboards: srcArtboards.map((srcArtboard, i) => ({\n id: `artboard-${i + 1}`,\n name: srcArtboard.name || 'Front',\n x: 0,\n y: 0,\n width: q(srcArtboard.width || 800) as number,\n height: q(srcArtboard.height || 800) as number,\n backgroundColor: srcArtboard.backgroundColor || 'transparent',\n // Pass the artboard's flag through — the server worker only paints the\n // background when this is true. Hardcoding `false` here silently dropped\n // explicitly-set artboard backgrounds from every realtime mockup while\n // the editor kept showing them (WYSIWYG drift).\n exportBackground: srcArtboard.exportBackground ?? false,\n elements: elementsByArtboard[i],\n distressTexture: srcArtboard.distressTexture,\n imageMask: srcArtboard.imageMask,\n })),\n };\n}\n\n/**\n * Serialize a single element from toJSON() format to export worker format.\n */\nfunction serializeElement(el: any): any {\n const type = el.type || el.transformType;\n\n if (type === 'image') {\n return serializeImageElement(el);\n }\n\n // Text, shape, group, path elements: map transformType → type, keep everything else\n // Text, shape, group, path elements: pass through as-is with type mapped.\n // Builtin mask URLs (builtin-mask:*) are kept as-is — the server worker\n // generates them procedurally via generateMaskBitmap().\n // Geometry is quantized (see `q`) so idle render-loop float drift doesn't\n // produce a new wire state every frame.\n // The `artboard` membership tag is CLIENT-side metadata (it already decided\n // which wire artboard this element belongs to) — strip it so the server\n // payload shape stays unchanged.\n const { artboard: _artboard, ...rest } = el;\n return quantizeGeometry({\n ...rest,\n type,\n });\n}\n\n/**\n * Serialize an image element, flattening transformData to top-level fields\n * as expected by SerializedImageElement and renderImageElement.\n */\nfunction serializeImageElement(el: any): any {\n const td = el.transformData || {};\n const hasMasks = el.masks && el.masks.length > 0;\n\n // Visible dimensions depend on crop and masks\n const fullWidth = td.width || 0;\n const fullHeight = td.height || 0;\n const visibleWidth = hasMasks ? fullWidth : fullWidth * (td.cropWidth ?? 1);\n const visibleHeight = hasMasks ? fullHeight : fullHeight * (td.cropHeight ?? 1);\n\n const serialized: any = {\n id: el.id,\n type: 'image' as const,\n x: el.x || 0,\n y: el.y || 0,\n width: visibleWidth,\n height: visibleHeight,\n rotation: el.rotation || 0,\n imageUrl: el.imageUrl,\n imageAspectRatio: el.imageAspectRatio,\n };\n\n // Crop (only if actually cropped and no masks)\n const isCropped = !hasMasks && (td.cropX !== 0 || td.cropY !== 0 || td.cropWidth !== 1 || td.cropHeight !== 1);\n if (isCropped) {\n // Note: crop values stay as fractions here — the server's Chrome tab\n // will convert to pixels using the actual bitmap dimensions after fetch.\n // The client doesn't have the bitmap dimensions available in onChange state.\n serialized.cropX = td.cropX;\n serialized.cropY = td.cropY;\n serialized.cropWidth = td.cropWidth;\n serialized.cropHeight = td.cropHeight;\n serialized.needsCropPixelConversion = true; // Flag for server to convert\n }\n\n // Flip and border radius\n if (td.flipHorizontal) serialized.flipHorizontal = td.flipHorizontal;\n if (td.flipVertical) serialized.flipVertical = td.flipVertical;\n if (td.borderRadius) serialized.borderRadius = td.borderRadius;\n if (el.opacity !== undefined) serialized.opacity = el.opacity;\n\n // Effects\n if (el.distressEffect) serialized.distressEffect = el.distressEffect;\n if (el.masks?.length > 0) serialized.masks = el.masks;\n if (el.blendMode) serialized.blendMode = el.blendMode;\n if (el.knockoutParts) serialized.knockoutParts = el.knockoutParts;\n if (el.stroke) serialized.stroke = el.stroke;\n\n // Quantize geometry so idle render-loop float drift doesn't churn the wire state.\n return quantizeGeometry(serialized);\n}\n","/**\n * Declarative design builder — compose a multi-element design by *intent*\n * (full-bleed background, anchored text, anchored shapes) and never by raw\n * coordinates.\n *\n * WHY THIS EXISTS\n * ---------------\n * The canvas/render contract positions every element by its CENTER point\n * (`x, y` is the centre, not the top-left — see CLAUDE.md). That convention is\n * correct for the interactive editor (rotation/scale pivot around centre) but is\n * a foot-gun for anyone hand-constructing a `canvas_state`: the natural\n * \"fill the placement\" attempt (`x:0, y:0, width:full`) centres the element on\n * the artboard's top-left CORNER, so it renders into the top-left quadrant. An\n * expert integrator hit exactly this and mis-diagnosed it as a server bug.\n *\n * This builder makes that class of mistake impossible: you describe *what* you\n * want (a background that fills, a title anchored top-centre, a star bottom-left)\n * and it emits a correct editor-shape canvas state. The centre-coordinate maths\n * lives here, once, instead of in every integrator's head.\n *\n * THREE RENDER TARGETS, ONE DESIGN\n * --------------------------------\n * The builder emits the EDITOR shape (`transformType` + nested `transformData`,\n * centre coords), which is exactly what both consumers already accept:\n * - `design.toJSON()` → `<SnowconeCanvas initialElements=… />` (interactive)\n * - `design.toServerRequest()` → `session.renderState(placement, …)` (realtime)\n * - (single asset on a placement → use `buildMockupUrl` in the SDK instead)\n *\n * So a developer can author a design headlessly, render it live, AND hand the\n * same design to the editor for the end user to drag/scale/rotate from there.\n */\n\nimport type {\n AnyElementConfig,\n ArtboardRole,\n ImageElementConfig,\n CustomElementConfig,\n ShapeElementConfig,\n ShapeTransformData,\n} from '../types/index.js';\nimport { serializeStateForServer, type ServerRenderRequest } from '../rendering/serialize-for-server.js';\nimport { EditorError } from '../errors/editorError.js';\n\n/** A nine-point anchor: where the element sits relative to the artboard. */\nexport type Anchor =\n | 'top-left' | 'top' | 'top-right'\n | 'left' | 'center' | 'right'\n | 'bottom-left' | 'bottom' | 'bottom-right';\n\nexport type ShapeKind =\n | 'rectangle' | 'circle' | 'ellipse' | 'triangle' | 'polygon' | 'star' | 'line';\n\n/** The artboard a design is composed on — one print placement. `width`/`height`\n * are the placement's pixel dimensions (e.g. 1480×2328 for a phone-case Front).\n * `name` MUST match the placement label you render (case-sensitive). */\nexport interface DesignArtboard {\n /** Placement label (case-sensitive — e.g. 'Front'). Defaults to 'Front'. */\n name?: string;\n width: number;\n height: number;\n /** Optional solid background fill behind all elements. Rendered everywhere\n * the design is — editor, exports, and server renders (realtime mockups). */\n backgroundColor?: string;\n /** Pacing role in a multi-page template (ADR-0086 albums). Template\n * metadata only — never sent on the server render wire. */\n role?: ArtboardRole;\n}\n\n/** One artboard of a saved multi-artboard state, as {@link DesignBuilder.fromState}\n * accepts and `toJSON()` emits. */\nexport interface StateArtboard {\n name: string;\n width: number;\n height: number;\n backgroundColor?: string;\n exportBackground?: boolean;\n /** Pacing role in a multi-page template (ADR-0086 albums). */\n role?: ArtboardRole;\n}\n\nexport interface FromStateOptions {\n /**\n * Which artboards to load:\n * - `'active'` (default) — today's behavior: the active artboard becomes the\n * builder's artboard; elements tagged for other artboards are skipped.\n * - `'all'` — multi-page templates (ADR-0086): every artboard and its\n * elements are kept, with `artboard` membership tags preserved, so\n * `toJSON()` / `toServerRequest()` stay multi-artboard. Template fields\n * scope per page via `TemplateField.artboard`.\n */\n artboards?: 'active' | 'all';\n}\n\n/** A stable layer name. Set it so you can bind the layer in an interactive\n * `<SnowconeCanvas/>` editor — `useImageBinding(name)` / `useTextBinding(name)`\n * find the element by `name`. Keep names unique per design. */\ntype NamedOption = { name?: string };\n\nexport interface BackgroundOptions extends NamedOption {\n /** Source image aspect ratio (w/h). When given, the image is COVER-fit (filled\n * + centre-cropped) with no distortion. When omitted, the image fills the\n * artboard box directly (correct when it's already the placement's aspect,\n * e.g. an AI image generated at the placement size). */\n aspectRatio?: number;\n opacity?: number;\n}\n\nexport interface ImageOptions extends NamedOption {\n anchor?: Anchor;\n /** Display width in artboard px. Defaults to a third of the artboard width. */\n width?: number;\n /** Display height in px. Defaults to `width / aspectRatio` (or square). */\n height?: number;\n aspectRatio?: number;\n opacity?: number;\n rotation?: number;\n /** Inset from the artboard edge for non-centre anchors, in px. Default 0. */\n padding?: number;\n}\n\nexport interface TextOptions extends NamedOption {\n anchor?: Anchor;\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n /** Horizontal text alignment. Named `textAlign` to match the canvas editor's\n * element field (not `align`), so the builder and the editor read the same. */\n textAlign?: 'left' | 'center' | 'right';\n bold?: boolean;\n italic?: boolean;\n rotation?: number;\n padding?: number;\n}\n\nexport interface ShapeOptions extends NamedOption {\n anchor?: Anchor;\n /** Square size, or explicit box. Default 200×200. */\n size?: number | { width: number; height: number };\n fill?: string;\n rotation?: number;\n opacity?: number;\n /** star points / polygon sides. */\n points?: number;\n sides?: number;\n padding?: number;\n}\n\nconst DEFAULT_ANCHOR: Anchor = 'center';\n\n// ── Typed layer tokens (ADR-0085 §1 / T1) ────────────────────────────────────\n\n/** The bindable layer kinds. Shapes are not bindable in v1. */\nexport type LayerKind = 'text' | 'image';\n\n/**\n * Any design builder, regardless of its compile-time layer-name unions.\n * CONSUMERS of a builder (props, context) must use this — `DesignBuilder`\n * (= `DesignBuilder<string, string>`) is not a supertype of a statically\n * typed builder because `layer()`'s name constraint narrows with the design.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- variance escape: `any` here means \"accept every name union\", checked at mint/mount instead\nexport type AnyDesign = DesignBuilder<any, any>;\n\ndeclare const LAYER_TOKEN_BRAND: unique symbol;\n/** Runtime marker carried by every minted token (survives serialization\n * boundaries within one realm; `Symbol.for` so duplicated module instances\n * still agree). */\nconst LAYER_TOKEN_MARKER = Symbol.for('snowcone.canvas.layerToken');\n\n/**\n * A validated claim that layer `N` of kind `K` exists in the design that\n * minted it. The ONLY currency `Editor.TextField` (and future `ImageField`)\n * accepts — bare strings are rejected (`E_LAYER_TOKEN_REQUIRED`). Mint with\n * {@link DesignBuilder.layer}; not constructible by hand (branded).\n *\n * Stable since ADR-0085 P4 (the default editor is rebuilt from this public\n * surface, CI-gated); additive-only from here.\n */\nexport interface LayerToken<K extends LayerKind = LayerKind, N extends string = string> {\n readonly kind: K;\n readonly name: N;\n /** @internal */\n readonly [LAYER_TOKEN_BRAND]: true;\n}\n\n/** Runtime check used by parts before trusting a `layer` prop. */\nexport function isLayerToken(value: unknown): value is LayerToken {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[LAYER_TOKEN_MARKER] === true &&\n typeof (value as { name?: unknown }).name === 'string' &&\n ((value as { kind?: unknown }).kind === 'text' || (value as { kind?: unknown }).kind === 'image')\n );\n}\n\nfunction mintLayerToken(kind: LayerKind, name: string): LayerToken {\n return Object.freeze({ kind, name, [LAYER_TOKEN_MARKER]: true }) as unknown as LayerToken;\n}\n\n/** The token kind inferred for name `N` against the builder's accumulated\n * per-kind name unions. A dynamically-loaded design (both unions degraded to\n * `string`) yields the full `'text' | 'image'` union — typing degrades, the\n * runtime validation in `layer()` is the net that still fires (0G#2). */\ntype InferKind<N extends string, TText extends string, TImage extends string> =\n [N] extends [TText] ? ([N] extends [TImage] ? LayerKind : 'text') : 'image';\n\n/** Kind of an element config, for token validation. `null` = not bindable. */\nfunction elementLayerKind(el: AnyElementConfig): LayerKind | null {\n const t = (el as { transformType?: string }).transformType;\n if (t === 'image') return 'image';\n if (t === 'shape' || t === 'group' || t === 'path') return null;\n // Every text transform ('custom', 'circle', 'arch', …) is a text layer.\n return 'text';\n}\n\nfunction hPart(a: Anchor): 'left' | 'center' | 'right' {\n if (a === 'top-left' || a === 'left' || a === 'bottom-left') return 'left';\n if (a === 'top-right' || a === 'right' || a === 'bottom-right') return 'right';\n return 'center';\n}\n\nfunction vPart(a: Anchor): 'top' | 'middle' | 'bottom' {\n if (a === 'top-left' || a === 'top' || a === 'top-right') return 'top';\n if (a === 'bottom-left' || a === 'bottom' || a === 'bottom-right') return 'bottom';\n return 'middle';\n}\n\n/**\n * Resolve an element's CENTRE (x, y) so its bounding box sits at `anchor` on the\n * artboard, inset by `padding`. This is the whole point of the builder: a\n * `center` anchor on a full-bleed element returns the artboard centre, never\n * (0, 0).\n */\nfunction anchorToCenter(\n anchor: Anchor,\n elW: number,\n elH: number,\n artW: number,\n artH: number,\n padding: number,\n): { x: number; y: number } {\n const h = hPart(anchor);\n const v = vPart(anchor);\n const x = h === 'left' ? elW / 2 + padding\n : h === 'right' ? artW - elW / 2 - padding\n : artW / 2;\n const y = v === 'top' ? elH / 2 + padding\n : v === 'bottom' ? artH - elH / 2 - padding\n : artH / 2;\n return { x, y };\n}\n\n/** Compute the centred cover-crop (fractions, 0–1) for a source of aspect `ar`\n * filling an `artW×artH` box, plus the full (uncropped) display size the editor\n * stores in `transformData`. Mirrors the editor's cover behaviour but needs only\n * the aspect ratio, not the pixel dimensions. */\nfunction coverCrop(ar: number, artW: number, artH: number) {\n const artAR = artW / artH;\n if (ar >= artAR) {\n // Source is wider than the box → crop the sides.\n const fullW = artH * ar;\n const cropWidth = artW / fullW; // = artAR / ar\n return {\n width: fullW, height: artH,\n cropX: (1 - cropWidth) / 2, cropY: 0, cropWidth, cropHeight: 1,\n };\n }\n // Source is taller than the box → crop top/bottom.\n const fullH = artW / ar;\n const cropHeight = artH / fullH; // = ar / artAR\n return {\n width: artW, height: fullH,\n cropX: 0, cropY: (1 - cropHeight) / 2, cropWidth: 1, cropHeight,\n };\n}\n\n/**\n * A composed design. Add elements by intent, then emit it for the editor or the\n * render path. Methods are chainable.\n *\n * The two type parameters accumulate the design's NAMED layer literals per\n * kind (ADR-0085 §1): naming a layer in `.text()` / `.image()` /\n * `.background()` widens the corresponding union, so `design.layer(\"naem\")`\n * is a compile error listing the valid names. Designs loaded from JSON\n * ({@link DesignBuilder.fromState}) degrade both unions to `string` — same\n * API, runtime-validated instead (the degradation is deliberate and LOUD in\n * docs; never block the dynamic case).\n */\nexport class DesignBuilder<out TText extends string = string, out TImage extends string = string> {\n private readonly artboard: Required<Pick<DesignArtboard, 'name' | 'width' | 'height'>> & {\n backgroundColor?: string;\n role?: ArtboardRole;\n };\n private readonly els: AnyElementConfig[] = [];\n private idCounter = 0;\n /** Every artboard of a multi-page state (fromState `{ artboards: 'all' }`);\n * unset for single-artboard builders. */\n private allArtboards?: StateArtboard[];\n\n constructor(artboard: DesignArtboard) {\n if (!(artboard.width > 0) || !(artboard.height > 0)) {\n throw new Error('design(): artboard width and height must be positive numbers');\n }\n this.artboard = {\n name: artboard.name || 'Front',\n width: artboard.width,\n height: artboard.height,\n backgroundColor: artboard.backgroundColor,\n role: artboard.role,\n };\n }\n\n /**\n * Seed a builder from a previously saved canvas state — the editor shape\n * `<SnowconeCanvas onChange>` emits (and `toJSON()` produces).\n *\n * This is how a saved editable template re-enters the turnkey embed path:\n * load the template's state JSON, lift it back into a `DesignBuilder`, and\n * hand it to `<RealtimeEmbed design={…}>`. By default the active artboard\n * becomes the builder's artboard; elements tagged for other artboards are\n * skipped (untagged elements are assumed to belong to the active one).\n *\n * Pass `{ artboards: 'all' }` to load a multi-page template (ADR-0086)\n * whole: every artboard (with its pacing `role`) and every element are\n * kept, membership tags preserved, so `toJSON()` / `toServerRequest()`\n * emit all pages and `TemplateField.artboard` can scope fields per page.\n */\n static fromState(\n state: {\n elements: AnyElementConfig[];\n artboards: StateArtboard[];\n activeArtboard: string;\n },\n options: FromStateOptions = {},\n ): DesignBuilder {\n const artboard =\n state.artboards.find((a) => a.name === state.activeArtboard) ?? state.artboards[0];\n if (!artboard) {\n throw new Error('DesignBuilder.fromState(): state has no artboards');\n }\n const builder = new DesignBuilder({\n name: artboard.name,\n width: artboard.width,\n height: artboard.height,\n backgroundColor: artboard.backgroundColor,\n role: artboard.role,\n });\n if (options.artboards === 'all') {\n builder.allArtboards = state.artboards.map((a) => ({ ...a }));\n for (const el of state.elements) {\n // Untagged elements belong to the active artboard (same assumption as\n // the active-only mode) — stamp them so the multi-artboard state keeps\n // them there instead of collapsing onto the first artboard.\n builder.els.push(el.artboard ? { ...el } : { ...el, artboard: artboard.name });\n }\n return builder;\n }\n for (const el of state.elements) {\n if (el.artboard && el.artboard !== artboard.name) continue;\n builder.els.push({ ...el });\n }\n return builder;\n }\n\n private nextId(prefix: string): string {\n return `${prefix}-${++this.idCounter}`;\n }\n\n /** Full-bleed background image (fills the whole artboard). Cover-fit (no\n * distortion) when `aspectRatio` is supplied. Inserted at the bottom of the\n * stack regardless of call order. The default layer name `'Background'`\n * joins the IMAGE-layer token union (auto-injected names are deliberately\n * not one union with text — ADR-0085 §1). */\n background<const N extends string>(\n imageUrl: string,\n opts: BackgroundOptions & { name: N },\n ): DesignBuilder<TText, TImage | N>;\n background(imageUrl: string, opts?: BackgroundOptions): DesignBuilder<TText, TImage | 'Background'>;\n background(imageUrl: string, opts: BackgroundOptions = {}): AnyDesign {\n const { width: artW, height: artH } = this.artboard;\n let td: ImageElementConfig['transformData'];\n if (opts.aspectRatio && opts.aspectRatio > 0) {\n const c = coverCrop(opts.aspectRatio, artW, artH);\n td = {\n type: 'image', width: c.width, height: c.height,\n cropX: c.cropX, cropY: c.cropY, cropWidth: c.cropWidth, cropHeight: c.cropHeight,\n flipHorizontal: false, flipVertical: false, borderRadius: 0,\n };\n } else {\n td = {\n type: 'image', width: artW, height: artH,\n cropX: 0, cropY: 0, cropWidth: 1, cropHeight: 1,\n flipHorizontal: false, flipVertical: false, borderRadius: 0,\n };\n }\n const el: ImageElementConfig = {\n id: this.nextId('bg'),\n transformType: 'image',\n x: artW / 2,\n y: artH / 2,\n rotation: 0,\n opacity: opts.opacity ?? 1,\n imageUrl,\n imageAspectRatio: opts.aspectRatio,\n transformData: td,\n name: opts.name ?? 'Background',\n };\n // Background goes underneath everything else.\n this.els.unshift(el);\n return this;\n }\n\n /** An anchored image element (not full-bleed). */\n image<const N extends string>(\n imageUrl: string,\n opts: ImageOptions & { name: N },\n ): DesignBuilder<TText, TImage | N>;\n image(imageUrl: string, opts?: ImageOptions): DesignBuilder<TText, TImage>;\n image(imageUrl: string, opts: ImageOptions = {}): AnyDesign {\n const { width: artW, height: artH } = this.artboard;\n const w = opts.width ?? Math.round(artW / 3);\n const h = opts.height ?? (opts.aspectRatio ? Math.round(w / opts.aspectRatio) : w);\n const { x, y } = anchorToCenter(opts.anchor ?? DEFAULT_ANCHOR, w, h, artW, artH, opts.padding ?? 0);\n const el: ImageElementConfig = {\n id: this.nextId('img'),\n transformType: 'image',\n x, y,\n rotation: opts.rotation ?? 0,\n opacity: opts.opacity ?? 1,\n imageUrl,\n imageAspectRatio: opts.aspectRatio,\n transformData: {\n type: 'image', width: w, height: h,\n cropX: 0, cropY: 0, cropWidth: 1, cropHeight: 1,\n flipHorizontal: false, flipVertical: false, borderRadius: 0,\n },\n ...(opts.name ? { name: opts.name } : {}),\n };\n this.els.push(el);\n return this;\n }\n\n /** An anchored text layer. Horizontal centring is exact (textAlign); vertical\n * anchoring uses the font size as the line-height estimate. */\n text<const N extends string>(\n text: string,\n opts: TextOptions & { name: N },\n ): DesignBuilder<TText | N, TImage>;\n text(text: string, opts?: TextOptions): DesignBuilder<TText, TImage>;\n text(text: string, opts: TextOptions = {}): AnyDesign {\n const { width: artW, height: artH } = this.artboard;\n const fontSize = opts.fontSize ?? Math.round(this.artboard.height / 12);\n // Text width is metric-dependent (unknown headless); horizontal placement is\n // handled by textAlign, so anchor only drives the vertical centre + the\n // horizontal *reference* point. Use a zero-width box horizontally so the\n // centre sits exactly on the anchor's x line.\n const { x, y } = anchorToCenter(opts.anchor ?? 'top', 0, fontSize, artW, artH, opts.padding ?? Math.round(artH / 20));\n const el: CustomElementConfig = {\n id: this.nextId('text'),\n transformType: 'custom',\n x, y,\n rotation: opts.rotation ?? 0,\n text,\n fontSize,\n fontFamily: opts.fontFamily ?? 'Anton',\n color: opts.color ?? '#ffffff',\n textAlign: opts.textAlign ?? 'center',\n bold: opts.bold ?? false,\n italic: opts.italic ?? false,\n transformData: { type: 'custom' },\n ...(opts.name ? { name: opts.name } : {}),\n };\n this.els.push(el);\n return this;\n }\n\n /**\n * Mint a {@link LayerToken} for a named layer — the only currency\n * `Editor.TextField` accepts (ADR-0085 §1). Compile time: `name` is\n * constrained to this design's declared layer names, kind inferred from\n * which per-kind union it falls in. Runtime: validated EAGERLY against the\n * built elements (nearest the mistake), so CMS/JSON-loaded designs get the\n * same protection with `string` typing.\n *\n * @throws EditorError `E_LAYER_NOT_FOUND` — no layer with this name; lists\n * the valid names per kind.\n * @throws EditorError `E_LAYER_WRONG_KIND` — the name matches a layer that\n * is not bindable (shape/group/path).\n */\n layer<N extends TText | TImage>(name: N): LayerToken<InferKind<N, TText, TImage>, N> {\n const el = this.els.find((e) => (e as { name?: string }).name === name);\n if (!el) {\n const textNames = this.layerNamesOfKind('text');\n const imageNames = this.layerNamesOfKind('image');\n throw new EditorError(\n 'E_LAYER_NOT_FOUND',\n `no layer \"${name}\" in this design. Text layers: ${JSON.stringify(textNames)}; ` +\n `image layers: ${JSON.stringify(imageNames)}. Fix the name, or add the layer ` +\n `first — e.g. design(…).text('…', { name: '${name}' }).`,\n { layerName: name, validNames: [...textNames, ...imageNames], textNames, imageNames, docSlug: 'embed-mode' },\n );\n }\n const kind = elementLayerKind(el);\n if (!kind) {\n throw new EditorError(\n 'E_LAYER_WRONG_KIND',\n `layer \"${name}\" is a ${(el as { transformType?: string }).transformType} element — ` +\n `only text and image layers are bindable. Bindable layers: ` +\n `${JSON.stringify([...this.layerNamesOfKind('text'), ...this.layerNamesOfKind('image')])}.`,\n { layerName: name, actualType: (el as { transformType?: string }).transformType, docSlug: 'embed-mode' },\n );\n }\n return mintLayerToken(kind, name) as LayerToken<InferKind<N, TText, TImage>, N>;\n }\n\n /** Names of bindable layers of one kind, in stack order. @internal */\n layerNamesOfKind(kind: LayerKind): string[] {\n return this.els\n .filter((e) => elementLayerKind(e) === kind)\n .map((e) => (e as { name?: string }).name)\n .filter((n): n is string => Boolean(n));\n }\n\n /** An anchored vector shape. */\n shape(kind: ShapeKind, opts: ShapeOptions = {}): this {\n const { width: artW, height: artH } = this.artboard;\n const size = opts.size ?? Math.round(artW / 6);\n const w = typeof size === 'number' ? size : size.width;\n const h = typeof size === 'number' ? size : size.height;\n const { x, y } = anchorToCenter(opts.anchor ?? DEFAULT_ANCHOR, w, h, artW, artH, opts.padding ?? 0);\n const transformData: ShapeTransformData = {\n type: 'shape',\n shapeType: kind,\n width: w,\n height: h,\n fillColor: opts.fill ?? '#000000',\n fillOpacity: opts.opacity ?? 1,\n ...(opts.points !== undefined ? { points: opts.points } : {}),\n ...(opts.sides !== undefined ? { sides: opts.sides } : {}),\n };\n const el: ShapeElementConfig = {\n id: this.nextId('shape'),\n transformType: 'shape',\n x, y,\n rotation: opts.rotation ?? 0,\n transformData,\n ...(opts.name ? { name: opts.name } : {}),\n };\n this.els.push(el);\n return this;\n }\n\n /** The composed elements, in render order (background first). Editor shape. */\n get elements(): AnyElementConfig[] {\n return this.els.map((e) => ({ ...e }));\n }\n\n /** Editor-shape canvas state for `<SnowconeCanvas initialElements=… />` and\n * `serializeStateForServer`. `activeArtboard` is the artboard name. A\n * builder loaded with `fromState(…, { artboards: 'all' })` emits every\n * page (artboard `role`s and element membership tags included). */\n toJSON(): {\n elements: AnyElementConfig[];\n artboards: StateArtboard[];\n activeArtboard: string;\n } {\n if (this.allArtboards) {\n return {\n // Elements added via builder methods after fromState are untagged —\n // they belong to the builder's (active) artboard; stamp them so the\n // multi-artboard state groups them correctly.\n elements: this.els.map((e) =>\n e.artboard ? { ...e } : { ...e, artboard: this.artboard.name },\n ),\n artboards: this.allArtboards.map((a) => ({ ...a })),\n activeArtboard: this.artboard.name,\n };\n }\n return {\n elements: this.elements,\n artboards: [{\n name: this.artboard.name,\n width: this.artboard.width,\n height: this.artboard.height,\n // A background set on a design is part of the design: flag it\n // renderable so exports and server renders paint it, matching the\n // editor (the export worker skips it without `exportBackground`).\n ...(this.artboard.backgroundColor\n ? {\n backgroundColor: this.artboard.backgroundColor,\n exportBackground: this.artboard.backgroundColor !== 'transparent',\n }\n : {}),\n ...(this.artboard.role ? { role: this.artboard.role } : {}),\n }],\n activeArtboard: this.artboard.name,\n };\n }\n\n /** Server-render payload for `RenderSession.renderState(placement, …)`.\n * Equivalent to `serializeStateForServer(design.toJSON())`. */\n toServerRequest(): ServerRenderRequest {\n return serializeStateForServer(this.toJSON());\n }\n}\n\n/**\n * Start a design on an artboard (one print placement).\n *\n * @example\n * const d = design({ name: 'Front', width: 1480, height: 2328 })\n * .background(aiImageUrl, { aspectRatio: 1480 / 2328, name: 'art' })\n * .text('YOUR NAME', { anchor: 'top', name: 'headline' })\n * .shape('star', { anchor: 'bottom-left', fill: '#000' });\n *\n * session.renderState('Front', d.toServerRequest()); // live\n * // …or hand d.toJSON() to <SnowconeCanvas/> for interactive editing — the\n * // `name`s let you bind layers with useImageBinding('art') / useTextBinding('headline').\n */\nexport function design(artboard: DesignArtboard): DesignBuilder<never, never> {\n return new DesignBuilder<never, never>(artboard);\n}\n\n/**\n * Resolve a layer's kind against a live element/config list — the mount-time\n * revalidation `Editor.TextField` runs (the token is a validated claim; the\n * mounted Root's design state is the authority). @internal\n */\nexport function layerKindOfConfig(el: AnyElementConfig): LayerKind | null {\n return elementLayerKind(el);\n}\n"],"names":["CANVAS_STATE_SCHEMA_VERSION","q","n","quantizeGeometry","el","serializeStateForServer","state","srcArtboards","_a","elementsByArtboard","tag","idx","ab","serializeElement","srcArtboard","type","serializeImageElement","_artboard","rest","td","hasMasks","fullWidth","fullHeight","visibleWidth","visibleHeight","serialized","DEFAULT_ANCHOR","LAYER_TOKEN_MARKER","isLayerToken","value","mintLayerToken","kind","name","elementLayerKind","t","hPart","a","vPart","anchorToCenter","anchor","elW","elH","artW","artH","padding","h","v","x","y","coverCrop","ar","artAR","fullW","cropWidth","fullH","cropHeight","DesignBuilder","artboard","options","builder","prefix","imageUrl","opts","c","w","text","fontSize","e","textNames","imageNames","EditorError","size","transformData","design","layerKindOfConfig"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as H } from "react/jsx-runtime";
2
2
  import { useContext as X, useSyncExternalStore as rt, useCallback as a, createContext as Q, useRef as me, useState as E, useEffect as ve, useMemo as q } from "react";
3
- import { y as Ve, N as it, a9 as st, aa as ct, G as ke, ab as dt, ac as at, ad as lt, ae as ut, af as mt, c as $e, z as ft, ag as At, ah as pt, ai as ht, E as xt, L as Ne, I as yt, g as Et, S as St, aj as bt, ak as De } from "./FontAnalyzer-BpsTYowD.js";
3
+ import { y as Ve, N as it, ab as st, ac as ct, G as ke, ad as dt, ae as at, af as lt, ag as ut, ah as mt, c as $e, z as ft, ai as At, aj as pt, ak as ht, E as xt, L as Ne, I as yt, g as Et, S as St, al as bt, am as De } from "./FontAnalyzer-CwIpgTGd.js";
4
4
  const We = Q(null), It = () => () => {
5
5
  }, Le = () => 0;
6
6
  function Jt() {
@@ -1205,4 +1205,4 @@ export {
1205
1205
  Ft as s,
1206
1206
  on as u
1207
1207
  };
1208
- //# sourceMappingURL=editorErrors-CK1RISFa.js.map
1208
+ //# sourceMappingURL=editorErrors-B2mb1sVN.js.map