@snowcone-app/canvas 0.17.0 → 0.17.2

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 (66) hide show
  1. package/dist/{CanvasStateV1-C6i6vU6p.js → CanvasStateV1-BG2hEc3-.js} +3 -3
  2. package/dist/{CanvasStateV1-C6i6vU6p.js.map → CanvasStateV1-BG2hEc3-.js.map} +1 -1
  3. package/dist/EditorCanvasImpl-DgXvuWXX.js +70 -0
  4. package/dist/EditorCanvasImpl-DgXvuWXX.js.map +1 -0
  5. package/dist/EditorContext-CGkfmbgo.js +1178 -0
  6. package/dist/EditorContext-CGkfmbgo.js.map +1 -0
  7. package/dist/{ElementFactory-C05JJkRp.js → ElementFactory-CuHSAPfg.js} +1469 -710
  8. package/dist/ElementFactory-CuHSAPfg.js.map +1 -0
  9. package/dist/{HybridHistoryManager-Bht98XRv.js → FontAnalyzer-CLv7_msL.js} +2104 -2903
  10. package/dist/FontAnalyzer-CLv7_msL.js.map +1 -0
  11. package/dist/{ImportManager-kRYfIX-D.js → ImportManager-C_wBjWaV.js} +147 -147
  12. package/dist/{ImportManager-kRYfIX-D.js.map → ImportManager-C_wBjWaV.js.map} +1 -1
  13. package/dist/SnowconeCanvas-wdckG-6O.js +1179 -0
  14. package/dist/SnowconeCanvas-wdckG-6O.js.map +1 -0
  15. package/dist/{UnifiedFontService-BAtqqkU4.js → UnifiedFontService-Dsu5cwfT.js} +141 -141
  16. package/dist/UnifiedFontService-Dsu5cwfT.js.map +1 -0
  17. package/dist/advanced.mjs +10 -10
  18. package/dist/components/editor/RootInternals.d.ts +7 -0
  19. package/dist/components/editor/designStateStore.d.ts +1 -1
  20. package/dist/components/embed/EmbedRoot.d.ts +23 -0
  21. package/dist/components/embed/SnowconeCanvas.d.ts +13 -0
  22. package/dist/components/embed/kernel.d.ts +114 -0
  23. package/dist/contexts/BoundLayerGuardContext.d.ts +36 -0
  24. package/dist/core/GroupElement.d.ts +0 -5
  25. package/dist/core/TextElement.d.ts +8 -3
  26. package/dist/core/paint-registry.d.ts +42 -0
  27. package/dist/core/rich-text-layout.d.ts +28 -0
  28. package/dist/{editorErrors-DbIEtCTt.js → editorErrors-BY4Pdg7a.js} +2 -2
  29. package/dist/{editorErrors-DbIEtCTt.js.map → editorErrors-BY4Pdg7a.js.map} +1 -1
  30. package/dist/{embed-Bdj4PKUA.js → embed-32ZVWfmj.js} +209 -200
  31. package/dist/embed-32ZVWfmj.js.map +1 -0
  32. package/dist/embed.mjs +21 -22
  33. package/dist/embed.mjs.map +1 -1
  34. package/dist/index.mjs +7 -7
  35. package/dist/internals.mjs +93 -93
  36. package/dist/rendering/element-painters.d.ts +16 -0
  37. package/dist/rendering/rich-text-renderer.d.ts +3 -28
  38. package/dist/{template-fields-GOM4MNfq.js → template-fields-Z9x13sjM.js} +32 -33
  39. package/dist/{template-fields-GOM4MNfq.js.map → template-fields-Z9x13sjM.js.map} +1 -1
  40. package/dist/testing.mjs +7 -7
  41. package/dist/transforms/ArchTransform.d.ts +0 -1
  42. package/dist/transforms/AscendTransform.d.ts +0 -1
  43. package/dist/transforms/CircleTransform.d.ts +0 -1
  44. package/dist/transforms/FlagTransform.d.ts +0 -1
  45. package/dist/transforms/LeanTransform.d.ts +0 -1
  46. package/dist/transforms/WaveTransform.d.ts +0 -1
  47. package/dist/{usePieceGuideBundle-CGERAQJl.js → usePieceGuideBundle-C1ipFnZd.js} +2 -2
  48. package/dist/{usePieceGuideBundle-CGERAQJl.js.map → usePieceGuideBundle-C1ipFnZd.js.map} +1 -1
  49. package/dist/{validation-DEd6EOCP.js → validation-C795COGQ.js} +4803 -4791
  50. package/dist/validation-C795COGQ.js.map +1 -0
  51. package/dist/vendor/ui/primitives/kbd.d.ts +1 -1
  52. package/dist/workers/export-worker.bundle.string.d.ts +1 -1
  53. package/package.json +7 -4
  54. package/dist/EditorCanvasImpl-DkgE5-C2.js +0 -68
  55. package/dist/EditorCanvasImpl-DkgE5-C2.js.map +0 -1
  56. package/dist/EditorContext-CyanYzo2.js +0 -1131
  57. package/dist/EditorContext-CyanYzo2.js.map +0 -1
  58. package/dist/ElementFactory-C05JJkRp.js.map +0 -1
  59. package/dist/HybridHistoryManager-Bht98XRv.js.map +0 -1
  60. package/dist/SnowconeCanvas-C3eZ9PP8.js +0 -1047
  61. package/dist/SnowconeCanvas-C3eZ9PP8.js.map +0 -1
  62. package/dist/UnifiedFontService-BAtqqkU4.js.map +0 -1
  63. package/dist/editorError-DMENTZ-3.js +0 -19
  64. package/dist/editorError-DMENTZ-3.js.map +0 -1
  65. package/dist/embed-Bdj4PKUA.js.map +0 -1
  66. package/dist/validation-DEd6EOCP.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"template-fields-GOM4MNfq.js","sources":["../src/hooks/useCommands.ts","../src/hooks/useSelectedElement.ts","../src/hooks/useDeadBindingWarning.ts","../src/hooks/useTextBinding.ts","../src/hooks/useImageBinding.ts","../src/hooks/useColorBinding.ts","../src/hooks/useFontBinding.ts","../src/hooks/useElementByName.ts","../src/rendering/serialize-for-server.ts","../src/design/design-builder.ts","../src/design/template-fields.ts"],"sourcesContent":["/**\n * useCommands - Hook for undo/redo and command history operations\n *\n * Provides a clean interface to command history functionality\n * Must be used within EditorProvider\n *\n * @example\n * ```tsx\n * function UndoRedoToolbar() {\n * const { undo, redo, canUndo, canRedo, clearHistory } = useCommands();\n *\n * return (\n * <div className=\"toolbar\">\n * <button onClick={undo} disabled={!canUndo}>\n * Undo (⌘Z)\n * </button>\n * <button onClick={redo} disabled={!canRedo}>\n * Redo (⌘⇧Z)\n * </button>\n * <button onClick={clearHistory}>\n * Clear History\n * </button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useCallback } from 'react';\nimport { useEditor } from '../contexts/EditorContext.js';\nimport { createLogger } from '../utils/logger.js';\n\nconst logger = createLogger('useCommands');\n\nexport interface UseCommandsReturn {\n // Undo/Redo\n undo: () => void;\n redo: () => void;\n canUndo: boolean;\n canRedo: boolean;\n\n // History management\n clearHistory: () => void;\n clearArtboardHistory: (artboardId: string) => void;\n\n // Command execution\n executeElementUpdate: ReturnType<typeof useEditor>['executeElementUpdate'];\n executeAddElement: ReturnType<typeof useEditor>['executeAddElement'];\n executeRemoveElement: ReturnType<typeof useEditor>['executeRemoveElement'];\n executeReorderElement: ReturnType<typeof useEditor>['executeReorderElement'];\n executeCreateArtboard: ReturnType<typeof useEditor>['executeCreateArtboard'];\n executeDeleteArtboard: ReturnType<typeof useEditor>['executeDeleteArtboard'];\n executeUpdateArtboard: ReturnType<typeof useEditor>['executeUpdateArtboard'];\n\n // Batch execution\n executeCommandBatch: (commands: Array<{ execute: () => void; undo: () => void }>) => void;\n}\n\n/**\n * Hook for accessing command history and undo/redo functionality\n * Must be used within EditorProvider\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { undo, redo, canUndo, canRedo } = useCommands();\n * const { selectedElement } = useEditor();\n *\n * const handleDelete = () => {\n * if (selectedElement) {\n * executeRemoveElement(selectedElement);\n * }\n * };\n *\n * return (\n * <div>\n * <button onClick={undo} disabled={!canUndo}>Undo</button>\n * <button onClick={redo} disabled={!canRedo}>Redo</button>\n * <button onClick={handleDelete}>Delete</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useCommands(): UseCommandsReturn {\n const context = useEditor();\n\n // Extract command history methods\n const {\n undo,\n redo,\n canUndo,\n canRedo,\n executeElementUpdate,\n executeAddElement,\n executeRemoveElement,\n executeReorderElement,\n executeCommandBatch,\n executeCreateArtboard,\n executeDeleteArtboard,\n executeUpdateArtboard,\n } = context;\n\n // Clear all history (not exposed in EditorContext yet, so create a placeholder)\n const clearHistory = useCallback(() => {\n logger.warn('[useCommands] clearHistory is not yet implemented in EditorContext');\n }, []);\n\n // Clear artboard-specific history (not exposed in EditorContext yet, so create a placeholder)\n const clearArtboardHistory = useCallback((_artboardId: string) => {\n logger.warn('[useCommands] clearArtboardHistory is not yet implemented in EditorContext');\n }, []);\n\n return {\n // Undo/Redo\n undo,\n redo,\n canUndo,\n canRedo,\n\n // History management\n clearHistory,\n clearArtboardHistory,\n\n // Command execution\n executeElementUpdate,\n executeAddElement,\n executeRemoveElement,\n executeReorderElement,\n executeCreateArtboard,\n executeDeleteArtboard,\n executeUpdateArtboard,\n\n // Batch execution\n executeCommandBatch,\n };\n}\n","import { useEditor } from '../contexts/EditorContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/**\n * Returns the currently selected element, or null if nothing is selected.\n *\n * This is a convenience wrapper around `useEditor()` that only extracts\n * selection state, keeping consumer components focused on what they need.\n *\n * @example\n * ```tsx\n * function PropertiesPanel() {\n * const selected = useSelectedElement();\n * if (!selected) return <p>Nothing selected</p>;\n * return <p>Selected: {selected.id}</p>;\n * }\n * ```\n */\nexport function useSelectedElement(): EditorElement | null {\n const { selectedElement } = useEditor();\n return selectedElement ?? null;\n}\n","/**\n * useDeadBindingWarning - Dev-only diagnostic for \"dead\" binding hooks.\n *\n * `useTextBinding('typo')` / `useImageBinding('typo')` on a layer name that\n * matches no element returns `isConnected: false` with no-op setters — by\n * design. But with no diagnostic, a typo'd layer name produces a dead control\n * with ZERO console output (the silent-failure finding from the embed-SDK DX\n * evaluation). This hook emits a one-time, dev-only `console.warn` naming the\n * requested layer and listing the canvas's named layers — the same\n * silent-blank-guard pattern as the SDK's `warnImagelessElements` /\n * `[RealtimeEmbed]` validateEmbed warnings.\n *\n * Timing is settle-aware: elements may legitimately arrive late\n * (`initialElements` seeding, an async state load), so warning on first render\n * would false-fire constantly. The `ElementStore` is immutable — every mutation\n * hands the context a NEW store instance, which re-runs the effect — so the\n * grace timer re-arms on every element change and only fires once the element\n * set has been quiet for {@link DEAD_BINDING_GRACE_MS} with still no match.\n * Connecting (or unmounting — StrictMode's throwaway first mount included)\n * clears the pending timer.\n *\n * @module\n */\n\nimport { useEffect } from 'react';\nimport type { ElementStore, StoreElement } from '../core/ElementStore.js';\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\n\n/** The binding hook flavor (font binds to TEXT layers — see TARGET_KIND). */\nexport type BindingKind = 'text' | 'image' | 'font';\n\n/**\n * How long the element set must be SETTLED (no store changes) with the binding\n * still unmatched before the warning fires. Long enough for `initialElements`\n * seeding and async state loads to land; short enough to surface a typo well\n * before the user gives up on the dead control.\n */\nexport const DEAD_BINDING_GRACE_MS = 3000;\n\nconst HOOK_NAME: Record<BindingKind, string> = {\n text: 'useTextBinding',\n image: 'useImageBinding',\n font: 'useFontBinding',\n};\n\nconst SETTER_NAME: Record<BindingKind, string> = {\n text: 'setText',\n image: 'setImageUrl',\n font: 'setFontFamily',\n};\n\n/** The element flavor each hook actually matches (font targets text layers). */\nconst TARGET_KIND: Record<BindingKind, 'text' | 'image'> = {\n text: 'text',\n image: 'image',\n font: 'text',\n};\n\n/** Layer names already warned about — the warning is one-time per page load. */\nconst warned = new Set<string>();\n\n/** Test-only: forget which dead bindings have already been warned about. */\nexport function resetDeadBindingWarnings(): void {\n warned.clear();\n}\n\nfunction kindOf(el: StoreElement): string {\n if (el instanceof TextElement) return 'text';\n if (el instanceof ImageElement) return 'image';\n return 'other';\n}\n\n/**\n * Build the dead-binding warning message. Exported for tests; the message\n * distinguishes the two failure shapes:\n * - the name matches a layer of the WRONG type → point at the right hook\n * - the name matches nothing → list the available named layers (or explain\n * how to name one when none carry a name)\n */\nexport function deadBindingWarning(kind: BindingKind, name: string, store: ElementStore): string {\n const tag = `[${HOOK_NAME[kind]}]`;\n const dead = `the binding is dead (isConnected: false, ${SETTER_NAME[kind]} is a no-op)`;\n\n const target = TARGET_KIND[kind];\n const sameName = store.getAllByName(name);\n if (sameName.length > 0) {\n const kinds = [...new Set(sameName.map(kindOf))];\n const other = target === 'text' ? 'image' : 'text';\n const hint =\n kinds.length === 1 && kinds[0] === other\n ? ` Bind it with ${HOOK_NAME[other]}(\"${name}\") instead.`\n : '';\n return (\n `${tag} the layer named \"${name}\" is ${kinds.join('/')}, not ${target} — ` +\n `${dead}.${hint}`\n );\n }\n\n const named = store\n .getAll()\n .filter((el) => el.name)\n .map((el) => `\"${el.name}\" (${kindOf(el)})`);\n const available = named.length\n ? `Named layers on this canvas: ${named.join(', ')}. Check the name for a typo.`\n : `No element on this canvas has a name — name one via design().${target}(…, ` +\n `{ name: '${name}' }) or the element's \\`name\\` property.`;\n return `${tag} no ${target} layer named \"${name}\" exists — ${dead}. ${available}`;\n}\n\n/**\n * Emit the one-time dead-binding warning once the element set settles with the\n * binding still disconnected. Used by `useTextBinding` / `useImageBinding`;\n * dev-only (compiled out of production via the `NODE_ENV` guard) and never\n * throws.\n */\nexport function useDeadBindingWarning(\n kind: BindingKind,\n name: string,\n store: ElementStore,\n isConnected: boolean,\n): void {\n useEffect(() => {\n if (isConnected) return;\n if (typeof process === 'undefined' || process.env?.NODE_ENV === 'production') return;\n const key = `${kind}:${name}`;\n if (warned.has(key)) return;\n\n // The store is immutable, so any element change re-runs this effect with a\n // new instance — re-arming the timer. It fires only once the element set\n // has been quiet for the full grace period with still no match.\n const timer = setTimeout(() => {\n if (warned.has(key)) return; // another component already warned for this name\n warned.add(key);\n console.warn(deadBindingWarning(kind, name, store));\n }, DEAD_BINDING_GRACE_MS);\n return () => clearTimeout(timer);\n }, [kind, name, store, isConnected]);\n}\n","/**\n * useTextBinding - Bidirectional text binding between HTML inputs and canvas text elements.\n *\n * Links an external input to one or more canvas text elements by their `name` property.\n * Multiple elements can share the same name — `setText` updates all of them.\n * Changes flow both ways: input → canvas via `setText`, and canvas edits\n * propagate back through the element store re-render.\n *\n * @example\n * ```tsx\n * function NameInput() {\n * const { text, setText, isConnected } = useTextBinding('headline');\n * return (\n * <input\n * value={text}\n * onChange={(e) => setText(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { TextElement } from '../core/TextElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport { useDeadBindingWarning } from './useDeadBindingWarning.js';\n\n/** Result returned by {@link useTextBinding}. */\nexport interface TextBindingResult {\n /** Current plain text content (from first bound element). Empty string if disconnected. */\n text: string;\n /** Update text on all bound elements. No-op if disconnected. */\n setText: (text: string) => void;\n /** The first bound element, or null if no matching text element exists. */\n element: EditorElement | null;\n /** All bound text elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching text element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Bind an HTML input to canvas text elements by their `name` property.\n *\n * Multiple elements can share the same name — `setText` updates all of them.\n * Text is read from the first matching element (render order).\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @returns Text value, setter, element references, and connection status.\n */\nexport function useTextBinding(name: string): TextBindingResult {\n const { elementStore } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n\n const textElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is TextElement => el instanceof TextElement);\n }, [elementStore, name]);\n\n const setText = useCallback(\n (newText: string) => {\n for (const el of textElements) {\n const cloned = el.clone() as TextElement;\n cloned.setText(newText);\n executeElementUpdate(el, cloned);\n }\n },\n [textElements, executeElementUpdate],\n );\n\n // Dev-only: a name that never matches produces a dead control with no-op\n // setters — warn once the element set settles so a typo isn't silent.\n useDeadBindingWarning('text', name, elementStore, textElements.length > 0);\n\n if (textElements.length === 0) {\n return { text: '', setText: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n text: textElements[0].getText(),\n setText,\n element: textElements[0],\n elements: textElements,\n isConnected: true,\n };\n}\n","/**\n * useImageBinding - Bidirectional image URL binding between external inputs and canvas image elements.\n *\n * Links an external input to one or more canvas image elements by their `name` property.\n * Multiple elements can share the same name — `setImageUrl` updates all of them.\n * Changes flow both ways: input → canvas via `setImageUrl`, and canvas edits\n * propagate back through the element store re-render.\n *\n * @example\n * ```tsx\n * function LogoUpload() {\n * const { imageUrl, setImageUrl, isConnected } = useImageBinding('logo');\n * return (\n * <input\n * type=\"url\"\n * value={imageUrl}\n * onChange={(e) => setImageUrl(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback, useRef } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport { useDeadBindingWarning } from './useDeadBindingWarning.js';\n\n/** How the image fits the element bounds. */\nexport type ImageFitMode = 'cover' | 'contain';\n\n/** Options for {@link useImageBinding}. */\nexport interface ImageBindingOptions {\n /** How the image fits the element bounds. Default: \"cover\". */\n fit?: ImageFitMode;\n}\n\n/** Result returned by {@link useImageBinding}. */\nexport interface ImageBindingResult {\n /** Current image URL (from first bound element). Empty string if disconnected. */\n imageUrl: string;\n /** Update image URL on all bound elements. No-op if disconnected. */\n setImageUrl: (url: string) => void;\n /** The first bound element, or null if no matching image element exists. */\n element: EditorElement | null;\n /** All bound image elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching image element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Calculate center-crop values so the visible area fills the original element bounds.\n */\nfunction coverCrop(\n origWidth: number,\n origHeight: number,\n imageAspectRatio: number,\n) {\n const placeholderAR = origWidth / origHeight;\n\n if (imageAspectRatio >= placeholderAR) {\n // Image is wider — match height, crop width\n const frameHeight = origHeight;\n const frameWidth = frameHeight * imageAspectRatio;\n const cropWidth = origWidth / frameWidth;\n return {\n width: frameWidth,\n height: frameHeight,\n cropX: (1 - cropWidth) / 2,\n cropY: 0,\n cropWidth,\n cropHeight: 1,\n };\n } else {\n // Image is taller — match width, crop height\n const frameWidth = origWidth;\n const frameHeight = frameWidth / imageAspectRatio;\n const cropHeight = origHeight / frameHeight;\n return {\n width: frameWidth,\n height: frameHeight,\n cropX: 0,\n cropY: (1 - cropHeight) / 2,\n cropWidth: 1,\n cropHeight,\n };\n }\n}\n\n/**\n * Bind an external input to canvas image elements by their `name` property.\n *\n * Multiple elements can share the same name — `setImageUrl` updates all of them.\n * The image URL is read from the first matching element (render order).\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @param options - Optional configuration (fit mode, etc.).\n * @returns Image URL value, setter, element references, and connection status.\n */\nexport function useImageBinding(name: string, options?: ImageBindingOptions): ImageBindingResult {\n const fit = options?.fit ?? 'cover';\n const { elementStore, setElements } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n\n const imageElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is ImageElement => el instanceof ImageElement);\n }, [elementStore, name]);\n\n // Capture the original placeholder dimensions once (from the first time we see each element).\n // All subsequent image replacements fit within this fixed box.\n const origDimsRef = useRef<Map<string, { width: number; height: number }>>(new Map());\n for (const el of imageElements) {\n if (!origDimsRef.current.has(el.id)) {\n const td = el.transformData;\n origDimsRef.current.set(el.id, {\n width: td.width * td.cropWidth,\n height: td.height * td.cropHeight,\n });\n }\n }\n\n // Use refs for values that change on every element update to keep setImageUrl stable.\n // Without this, setImageUrl gets a new identity after every executeElementUpdate call,\n // which causes infinite loops when used in useEffect dependency arrays.\n const imageElementsRef = useRef(imageElements);\n imageElementsRef.current = imageElements;\n const executeRef = useRef(executeElementUpdate);\n executeRef.current = executeElementUpdate;\n const setElementsRef = useRef(setElements);\n setElementsRef.current = setElements;\n\n const setImageUrl = useCallback(\n (url: string) => {\n const currentElements = imageElementsRef.current;\n const exec = executeRef.current;\n\n for (const el of currentElements) {\n const orig = origDimsRef.current.get(el.id);\n if (!orig) continue;\n const { width: origW, height: origH } = orig;\n\n const cloned = el.clone() as ImageElement;\n cloned.imageLoaded = false;\n cloned.imageElement = null;\n cloned.isCropping = false;\n cloned.imageUrl = url;\n\n if (fit === 'cover') {\n cloned.preserveDimensions = true;\n\n cloned.onLoadCallback = (loadedElement) => {\n const imgAR = loadedElement.imageAspectRatio || 1;\n const crop = coverCrop(origW, origH, imgAR);\n\n loadedElement.transformData.width = crop.width;\n loadedElement.transformData.height = crop.height;\n loadedElement.transformData.cropX = crop.cropX;\n loadedElement.transformData.cropY = crop.cropY;\n loadedElement.transformData.cropWidth = crop.cropWidth;\n loadedElement.transformData.cropHeight = crop.cropHeight;\n\n // Single atomic update: command history + element store.\n // This ensures auto-export only sees the final cropped state,\n // not an intermediate unloaded state that would produce wrong mockups.\n exec(el, loadedElement);\n };\n } else {\n cloned.preserveDimensions = false;\n\n cloned.onLoadCallback = (loadedElement) => {\n const w = loadedElement.transformData.width;\n const h = loadedElement.transformData.height;\n const scale = Math.min(origW / w, origH / h, 1);\n if (scale < 1) {\n loadedElement.transformData.width = w * scale;\n loadedElement.transformData.height = h * scale;\n }\n loadedElement.transformData.cropX = 0;\n loadedElement.transformData.cropY = 0;\n loadedElement.transformData.cropWidth = 1;\n loadedElement.transformData.cropHeight = 1;\n\n exec(el, loadedElement);\n };\n }\n\n // Start loading — onLoadCallback will update the store via exec()\n // once the image is ready with final dimensions/crop.\n cloned.loadImage(url);\n }\n },\n [fit], // Stable deps only — imageElements/executeElementUpdate/setElements accessed via refs\n );\n\n // Dev-only: a name that never matches produces a dead control with no-op\n // setters — warn once the element set settles so a typo isn't silent.\n useDeadBindingWarning('image', name, elementStore, imageElements.length > 0);\n\n if (imageElements.length === 0) {\n return { imageUrl: '', setImageUrl: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n imageUrl: imageElements[0].imageUrl,\n setImageUrl,\n element: imageElements[0],\n elements: imageElements,\n isConnected: true,\n };\n}\n","/**\n * useColorBinding - Bind an external color control to every element painted a given color.\n *\n * Unlike `useTextBinding` / `useImageBinding` (which bind by element `name`),\n * a color binding matches by VALUE: every element whose text color, shape\n * fill, path stroke color, or stroke-effect color equals the bound color is\n * updated together. This is how an editable template exposes \"Accent Color\" —\n * the designer picks a color once, and the buyer's swatch repaints every\n * element that used it.\n *\n * The hook tracks the *currently applied* color: after `setColor('#00FF00')`,\n * subsequent matches (and the next `setColor`) are computed against the new\n * color, so repeated changes keep following the same set of elements.\n *\n * @example\n * ```tsx\n * function AccentSwatch({ field }: { field: { color: string; label: string } }) {\n * const { color, setColor, isConnected } = useColorBinding(field.color);\n * return (\n * <input\n * type=\"color\"\n * value={color}\n * onChange={(e) => setColor(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useState, useCallback } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/** Result returned by {@link useColorBinding}. */\nexport interface ColorBindingResult {\n /** The currently applied color (normalized uppercase hex). */\n color: string;\n /** Repaint all matching elements. No-op if the value is unchanged. */\n setColor: (color: string) => void;\n /** All elements currently matching the applied color. */\n elements: readonly EditorElement[];\n /** Whether at least one element currently carries the applied color. */\n isConnected: boolean;\n}\n\nconst EMPTY: readonly EditorElement[] = [];\n\n/** Normalize any hex color to uppercase 6-digit form (#RGB → #RRGGBB). */\nfunction normalizeHex(color: string): string {\n let hex = color.trim().toUpperCase();\n if (hex.length === 4 && hex.startsWith('#')) {\n hex = '#' + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];\n }\n return hex;\n}\n\n/** The color-carrying slots a canvas element may have. */\ninterface ColorCarrier {\n color?: unknown;\n setColor?: (c: string) => void;\n transformData?: { fillColor?: unknown; strokeColor?: unknown };\n stroke?: { enabled?: boolean; color?: unknown };\n}\n\nfunction slotMatches(value: unknown, match: string): boolean {\n return typeof value === 'string' && normalizeHex(value) === match;\n}\n\n/** Whether any color slot on the element equals `match` (normalized hex). */\nfunction elementMatchesColor(el: EditorElement, match: string): boolean {\n const c = el as unknown as ColorCarrier;\n return (\n slotMatches(c.color, match) ||\n slotMatches(c.transformData?.fillColor, match) ||\n slotMatches(c.transformData?.strokeColor, match) ||\n (c.stroke?.enabled === true && slotMatches(c.stroke.color, match))\n );\n}\n\n/**\n * Repaint every matching slot on a clone of `el`. Returns the clone if any\n * slot matched, or null if the element doesn't carry the color.\n */\nfunction applyColorToClone(\n el: EditorElement,\n match: string,\n newColor: string,\n): EditorElement | null {\n if (!elementMatchesColor(el, match)) return null;\n\n const cloned = el.clone() as EditorElement;\n const src = el as unknown as ColorCarrier;\n const dst = cloned as unknown as ColorCarrier;\n\n // Text elements: use setColor so richText spans update too.\n if (slotMatches(src.color, match)) {\n if (typeof dst.setColor === 'function') {\n dst.setColor(newColor);\n } else {\n dst.color = newColor;\n }\n }\n\n // Shape/Path elements: fill and stroke colors live in transformData.\n if (slotMatches(src.transformData?.fillColor, match)) {\n dst.transformData = { ...dst.transformData, fillColor: newColor };\n }\n if (slotMatches(src.transformData?.strokeColor, match)) {\n dst.transformData = { ...dst.transformData, strokeColor: newColor };\n }\n\n // Stroke effect (outline) color.\n if (src.stroke?.enabled === true && slotMatches(src.stroke.color, match)) {\n dst.stroke = { ...dst.stroke, color: newColor };\n }\n\n return cloned;\n}\n\n/**\n * Bind an external color control to all elements painted `originalColor`.\n *\n * Must be used within an `EditorProvider`.\n *\n * @param originalColor - The hex color to match (the template field's `color`).\n * @returns Applied color, setter, matching elements, and connection status.\n */\nexport function useColorBinding(originalColor: string): ColorBindingResult {\n const { elementStore } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n\n const [applied, setApplied] = useState(() => normalizeHex(originalColor));\n\n // If the hook is re-bound to a different template field, restart tracking.\n const [prevOriginal, setPrevOriginal] = useState(originalColor);\n if (originalColor !== prevOriginal) {\n setPrevOriginal(originalColor);\n setApplied(normalizeHex(originalColor));\n }\n\n // ElementStore is immutable — every mutation hands the context a new store\n // instance, so this memo recomputes whenever elements change.\n const matching = useMemo(\n () => elementStore.getAll().filter((el) => elementMatchesColor(el, applied)),\n [elementStore, applied],\n );\n\n const setColor = useCallback(\n (newColor: string) => {\n const normalizedNew = normalizeHex(newColor);\n if (normalizedNew === applied) return;\n\n for (const el of elementStore.getAll()) {\n const cloned = applyColorToClone(el, applied, newColor);\n if (cloned) executeElementUpdate(el, cloned);\n }\n setApplied(normalizedNew);\n },\n [elementStore, applied, executeElementUpdate],\n );\n\n if (matching.length === 0) {\n return { color: applied, setColor, elements: EMPTY, isConnected: false };\n }\n\n return { color: applied, setColor, elements: matching, isConnected: true };\n}\n","/**\n * useFontBinding - Bind an external font picker to canvas text elements.\n *\n * Links a control to one or more canvas TEXT elements by their `name` property\n * (same matching as `useTextBinding`). `setFontFamily` updates all of them and\n * loads the Google Font into the document, so a family the page hasn't used\n * yet still renders. This is how an editable template exposes a\n * designer-curated font choice (a `select` template field with\n * `property: 'fontFamily'`).\n *\n * Fonts must be on Google Fonts — the picker ecosystem is Google-only so\n * designs render identically across platforms (system fonts are deny-listed).\n *\n * @example\n * ```tsx\n * function FontPicker({ options }: { options: string[] }) {\n * const { fontFamily, setFontFamily, isConnected } = useFontBinding('headline');\n * if (!isConnected) return null;\n * return (\n * <select value={fontFamily} onChange={(e) => setFontFamily(e.target.value)}>\n * {options.map((f) => <option key={f} value={f}>{f}</option>)}\n * </select>\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { TextElement } from '../core/TextElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport { FontAnalyzer } from '../utils/FontAnalyzer.js';\nimport { UnifiedFontService } from '../utils/UnifiedFontService.js';\nimport { useDeadBindingWarning } from './useDeadBindingWarning.js';\n\n/** Result returned by {@link useFontBinding}. */\nexport interface FontBindingResult {\n /** Current font family (from first bound element). Empty string if disconnected. */\n fontFamily: string;\n /** Update the font family on all bound text elements. No-op if disconnected. */\n setFontFamily: (fontFamily: string) => void;\n /** The first bound element, or null if no matching text element exists. */\n element: EditorElement | null;\n /** All bound text elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching text element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Bind a font control to canvas text elements by their `name` property.\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @returns Font family, setter, element references, and connection status.\n */\nexport function useFontBinding(name: string): FontBindingResult {\n const { elementStore } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n\n const textElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is TextElement => el instanceof TextElement);\n }, [elementStore, name]);\n\n const setFontFamily = useCallback(\n (newFamily: string) => {\n // Make sure the family is actually available in the document — a buyer\n // picking a font the page never used would otherwise see the fallback.\n UnifiedFontService.loadGoogleFontByName(newFamily);\n\n for (const el of textElements) {\n if (el.fontFamily === newFamily) continue;\n // Old-family glyph caches are per-font and large on iOS — drop them\n // when switching, same as the editor's own font picker.\n FontAnalyzer.clearCache(el.fontFamily);\n const cloned = el.clone() as TextElement;\n cloned.fontFamily = newFamily;\n executeElementUpdate(el, cloned);\n }\n },\n [textElements, executeElementUpdate],\n );\n\n useDeadBindingWarning('font', name, elementStore, textElements.length > 0);\n\n if (textElements.length === 0) {\n return { fontFamily: '', setFontFamily: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n fontFamily: textElements[0].fontFamily,\n setFontFamily,\n element: textElements[0],\n elements: textElements,\n isConnected: true,\n };\n}\n","/**\n * useElementByName - Look up an element by its `name` property.\n *\n * General-purpose complement to {@link useElementById}. Returns the first\n * element whose `name` matches, following render order.\n *\n * @example\n * ```tsx\n * function ElementInspector({ elementName }: { elementName: string }) {\n * const element = useElementByName(elementName);\n * if (!element) return <p>Element not found</p>;\n * return <p>Position: ({element.x}, {element.y})</p>;\n * }\n * ```\n *\n * @module\n */\n\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/**\n * Returns the first element matching the given name, or null.\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to look up, or null to skip the lookup.\n * @returns The matching element, or null if no element matches.\n */\nexport function useElementByName(name: string | null): EditorElement | null {\n const { elementStore } = useElementsContext();\n if (!name) return null;\n return elementStore.getByName(name) ?? null;\n}\n","/**\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 }>;\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 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 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}\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 * @experimental ADR-0085; subject to change until the P4 gate.\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 };\n private readonly els: AnyElementConfig[] = [];\n private idCounter = 0;\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 };\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={…}>`. The active artboard becomes the\n * builder's artboard; elements tagged for other artboards are skipped\n * (untagged elements are assumed to belong to the active one).\n */\n static fromState(state: {\n elements: AnyElementConfig[];\n artboards: Array<{ name: string; width: number; height: number; backgroundColor?: string }>;\n activeArtboard: string;\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 });\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 * @experimental ADR-0085; subject to change until the P4 gate.\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. */\n toJSON(): {\n elements: AnyElementConfig[];\n artboards: Array<{ name: string; width: number; height: number; backgroundColor?: string; exportBackground?: boolean }>;\n activeArtboard: string;\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 }],\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","/**\n * Template fields — the manifest that turns a saved design into an editable template.\n *\n * An editable template is a saved canvas state plus a list of {@link TemplateField}s\n * describing which parts a buyer may change. Text and image fields reference\n * elements by their `name` property (the same names `useTextBinding` /\n * `useImageBinding` bind to); color fields match by value (`useColorBinding`).\n *\n * The fields are stored ON the state document itself under the\n * {@link TEMPLATE_FIELDS_KEY} key (`personalization`) — the same convention\n * snowcone.app's own editor uses, so a template authored in a host app\n * round-trips through Edit / Remix / the PDP unchanged:\n *\n * ```json\n * {\n * \"elements\": [...],\n * \"artboards\": [...],\n * \"activeArtboard\": \"Front\",\n * \"personalization\": [\n * { \"type\": \"text\", \"name\": \"headline\", \"label\": \"Your name\" }\n * ]\n * }\n * ```\n *\n * Write with {@link withTemplateFields}, read back with {@link readTemplateFields}.\n *\n * @module\n */\n\nimport { z } from 'zod';\nimport type { ImageFitMode } from '../hooks/useImageBinding.js';\n\n/** A buyer-editable text slot, bound to elements by their `name` property. */\nexport interface TemplateTextField {\n type: 'text';\n /** Element name (matches canvas layer `name` property). */\n name: string;\n /** Label shown to the buyer (e.g. \"Your name\"). */\n label: string;\n /** Placeholder text for the input. */\n placeholder?: string;\n /**\n * Designer's length limit for the buyer's text — long names overflow fixed\n * layouts. Intent, not enforcement: your control applies it (an input's\n * `maxLength`); the canvas itself doesn't truncate.\n */\n maxLength?: number;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/** A buyer-swappable image slot, bound to elements by their `name` property. */\nexport interface TemplateImageField {\n type: 'image';\n /** Element name (matches canvas layer `name` property). */\n name: string;\n /** Label shown to the buyer (e.g. \"Upload your logo\"). */\n label: string;\n /** Placeholder text for the input. */\n placeholder?: string;\n /** How the buyer's image fits the original element bounds. Default: \"cover\". */\n fit?: ImageFitMode;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/** A buyer-editable color, matched by value across all elements that use it. */\nexport interface TemplateColorField {\n type: 'color';\n /** Original hex color to match (e.g. \"#FF0000\"). */\n color: string;\n /** Label shown to the buyer (e.g. \"Accent color\"). */\n label: string;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/**\n * A pick-one choice over designer-authored options — \"one of these 3 badges\",\n * a curated font list, preset text variants. Binds to elements by `name` and\n * swaps ONE property on them, chosen by `property`:\n *\n * - `'text'` → apply via `useTextBinding(name).setText(value)`\n * - `'imageUrl'` → apply via `useImageBinding(name).setImageUrl(value)`\n * - `'fontFamily'` → apply via `useFontBinding(name).setFontFamily(value)`\n * (values must be Google Fonts families)\n */\nexport interface TemplateSelectField {\n type: 'select';\n /** Element name (matches canvas layer `name` property). */\n name: string;\n /** Label shown to the buyer (e.g. \"Badge\"). */\n label: string;\n /** Which property of the bound element(s) the chosen value sets. */\n property: 'text' | 'imageUrl' | 'fontFamily';\n /** The designer-authored choices, in display order. */\n options: Array<{ label: string; value: string }>;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/** One buyer-editable aspect of a template. */\nexport type TemplateField =\n | TemplateTextField\n | TemplateImageField\n | TemplateColorField\n | TemplateSelectField;\n\n/**\n * The key template fields are stored under on the state document.\n * Kept as `personalization` for compatibility with designs saved on snowcone.app.\n */\nexport const TEMPLATE_FIELDS_KEY = 'personalization';\n\nconst templateFieldSchema = z.discriminatedUnion('type', [\n z.object({\n type: z.literal('text'),\n name: z.string().min(1),\n label: z.string(),\n placeholder: z.string().optional(),\n maxLength: z.number().int().positive().optional(),\n group: z.string().optional(),\n }),\n z.object({\n type: z.literal('image'),\n name: z.string().min(1),\n label: z.string(),\n placeholder: z.string().optional(),\n fit: z.union([z.literal('cover'), z.literal('contain')]).optional(),\n group: z.string().optional(),\n }),\n z.object({\n type: z.literal('color'),\n color: z.string().min(1),\n label: z.string(),\n group: z.string().optional(),\n }),\n z.object({\n type: z.literal('select'),\n name: z.string().min(1),\n label: z.string(),\n property: z.union([z.literal('text'), z.literal('imageUrl'), z.literal('fontFamily')]),\n options: z.array(z.object({ label: z.string(), value: z.string() })).min(1),\n group: z.string().optional(),\n }),\n]);\n\n/**\n * Read the template fields off a parsed state document.\n *\n * Accepts any JSON value — a `CanvasState` from `onChange`, a `CanvasStateV1`\n * document, or a raw `JSON.parse` of a stored state file. Legacy fields with\n * no `type` are normalized to text fields; malformed entries are dropped.\n * Returns `[]` when the document carries no fields (a plain, non-template design).\n */\nexport function readTemplateFields(state: unknown): TemplateField[] {\n if (!state || typeof state !== 'object') return [];\n const raw = (state as Record<string, unknown>)[TEMPLATE_FIELDS_KEY];\n if (!Array.isArray(raw)) return [];\n\n const fields: TemplateField[] = [];\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue;\n const candidate = 'type' in entry ? entry : { ...entry, type: 'text' };\n const parsed = templateFieldSchema.safeParse(candidate);\n if (parsed.success) fields.push(parsed.data as TemplateField);\n }\n return fields;\n}\n\n/**\n * Attach template fields to a state document before persisting it.\n *\n * ```ts\n * onChange={(state) => setDraft(state)}\n * // …on save:\n * save(JSON.stringify(withTemplateFields(draft, fields)));\n * ```\n */\nexport function withTemplateFields<T extends object>(\n state: T,\n fields: TemplateField[],\n): T & { [TEMPLATE_FIELDS_KEY]: TemplateField[] } {\n return { ...state, [TEMPLATE_FIELDS_KEY]: fields };\n}\n"],"names":["logger","createLogger","useCommands","context","useEditor","undo","redo","canUndo","canRedo","executeElementUpdate","executeAddElement","executeRemoveElement","executeReorderElement","executeCommandBatch","executeCreateArtboard","executeDeleteArtboard","executeUpdateArtboard","clearHistory","useCallback","clearArtboardHistory","_artboardId","useSelectedElement","selectedElement","DEAD_BINDING_GRACE_MS","HOOK_NAME","SETTER_NAME","TARGET_KIND","warned","kindOf","el","TextElement","ImageElement","deadBindingWarning","kind","name","store","tag","dead","target","sameName","kinds","other","hint","named","available","useDeadBindingWarning","isConnected","useEffect","process","key","timer","NOOP","EMPTY","useTextBinding","elementStore","useElementsContext","useCommandContext","textElements","useMemo","setText","newText","cloned","coverCrop","origWidth","origHeight","imageAspectRatio","placeholderAR","frameHeight","frameWidth","cropWidth","cropHeight","useImageBinding","options","fit","setElements","imageElements","origDimsRef","useRef","td","imageElementsRef","executeRef","setElementsRef","setImageUrl","url","currentElements","exec","orig","origW","origH","loadedElement","imgAR","crop","w","h","scale","normalizeHex","color","hex","slotMatches","value","match","elementMatchesColor","c","_a","_b","_c","applyColorToClone","newColor","src","dst","useColorBinding","originalColor","applied","setApplied","useState","prevOriginal","setPrevOriginal","matching","setColor","normalizedNew","useFontBinding","setFontFamily","newFamily","UnifiedFontService","FontAnalyzer","useElementByName","CANVAS_STATE_SCHEMA_VERSION","q","n","quantizeGeometry","serializeStateForServer","state","srcArtboards","elementsByArtboard","idx","ab","serializeElement","srcArtboard","i","type","serializeImageElement","_artboard","rest","hasMasks","fullWidth","fullHeight","visibleWidth","visibleHeight","serialized","DEFAULT_ANCHOR","LAYER_TOKEN_MARKER","isLayerToken","mintLayerToken","elementLayerKind","t","hPart","a","vPart","anchorToCenter","anchor","elW","elH","artW","artH","padding","v","x","y","ar","artAR","fullW","fullH","DesignBuilder","artboard","builder","prefix","imageUrl","opts","text","fontSize","e","textNames","imageNames","EditorError","size","transformData","design","layerKindOfConfig","TEMPLATE_FIELDS_KEY","templateFieldSchema","z.discriminatedUnion","z.object","z.literal","z.string","z.number","z.union","z.array","readTemplateFields","raw","fields","entry","candidate","parsed","withTemplateFields"],"mappings":"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"}
1
+ {"version":3,"file":"template-fields-Z9x13sjM.js","sources":["../src/hooks/useCommands.ts","../src/hooks/useSelectedElement.ts","../src/hooks/useDeadBindingWarning.ts","../src/hooks/useTextBinding.ts","../src/hooks/useImageBinding.ts","../src/hooks/useColorBinding.ts","../src/hooks/useFontBinding.ts","../src/hooks/useElementByName.ts","../src/rendering/serialize-for-server.ts","../src/design/design-builder.ts","../src/design/template-fields.ts"],"sourcesContent":["/**\n * useCommands - Hook for undo/redo and command history operations\n *\n * Provides a clean interface to command history functionality\n * Must be used within EditorProvider\n *\n * @example\n * ```tsx\n * function UndoRedoToolbar() {\n * const { undo, redo, canUndo, canRedo, clearHistory } = useCommands();\n *\n * return (\n * <div className=\"toolbar\">\n * <button onClick={undo} disabled={!canUndo}>\n * Undo (⌘Z)\n * </button>\n * <button onClick={redo} disabled={!canRedo}>\n * Redo (⌘⇧Z)\n * </button>\n * <button onClick={clearHistory}>\n * Clear History\n * </button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useCallback } from 'react';\nimport { useEditor } from '../contexts/EditorContext.js';\nimport { createLogger } from '../utils/logger.js';\n\nconst logger = createLogger('useCommands');\n\nexport interface UseCommandsReturn {\n // Undo/Redo\n undo: () => void;\n redo: () => void;\n canUndo: boolean;\n canRedo: boolean;\n\n // History management\n clearHistory: () => void;\n clearArtboardHistory: (artboardId: string) => void;\n\n // Command execution\n executeElementUpdate: ReturnType<typeof useEditor>['executeElementUpdate'];\n executeAddElement: ReturnType<typeof useEditor>['executeAddElement'];\n executeRemoveElement: ReturnType<typeof useEditor>['executeRemoveElement'];\n executeReorderElement: ReturnType<typeof useEditor>['executeReorderElement'];\n executeCreateArtboard: ReturnType<typeof useEditor>['executeCreateArtboard'];\n executeDeleteArtboard: ReturnType<typeof useEditor>['executeDeleteArtboard'];\n executeUpdateArtboard: ReturnType<typeof useEditor>['executeUpdateArtboard'];\n\n // Batch execution\n executeCommandBatch: (commands: Array<{ execute: () => void; undo: () => void }>) => void;\n}\n\n/**\n * Hook for accessing command history and undo/redo functionality\n * Must be used within EditorProvider\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { undo, redo, canUndo, canRedo } = useCommands();\n * const { selectedElement } = useEditor();\n *\n * const handleDelete = () => {\n * if (selectedElement) {\n * executeRemoveElement(selectedElement);\n * }\n * };\n *\n * return (\n * <div>\n * <button onClick={undo} disabled={!canUndo}>Undo</button>\n * <button onClick={redo} disabled={!canRedo}>Redo</button>\n * <button onClick={handleDelete}>Delete</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useCommands(): UseCommandsReturn {\n const context = useEditor();\n\n // Extract command history methods\n const {\n undo,\n redo,\n canUndo,\n canRedo,\n executeElementUpdate,\n executeAddElement,\n executeRemoveElement,\n executeReorderElement,\n executeCommandBatch,\n executeCreateArtboard,\n executeDeleteArtboard,\n executeUpdateArtboard,\n } = context;\n\n // Clear all history (not exposed in EditorContext yet, so create a placeholder)\n const clearHistory = useCallback(() => {\n logger.warn('[useCommands] clearHistory is not yet implemented in EditorContext');\n }, []);\n\n // Clear artboard-specific history (not exposed in EditorContext yet, so create a placeholder)\n const clearArtboardHistory = useCallback((_artboardId: string) => {\n logger.warn('[useCommands] clearArtboardHistory is not yet implemented in EditorContext');\n }, []);\n\n return {\n // Undo/Redo\n undo,\n redo,\n canUndo,\n canRedo,\n\n // History management\n clearHistory,\n clearArtboardHistory,\n\n // Command execution\n executeElementUpdate,\n executeAddElement,\n executeRemoveElement,\n executeReorderElement,\n executeCreateArtboard,\n executeDeleteArtboard,\n executeUpdateArtboard,\n\n // Batch execution\n executeCommandBatch,\n };\n}\n","import { useEditor } from '../contexts/EditorContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/**\n * Returns the currently selected element, or null if nothing is selected.\n *\n * This is a convenience wrapper around `useEditor()` that only extracts\n * selection state, keeping consumer components focused on what they need.\n *\n * @example\n * ```tsx\n * function PropertiesPanel() {\n * const selected = useSelectedElement();\n * if (!selected) return <p>Nothing selected</p>;\n * return <p>Selected: {selected.id}</p>;\n * }\n * ```\n */\nexport function useSelectedElement(): EditorElement | null {\n const { selectedElement } = useEditor();\n return selectedElement ?? null;\n}\n","/**\n * useDeadBindingWarning - Dev-only diagnostic for \"dead\" binding hooks.\n *\n * `useTextBinding('typo')` / `useImageBinding('typo')` on a layer name that\n * matches no element returns `isConnected: false` with no-op setters — by\n * design. But with no diagnostic, a typo'd layer name produces a dead control\n * with ZERO console output (the silent-failure finding from the embed-SDK DX\n * evaluation). This hook emits a one-time, dev-only `console.warn` naming the\n * requested layer and listing the canvas's named layers — the same\n * silent-blank-guard pattern as the SDK's `warnImagelessElements` /\n * `[RealtimeEmbed]` validateEmbed warnings.\n *\n * Timing is settle-aware: elements may legitimately arrive late\n * (`initialElements` seeding, an async state load), so warning on first render\n * would false-fire constantly. The `ElementStore` is immutable — every mutation\n * hands the context a NEW store instance, which re-runs the effect — so the\n * grace timer re-arms on every element change and only fires once the element\n * set has been quiet for {@link DEAD_BINDING_GRACE_MS} with still no match.\n * Connecting (or unmounting — StrictMode's throwaway first mount included)\n * clears the pending timer.\n *\n * @module\n */\n\nimport { useEffect } from 'react';\nimport type { ElementStore, StoreElement } from '../core/ElementStore.js';\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\n\n/** The binding hook flavor (font binds to TEXT layers — see TARGET_KIND). */\nexport type BindingKind = 'text' | 'image' | 'font';\n\n/**\n * How long the element set must be SETTLED (no store changes) with the binding\n * still unmatched before the warning fires. Long enough for `initialElements`\n * seeding and async state loads to land; short enough to surface a typo well\n * before the user gives up on the dead control.\n */\nexport const DEAD_BINDING_GRACE_MS = 3000;\n\nconst HOOK_NAME: Record<BindingKind, string> = {\n text: 'useTextBinding',\n image: 'useImageBinding',\n font: 'useFontBinding',\n};\n\nconst SETTER_NAME: Record<BindingKind, string> = {\n text: 'setText',\n image: 'setImageUrl',\n font: 'setFontFamily',\n};\n\n/** The element flavor each hook actually matches (font targets text layers). */\nconst TARGET_KIND: Record<BindingKind, 'text' | 'image'> = {\n text: 'text',\n image: 'image',\n font: 'text',\n};\n\n/** Layer names already warned about — the warning is one-time per page load. */\nconst warned = new Set<string>();\n\n/** Test-only: forget which dead bindings have already been warned about. */\nexport function resetDeadBindingWarnings(): void {\n warned.clear();\n}\n\nfunction kindOf(el: StoreElement): string {\n if (el instanceof TextElement) return 'text';\n if (el instanceof ImageElement) return 'image';\n return 'other';\n}\n\n/**\n * Build the dead-binding warning message. Exported for tests; the message\n * distinguishes the two failure shapes:\n * - the name matches a layer of the WRONG type → point at the right hook\n * - the name matches nothing → list the available named layers (or explain\n * how to name one when none carry a name)\n */\nexport function deadBindingWarning(kind: BindingKind, name: string, store: ElementStore): string {\n const tag = `[${HOOK_NAME[kind]}]`;\n const dead = `the binding is dead (isConnected: false, ${SETTER_NAME[kind]} is a no-op)`;\n\n const target = TARGET_KIND[kind];\n const sameName = store.getAllByName(name);\n if (sameName.length > 0) {\n const kinds = [...new Set(sameName.map(kindOf))];\n const other = target === 'text' ? 'image' : 'text';\n const hint =\n kinds.length === 1 && kinds[0] === other\n ? ` Bind it with ${HOOK_NAME[other]}(\"${name}\") instead.`\n : '';\n return (\n `${tag} the layer named \"${name}\" is ${kinds.join('/')}, not ${target} — ` +\n `${dead}.${hint}`\n );\n }\n\n const named = store\n .getAll()\n .filter((el) => el.name)\n .map((el) => `\"${el.name}\" (${kindOf(el)})`);\n const available = named.length\n ? `Named layers on this canvas: ${named.join(', ')}. Check the name for a typo.`\n : `No element on this canvas has a name — name one via design().${target}(…, ` +\n `{ name: '${name}' }) or the element's \\`name\\` property.`;\n return `${tag} no ${target} layer named \"${name}\" exists — ${dead}. ${available}`;\n}\n\n/**\n * Emit the one-time dead-binding warning once the element set settles with the\n * binding still disconnected. Used by `useTextBinding` / `useImageBinding`;\n * dev-only (compiled out of production via the `NODE_ENV` guard) and never\n * throws.\n */\nexport function useDeadBindingWarning(\n kind: BindingKind,\n name: string,\n store: ElementStore,\n isConnected: boolean,\n): void {\n useEffect(() => {\n if (isConnected) return;\n if (typeof process === 'undefined' || process.env?.NODE_ENV === 'production') return;\n const key = `${kind}:${name}`;\n if (warned.has(key)) return;\n\n // The store is immutable, so any element change re-runs this effect with a\n // new instance — re-arming the timer. It fires only once the element set\n // has been quiet for the full grace period with still no match.\n const timer = setTimeout(() => {\n if (warned.has(key)) return; // another component already warned for this name\n warned.add(key);\n console.warn(deadBindingWarning(kind, name, store));\n }, DEAD_BINDING_GRACE_MS);\n return () => clearTimeout(timer);\n }, [kind, name, store, isConnected]);\n}\n","/**\n * useTextBinding - Bidirectional text binding between HTML inputs and canvas text elements.\n *\n * Links an external input to one or more canvas text elements by their `name` property.\n * Multiple elements can share the same name — `setText` updates all of them.\n * Changes flow both ways: input → canvas via `setText`, and canvas edits\n * propagate back through the element store re-render.\n *\n * @example\n * ```tsx\n * function NameInput() {\n * const { text, setText, isConnected } = useTextBinding('headline');\n * return (\n * <input\n * value={text}\n * onChange={(e) => setText(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { TextElement } from '../core/TextElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport { useDeadBindingWarning } from './useDeadBindingWarning.js';\n\n/** Result returned by {@link useTextBinding}. */\nexport interface TextBindingResult {\n /** Current plain text content (from first bound element). Empty string if disconnected. */\n text: string;\n /** Update text on all bound elements. No-op if disconnected. */\n setText: (text: string) => void;\n /** The first bound element, or null if no matching text element exists. */\n element: EditorElement | null;\n /** All bound text elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching text element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Bind an HTML input to canvas text elements by their `name` property.\n *\n * Multiple elements can share the same name — `setText` updates all of them.\n * Text is read from the first matching element (render order).\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @returns Text value, setter, element references, and connection status.\n */\nexport function useTextBinding(name: string): TextBindingResult {\n const { elementStore } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n\n const textElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is TextElement => el instanceof TextElement);\n }, [elementStore, name]);\n\n const setText = useCallback(\n (newText: string) => {\n for (const el of textElements) {\n const cloned = el.clone() as TextElement;\n cloned.setText(newText);\n executeElementUpdate(el, cloned);\n }\n },\n [textElements, executeElementUpdate],\n );\n\n // Dev-only: a name that never matches produces a dead control with no-op\n // setters — warn once the element set settles so a typo isn't silent.\n useDeadBindingWarning('text', name, elementStore, textElements.length > 0);\n\n if (textElements.length === 0) {\n return { text: '', setText: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n text: textElements[0].getText(),\n setText,\n element: textElements[0],\n elements: textElements,\n isConnected: true,\n };\n}\n","/**\n * useImageBinding - Bidirectional image URL binding between external inputs and canvas image elements.\n *\n * Links an external input to one or more canvas image elements by their `name` property.\n * Multiple elements can share the same name — `setImageUrl` updates all of them.\n * Changes flow both ways: input → canvas via `setImageUrl`, and canvas edits\n * propagate back through the element store re-render.\n *\n * @example\n * ```tsx\n * function LogoUpload() {\n * const { imageUrl, setImageUrl, isConnected } = useImageBinding('logo');\n * return (\n * <input\n * type=\"url\"\n * value={imageUrl}\n * onChange={(e) => setImageUrl(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback, useRef } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport { useDeadBindingWarning } from './useDeadBindingWarning.js';\n\n/** How the image fits the element bounds. */\nexport type ImageFitMode = 'cover' | 'contain';\n\n/** Options for {@link useImageBinding}. */\nexport interface ImageBindingOptions {\n /** How the image fits the element bounds. Default: \"cover\". */\n fit?: ImageFitMode;\n}\n\n/** Result returned by {@link useImageBinding}. */\nexport interface ImageBindingResult {\n /** Current image URL (from first bound element). Empty string if disconnected. */\n imageUrl: string;\n /** Update image URL on all bound elements. No-op if disconnected. */\n setImageUrl: (url: string) => void;\n /** The first bound element, or null if no matching image element exists. */\n element: EditorElement | null;\n /** All bound image elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching image element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Calculate center-crop values so the visible area fills the original element bounds.\n */\nfunction coverCrop(\n origWidth: number,\n origHeight: number,\n imageAspectRatio: number,\n) {\n const placeholderAR = origWidth / origHeight;\n\n if (imageAspectRatio >= placeholderAR) {\n // Image is wider — match height, crop width\n const frameHeight = origHeight;\n const frameWidth = frameHeight * imageAspectRatio;\n const cropWidth = origWidth / frameWidth;\n return {\n width: frameWidth,\n height: frameHeight,\n cropX: (1 - cropWidth) / 2,\n cropY: 0,\n cropWidth,\n cropHeight: 1,\n };\n } else {\n // Image is taller — match width, crop height\n const frameWidth = origWidth;\n const frameHeight = frameWidth / imageAspectRatio;\n const cropHeight = origHeight / frameHeight;\n return {\n width: frameWidth,\n height: frameHeight,\n cropX: 0,\n cropY: (1 - cropHeight) / 2,\n cropWidth: 1,\n cropHeight,\n };\n }\n}\n\n/**\n * Bind an external input to canvas image elements by their `name` property.\n *\n * Multiple elements can share the same name — `setImageUrl` updates all of them.\n * The image URL is read from the first matching element (render order).\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @param options - Optional configuration (fit mode, etc.).\n * @returns Image URL value, setter, element references, and connection status.\n */\nexport function useImageBinding(name: string, options?: ImageBindingOptions): ImageBindingResult {\n const fit = options?.fit ?? 'cover';\n const { elementStore, setElements } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n\n const imageElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is ImageElement => el instanceof ImageElement);\n }, [elementStore, name]);\n\n // Capture the original placeholder dimensions once (from the first time we see each element).\n // All subsequent image replacements fit within this fixed box.\n const origDimsRef = useRef<Map<string, { width: number; height: number }>>(new Map());\n for (const el of imageElements) {\n if (!origDimsRef.current.has(el.id)) {\n const td = el.transformData;\n origDimsRef.current.set(el.id, {\n width: td.width * td.cropWidth,\n height: td.height * td.cropHeight,\n });\n }\n }\n\n // Use refs for values that change on every element update to keep setImageUrl stable.\n // Without this, setImageUrl gets a new identity after every executeElementUpdate call,\n // which causes infinite loops when used in useEffect dependency arrays.\n const imageElementsRef = useRef(imageElements);\n imageElementsRef.current = imageElements;\n const executeRef = useRef(executeElementUpdate);\n executeRef.current = executeElementUpdate;\n const setElementsRef = useRef(setElements);\n setElementsRef.current = setElements;\n\n const setImageUrl = useCallback(\n (url: string) => {\n const currentElements = imageElementsRef.current;\n const exec = executeRef.current;\n\n for (const el of currentElements) {\n const orig = origDimsRef.current.get(el.id);\n if (!orig) continue;\n const { width: origW, height: origH } = orig;\n\n const cloned = el.clone() as ImageElement;\n cloned.imageLoaded = false;\n cloned.imageElement = null;\n cloned.isCropping = false;\n cloned.imageUrl = url;\n\n if (fit === 'cover') {\n cloned.preserveDimensions = true;\n\n cloned.onLoadCallback = (loadedElement) => {\n const imgAR = loadedElement.imageAspectRatio || 1;\n const crop = coverCrop(origW, origH, imgAR);\n\n loadedElement.transformData.width = crop.width;\n loadedElement.transformData.height = crop.height;\n loadedElement.transformData.cropX = crop.cropX;\n loadedElement.transformData.cropY = crop.cropY;\n loadedElement.transformData.cropWidth = crop.cropWidth;\n loadedElement.transformData.cropHeight = crop.cropHeight;\n\n // Single atomic update: command history + element store.\n // This ensures auto-export only sees the final cropped state,\n // not an intermediate unloaded state that would produce wrong mockups.\n exec(el, loadedElement);\n };\n } else {\n cloned.preserveDimensions = false;\n\n cloned.onLoadCallback = (loadedElement) => {\n const w = loadedElement.transformData.width;\n const h = loadedElement.transformData.height;\n const scale = Math.min(origW / w, origH / h, 1);\n if (scale < 1) {\n loadedElement.transformData.width = w * scale;\n loadedElement.transformData.height = h * scale;\n }\n loadedElement.transformData.cropX = 0;\n loadedElement.transformData.cropY = 0;\n loadedElement.transformData.cropWidth = 1;\n loadedElement.transformData.cropHeight = 1;\n\n exec(el, loadedElement);\n };\n }\n\n // Start loading — onLoadCallback will update the store via exec()\n // once the image is ready with final dimensions/crop.\n cloned.loadImage(url);\n }\n },\n [fit], // Stable deps only — imageElements/executeElementUpdate/setElements accessed via refs\n );\n\n // Dev-only: a name that never matches produces a dead control with no-op\n // setters — warn once the element set settles so a typo isn't silent.\n useDeadBindingWarning('image', name, elementStore, imageElements.length > 0);\n\n if (imageElements.length === 0) {\n return { imageUrl: '', setImageUrl: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n imageUrl: imageElements[0].imageUrl,\n setImageUrl,\n element: imageElements[0],\n elements: imageElements,\n isConnected: true,\n };\n}\n","/**\n * useColorBinding - Bind an external color control to every element painted a given color.\n *\n * Unlike `useTextBinding` / `useImageBinding` (which bind by element `name`),\n * a color binding matches by VALUE: every element whose text color, shape\n * fill, path stroke color, or stroke-effect color equals the bound color is\n * updated together. This is how an editable template exposes \"Accent Color\" —\n * the designer picks a color once, and the buyer's swatch repaints every\n * element that used it.\n *\n * The hook tracks the *currently applied* color: after `setColor('#00FF00')`,\n * subsequent matches (and the next `setColor`) are computed against the new\n * color, so repeated changes keep following the same set of elements.\n *\n * @example\n * ```tsx\n * function AccentSwatch({ field }: { field: { color: string; label: string } }) {\n * const { color, setColor, isConnected } = useColorBinding(field.color);\n * return (\n * <input\n * type=\"color\"\n * value={color}\n * onChange={(e) => setColor(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useState, useCallback } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/** Result returned by {@link useColorBinding}. */\nexport interface ColorBindingResult {\n /** The currently applied color (normalized uppercase hex). */\n color: string;\n /** Repaint all matching elements. No-op if the value is unchanged. */\n setColor: (color: string) => void;\n /** All elements currently matching the applied color. */\n elements: readonly EditorElement[];\n /** Whether at least one element currently carries the applied color. */\n isConnected: boolean;\n}\n\nconst EMPTY: readonly EditorElement[] = [];\n\n/** Normalize any hex color to uppercase 6-digit form (#RGB → #RRGGBB). */\nfunction normalizeHex(color: string): string {\n let hex = color.trim().toUpperCase();\n if (hex.length === 4 && hex.startsWith('#')) {\n hex = '#' + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];\n }\n return hex;\n}\n\n/** The color-carrying slots a canvas element may have. */\ninterface ColorCarrier {\n color?: unknown;\n setColor?: (c: string) => void;\n transformData?: { fillColor?: unknown; strokeColor?: unknown };\n stroke?: { enabled?: boolean; color?: unknown };\n}\n\nfunction slotMatches(value: unknown, match: string): boolean {\n return typeof value === 'string' && normalizeHex(value) === match;\n}\n\n/** Whether any color slot on the element equals `match` (normalized hex). */\nfunction elementMatchesColor(el: EditorElement, match: string): boolean {\n const c = el as unknown as ColorCarrier;\n return (\n slotMatches(c.color, match) ||\n slotMatches(c.transformData?.fillColor, match) ||\n slotMatches(c.transformData?.strokeColor, match) ||\n (c.stroke?.enabled === true && slotMatches(c.stroke.color, match))\n );\n}\n\n/**\n * Repaint every matching slot on a clone of `el`. Returns the clone if any\n * slot matched, or null if the element doesn't carry the color.\n */\nfunction applyColorToClone(\n el: EditorElement,\n match: string,\n newColor: string,\n): EditorElement | null {\n if (!elementMatchesColor(el, match)) return null;\n\n const cloned = el.clone() as EditorElement;\n const src = el as unknown as ColorCarrier;\n const dst = cloned as unknown as ColorCarrier;\n\n // Text elements: use setColor so richText spans update too.\n if (slotMatches(src.color, match)) {\n if (typeof dst.setColor === 'function') {\n dst.setColor(newColor);\n } else {\n dst.color = newColor;\n }\n }\n\n // Shape/Path elements: fill and stroke colors live in transformData.\n if (slotMatches(src.transformData?.fillColor, match)) {\n dst.transformData = { ...dst.transformData, fillColor: newColor };\n }\n if (slotMatches(src.transformData?.strokeColor, match)) {\n dst.transformData = { ...dst.transformData, strokeColor: newColor };\n }\n\n // Stroke effect (outline) color.\n if (src.stroke?.enabled === true && slotMatches(src.stroke.color, match)) {\n dst.stroke = { ...dst.stroke, color: newColor };\n }\n\n return cloned;\n}\n\n/**\n * Bind an external color control to all elements painted `originalColor`.\n *\n * Must be used within an `EditorProvider`.\n *\n * @param originalColor - The hex color to match (the template field's `color`).\n * @returns Applied color, setter, matching elements, and connection status.\n */\nexport function useColorBinding(originalColor: string): ColorBindingResult {\n const { elementStore } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n\n const [applied, setApplied] = useState(() => normalizeHex(originalColor));\n\n // If the hook is re-bound to a different template field, restart tracking.\n const [prevOriginal, setPrevOriginal] = useState(originalColor);\n if (originalColor !== prevOriginal) {\n setPrevOriginal(originalColor);\n setApplied(normalizeHex(originalColor));\n }\n\n // ElementStore is immutable — every mutation hands the context a new store\n // instance, so this memo recomputes whenever elements change.\n const matching = useMemo(\n () => elementStore.getAll().filter((el) => elementMatchesColor(el, applied)),\n [elementStore, applied],\n );\n\n const setColor = useCallback(\n (newColor: string) => {\n const normalizedNew = normalizeHex(newColor);\n if (normalizedNew === applied) return;\n\n for (const el of elementStore.getAll()) {\n const cloned = applyColorToClone(el, applied, newColor);\n if (cloned) executeElementUpdate(el, cloned);\n }\n setApplied(normalizedNew);\n },\n [elementStore, applied, executeElementUpdate],\n );\n\n if (matching.length === 0) {\n return { color: applied, setColor, elements: EMPTY, isConnected: false };\n }\n\n return { color: applied, setColor, elements: matching, isConnected: true };\n}\n","/**\n * useFontBinding - Bind an external font picker to canvas text elements.\n *\n * Links a control to one or more canvas TEXT elements by their `name` property\n * (same matching as `useTextBinding`). `setFontFamily` updates all of them and\n * loads the Google Font into the document, so a family the page hasn't used\n * yet still renders. This is how an editable template exposes a\n * designer-curated font choice (a `select` template field with\n * `property: 'fontFamily'`).\n *\n * Fonts must be on Google Fonts — the picker ecosystem is Google-only so\n * designs render identically across platforms (system fonts are deny-listed).\n *\n * @example\n * ```tsx\n * function FontPicker({ options }: { options: string[] }) {\n * const { fontFamily, setFontFamily, isConnected } = useFontBinding('headline');\n * if (!isConnected) return null;\n * return (\n * <select value={fontFamily} onChange={(e) => setFontFamily(e.target.value)}>\n * {options.map((f) => <option key={f} value={f}>{f}</option>)}\n * </select>\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { TextElement } from '../core/TextElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport { FontAnalyzer } from '../utils/FontAnalyzer.js';\nimport { UnifiedFontService } from '../utils/UnifiedFontService.js';\nimport { useDeadBindingWarning } from './useDeadBindingWarning.js';\n\n/** Result returned by {@link useFontBinding}. */\nexport interface FontBindingResult {\n /** Current font family (from first bound element). Empty string if disconnected. */\n fontFamily: string;\n /** Update the font family on all bound text elements. No-op if disconnected. */\n setFontFamily: (fontFamily: string) => void;\n /** The first bound element, or null if no matching text element exists. */\n element: EditorElement | null;\n /** All bound text elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching text element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Bind a font control to canvas text elements by their `name` property.\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @returns Font family, setter, element references, and connection status.\n */\nexport function useFontBinding(name: string): FontBindingResult {\n const { elementStore } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n\n const textElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is TextElement => el instanceof TextElement);\n }, [elementStore, name]);\n\n const setFontFamily = useCallback(\n (newFamily: string) => {\n // Make sure the family is actually available in the document — a buyer\n // picking a font the page never used would otherwise see the fallback.\n UnifiedFontService.loadGoogleFontByName(newFamily);\n\n for (const el of textElements) {\n if (el.fontFamily === newFamily) continue;\n // Old-family glyph caches are per-font and large on iOS — drop them\n // when switching, same as the editor's own font picker.\n FontAnalyzer.clearCache(el.fontFamily);\n const cloned = el.clone() as TextElement;\n cloned.fontFamily = newFamily;\n executeElementUpdate(el, cloned);\n }\n },\n [textElements, executeElementUpdate],\n );\n\n useDeadBindingWarning('font', name, elementStore, textElements.length > 0);\n\n if (textElements.length === 0) {\n return { fontFamily: '', setFontFamily: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n fontFamily: textElements[0].fontFamily,\n setFontFamily,\n element: textElements[0],\n elements: textElements,\n isConnected: true,\n };\n}\n","/**\n * useElementByName - Look up an element by its `name` property.\n *\n * General-purpose complement to {@link useElementById}. Returns the first\n * element whose `name` matches, following render order.\n *\n * @example\n * ```tsx\n * function ElementInspector({ elementName }: { elementName: string }) {\n * const element = useElementByName(elementName);\n * if (!element) return <p>Element not found</p>;\n * return <p>Position: ({element.x}, {element.y})</p>;\n * }\n * ```\n *\n * @module\n */\n\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/**\n * Returns the first element matching the given name, or null.\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to look up, or null to skip the lookup.\n * @returns The matching element, or null if no element matches.\n */\nexport function useElementByName(name: string | null): EditorElement | null {\n const { elementStore } = useElementsContext();\n if (!name) return null;\n return elementStore.getByName(name) ?? null;\n}\n","/**\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 }>;\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 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 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}\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 * @experimental ADR-0085; subject to change until the P4 gate.\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 };\n private readonly els: AnyElementConfig[] = [];\n private idCounter = 0;\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 };\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={…}>`. The active artboard becomes the\n * builder's artboard; elements tagged for other artboards are skipped\n * (untagged elements are assumed to belong to the active one).\n */\n static fromState(state: {\n elements: AnyElementConfig[];\n artboards: Array<{ name: string; width: number; height: number; backgroundColor?: string }>;\n activeArtboard: string;\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 });\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 * @experimental ADR-0085; subject to change until the P4 gate.\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. */\n toJSON(): {\n elements: AnyElementConfig[];\n artboards: Array<{ name: string; width: number; height: number; backgroundColor?: string; exportBackground?: boolean }>;\n activeArtboard: string;\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 }],\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","/**\n * Template fields — the manifest that turns a saved design into an editable template.\n *\n * An editable template is a saved canvas state plus a list of {@link TemplateField}s\n * describing which parts a buyer may change. Text and image fields reference\n * elements by their `name` property (the same names `useTextBinding` /\n * `useImageBinding` bind to); color fields match by value (`useColorBinding`).\n *\n * The fields are stored ON the state document itself under the\n * {@link TEMPLATE_FIELDS_KEY} key (`personalization`) — the same convention\n * snowcone.app's own editor uses, so a template authored in a host app\n * round-trips through Edit / Remix / the PDP unchanged:\n *\n * ```json\n * {\n * \"elements\": [...],\n * \"artboards\": [...],\n * \"activeArtboard\": \"Front\",\n * \"personalization\": [\n * { \"type\": \"text\", \"name\": \"headline\", \"label\": \"Your name\" }\n * ]\n * }\n * ```\n *\n * Write with {@link withTemplateFields}, read back with {@link readTemplateFields}.\n *\n * @module\n */\n\nimport { z } from 'zod';\nimport type { ImageFitMode } from '../hooks/useImageBinding.js';\n\n/** A buyer-editable text slot, bound to elements by their `name` property. */\nexport interface TemplateTextField {\n type: 'text';\n /** Element name (matches canvas layer `name` property). */\n name: string;\n /** Label shown to the buyer (e.g. \"Your name\"). */\n label: string;\n /** Placeholder text for the input. */\n placeholder?: string;\n /**\n * Designer's length limit for the buyer's text — long names overflow fixed\n * layouts. Intent, not enforcement: your control applies it (an input's\n * `maxLength`); the canvas itself doesn't truncate.\n */\n maxLength?: number;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/** A buyer-swappable image slot, bound to elements by their `name` property. */\nexport interface TemplateImageField {\n type: 'image';\n /** Element name (matches canvas layer `name` property). */\n name: string;\n /** Label shown to the buyer (e.g. \"Upload your logo\"). */\n label: string;\n /** Placeholder text for the input. */\n placeholder?: string;\n /** How the buyer's image fits the original element bounds. Default: \"cover\". */\n fit?: ImageFitMode;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/** A buyer-editable color, matched by value across all elements that use it. */\nexport interface TemplateColorField {\n type: 'color';\n /** Original hex color to match (e.g. \"#FF0000\"). */\n color: string;\n /** Label shown to the buyer (e.g. \"Accent color\"). */\n label: string;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/**\n * A pick-one choice over designer-authored options — \"one of these 3 badges\",\n * a curated font list, preset text variants. Binds to elements by `name` and\n * swaps ONE property on them, chosen by `property`:\n *\n * - `'text'` → apply via `useTextBinding(name).setText(value)`\n * - `'imageUrl'` → apply via `useImageBinding(name).setImageUrl(value)`\n * - `'fontFamily'` → apply via `useFontBinding(name).setFontFamily(value)`\n * (values must be Google Fonts families)\n */\nexport interface TemplateSelectField {\n type: 'select';\n /** Element name (matches canvas layer `name` property). */\n name: string;\n /** Label shown to the buyer (e.g. \"Badge\"). */\n label: string;\n /** Which property of the bound element(s) the chosen value sets. */\n property: 'text' | 'imageUrl' | 'fontFamily';\n /** The designer-authored choices, in display order. */\n options: Array<{ label: string; value: string }>;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/** One buyer-editable aspect of a template. */\nexport type TemplateField =\n | TemplateTextField\n | TemplateImageField\n | TemplateColorField\n | TemplateSelectField;\n\n/**\n * The key template fields are stored under on the state document.\n * Kept as `personalization` for compatibility with designs saved on snowcone.app.\n */\nexport const TEMPLATE_FIELDS_KEY = 'personalization';\n\nconst templateFieldSchema = z.discriminatedUnion('type', [\n z.object({\n type: z.literal('text'),\n name: z.string().min(1),\n label: z.string(),\n placeholder: z.string().optional(),\n maxLength: z.number().int().positive().optional(),\n group: z.string().optional(),\n }),\n z.object({\n type: z.literal('image'),\n name: z.string().min(1),\n label: z.string(),\n placeholder: z.string().optional(),\n fit: z.union([z.literal('cover'), z.literal('contain')]).optional(),\n group: z.string().optional(),\n }),\n z.object({\n type: z.literal('color'),\n color: z.string().min(1),\n label: z.string(),\n group: z.string().optional(),\n }),\n z.object({\n type: z.literal('select'),\n name: z.string().min(1),\n label: z.string(),\n property: z.union([z.literal('text'), z.literal('imageUrl'), z.literal('fontFamily')]),\n options: z.array(z.object({ label: z.string(), value: z.string() })).min(1),\n group: z.string().optional(),\n }),\n]);\n\n/**\n * Read the template fields off a parsed state document.\n *\n * Accepts any JSON value — a `CanvasState` from `onChange`, a `CanvasStateV1`\n * document, or a raw `JSON.parse` of a stored state file. Legacy fields with\n * no `type` are normalized to text fields; malformed entries are dropped.\n * Returns `[]` when the document carries no fields (a plain, non-template design).\n */\nexport function readTemplateFields(state: unknown): TemplateField[] {\n if (!state || typeof state !== 'object') return [];\n const raw = (state as Record<string, unknown>)[TEMPLATE_FIELDS_KEY];\n if (!Array.isArray(raw)) return [];\n\n const fields: TemplateField[] = [];\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue;\n const candidate = 'type' in entry ? entry : { ...entry, type: 'text' };\n const parsed = templateFieldSchema.safeParse(candidate);\n if (parsed.success) fields.push(parsed.data as TemplateField);\n }\n return fields;\n}\n\n/**\n * Attach template fields to a state document before persisting it.\n *\n * ```ts\n * onChange={(state) => setDraft(state)}\n * // …on save:\n * save(JSON.stringify(withTemplateFields(draft, fields)));\n * ```\n */\nexport function withTemplateFields<T extends object>(\n state: T,\n fields: TemplateField[],\n): T & { [TEMPLATE_FIELDS_KEY]: TemplateField[] } {\n return { ...state, [TEMPLATE_FIELDS_KEY]: fields };\n}\n"],"names":["logger","createLogger","useCommands","context","useEditor","undo","redo","canUndo","canRedo","executeElementUpdate","executeAddElement","executeRemoveElement","executeReorderElement","executeCommandBatch","executeCreateArtboard","executeDeleteArtboard","executeUpdateArtboard","clearHistory","useCallback","clearArtboardHistory","_artboardId","useSelectedElement","selectedElement","DEAD_BINDING_GRACE_MS","HOOK_NAME","SETTER_NAME","TARGET_KIND","warned","kindOf","el","TextElement","ImageElement","deadBindingWarning","kind","name","store","tag","dead","target","sameName","kinds","other","hint","named","available","useDeadBindingWarning","isConnected","useEffect","process","key","timer","NOOP","EMPTY","useTextBinding","elementStore","useElementsContext","useCommandContext","textElements","useMemo","setText","newText","cloned","coverCrop","origWidth","origHeight","imageAspectRatio","placeholderAR","frameHeight","frameWidth","cropWidth","cropHeight","useImageBinding","options","fit","setElements","imageElements","origDimsRef","useRef","td","imageElementsRef","executeRef","setElementsRef","setImageUrl","url","currentElements","exec","orig","origW","origH","loadedElement","imgAR","crop","w","h","scale","normalizeHex","color","hex","slotMatches","value","match","elementMatchesColor","c","_a","_b","_c","applyColorToClone","newColor","src","dst","useColorBinding","originalColor","applied","setApplied","useState","prevOriginal","setPrevOriginal","matching","setColor","normalizedNew","useFontBinding","setFontFamily","newFamily","UnifiedFontService","FontAnalyzer","useElementByName","CANVAS_STATE_SCHEMA_VERSION","q","n","quantizeGeometry","serializeStateForServer","state","srcArtboards","elementsByArtboard","idx","ab","serializeElement","srcArtboard","i","type","serializeImageElement","_artboard","rest","hasMasks","fullWidth","fullHeight","visibleWidth","visibleHeight","serialized","DEFAULT_ANCHOR","LAYER_TOKEN_MARKER","isLayerToken","mintLayerToken","elementLayerKind","t","hPart","a","vPart","anchorToCenter","anchor","elW","elH","artW","artH","padding","v","x","y","ar","artAR","fullW","fullH","DesignBuilder","artboard","builder","prefix","imageUrl","opts","text","fontSize","e","textNames","imageNames","EditorError","size","transformData","design","layerKindOfConfig","TEMPLATE_FIELDS_KEY","templateFieldSchema","z.discriminatedUnion","z.object","z.literal","z.string","z.number","z.union","z.array","readTemplateFields","raw","fields","entry","candidate","parsed","withTemplateFields"],"mappings":"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"}
package/dist/testing.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import { e as U, g as I, a as R } from "./editorErrors-DbIEtCTt.js";
1
+ import { e as U, g as I, a as R } from "./editorErrors-BY4Pdg7a.js";
2
2
  import { jsx as a } from "react/jsx-runtime";
3
3
  import { useState as d } from "react";
4
- import { E as s } from "./EditorContext-CyanYzo2.js";
4
+ import { E as s } from "./EditorContext-CGkfmbgo.js";
5
5
  import { T as m } from "./ThemeContext-Rjz25x9X.js";
6
- import { H as u, A as h, g, I as A } from "./HybridHistoryManager-Bht98XRv.js";
6
+ import { E as u, A as h, g, I as E } from "./FontAnalyzer-CLv7_msL.js";
7
7
  const M = ({
8
8
  children: t,
9
9
  initialElements: e = [],
@@ -61,7 +61,7 @@ function p(t = "custom", e = {}) {
61
61
  });
62
62
  }
63
63
  function k(t = {}) {
64
- return new A({
64
+ return new E({
65
65
  imageUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
66
66
  imageAspectRatio: 1,
67
67
  x: 100,
@@ -81,7 +81,7 @@ function v(t = {}) {
81
81
  ...t
82
82
  });
83
83
  }
84
- async function E(t, e = 5e3, n = 100) {
84
+ async function A(t, e = 5e3, n = 100) {
85
85
  const r = Date.now();
86
86
  for (; !t(); ) {
87
87
  if (Date.now() - r > e)
@@ -91,7 +91,7 @@ async function E(t, e = 5e3, n = 100) {
91
91
  }
92
92
  async function C(t, e = 5e3) {
93
93
  let n = null;
94
- return await E(() => !1, e), n;
94
+ return await A(() => !1, e), n;
95
95
  }
96
96
  function T(t, e = {}) {
97
97
  const n = new KeyboardEvent("keydown", {
@@ -139,7 +139,7 @@ export {
139
139
  K as getElementBounds,
140
140
  R as resetContainedEditorErrors,
141
141
  T as simulateKeyPress,
142
- E as waitFor,
142
+ A as waitFor,
143
143
  C as waitForExport
144
144
  };
145
145
  //# sourceMappingURL=testing.mjs.map
@@ -5,7 +5,6 @@ export declare class ArchTransform extends TextElement {
5
5
  constructor(config?: Partial<ArchElementConfig>);
6
6
  getBoundingBox(): BoundingBox;
7
7
  getVisualBoundingBox(): BoundingBox;
8
- render(ctx: CanvasRenderingContext2D, _isSelected?: boolean, _isHovered?: boolean): void;
9
8
  resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void;
10
9
  getEnabledAnchors(): ResizeAnchor[];
11
10
  toJSON(): ArchElementConfig;
@@ -5,7 +5,6 @@ export declare class AscendTransform extends TextElement {
5
5
  constructor(config?: Partial<AscendElementConfig>);
6
6
  getBoundingBox(): BoundingBox;
7
7
  getVisualBoundingBox(): BoundingBox;
8
- render(ctx: CanvasRenderingContext2D, _isSelected?: boolean, _isHovered?: boolean): void;
9
8
  resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void;
10
9
  getEnabledAnchors(): ResizeAnchor[];
11
10
  toJSON(): AscendElementConfig;
@@ -19,7 +19,6 @@ export declare class CircleTransform extends TextElement {
19
19
  * Uses canvas arc path like SVG path in original
20
20
  * Now uses shared rendering function
21
21
  */
22
- render(ctx: CanvasRenderingContext2D, _isSelected?: boolean, _isHovered?: boolean): void;
23
22
  /**
24
23
  * Handle resize
25
24
  * For circle, we maintain uniform scale and keep scale on the transform
@@ -5,7 +5,6 @@ export declare class FlagTransform extends TextElement {
5
5
  constructor(config?: Partial<FlagElementConfig>);
6
6
  getBoundingBox(): BoundingBox;
7
7
  getVisualBoundingBox(): BoundingBox;
8
- render(ctx: CanvasRenderingContext2D, _isSelected?: boolean, _isHovered?: boolean): void;
9
8
  resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void;
10
9
  getEnabledAnchors(): ResizeAnchor[];
11
10
  toJSON(): FlagElementConfig;
@@ -5,7 +5,6 @@ export declare class LeanTransform extends TextElement {
5
5
  constructor(config?: Partial<LeanElementConfig>);
6
6
  getBoundingBox(): BoundingBox;
7
7
  getVisualBoundingBox(): BoundingBox;
8
- render(ctx: CanvasRenderingContext2D, _isSelected?: boolean, _isHovered?: boolean): void;
9
8
  resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void;
10
9
  getEnabledAnchors(): ResizeAnchor[];
11
10
  toJSON(): LeanElementConfig;
@@ -5,7 +5,6 @@ export declare class WaveTransform extends TextElement {
5
5
  constructor(config?: Partial<WaveElementConfig>);
6
6
  getBoundingBox(): BoundingBox;
7
7
  getVisualBoundingBox(): BoundingBox;
8
- render(ctx: CanvasRenderingContext2D, _isSelected?: boolean, _isHovered?: boolean): void;
9
8
  resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void;
10
9
  getEnabledAnchors(): ResizeAnchor[];
11
10
  /**
@@ -1,4 +1,4 @@
1
- import { y as r } from "./HybridHistoryManager-Bht98XRv.js";
1
+ import { y as r } from "./FontAnalyzer-CLv7_msL.js";
2
2
  import { useState as a, useEffect as d } from "react";
3
3
  const u = "https://api.snowcone.app";
4
4
  function h(e) {
@@ -58,4 +58,4 @@ export {
58
58
  l as p,
59
59
  p as u
60
60
  };
61
- //# sourceMappingURL=usePieceGuideBundle-CGERAQJl.js.map
61
+ //# sourceMappingURL=usePieceGuideBundle-C1ipFnZd.js.map