@snowcone-app/canvas 0.16.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{CanvasStateV1-BQD8fCBU.js → CanvasStateV1-C6i6vU6p.js} +63 -63
- package/dist/{CanvasStateV1-BQD8fCBU.js.map → CanvasStateV1-C6i6vU6p.js.map} +1 -1
- package/dist/EditorCanvasImpl-C8SsT3C2.js +68 -0
- package/dist/EditorCanvasImpl-C8SsT3C2.js.map +1 -0
- package/dist/EditorContext-CyanYzo2.js +1131 -0
- package/dist/EditorContext-CyanYzo2.js.map +1 -0
- package/dist/{ImportManager-eotl7r5d.js → ElementFactory-C05JJkRp.js} +1510 -5121
- package/dist/ElementFactory-C05JJkRp.js.map +1 -0
- package/dist/{HybridHistoryManager-NL3zyEl4.js → HybridHistoryManager-Bht98XRv.js} +98 -98
- package/dist/{HybridHistoryManager-NL3zyEl4.js.map → HybridHistoryManager-Bht98XRv.js.map} +1 -1
- package/dist/ImportManager-kRYfIX-D.js +3617 -0
- package/dist/ImportManager-kRYfIX-D.js.map +1 -0
- package/dist/SnowconeCanvas-C3eZ9PP8.js +1047 -0
- package/dist/SnowconeCanvas-C3eZ9PP8.js.map +1 -0
- package/dist/ThemeContext-Rjz25x9X.js +75 -0
- package/dist/ThemeContext-Rjz25x9X.js.map +1 -0
- package/dist/UnifiedFontService-BAtqqkU4.js +1478 -0
- package/dist/UnifiedFontService-BAtqqkU4.js.map +1 -0
- package/dist/advanced.mjs +210 -207
- package/dist/advanced.mjs.map +1 -1
- package/dist/api/embed.d.ts +8 -4
- package/dist/api/testing.d.ts +1 -0
- package/dist/components/editor/EditorBuy.d.ts +49 -0
- package/dist/components/editor/EditorCanvas.d.ts +30 -0
- package/dist/components/editor/EditorCanvasImpl.d.ts +33 -0
- package/dist/components/editor/EditorPreview.d.ts +39 -0
- package/dist/components/editor/EditorRoot.d.ts +86 -0
- package/dist/components/editor/EditorTextField.d.ts +22 -0
- package/dist/components/editor/RootInternals.d.ts +77 -0
- package/dist/components/editor/designStateStore.d.ts +45 -0
- package/dist/components/editor/index.d.ts +23 -0
- package/dist/components/embed-realtime/RealtimeCanvas.d.ts +1 -1
- package/dist/components/embed-realtime/RealtimeEmbed.d.ts +5 -78
- package/dist/components/embed-realtime/RealtimeMockup.d.ts +5 -34
- package/dist/components/embed-realtime/context.d.ts +12 -2
- package/dist/components/embed-realtime/validateEmbed.d.ts +17 -3
- package/dist/design/design-builder.d.ts +81 -6
- package/dist/editorError-DMENTZ-3.js +19 -0
- package/dist/editorError-DMENTZ-3.js.map +1 -0
- package/dist/editorErrors-DbIEtCTt.js +29 -0
- package/dist/editorErrors-DbIEtCTt.js.map +1 -0
- package/dist/embed-B5kewSQY.js +738 -0
- package/dist/embed-B5kewSQY.js.map +1 -0
- package/dist/embed.mjs +38 -230
- package/dist/embed.mjs.map +1 -1
- package/dist/errors/editorError.d.ts +57 -0
- package/dist/iconify-D6eVIEy4.js +1089 -0
- package/dist/iconify-D6eVIEy4.js.map +1 -0
- package/dist/index.mjs +48 -46
- package/dist/index.mjs.map +1 -1
- package/dist/internals.mjs +105 -104
- package/dist/internals.mjs.map +1 -1
- package/dist/template-fields-GOM4MNfq.js +643 -0
- package/dist/template-fields-GOM4MNfq.js.map +1 -0
- package/dist/testing/editorErrors.d.ts +30 -0
- package/dist/testing.mjs +28 -23
- package/dist/testing.mjs.map +1 -1
- package/dist/{usePieceGuideBundle-Cyl695jv.js → usePieceGuideBundle-CGERAQJl.js} +2 -2
- package/dist/{usePieceGuideBundle-Cyl695jv.js.map → usePieceGuideBundle-CGERAQJl.js.map} +1 -1
- package/dist/validation-DEd6EOCP.js +19947 -0
- package/dist/validation-DEd6EOCP.js.map +1 -0
- package/dist/workers/export-worker.bundle.string.d.ts +1 -1
- package/package.json +12 -4
- package/dist/ImportManager-eotl7r5d.js.map +0 -1
- package/dist/ThemeContext-D9AaUw9v.js +0 -1200
- package/dist/ThemeContext-D9AaUw9v.js.map +0 -1
- package/dist/template-fields-PcSPApl5.js +0 -1632
- package/dist/template-fields-PcSPApl5.js.map +0 -1
- package/dist/validation-BpEod0Rn.js +0 -22953
- package/dist/validation-BpEod0Rn.js.map +0 -1
package/dist/advanced.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"advanced.mjs","sources":["../src/components/primitives/Panel.tsx","../src/components/primitives/ControlGroup.tsx","../src/components/primitives/ButtonGroup.tsx","../src/components/primitives/Toggle.tsx","../src/components/primitives/Switch.tsx","../src/components/primitives/SecondaryToolbar.tsx","../src/kits/serialization.ts","../src/hooks/useProjectLoader.ts","../src/hooks/useBreakpoint.ts","../src/hooks/usePerformance.ts","../src/hooks/useElementById.ts","../src/hooks/useLayerDragDrop.ts","../src/hooks/useLayerSelection.ts","../src/components/FontSizeDropdown.tsx","../src/components/text-toolbar/FontSizeGroup.tsx","../src/plugins/ElementTypePlugin.ts","../src/core/EventBus.ts","../src/hooks/useCanvasEvents.ts","../src/components/VisualGuideOverlay.tsx","../src/api/advanced.ts"],"sourcesContent":["/**\n * Panel - Collapsible panel with optional enable/disable toggle\n *\n * A reusable panel component that eliminates boilerplate for property panels.\n * Supports:\n * - Simple panel (no toggle, no expand)\n * - Collapsible panel (no toggle, with expand/collapse)\n * - Toggle panel (with enable/disable toggle and expand/collapse)\n *\n * @example\n * // Simple panel\n * <Panel title=\"Settings\">\n * <p>Panel content</p>\n * </Panel>\n *\n * @example\n * // Collapsible panel\n * <Panel title=\"Settings\" collapsible expanded={expanded} onExpandChange={setExpanded}>\n * <p>Panel content</p>\n * </Panel>\n *\n * @example\n * // Toggle panel (enable/disable + expand/collapse)\n * <Panel\n * title=\"Stroke\"\n * enableable\n * enabled={enabled}\n * onEnabledChange={setEnabled}\n * expanded={expanded}\n * onExpandChange={setExpanded}\n * >\n * <p>Panel content (only shown when enabled and expanded)</p>\n * </Panel>\n */\n\nimport React from 'react';\n\nexport interface PanelProps {\n /** Panel title */\n title: string;\n\n /** Panel icon (optional) */\n icon?: React.ReactNode;\n\n /** Panel content */\n children: React.ReactNode;\n\n /** Enable toggle functionality (checkbox to enable/disable) */\n enableable?: boolean;\n\n /** Enabled state (only used if enableable=true) */\n enabled?: boolean;\n\n /** Callback when enabled state changes */\n onEnabledChange?: (enabled: boolean) => void;\n\n /** Enable expand/collapse functionality */\n collapsible?: boolean;\n\n /** Expanded state (only used if collapsible=true or enableable=true) */\n expanded?: boolean;\n\n /** Callback when expanded state changes */\n onExpandChange?: (expanded: boolean) => void;\n\n /** Additional CSS classes */\n className?: string;\n}\n\nexport const Panel: React.FC<PanelProps> = ({\n title,\n icon,\n children,\n enableable = false,\n enabled = false,\n onEnabledChange,\n collapsible = false,\n expanded = true,\n onExpandChange,\n className = '',\n}) => {\n // Determine if content should be shown\n const showContent = enableable ? enabled && expanded : collapsible ? expanded : true;\n\n // Determine if expand button should be shown\n const showExpandButton = (enableable && enabled) || collapsible;\n\n const handleToggle = () => {\n if (onEnabledChange) {\n onEnabledChange(!enabled);\n }\n };\n\n const handleExpandToggle = () => {\n if (onExpandChange) {\n onExpandChange(!expanded);\n }\n };\n\n return (\n <div className={`sc:rounded-xl sc:border sc:border-border-primary sc:bg-bg-secondary sc:p-xl ${className}`}>\n {/* Header */}\n <div className=\"sc:flex sc:items-center sc:justify-between\">\n {/* Left side: toggle + title */}\n <div className=\"sc:flex sc:items-center sc:gap-md\">\n {enableable && (\n <label className=\"sc:flex sc:cursor-pointer sc:items-center\">\n <input\n type=\"checkbox\"\n checked={enabled}\n onChange={handleToggle}\n className=\"sc:h-4 sc:w-4 sc:cursor-pointer sc:rounded sc:border-border-primary sc:bg-bg-input sc:accent-accent-primary\"\n />\n </label>\n )}\n <div className=\"sc:flex sc:items-center sc:gap-md\">\n {icon && <span className=\"sc:flex sc:items-center sc:text-text-secondary\">{icon}</span>}\n <h3 className=\"sc:text-xl sc:font-semibold sc:text-text-primary\">{title}</h3>\n </div>\n </div>\n\n {/* Right side: expand button */}\n {showExpandButton && (\n <button\n onClick={handleExpandToggle}\n className=\"sc:rounded-lg sc:border-none sc:bg-transparent sc:px-md sc:py-xs sc:text-base sc:text-text-muted sc:transition-colors sc:duration-fast sc:hover:text-text-primary\"\n title={expanded ? 'Collapse' : 'Expand'}\n >\n {expanded ? '▼' : '▶'}\n </button>\n )}\n </div>\n\n {/* Content */}\n {showContent && <div className=\"sc:mt-xl sc:flex sc:flex-col sc:gap-xl\">{children}</div>}\n </div>\n );\n};\n\nexport default Panel;\n","/**\n * ControlGroup - Label + control wrapper with consistent spacing\n *\n * Provides a consistent layout for form controls with labels.\n * Eliminates repeated label + wrapper markup.\n *\n * @example\n * <ControlGroup label=\"Width\">\n * <input type=\"range\" ... />\n * </ControlGroup>\n *\n * @example\n * <ControlGroup label=\"Color\" inline>\n * <ColorPickerDropdown ... />\n * </ControlGroup>\n */\n\nimport React from 'react';\n\nexport interface ControlGroupProps {\n /** Label text */\n label: string;\n\n /** Optional label suffix (e.g., value display \"50px\") */\n labelSuffix?: React.ReactNode;\n\n /** Control element(s) */\n children: React.ReactNode;\n\n /** Inline layout (label and control side-by-side) */\n inline?: boolean;\n\n /** Additional CSS classes */\n className?: string;\n}\n\nexport const ControlGroup: React.FC<ControlGroupProps> = ({\n label,\n labelSuffix,\n children,\n inline = false,\n className = '',\n}) => {\n return (\n <div className={`sc:flex ${inline ? 'sc:items-center sc:justify-between' : 'sc:flex-col'} sc:gap-sm ${className}`}>\n {/* Label */}\n <label className=\"sc:flex sc:items-center sc:justify-between sc:text-base sc:font-medium sc:text-text-secondary\">\n <span>{label}</span>\n {labelSuffix && <span className=\"sc:font-semibold sc:text-accent-primary\">{labelSuffix}</span>}\n </label>\n\n {/* Control */}\n <div className={inline ? 'sc:flex-shrink-0' : ''}>{children}</div>\n </div>\n );\n};\n\nexport default ControlGroup;\n","/**\n * ButtonGroup - Group of buttons with active state\n *\n * A reusable button group for selecting one option from multiple choices.\n * Eliminates 10-15 lines of repeated button group markup.\n *\n * @example\n * <ButtonGroup\n * options={[\n * { value: 'butt', label: 'Butt', tooltip: 'Flat edges' },\n * { value: 'round', label: 'Round', tooltip: 'Rounded edges' },\n * { value: 'square', label: 'Square', tooltip: 'Extended square edges' }\n * ]}\n * value={lineCap}\n * onChange={setLineCap}\n * />\n */\n\nimport React from 'react';\n\nexport interface ButtonGroupOption<T = string> {\n /** Option value */\n value: T;\n\n /** Display label */\n label: string;\n\n /** Optional icon */\n icon?: React.ReactNode;\n\n /** Optional tooltip */\n tooltip?: string;\n\n /** Disabled state */\n disabled?: boolean;\n}\n\nexport interface ButtonGroupProps<T = string> {\n /** Available options */\n options: ButtonGroupOption<T>[];\n\n /** Current value */\n value: T;\n\n /** Callback when value changes */\n onChange: (value: T) => void;\n\n /** Full width buttons */\n fullWidth?: boolean;\n\n /** Additional CSS classes */\n className?: string;\n}\n\nexport function ButtonGroup<T = string>({\n options,\n value,\n onChange,\n fullWidth = true,\n className = '',\n}: ButtonGroupProps<T>) {\n return (\n <div className={`sc:flex sc:gap-sm ${className}`}>\n {options.map((option) => {\n const isActive = option.value === value;\n\n return (\n <button\n key={String(option.value)}\n onClick={() => !option.disabled && onChange(option.value)}\n disabled={option.disabled}\n title={option.tooltip}\n className={` ${fullWidth ? 'sc:flex-1' : ''} sc:flex sc:items-center sc:justify-center sc:gap-xs sc:rounded-lg sc:border sc:px-md sc:py-sm sc:text-base sc:font-medium sc:transition-all sc:duration-fast ${\n isActive\n ? 'sc:border-accent-primary sc:bg-accent-primary sc:text-text-on-accent'\n : 'sc:border-border-primary sc:bg-bg-tertiary sc:text-text-secondary sc:hover:border-border-secondary sc:hover:bg-bg-hover sc:hover:text-text-primary'\n } ${option.disabled ? 'sc:cursor-not-allowed sc:opacity-disabled' : 'sc:cursor-pointer'} `}\n >\n {option.icon && <span className=\"sc:flex sc:items-center\">{option.icon}</span>}\n <span>{option.label}</span>\n </button>\n );\n })}\n </div>\n );\n}\n\nexport default ButtonGroup;\n","/**\n * Toggle - Styled checkbox for enable/disable controls\n *\n * A consistent toggle/checkbox component with label.\n * Eliminates 5-10 lines of repeated toggle markup.\n *\n * @example\n * <Toggle\n * label=\"Enable Stroke\"\n * checked={enabled}\n * onChange={setEnabled}\n * />\n *\n * @example\n * <Toggle\n * label=\"Flip Horizontal\"\n * checked={flipH}\n * onChange={setFlipH}\n * icon={<LuFlipHorizontal2 />}\n * />\n */\n\nimport React from 'react';\n\nexport interface ToggleProps {\n /** Label text */\n label: string;\n\n /** Optional icon */\n icon?: React.ReactNode;\n\n /** Checked state */\n checked: boolean;\n\n /** Callback when checked state changes */\n onChange: (checked: boolean) => void;\n\n /** Disabled state */\n disabled?: boolean;\n\n /** Additional CSS classes */\n className?: string;\n}\n\nexport const Toggle: React.FC<ToggleProps> = ({ label, icon, checked, onChange, disabled = false, className = '' }) => {\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n onChange(e.target.checked);\n };\n\n return (\n <label\n className={`sc:flex sc:cursor-pointer sc:items-center sc:gap-md ${disabled ? 'sc:cursor-not-allowed sc:opacity-disabled' : ''} ${className}`}\n >\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={handleChange}\n disabled={disabled}\n className=\"sc:h-4 sc:w-4 sc:cursor-pointer sc:rounded sc:border-border-primary sc:bg-bg-input sc:accent-accent-primary sc:disabled:cursor-not-allowed\"\n />\n <span className=\"sc:flex sc:items-center sc:gap-sm sc:text-base sc:font-medium sc:text-text-primary\">\n {icon && <span className=\"sc:flex sc:items-center sc:text-text-secondary\">{icon}</span>}\n <span>{label}</span>\n </span>\n </label>\n );\n};\n\nexport default Toggle;\n","/**\n * Switch - Toggle switch component\n *\n * Features:\n * - Accessible checkbox input\n * - Smooth animation\n * - Fixed border radius (not affected by theme)\n * - Support for labels\n *\n * Usage:\n * ```tsx\n * <Switch\n * checked={isEnabled}\n * onChange={setIsEnabled}\n * label=\"Enable feature\"\n * />\n * ```\n */\n\nimport React from 'react';\n\nexport interface SwitchProps {\n checked: boolean;\n onChange: (checked: boolean) => void;\n label?: string;\n disabled?: boolean;\n className?: string;\n}\n\nconst Switch: React.FC<SwitchProps> = ({\n checked,\n onChange,\n label,\n disabled = false,\n className = '',\n}) => {\n const handleChange = () => {\n if (!disabled) {\n onChange(!checked);\n }\n };\n\n return (\n <label\n className={`sc:flex sc:items-center sc:gap-md sc:cursor-pointer sc:select-none ${disabled ? 'sc:opacity-50 sc:cursor-not-allowed' : ''} ${className}`.trim()}\n >\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={handleChange}\n disabled={disabled}\n className=\"sc:absolute sc:opacity-0 sc:w-0 sc:h-0 sc:peer\"\n />\n <span\n className={`\n sc:relative sc:inline-block sc:w-9 sc:h-5 sc:rounded-[999px] sc:transition-colors\n ${checked ? 'sc:bg-accent-primary' : 'sc:bg-bg-tertiary'}\n sc:after:content-[''] sc:after:absolute sc:after:top-[2px] sc:after:left-[2px]\n sc:after:w-4 sc:after:h-4 sc:after:bg-white sc:after:rounded-full\n sc:after:transition-transform sc:after:shadow-md\n ${checked ? 'sc:after:translate-x-4' : ''}\n `.trim()}\n />\n {label && <span className=\"sc:text-md sc:text-text-primary sc:font-medium\">{label}</span>}\n </label>\n );\n};\n\nexport default Switch;\n","/**\n * SecondaryToolbar - Overflow toolbar for uncommonly used controls\n *\n * Renders as a proper toolbar positioned below the primary toolbar,\n * right-aligned with a gap above it. Used for 3-dot overflow menus.\n *\n * Features:\n * - Same height and styling as primary toolbar\n * - Positioned below trigger, right-aligned\n * - Gap between primary and secondary toolbar\n * - Click-outside handling via Dropdown primitive\n *\n * Usage:\n * ```tsx\n * <SecondaryToolbar\n * isOpen={showMore}\n * onToggle={setShowMore}\n * trigger={<Button variant=\"toolbar\" icon={<DotsIcon />} />}\n * >\n * <Button variant=\"toolbar\" icon={<Icon />} onClick={handler} />\n * <Switch checked={value} onChange={handler} label=\"Label\" />\n * </SecondaryToolbar>\n * ```\n */\n\nimport React from 'react';\nimport { Icon } from '@iconify/react';\nimport { Button, Tooltip, TooltipTrigger, TooltipContent } from '../ui';\nimport { Dropdown } from './index';\n\nexport interface SecondaryToolbarProps {\n isOpen: boolean;\n onToggle: (open: boolean) => void;\n trigger?: React.ReactNode;\n children: React.ReactNode;\n}\n\nconst SecondaryToolbar: React.FC<SecondaryToolbarProps> = ({\n isOpen,\n onToggle,\n trigger,\n children,\n}) => {\n // Default trigger: 3-dot icon button with tooltip\n // The Button has its own onClick to ensure clicks are captured even when\n // wrapped by Radix's TooltipTrigger which can interfere with event bubbling\n const defaultTrigger = (\n <Tooltip delayDuration={0}>\n <TooltipTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"toolbar\"\n onClick={(e) => {\n // Stop propagation to prevent Dropdown's wrapper div from double-toggling\n e.stopPropagation();\n onToggle(!isOpen);\n }}\n >\n <Icon icon=\"lucide:ellipsis-vertical\" className=\"sc:size-6\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent>\n <p>More</p>\n </TooltipContent>\n </Tooltip>\n );\n\n return (\n <Dropdown\n isOpen={isOpen}\n onToggle={onToggle}\n trigger={trigger || defaultTrigger}\n position=\"bottom\"\n align=\"end\"\n offset={4}\n >\n {/* Secondary toolbar - matches primary toolbar styling */}\n <div className=\"sc:flex sc:items-center sc:gap-sm sc:bg-bg-primary sc:border sc:border-border-primary sc:rounded-xl sc:px-xl sc:py-md sc:shadow-sm sc:h-[52px]\">\n {children}\n </div>\n </Dropdown>\n );\n};\n\nexport default SecondaryToolbar;\n","/**\n * Kit Serialization - JSON import/export for kit definitions\n *\n * Provides functions to serialize a KitDefinition to a JSON-safe format\n * and to load a KitDefinition from JSON. Supports extending base presets\n * via the `extends` field, making it easy to persist and share kit\n * configurations.\n *\n * @example Serialize a kit to JSON\n * ```ts\n * import { serializeKit, COMPACT_CUSTOMIZER } from '@snowcone-app/canvas';\n *\n * const json = serializeKit(COMPACT_CUSTOMIZER);\n * const stored = JSON.stringify(json);\n * ```\n *\n * @example Load a kit from JSON\n * ```ts\n * import { loadKitFromJSON } from '@snowcone-app/canvas';\n *\n * const kit = loadKitFromJSON({\n * name: 'my-custom-kit',\n * extends: 'compact-customizer',\n * capabilities: { effects: ['stroke', 'mask'] },\n * behavior: { autoExport: true },\n * });\n * ```\n */\n\nimport type { EditorCapabilities } from '../types/capabilities.js';\nimport type { KitDefinition, KitPresetId } from './types.js';\nimport { extendKit, createKit } from './compose.js';\nimport { resolveKit } from './registry.js';\nimport { validateKit } from './validation.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * JSON-safe representation of a KitDefinition.\n *\n * When `extends` is provided, the kit is built by resolving the base\n * preset and applying the remaining fields as overrides. When `extends`\n * is omitted, a new kit is created from scratch using `createKit`.\n */\nexport interface KitJSON {\n /** Kit name (required) */\n name: string;\n /** Optional base preset to extend from */\n extends?: KitPresetId;\n /** Partial capability overrides */\n capabilities?: Partial<EditorCapabilities>;\n /** Partial behavior overrides */\n behavior?: Partial<KitDefinition['behavior']>;\n /** Partial layout overrides */\n layout?: Partial<KitDefinition['layout']>;\n /** Partial style overrides */\n style?: Partial<KitDefinition['style']>;\n /** Content configuration */\n content?: KitDefinition['content'];\n}\n\n// ---------------------------------------------------------------------------\n// Serialization\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize a KitDefinition to a JSON-safe format.\n *\n * The resulting object can be safely passed to `JSON.stringify()` and\n * later restored with `loadKitFromJSON()`. All fields from the kit\n * are included in the output for a complete, self-contained representation.\n *\n * @param kit - The KitDefinition to serialize\n * @returns A JSON-safe KitJSON object\n *\n * @example\n * ```ts\n * const json = serializeKit(myKit);\n * localStorage.setItem('kit', JSON.stringify(json));\n * ```\n */\nexport function serializeKit(kit: KitDefinition): KitJSON {\n const json: KitJSON = {\n name: kit.name,\n capabilities: {\n elements: [...kit.capabilities.elements],\n transforms: [...kit.capabilities.transforms],\n effects: [...kit.capabilities.effects],\n panels: [...kit.capabilities.panels],\n tools: [...kit.capabilities.tools],\n features: { ...kit.capabilities.features },\n },\n behavior: { ...kit.behavior },\n layout: {\n slots: {\n ...(kit.layout.slots.topbar && { topbar: [...kit.layout.slots.topbar] }),\n ...(kit.layout.slots.left && { left: [...kit.layout.slots.left] }),\n ...(kit.layout.slots.canvas && { canvas: [...kit.layout.slots.canvas] }),\n ...(kit.layout.slots.right && { right: [...kit.layout.slots.right] }),\n ...(kit.layout.slots.bottombar && { bottombar: [...kit.layout.slots.bottombar] }),\n },\n },\n style: { ...kit.style },\n };\n\n if (kit.content) {\n json.content = { ...kit.content };\n }\n\n return json;\n}\n\n// ---------------------------------------------------------------------------\n// Deserialization\n// ---------------------------------------------------------------------------\n\n/**\n * Load a KitDefinition from a JSON representation.\n *\n * If `extends` is provided, the corresponding preset is resolved and the\n * remaining fields are applied as overrides via `extendKit`. If `extends`\n * is omitted, a new kit is created from scratch via `createKit`.\n *\n * The result is validated with `validateKit`. If validation produces\n * errors (fatal issues), an error is thrown. Warnings are logged to\n * the console but do not prevent loading.\n *\n * @param json - The KitJSON object to deserialize\n * @returns A complete, validated KitDefinition\n * @throws {Error} If the kit name is missing\n * @throws {Error} If a preset ID in `extends` is invalid\n * @throws {Error} If the resulting kit fails validation (has errors)\n *\n * @example Extend a preset\n * ```ts\n * const kit = loadKitFromJSON({\n * name: 'my-pro-variant',\n * extends: 'pro-studio',\n * behavior: { autoExport: true },\n * });\n * ```\n *\n * @example Create from scratch\n * ```ts\n * const kit = loadKitFromJSON({\n * name: 'minimal-editor',\n * capabilities: { elements: ['text'] },\n * behavior: { toolbar: 'none', selection: 'single', panelMode: 'inline', onEmptyClick: 'noop', autoExport: false },\n * });\n * ```\n */\nexport function loadKitFromJSON(json: KitJSON): KitDefinition {\n if (!json.name || json.name.trim() === '') {\n throw new Error('[loadKitFromJSON] Kit name is required');\n }\n\n // Build overrides object from the JSON fields (excluding name and extends)\n const overrides: Record<string, unknown> = {};\n if (json.capabilities) overrides.capabilities = json.capabilities;\n if (json.behavior) overrides.behavior = json.behavior;\n if (json.layout) overrides.layout = json.layout;\n if (json.style) overrides.style = json.style;\n if (json.content) overrides.content = json.content;\n\n let kit: KitDefinition;\n\n if (json.extends) {\n // Resolve the base preset and apply overrides\n const base = resolveKit(json.extends);\n kit = extendKit(base, { name: json.name, ...overrides });\n } else {\n // Create from scratch with defaults\n kit = createKit({ name: json.name, ...overrides });\n }\n\n // Validate the result\n const validation = validateKit(kit);\n\n if (validation.warnings.length > 0) {\n for (const warning of validation.warnings) {\n console.warn(`[loadKitFromJSON] Warning: ${warning}`);\n }\n }\n\n if (!validation.valid) {\n throw new Error(\n `[loadKitFromJSON] Kit \"${json.name}\" failed validation:\\n${validation.errors.join('\\n')}`\n );\n }\n\n return kit;\n}\n","/**\n * useProjectLoader - Hook for loading saved projects programmatically\n * Supports loading from JSON, files, and templates\n */\n\nimport { useState, useCallback } from 'react';\nimport { useEditor } from '../contexts/EditorContext.js';\nimport { ImportManager } from '../utils/ImportManager.js';\nimport { createLogger } from '../utils/logger.js';\nimport type { CanvasDocument } from '../utils/ExportManager.js';\nimport type { AnyElementConfig } from '../types/index.js';\n\nconst logger = createLogger('useProjectLoader');\n\nexport interface ProjectTemplate {\n name: string;\n description?: string;\n artboards: Array<{\n name: string;\n width: number;\n height: number;\n backgroundColor: string;\n elements?: AnyElementConfig[];\n }>;\n}\n\nexport interface ProjectMetadata {\n version: string;\n timestamp: string;\n artboardCount: number;\n elementCount: number;\n}\n\nexport interface UseProjectLoaderReturn {\n // Load from JSON\n loadFromJSON: (json: string | CanvasDocument) => Promise<void>;\n\n // Load from file\n loadFromFile: (file: File) => Promise<void>;\n\n // Create from template\n loadTemplate: (template: ProjectTemplate) => Promise<void>;\n\n // State\n isLoading: boolean;\n loadError: Error | null;\n currentProject: CanvasDocument | null;\n\n // Metadata\n projectMetadata: ProjectMetadata | null;\n}\n\n/**\n * Hook for loading saved projects\n * Must be used within EditorProvider\n *\n * @example Load from API\n * ```tsx\n * function ProductCustomizer({ productId }: { productId: string }) {\n * const { loadFromJSON, isLoading } = useProjectLoader();\n *\n * useEffect(() => {\n * fetch(`/api/designs/${productId}`)\n * .then(res => res.json())\n * .then(design => loadFromJSON(design))\n * .catch(console.error);\n * }, [productId]);\n *\n * if (isLoading) return <div>Loading design...</div>;\n *\n * return <Canvas />;\n * }\n * ```\n *\n * @example Load from template\n * ```tsx\n * function TemplateGallery() {\n * const { loadTemplate } = useProjectLoader();\n *\n * const handleSelectTemplate = (template: ProjectTemplate) => {\n * loadTemplate(template);\n * };\n *\n * return <div>Template gallery...</div>;\n * }\n * ```\n */\nexport function useProjectLoader(): UseProjectLoaderReturn {\n const {\n handleLoadWorkspace,\n } = useEditor();\n\n const [isLoading, setIsLoading] = useState(false);\n const [loadError, setLoadError] = useState<Error | null>(null);\n const [currentProject, setCurrentProject] = useState<CanvasDocument | null>(null);\n const [projectMetadata, setProjectMetadata] = useState<ProjectMetadata | null>(null);\n\n /**\n * Load from JSON string or CanvasDocument object\n */\n const loadFromJSON = useCallback(async (json: string | CanvasDocument) => {\n setIsLoading(true);\n setLoadError(null);\n\n try {\n // Parse JSON if it's a string\n const document: CanvasDocument = typeof json === 'string' ? JSON.parse(json) : json;\n\n // Validate document structure\n if (!document.artboards || !Array.isArray(document.artboards)) {\n throw new Error('Invalid document format: missing artboards array');\n }\n\n // Import the document using ImportManager\n // Note: We need to create a JSON string and then parse it\n const jsonString = JSON.stringify(document);\n const blob = new Blob([jsonString], { type: 'application/json' });\n const file = new File([blob], 'project.json', { type: 'application/json' });\n\n const result = await ImportManager.importFromJSON(file);\n\n if (!result.success || !result.artboards || !result.elements) {\n throw new Error(result.error || 'Failed to import project');\n }\n\n // Load into editor\n handleLoadWorkspace(result.artboards, result.elements, result.activeArtboardId || null);\n\n // Update state\n setCurrentProject(document);\n\n // Calculate metadata\n const totalElements = document.artboards.reduce((sum, ab) => sum + (ab.elements?.length || 0), 0);\n setProjectMetadata({\n version: document.metadata.version,\n timestamp: document.metadata.timestamp,\n artboardCount: document.artboards.length,\n elementCount: totalElements,\n });\n\n } catch (error) {\n logger.error('[useProjectLoader] Failed to load project:', error);\n setLoadError(error as Error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n }, [handleLoadWorkspace]);\n\n /**\n * Load from file (File API)\n */\n const loadFromFile = useCallback(async (file: File) => {\n setIsLoading(true);\n setLoadError(null);\n\n try {\n // Read file as text\n const text = await file.text();\n\n // Load from JSON\n await loadFromJSON(text);\n } catch (error) {\n logger.error('[useProjectLoader] Failed to load file:', error);\n setLoadError(error as Error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n }, [loadFromJSON]);\n\n /**\n * Create from template\n * Templates define artboards with optional default elements\n */\n const loadTemplate = useCallback(async (template: ProjectTemplate) => {\n setIsLoading(true);\n setLoadError(null);\n\n try {\n // Convert template to CanvasDocument format\n const document: CanvasDocument = {\n metadata: {\n version: '2.0.0',\n timestamp: new Date().toISOString(),\n appVersion: '1.0.0',\n },\n artboards: template.artboards.map((artboard, index) => ({\n id: `artboard-${Date.now()}-${index}`,\n name: artboard.name,\n x: 0,\n y: 0,\n width: artboard.width,\n height: artboard.height,\n backgroundColor: artboard.backgroundColor,\n elements: artboard.elements || [],\n })),\n activeArtboardId: null, // First artboard will be active by default\n };\n\n // Load the document\n await loadFromJSON(document);\n\n } catch (error) {\n logger.error('[useProjectLoader] Failed to load template:', error);\n setLoadError(error as Error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n }, [loadFromJSON]);\n\n return {\n loadFromJSON,\n loadFromFile,\n loadTemplate,\n isLoading,\n loadError,\n currentProject,\n projectMetadata,\n };\n}\n","/**\n * useBreakpoint - Hook for responsive breakpoint detection\n * Provides responsive utilities for building adaptive UIs\n *\n * @example\n * ```tsx\n * function ResponsiveLayout() {\n * const { isAtLeast, breakpoint } = useBreakpoint();\n *\n * return (\n * <div>\n * {isAtLeast('md') ? (\n * <div className=\"desktop-layout\">\n * <LayersPanel />\n * <Canvas />\n * <ExportPanel />\n * </div>\n * ) : (\n * <div className=\"mobile-layout\">\n * <Canvas />\n * </div>\n * )}\n * <p>Current breakpoint: {breakpoint}</p>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useState, useEffect, useMemo } from 'react';\n\nexport interface BreakpointConfig {\n xs?: number; // Extra small (default: 320)\n sm?: number; // Small (default: 480)\n md?: number; // Medium (default: 768)\n lg?: number; // Large (default: 1024)\n xl?: number; // Extra large (default: 1280)\n xxl?: number; // Extra extra large (default: 1536)\n}\n\nexport type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';\n\nexport interface UseBreakpointReturn {\n breakpoint: Breakpoint;\n width: number;\n isXs: boolean;\n isSm: boolean;\n isMd: boolean;\n isLg: boolean;\n isXl: boolean;\n isXxl: boolean;\n isAtLeast: (bp: Breakpoint) => boolean;\n isAtMost: (bp: Breakpoint) => boolean;\n}\n\nconst DEFAULT_BREAKPOINTS: Required<BreakpointConfig> = {\n xs: 320,\n sm: 480,\n md: 768,\n lg: 1024,\n xl: 1280,\n xxl: 1536,\n};\n\n/**\n * Get current breakpoint based on window width\n */\nfunction getCurrentBreakpoint(width: number, config: Required<BreakpointConfig>): Breakpoint {\n if (width >= config.xxl) return 'xxl';\n if (width >= config.xl) return 'xl';\n if (width >= config.lg) return 'lg';\n if (width >= config.md) return 'md';\n if (width >= config.sm) return 'sm';\n return 'xs';\n}\n\n/**\n * Hook for responsive breakpoint detection\n */\nexport function useBreakpoint(config?: BreakpointConfig): UseBreakpointReturn {\n const breakpointConfig: Required<BreakpointConfig> = {\n ...DEFAULT_BREAKPOINTS,\n ...config,\n };\n\n const [width, setWidth] = useState<number>(() => {\n if (typeof window !== 'undefined') {\n return window.innerWidth;\n }\n return DEFAULT_BREAKPOINTS.lg; // Default for SSR\n });\n\n // Update width on window resize\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const handleResize = () => {\n setWidth(window.innerWidth);\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, []);\n\n // Calculate current breakpoint\n const breakpoint = useMemo(\n () => getCurrentBreakpoint(width, breakpointConfig),\n [width, breakpointConfig]\n );\n\n // Boolean helpers for each breakpoint\n const isXs = breakpoint === 'xs';\n const isSm = breakpoint === 'sm';\n const isMd = breakpoint === 'md';\n const isLg = breakpoint === 'lg';\n const isXl = breakpoint === 'xl';\n const isXxl = breakpoint === 'xxl';\n\n // Breakpoint ordering for comparisons\n const breakpointOrder: Breakpoint[] = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];\n\n // Check if current breakpoint is at least the specified breakpoint\n const isAtLeast = (bp: Breakpoint): boolean => {\n const currentIndex = breakpointOrder.indexOf(breakpoint);\n const targetIndex = breakpointOrder.indexOf(bp);\n return currentIndex >= targetIndex;\n };\n\n // Check if current breakpoint is at most the specified breakpoint\n const isAtMost = (bp: Breakpoint): boolean => {\n const currentIndex = breakpointOrder.indexOf(breakpoint);\n const targetIndex = breakpointOrder.indexOf(bp);\n return currentIndex <= targetIndex;\n };\n\n return {\n breakpoint,\n width,\n isXs,\n isSm,\n isMd,\n isLg,\n isXl,\n isXxl,\n isAtLeast,\n isAtMost,\n };\n}\n","/**\n * usePerformance - Hook for performance monitoring\n * Provides metrics for tracking editor performance\n *\n * @example\n * ```tsx\n * function PerformanceMonitor() {\n * const { metrics, startProfiling } = usePerformance();\n *\n * useEffect(() => {\n * startProfiling();\n * }, []);\n *\n * if (metrics.fps < 30) {\n * console.warn('Low FPS:', metrics.fps);\n * }\n *\n * return (\n * <div>\n * <p>FPS: {Math.round(metrics.fps)}</p>\n * <p>Render Time: {metrics.avgRenderTime.toFixed(2)}ms</p>\n * <p>Elements: {metrics.elementCount}</p>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useState, useCallback, useRef, useEffect } from 'react';\n\nexport interface PerformanceMetrics {\n fps: number;\n avgRenderTime: number;\n peakRenderTime: number;\n memoryUsage: number;\n elementCount: number;\n exportCount: number;\n avgExportTime: number;\n}\n\nexport interface UsePerformanceReturn {\n metrics: PerformanceMetrics;\n startProfiling: () => void;\n stopProfiling: () => void;\n clearMetrics: () => void;\n getReport: () => string;\n isProfiling: boolean;\n}\n\nconst DEFAULT_METRICS: PerformanceMetrics = {\n fps: 0,\n avgRenderTime: 0,\n peakRenderTime: 0,\n memoryUsage: 0,\n elementCount: 0,\n exportCount: 0,\n avgExportTime: 0,\n};\n\n/**\n * Hook for performance monitoring\n */\nexport function usePerformance(): UsePerformanceReturn {\n const [metrics, setMetrics] = useState<PerformanceMetrics>(DEFAULT_METRICS);\n const [isProfiling, setIsProfiling] = useState(false);\n\n const frameTimesRef = useRef<number[]>([]);\n const renderTimesRef = useRef<number[]>([]);\n const exportTimesRef = useRef<number[]>([]);\n const lastFrameTimeRef = useRef<number>(performance.now());\n const rafIdRef = useRef<number | null>(null);\n\n // Calculate FPS from frame times\n const calculateFPS = useCallback((frameTimes: number[]): number => {\n if (frameTimes.length === 0) return 0;\n const avg = frameTimes.reduce((sum, time) => sum + time, 0) / frameTimes.length;\n return 1000 / avg; // Convert to FPS\n }, []);\n\n // Calculate average\n const calculateAverage = useCallback((values: number[]): number => {\n if (values.length === 0) return 0;\n return values.reduce((sum, val) => sum + val, 0) / values.length;\n }, []);\n\n // Get memory usage (if available)\n const getMemoryUsage = useCallback((): number => {\n if ('memory' in performance && (performance as { memory?: { usedJSHeapSize: number } }).memory) {\n const memory = (performance as { memory: { usedJSHeapSize: number } }).memory;\n return memory.usedJSHeapSize / (1024 * 1024); // Convert to MB\n }\n return 0;\n }, []);\n\n // Update metrics\n const updateMetrics = useCallback(() => {\n const newMetrics: PerformanceMetrics = {\n fps: calculateFPS(frameTimesRef.current),\n avgRenderTime: calculateAverage(renderTimesRef.current),\n peakRenderTime: Math.max(...renderTimesRef.current, 0),\n memoryUsage: getMemoryUsage(),\n elementCount: 0, // Would need to be updated by editor context\n exportCount: exportTimesRef.current.length,\n avgExportTime: calculateAverage(exportTimesRef.current),\n };\n\n setMetrics(newMetrics);\n }, [calculateFPS, calculateAverage, getMemoryUsage]);\n\n // Frame measurement loop\n const measureFrame = useCallback(() => {\n const now = performance.now();\n const frameTime = now - lastFrameTimeRef.current;\n lastFrameTimeRef.current = now;\n\n // Add frame time\n frameTimesRef.current.push(frameTime);\n if (frameTimesRef.current.length > 60) {\n frameTimesRef.current.shift(); // Keep last 60 frames (1 second at 60fps)\n }\n\n // Update metrics every 30 frames (~500ms at 60fps)\n if (frameTimesRef.current.length % 30 === 0) {\n updateMetrics();\n }\n\n if (isProfiling) {\n rafIdRef.current = requestAnimationFrame(measureFrame);\n }\n }, [isProfiling, updateMetrics]);\n\n // Start profiling\n const startProfiling = useCallback(() => {\n setIsProfiling(true);\n lastFrameTimeRef.current = performance.now();\n rafIdRef.current = requestAnimationFrame(measureFrame);\n }, [measureFrame]);\n\n // Stop profiling\n const stopProfiling = useCallback(() => {\n setIsProfiling(false);\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n rafIdRef.current = null;\n }\n }, []);\n\n // Clear metrics\n const clearMetrics = useCallback(() => {\n frameTimesRef.current = [];\n renderTimesRef.current = [];\n exportTimesRef.current = [];\n setMetrics(DEFAULT_METRICS);\n }, []);\n\n // Get human-readable report\n const getReport = useCallback((): string => {\n return `\nPerformance Report\n==================\nFPS: ${metrics.fps.toFixed(1)}\nAvg Render Time: ${metrics.avgRenderTime.toFixed(2)}ms\nPeak Render Time: ${metrics.peakRenderTime.toFixed(2)}ms\nMemory Usage: ${metrics.memoryUsage.toFixed(1)}MB\nElement Count: ${metrics.elementCount}\nExport Count: ${metrics.exportCount}\nAvg Export Time: ${metrics.avgExportTime.toFixed(2)}ms\n `.trim();\n }, [metrics]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n };\n }, []);\n\n return {\n metrics,\n startProfiling,\n stopProfiling,\n clearMetrics,\n getReport,\n isProfiling,\n };\n}\n","import { useEditor } from '../contexts/EditorContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/**\n * Returns a specific element by its ID, or null if not found.\n *\n * Useful for components that need to display or react to a particular\n * element without subscribing to the full editor state.\n *\n * @param id - The element ID to look up, or null to skip the lookup.\n * @returns The matching element, or null if the ID is null or no element matches.\n *\n * @example\n * ```tsx\n * function ElementInspector({ elementId }: { elementId: string }) {\n * const element = useElementById(elementId);\n * if (!element) return <p>Element not found</p>;\n * return <p>Position: ({element.x}, {element.y})</p>;\n * }\n * ```\n */\nexport function useElementById(id: string | null): EditorElement | null {\n const { getElementById } = useEditor();\n if (!id) return null;\n return getElementById(id) ?? null;\n}\n","/**\n * useLayerDragDrop - Hook for layer drag-drop reordering\n *\n * Extracts drag-drop reordering logic from LayersPanel.tsx into a\n * reusable hook. Handles drag state management, drop position\n * calculation (before/after/into for groups), and dispatches to\n * the appropriate reorder callback.\n *\n * @example\n * ```tsx\n * function LayersList({ elements, onReorder }) {\n * const { draggedId, dragOverId, dropPosition, handlers } = useLayerDragDrop({\n * onReorder,\n * findElement: (id) => elements.find(e => e.id === id) ?? null,\n * findParentGroup: (id) => findGroupParent(elements, id),\n * });\n *\n * return elements.map(el => (\n * <div\n * key={el.id}\n * draggable\n * onDragStart={(e) => handlers.onDragStart(e, el.id)}\n * onDragOver={(e) => handlers.onDragOver(e, el.id)}\n * onDrop={(e) => handlers.onDrop(e, el.id)}\n * onDragEnd={handlers.onDragEnd}\n * />\n * ));\n * }\n * ```\n */\n\nimport { useState, useCallback } from 'react';\nimport { GroupElement } from '../core/GroupElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\nexport interface UseLayerDragDropOptions {\n /**\n * Called when reordering top-level elements or elements in different groups.\n * Position is already inverted for reversed display order.\n */\n onReorder: (draggedId: string, targetId: string, position: 'before' | 'after') => void;\n\n /**\n * Called when reordering elements within the same group.\n * Position is already inverted for reversed display order.\n */\n onGroupReorder?: (\n draggedId: string,\n targetId: string,\n position: 'before' | 'after',\n groupId: string\n ) => void;\n\n /**\n * Called when dropping an element into a group (center drop zone).\n */\n onDropIntoGroup?: (draggedId: string, groupId: string) => void;\n\n /**\n * Called when dropping a top-level element between children of a group.\n * Position is already inverted for reversed display order.\n */\n onDropIntoGroupAtPosition?: (\n draggedId: string,\n groupId: string,\n targetChildId: string,\n position: 'before' | 'after'\n ) => void;\n\n /**\n * Look up an element by ID. Required for determining if a target is a group.\n */\n findElement?: (id: string) => EditorElement | null;\n\n /**\n * Find the parent group of an element. Required for intra-group reordering.\n */\n findParentGroup?: (id: string) => GroupElement | null;\n}\n\nexport interface UseLayerDragDropReturn {\n /** ID of the currently dragged element, or null */\n draggedId: string | null;\n /** ID of the element being dragged over, or null */\n dragOverId: string | null;\n /** Where the dragged element will be dropped relative to the target */\n dropPosition: 'before' | 'after' | 'into' | null;\n /** Event handlers to attach to draggable layer items */\n handlers: {\n onDragStart: (e: React.DragEvent, elementId: string) => void;\n onDragOver: (e: React.DragEvent, elementId: string) => void;\n onDrop: (e: React.DragEvent, targetId: string) => void;\n onDragEnd: () => void;\n };\n}\n\n/**\n * Hook for managing drag-drop reordering in layer panels.\n *\n * Calculates drop position based on mouse position within the target element:\n * - For groups: top 25% = before, middle 50% = into, bottom 25% = after\n * - For non-groups: top/bottom split at 40%/60%\n *\n * Handles the display-order inversion: layers are displayed in reverse\n * order (topmost layer first), so visual \"before\" maps to array \"after\"\n * and vice versa. This inversion is applied before calling the callbacks.\n */\nexport function useLayerDragDrop(options: UseLayerDragDropOptions): UseLayerDragDropReturn {\n const {\n onReorder,\n onGroupReorder,\n onDropIntoGroup,\n onDropIntoGroupAtPosition,\n findElement,\n findParentGroup,\n } = options;\n\n const [draggedId, setDraggedId] = useState<string | null>(null);\n const [dragOverId, setDragOverId] = useState<string | null>(null);\n const [dropPosition, setDropPosition] = useState<'before' | 'after' | 'into' | null>(null);\n\n const handleDragStart = useCallback((e: React.DragEvent, elementId: string) => {\n e.dataTransfer.effectAllowed = 'move';\n // Delay setting dragged state until after browser captures drag image\n requestAnimationFrame(() => {\n setDraggedId(elementId);\n });\n }, []);\n\n const handleDragOver = useCallback(\n (e: React.DragEvent, elementId: string) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n\n const rect = e.currentTarget.getBoundingClientRect();\n const mouseY = e.clientY;\n const relativeY = mouseY - rect.top;\n const height = rect.height;\n\n // Check if target is a group\n const targetElement = findElement?.(elementId);\n const isGroup = targetElement instanceof GroupElement;\n\n let position: 'before' | 'after' | 'into';\n\n if (isGroup) {\n // For groups: top 25% = before, bottom 25% = after, middle 50% = into\n if (relativeY < height * 0.25) {\n position = 'before';\n } else if (relativeY > height * 0.75) {\n position = 'after';\n } else {\n position = 'into';\n }\n } else {\n // For non-groups: top 40% = before, bottom 40% = after, middle = split\n if (relativeY < height * 0.4) {\n position = 'before';\n } else if (relativeY > height * 0.6) {\n position = 'after';\n } else {\n position = relativeY < height / 2 ? 'before' : 'after';\n }\n }\n\n setDragOverId(elementId);\n setDropPosition(position);\n },\n [findElement]\n );\n\n const resetDragState = useCallback(() => {\n setDraggedId(null);\n setDragOverId(null);\n setDropPosition(null);\n }, []);\n\n const handleDrop = useCallback(\n (e: React.DragEvent, targetId: string) => {\n e.preventDefault();\n if (!draggedId || draggedId === targetId || !dropPosition) {\n resetDragState();\n return;\n }\n\n if (dropPosition === 'into') {\n // Dropping into a group\n if (onDropIntoGroup) {\n onDropIntoGroup(draggedId, targetId);\n }\n } else {\n // Reordering (before/after)\n // Check if both elements are children of the same group\n const draggedParent = findParentGroup?.(draggedId);\n const targetParent = findParentGroup?.(targetId);\n\n // Layers are displayed in reverse order, so invert the position\n // Visual \"before\" = Array \"after\", Visual \"after\" = Array \"before\"\n const arrayPosition = dropPosition === 'before' ? 'after' : 'before';\n\n if (draggedParent && targetParent && draggedParent.id === targetParent.id) {\n // Both are children of the same group - reorder within the group\n if (onGroupReorder) {\n onGroupReorder(draggedId, targetId, arrayPosition, draggedParent.id!);\n }\n } else if (!draggedParent && targetParent && onDropIntoGroupAtPosition) {\n // Dragging from top-level into between children of a group\n onDropIntoGroupAtPosition(draggedId, targetParent.id!, targetId, arrayPosition);\n } else {\n // Top-level reordering\n onReorder(draggedId, targetId, arrayPosition);\n }\n }\n\n // Clear drag state immediately since dragEnd may not fire when DOM changes\n resetDragState();\n },\n [\n draggedId,\n dropPosition,\n onReorder,\n onGroupReorder,\n onDropIntoGroup,\n onDropIntoGroupAtPosition,\n findParentGroup,\n resetDragState,\n ]\n );\n\n return {\n draggedId,\n dragOverId,\n dropPosition,\n handlers: {\n onDragStart: handleDragStart,\n onDragOver: handleDragOver,\n onDrop: handleDrop,\n onDragEnd: resetDragState,\n },\n };\n}\n","/**\n * useLayerSelection - Hook for layer selection logic\n *\n * Extracts the selection logic (shift-click multi-select, single-click,\n * deselection) from LayersPanel.tsx into a reusable hook.\n *\n * @example\n * ```tsx\n * function LayersList({ elements }) {\n * const [selectedId, setSelectedId] = useState<string | null>(null);\n * const [multiSelection, setMultiSelection] = useState<string[]>([]);\n *\n * const { isSelected, handleClick } = useLayerSelection({\n * selectedId,\n * multiSelection,\n * onSelectionChange: setSelectedId,\n * onMultiSelectionChange: setMultiSelection,\n * });\n *\n * return elements.map(el => (\n * <div\n * key={el.id}\n * className={isSelected(el.id) ? 'selected' : ''}\n * onClick={(e) => handleClick(e, el.id)}\n * />\n * ));\n * }\n * ```\n */\n\nimport { useCallback } from 'react';\n\nexport interface UseLayerSelectionOptions {\n /** Currently selected single element ID, or null */\n selectedId: string | null;\n /** Array of multi-selected element IDs */\n multiSelection: string[];\n /** Callback for single selection changes */\n onSelectionChange: (id: string | null) => void;\n /** Callback for multi-selection changes */\n onMultiSelectionChange: (ids: string[]) => void;\n}\n\nexport interface UseLayerSelectionReturn {\n /**\n * Check if an element is selected (either single or multi-selected).\n * Does not account for activeChildElement priority -- callers handling\n * group children should check that separately.\n */\n isSelected: (id: string) => boolean;\n\n /**\n * Handle a click on a layer element.\n * - Normal click: selects only this element, clears multi-selection\n * - Shift+click: toggles the element in/out of multi-selection\n */\n handleClick: (e: React.MouseEvent | React.KeyboardEvent, elementId: string) => void;\n}\n\n/**\n * Hook for managing layer selection state.\n *\n * Implements the following selection behaviors:\n *\n * **Normal click:**\n * - Selects only the clicked element\n * - Clears any multi-selection\n *\n * **Shift+click with existing multi-selection:**\n * - If element is in multi-selection: removes it\n * - If only 1 remains: converts back to single selection\n * - If 0 remain: clears selection\n * - If element is not in multi-selection: adds it\n *\n * **Shift+click with single selection:**\n * - If clicking the already-selected element: no-op\n * - If clicking a different element: starts multi-selection with both\n *\n * **Shift+click with no selection:**\n * - Selects the clicked element as a single selection\n */\nexport function useLayerSelection(options: UseLayerSelectionOptions): UseLayerSelectionReturn {\n const { selectedId, multiSelection, onSelectionChange, onMultiSelectionChange } = options;\n\n const isSelected = useCallback(\n (id: string): boolean => {\n return id === selectedId || multiSelection.includes(id);\n },\n [selectedId, multiSelection]\n );\n\n const handleClick = useCallback(\n (e: React.MouseEvent | React.KeyboardEvent, elementId: string) => {\n if (e.shiftKey) {\n // Shift-click: multi-selection behavior\n const isInMultiSelection = multiSelection.includes(elementId);\n const isCurrentlySelected = selectedId === elementId;\n\n if (multiSelection.length > 0) {\n // Already have multi-selection\n if (isInMultiSelection) {\n // Remove from multi-selection\n const newSelection = multiSelection.filter((id) => id !== elementId);\n if (newSelection.length === 1) {\n // Only one left, convert back to single selection\n onMultiSelectionChange([]);\n onSelectionChange(newSelection[0]);\n } else if (newSelection.length === 0) {\n // All deselected\n onMultiSelectionChange([]);\n onSelectionChange(null);\n } else {\n // Keep multi-selection\n onMultiSelectionChange(newSelection);\n }\n } else {\n // Add to multi-selection\n onMultiSelectionChange([...multiSelection, elementId]);\n }\n } else if (isCurrentlySelected) {\n // Single selection exists and shift-clicking it: do nothing\n return;\n } else if (selectedId !== null) {\n // Convert single selection to multi-selection\n onMultiSelectionChange([selectedId, elementId]);\n onSelectionChange(null);\n } else {\n // No selection, just select this element\n onSelectionChange(elementId);\n }\n } else {\n // Normal click: select only this element\n onSelectionChange(elementId);\n onMultiSelectionChange([]);\n }\n },\n [selectedId, multiSelection, onSelectionChange, onMultiSelectionChange]\n );\n\n return { isSelected, handleClick };\n}\n","/**\n * FontSizeDropdown - Dropdown for selecting font size\n */\n\nimport { memo } from 'react';\nimport { Icon } from '@iconify/react';\nimport { Popover, PopoverTrigger, PopoverContent } from './ui';\n\nexport interface FontSizeDropdownProps {\n value: number;\n onChange: (size: number) => void;\n fontSizes: number[];\n isOpen: boolean;\n onToggle: (open: boolean) => void;\n}\n\nconst FontSizeDropdown = memo(({ value, onChange, fontSizes, isOpen, onToggle }: FontSizeDropdownProps) => {\n const handleSelect = (size: number) => {\n onChange(size);\n onToggle(false);\n };\n\n return (\n <Popover open={isOpen} onOpenChange={onToggle}>\n <PopoverTrigger asChild>\n <button className=\"sc-toolbar-btn\" aria-label=\"Font size\">\n <Icon icon=\"radix-icons:font-size\" className=\"sc:size-5\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"sc:w-[120px] sc:max-h-[300px] sc:overflow-y-auto sc:p-1\" maxZIndex>\n <div className=\"sc:flex sc:flex-col sc:gap-0.5\">\n {fontSizes.map((size) => (\n <button\n key={size}\n onClick={() => handleSelect(size)}\n className={`sc:w-full sc:rounded-md sc:px-3 sc:py-1.5 sc:text-center sc:text-sm sc:transition-colors sc:hover:bg-muted ${\n value === size ? 'sc:bg-muted sc:font-medium' : ''\n }`}\n >\n {size}\n </button>\n ))}\n </div>\n </PopoverContent>\n </Popover>\n );\n});\n\nFontSizeDropdown.displayName = 'FontSizeDropdown';\n\nexport default FontSizeDropdown;\n","import { useState } from 'react';\nimport { Tooltip, TooltipTrigger, TooltipContent } from '../ui';\nimport { FONT_SIZES } from '../../constants.js';\nimport { clickProtection } from '../../utils/clickProtection.js';\nimport type { UseTextToolbarReturn } from '../../hooks/useTextToolbar.js';\n\nexport interface FontSizeGroupProps {\n toolbar: UseTextToolbarReturn;\n}\n\nexport function FontSizeGroup({ toolbar }: FontSizeGroupProps) {\n const { fontSize, handleFontSizeIncrement, handleFontSizeChange } = toolbar;\n const [openDropdown, setOpenDropdown] = useState(false);\n\n return (\n <div className=\"sc-toolbar-group\">\n <Tooltip delayDuration={300}>\n <TooltipTrigger asChild>\n <button className=\"sc-toolbar-btn-sm\" onClick={() => handleFontSizeIncrement(-1)}>\n -\n </button>\n </TooltipTrigger>\n <TooltipContent>Decrease Font Size</TooltipContent>\n </Tooltip>\n <Tooltip delayDuration={300}>\n <TooltipTrigger asChild>\n <div style={{ position: 'relative' }}>\n <button\n className=\"sc-toolbar-font-size-display\"\n onClick={() => {\n clickProtection.protectNextClick();\n setOpenDropdown(!openDropdown);\n }}\n >\n {fontSize}\n </button>\n {openDropdown && (\n <div\n className=\"font-dropdown\"\n ref={(el: HTMLDivElement | null) => {\n if (el) {\n const handleClickOutside = (event: MouseEvent) => {\n const target = event.target as HTMLElement;\n if (!el.contains(target) && !target.closest('.sc-toolbar-font-size-display')) {\n setOpenDropdown(false);\n }\n };\n setTimeout(() => {\n document.addEventListener('mousedown', handleClickOutside);\n }, 0);\n }\n }}\n >\n {FONT_SIZES.map((size) => (\n <button\n key={size}\n className={`font-dropdown-option ${fontSize === size ? 'selected' : ''}`}\n onClick={() => {\n handleFontSizeChange(size);\n setOpenDropdown(false);\n }}\n >\n {size}\n </button>\n ))}\n </div>\n )}\n </div>\n </TooltipTrigger>\n <TooltipContent>Font Size</TooltipContent>\n </Tooltip>\n <Tooltip delayDuration={300}>\n <TooltipTrigger asChild>\n <button className=\"sc-toolbar-btn-sm\" onClick={() => handleFontSizeIncrement(1)}>\n +\n </button>\n </TooltipTrigger>\n <TooltipContent>Increase Font Size</TooltipContent>\n </Tooltip>\n </div>\n );\n}\n","/**\n * ElementTypePlugin - Plugin API for registering custom element types\n *\n * Allows external developers to add new element types without modifying\n * core canvas files. Custom types integrate with the transform registry\n * so that GroupElement reconstruction, ElementFactory, and all other\n * registry consumers work seamlessly.\n *\n * @example\n * ```typescript\n * import { registerElementType } from '@snowcone-app/canvas/advanced';\n *\n * registerElementType({\n * type: 'custom-shape',\n * Component: CustomShapeElement,\n * label: 'Custom Shape',\n * icon: 'lucide:shapes',\n * category: 'custom',\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nimport {\n registerTransform,\n unregisterTransform,\n getTransformById,\n _resetDynamicTransforms,\n} from '../transforms/registry.js';\n\n/**\n * Registration descriptor for a custom element type.\n */\nexport interface ElementTypeRegistration {\n /** Unique type identifier (must not conflict with built-in types) */\n type: string;\n\n /**\n * Element class constructor.\n * Must accept a config object and produce a valid element instance\n * (i.e., something that extends BaseElement).\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Constructors have varying signatures\n Component: new (config: any) => any;\n\n /** Display label for UI (e.g., \"Custom Shape\") */\n label: string;\n\n /** Icon identifier in Iconify format (e.g., \"lucide:shapes\") */\n icon?: string;\n\n /** Category for grouping in the add-element menu */\n category?: 'text' | 'image' | 'shape' | 'custom';\n\n /** Default config for new elements of this type */\n defaultConfig?: Record<string, unknown>;\n}\n\n/**\n * Internal store for custom element type registrations.\n * Keyed by type string.\n */\nconst _customElementTypes = new Map<string, ElementTypeRegistration>();\n\n/**\n * Register a custom element type.\n *\n * - Validates the registration (type must be non-empty, Component must be a constructor)\n * - Prevents conflicts with built-in types and duplicate registrations\n * - Adds the type to the transform registry so GroupElement, ElementFactory,\n * and other registry consumers recognize it\n *\n * @param registration - The element type registration descriptor\n * @throws Error if type conflicts with a built-in type\n * @throws Error if type is already registered as a custom type\n * @throws Error if registration is invalid (missing type/Component/label)\n */\nexport function registerElementType(registration: ElementTypeRegistration): void {\n // Validate required fields\n if (!registration.type || typeof registration.type !== 'string') {\n throw new Error('ElementTypeRegistration requires a non-empty \"type\" string');\n }\n if (typeof registration.Component !== 'function') {\n throw new Error(\n `ElementTypeRegistration for \"${registration.type}\" requires a \"Component\" constructor function`\n );\n }\n if (!registration.label || typeof registration.label !== 'string') {\n throw new Error(\n `ElementTypeRegistration for \"${registration.type}\" requires a non-empty \"label\" string`\n );\n }\n\n // registerTransform will throw if the id conflicts with built-in or duplicate\n registerTransform(registration.type, {\n id: registration.type,\n label: registration.label,\n Component: registration.Component,\n });\n\n // Store the full registration (including icon, category, defaultConfig)\n _customElementTypes.set(registration.type, { ...registration });\n}\n\n/**\n * Unregister a custom element type.\n *\n * Removes the type from both the plugin registry and the transform registry.\n * Built-in types cannot be unregistered.\n *\n * @param type - The type identifier to unregister\n * @throws Error if attempting to unregister a built-in type\n */\nexport function unregisterElementType(type: string): void {\n // unregisterTransform throws if it's a built-in type\n unregisterTransform(type);\n _customElementTypes.delete(type);\n}\n\n/**\n * Get all registered custom element types.\n *\n * Returns only plugin-registered types, not built-in types.\n * The returned array is a snapshot; mutations do not affect the registry.\n *\n * @returns Array of ElementTypeRegistration descriptors\n */\nexport function getCustomElementTypes(): ElementTypeRegistration[] {\n return Array.from(_customElementTypes.values());\n}\n\n/**\n * Check if a type identifier is registered (built-in or custom).\n *\n * @param type - The type identifier to check\n * @returns true if the type exists in the transform registry\n */\nexport function isRegisteredElementType(type: string): boolean {\n return getTransformById(type) !== undefined;\n}\n\n/**\n * Reset all custom element type registrations (for testing only).\n * Also resets the underlying dynamic transforms in the registry.\n * @internal\n */\nexport function _resetCustomElementTypes(): void {\n _customElementTypes.clear();\n _resetDynamicTransforms();\n}\n","/**\n * Canvas Event Bus\n *\n * A typed publish/subscribe event system for cross-component communication\n * within the canvas editor. Components can emit and listen for events without\n * direct coupling, enabling loose coordination between panels, tools, and\n * the canvas itself.\n *\n * All event types are defined in {@link CanvasEventMap} for compile-time\n * type safety -- subscribers receive correctly-typed payloads and emitters\n * are checked against the event map at compile time.\n *\n * A singleton instance is available via {@link canvasEventBus} for use\n * across the editor. The {@link useCanvasEvents} hook provides React\n * integration.\n *\n * @example Subscribe to element creation\n * ```ts\n * import { canvasEventBus } from '@snowcone-app/canvas/advanced';\n *\n * const unsub = canvasEventBus.on('element:created', ({ elementId, type }) => {\n * console.log(`Created ${type} element: ${elementId}`);\n * });\n *\n * // Later: clean up\n * unsub();\n * ```\n *\n * @example One-shot listener\n * ```ts\n * canvasEventBus.once('export:completed', ({ durationMs }) => {\n * console.log(`Export finished in ${durationMs}ms`);\n * });\n * ```\n */\n\n// ---------------------------------------------------------------------------\n// Event Map\n// ---------------------------------------------------------------------------\n\n/**\n * Map of all canvas event names to their payload types.\n *\n * Extend this interface (via module augmentation) to add custom events:\n * ```ts\n * declare module '@snowcone-app/canvas/advanced' {\n * interface CanvasEventMap {\n * 'my-plugin:action': { value: number };\n * }\n * }\n * ```\n */\nexport interface CanvasEventMap {\n /** Fired after a new element is added to the canvas */\n 'element:created': { elementId: string; type: string };\n /** Fired after one or more properties of an element change */\n 'element:updated': { elementId: string; changes: string[] };\n /** Fired after an element is removed from the canvas */\n 'element:deleted': { elementId: string };\n /** Fired when the selected element changes (null = deselected) */\n 'element:selected': { elementId: string | null };\n /** Fired when the active artboard changes */\n 'artboard:changed': { artboardId: string; artboardName: string };\n /** Fired when an export operation begins */\n 'export:started': { artboardId: string };\n /** Fired when an export operation completes successfully */\n 'export:completed': { artboardId: string; durationMs: number };\n /** Fired when an export operation fails */\n 'export:error': { artboardId: string; error: Error };\n /** Fired after an undo operation */\n 'undo': { commandDescription?: string };\n /** Fired after a redo operation */\n 'redo': { commandDescription?: string };\n}\n\n// ---------------------------------------------------------------------------\n// Handler type\n// ---------------------------------------------------------------------------\n\n/** Strongly-typed event handler for a specific event name */\nexport type CanvasEventHandler<K extends keyof CanvasEventMap> = (\n event: CanvasEventMap[K],\n) => void;\n\n// ---------------------------------------------------------------------------\n// CanvasEventBus\n// ---------------------------------------------------------------------------\n\n/**\n * A typed event bus for the canvas editor.\n *\n * Provides subscribe ({@link on}, {@link once}), publish ({@link emit}),\n * and unsubscribe ({@link off}) operations with full type safety.\n *\n * Listeners for a given event are called in the order they were registered.\n * Errors thrown inside a handler are caught and logged to `console.error`\n * so that one failing handler does not prevent others from executing.\n */\nexport class CanvasEventBus {\n /** Map of event name to the set of registered handler functions */\n private listeners = new Map<string, Set<Function>>();\n\n // -----------------------------------------------------------------------\n // Subscribe\n // -----------------------------------------------------------------------\n\n /**\n * Subscribe to an event.\n *\n * @param event - The event name to listen for\n * @param handler - Callback invoked with the event payload\n * @returns An unsubscribe function -- call it to remove this handler\n */\n on<K extends keyof CanvasEventMap>(\n event: K,\n handler: CanvasEventHandler<K>,\n ): () => void {\n let handlers = this.listeners.get(event as string);\n if (!handlers) {\n handlers = new Set();\n this.listeners.set(event as string, handlers);\n }\n handlers.add(handler);\n\n // Return unsubscribe function\n return () => {\n handlers!.delete(handler);\n if (handlers!.size === 0) {\n this.listeners.delete(event as string);\n }\n };\n }\n\n /**\n * Subscribe to an event for a single invocation.\n *\n * The handler is automatically removed after the first time the event\n * fires. The returned unsubscribe function can also be used to cancel\n * before the event fires.\n *\n * @param event - The event name to listen for\n * @param handler - Callback invoked once with the event payload\n * @returns An unsubscribe function\n */\n once<K extends keyof CanvasEventMap>(\n event: K,\n handler: CanvasEventHandler<K>,\n ): () => void {\n const wrapper = ((data: CanvasEventMap[K]) => {\n unsubscribe();\n handler(data);\n }) as CanvasEventHandler<K>;\n\n const unsubscribe = this.on(event, wrapper);\n return unsubscribe;\n }\n\n // -----------------------------------------------------------------------\n // Publish\n // -----------------------------------------------------------------------\n\n /**\n * Emit an event, invoking all registered handlers for that event name.\n *\n * Handlers are called synchronously in registration order. If a handler\n * throws, the error is logged and remaining handlers still execute.\n *\n * @param event - The event name to emit\n * @param data - The payload to pass to each handler\n */\n emit<K extends keyof CanvasEventMap>(\n event: K,\n data: CanvasEventMap[K],\n ): void {\n const handlers = this.listeners.get(event as string);\n if (!handlers) return;\n\n // Iterate over a snapshot so that handlers can safely unsubscribe\n for (const handler of [...handlers]) {\n try {\n handler(data);\n } catch (err) {\n console.error(\n `[CanvasEventBus] Error in handler for \"${String(event)}\":`,\n err,\n );\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Unsubscribe\n // -----------------------------------------------------------------------\n\n /**\n * Remove listeners.\n *\n * - If called with an event name, removes all handlers for that event.\n * - If called with no arguments, removes all handlers for all events.\n *\n * @param event - Optional event name to clear. Omit to clear everything.\n */\n off<K extends keyof CanvasEventMap>(event?: K): void {\n if (event === undefined) {\n this.listeners.clear();\n } else {\n this.listeners.delete(event as string);\n }\n }\n\n // -----------------------------------------------------------------------\n // Introspection\n // -----------------------------------------------------------------------\n\n /**\n * Get the number of registered listeners for a specific event.\n *\n * @param event - The event name to query\n * @returns The number of handlers currently registered\n */\n listenerCount(event: keyof CanvasEventMap): number {\n return this.listeners.get(event as string)?.size ?? 0;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Singleton\n// ---------------------------------------------------------------------------\n\n/**\n * The singleton CanvasEventBus instance shared across the editor.\n *\n * Use this directly or via the {@link useCanvasEvents} hook in React\n * components.\n */\nexport const canvasEventBus = new CanvasEventBus();\n","/**\n * useCanvasEvents Hook\n *\n * Provides access to the singleton {@link CanvasEventBus} instance for\n * cross-component communication within the canvas editor.\n *\n * This is a thin convenience wrapper -- it simply returns the shared\n * singleton so that React components have a consistent hook-based API\n * for accessing the event bus.\n *\n * @example Subscribe to events in a component\n * ```tsx\n * import { useCanvasEvents } from '@snowcone-app/canvas/advanced';\n *\n * function MyPanel() {\n * const events = useCanvasEvents();\n *\n * useEffect(() => {\n * const unsub = events.on('element:selected', ({ elementId }) => {\n * console.log('Selected:', elementId);\n * });\n * return unsub;\n * }, [events]);\n *\n * return <div>My Panel</div>;\n * }\n * ```\n *\n * @example Emit events from a component\n * ```tsx\n * function ExportButton() {\n * const events = useCanvasEvents();\n *\n * const handleExport = () => {\n * events.emit('export:started', { artboardId: 'ab-1' });\n * };\n *\n * return <button onClick={handleExport}>Export</button>;\n * }\n * ```\n */\n\nimport { canvasEventBus, type CanvasEventBus } from '../core/EventBus.js';\n\n/**\n * Returns the singleton {@link CanvasEventBus} instance.\n *\n * The returned reference is stable across renders -- it always points to\n * the same singleton, so it is safe to include in dependency arrays.\n */\nexport function useCanvasEvents(): CanvasEventBus {\n return canvasEventBus;\n}\n","/**\n * ADR-0054: SVG-based visual-guide overlay.\n *\n * Duplicated intentionally between:\n * - snowcone-loom/app/components/VisualGuideOverlay.tsx (Loom editor)\n * - ui-components/packages/canvas/src/components/VisualGuideOverlay.tsx\n * (this file — consumed by the Snowcone consumer editor via\n * @snowcone-app/canvas)\n * Loom doesn't depend on @snowcone-app/canvas. Keep these two files\n * byte-identical below the header; if you change one, mirror the other.\n *\n * Renders structured piece guides (boundary, safe area, zones, labels) as\n * stacked SVG layers on top of an artboard preview. The entire overlay\n * lives inside a single `<svg viewBox=\"0 0 artboardWidth artboardHeight\">`\n * so piece-local coordinates compose directly and strokes can opt into\n * `vector-effect=\"non-scaling-stroke\"` to stay visually crisp at every\n * viewport scale.\n *\n * Falls back cleanly when no guides are authored:\n * - `boundary` → the piece's bounding rect (0,0 to pieceWidth, pieceHeight)\n * - `safeArea` → an inset rect derived from `safeAreaInsetPx` when present,\n * omitted otherwise\n */\n\nimport { useMemo } from \"react\";\n\n// ── Types (mirror the backend layouts.ts shape) ───────────────────────────────\n\nexport type SvgPath =\n | { kind: \"rect\"; x: number; y: number; width: number; height: number }\n | {\n kind: \"roundedRect\";\n x: number;\n y: number;\n width: number;\n height: number;\n rx?: number;\n ry?: number;\n }\n | { kind: \"path\"; d: string };\n\nexport interface ZoneShape {\n kind: \"wrap\" | \"binding\" | \"dieCut\" | \"warning\" | \"custom\";\n label?: string;\n path: SvgPath;\n}\n\nexport interface GuideLabel {\n text: string;\n x: number;\n y: number;\n anchor?: \"tl\" | \"tc\" | \"tr\" | \"cl\" | \"c\" | \"cr\" | \"bl\" | \"bc\" | \"br\";\n}\n\nexport interface PieceGuide {\n boundary?: SvgPath;\n safeArea?: SvgPath;\n zones?: ZoneShape[];\n labels?: GuideLabel[];\n /**\n * Corner radius of the piece's *physical outer shape* (e.g. a phone\n * case's outer curve) in piece-local pixels. When set, the bleed\n * ring's outer edge uses a rounded rectangle of `(pieceWidth,\n * pieceHeight)` with this radius, and the entire piece render is\n * clipped to that shape — so nothing bleeds into the sharp corners\n * of the piece's bounding rect. Omit / 0 = sharp corners (current\n * default behaviour).\n */\n outerRadius?: number;\n}\n\nexport interface OverlayPiece {\n /** Stable piece id (matches `placements[].name`). */\n id: string;\n /** Piece's top-left position on the artboard, in artboard pixel coords. */\n x: number;\n y: number;\n /** Piece-local dimensions in px. */\n width: number;\n height: number;\n /**\n * Fallback safe-area inset when no authored `safeArea` is present. In\n * piece-local pixels (caller converts from inches). Omit to skip the\n * fallback safe area entirely.\n */\n safeAreaInsetPx?: number;\n /** Clockwise rotation in degrees about the piece center. Default 0. */\n rotation?: number;\n}\n\nexport interface VisualGuideOverlayStyle {\n bleedFill?: string;\n bleedOpacity?: number;\n boundaryStroke?: string;\n boundaryStrokeWidth?: number;\n /**\n * Safe-area wash: fills the region outside the safe area (inside the\n * piece) with a semi-transparent color. Replaces the old dashed\n * safe-area outline as the default — de-emphasizes the bleed margin so\n * the safe area reads as the \"clean\" zone without adding line chrome.\n */\n safeAreaWashFill?: string;\n safeAreaWashOpacity?: number;\n /**\n * Corner radius (in viewBox px) for the wash's outer edge so it hugs\n * rounded products (phone cases, rounded cards, etc.) instead of\n * boxing sharp corners. Pass `0` for hard corners. Omit to use an\n * artboard-proportional default (~2% of the shorter axis).\n */\n safeAreaWashCornerRadius?: number;\n /**\n * Safe-area stroke settings — legacy dashed outline. Disabled by default\n * (wash does the work). Set `safeAreaStroke` to any color to re-enable.\n */\n safeAreaStroke?: string;\n safeAreaStrokeWidth?: number;\n safeAreaDashArray?: string;\n zoneFill?: Partial<Record<ZoneShape[\"kind\"], string>>;\n zoneStroke?: Partial<Record<ZoneShape[\"kind\"], string>>;\n labelColor?: string;\n labelFontSize?: number;\n labelFontFamily?: string;\n}\n\nexport interface VisualGuideOverlayProps {\n /** Artboard dimensions in px — defines the SVG viewBox. */\n artboardWidth: number;\n artboardHeight: number;\n /**\n * @deprecated Do not use — leave at 0 (the default).\n *\n * This was intended to mirror SnowconeCanvas's `fixedMargin`, but\n * `fixedMargin` is in CSS pixels (converted to world units via\n * `worldMargin = fixedMargin / zoom` in useCanvasLayout.ts) while\n * this prop treated the value as viewBox/world units. The two only\n * match at zoom === 1, so passing a non-zero value silently drifted\n * the overlay content by tens of pixels at typical editor zooms.\n *\n * Correct usage: keep the SVG viewBox equal to the artboard, and\n * position the wrapping div with `inset: ${fixedMargin}px` so the\n * overlay covers only the artboard region of the padded canvas.\n */\n canvasPadding?: number;\n /** Pieces to render guides for. */\n pieces: OverlayPiece[];\n /** Authored guides keyed by piece id. Pieces without a guide fall back. */\n pieceGuides?: Record<string, PieceGuide>;\n /** Per-layer visibility toggles. All default to true. */\n show?: {\n bleed?: boolean;\n zones?: boolean;\n boundary?: boolean;\n safeArea?: boolean;\n labels?: boolean;\n };\n /** Optional style overrides. */\n style?: VisualGuideOverlayStyle;\n /** `className` applied to the wrapping SVG element. */\n className?: string;\n}\n\n// ── Defaults ──────────────────────────────────────────────────────────────────\n\nconst DEFAULT_STYLE: Required<\n Omit<VisualGuideOverlayStyle, \"zoneFill\" | \"zoneStroke\">\n> & {\n zoneFill: Record<ZoneShape[\"kind\"], string>;\n zoneStroke: Record<ZoneShape[\"kind\"], string>;\n} = {\n bleedFill: \"#ffffff\",\n bleedOpacity: 0.5,\n // Subtle boundary — the wash already draws the eye to the safe area,\n // so the piece outline is just a quiet frame for context. Consumers\n // can dial it up to a harder blue / thicker stroke via style overrides.\n // Default to no boundary outline — the bleed wash's soft inner edge\n // carries the design-canvas edge on its own. Consumers can set any\n // colour via `style.boundaryStroke` to opt back in.\n boundaryStroke: \"\",\n boundaryStrokeWidth: 1,\n // New default: 25%-opacity white wash over the non-safe area. Apple-y\n // \"fade out the edges\" approach rather than a dashed line, so the safe\n // area reads as the clean canvas rather than a bordered box.\n safeAreaWashFill: \"#ffffff\",\n safeAreaWashOpacity: 0.25,\n // Sentinel: -1 means \"auto\" — the component computes an artboard-\n // proportional radius at render time. A real value overrides.\n safeAreaWashCornerRadius: -1,\n // Legacy dashed safe-area outline — disabled by default now. Set\n // `safeAreaStroke` to a color to re-enable for any consumer that\n // prefers the old look.\n safeAreaStroke: \"\",\n safeAreaStrokeWidth: 1.5,\n safeAreaDashArray: \"6 4\",\n zoneFill: {\n wrap: \"rgba(148, 163, 184, 0.25)\", // slate-400\n binding: \"rgba(234, 88, 12, 0.20)\", // orange-600\n // A die-cut is material *removed* from the product, so the fill reads\n // as a dark \"hole\" rather than a coloured region. 70% black gives\n // strong contrast over any artwork underneath.\n dieCut: \"rgba(0, 0, 0, 0.7)\",\n warning: \"rgba(245, 158, 11, 0.25)\", // amber-500\n custom: \"rgba(100, 100, 100, 0.15)\",\n },\n zoneStroke: {\n wrap: \"#64748b\",\n binding: \"#ea580c\",\n // White outline on die-cut pairs with the black fill to read as a\n // clean perforation. A red outline would compete with the fill.\n dieCut: \"#ffffff\",\n warning: \"#f59e0b\",\n custom: \"#6b7280\",\n },\n labelColor: \"#111827\",\n // Placeholder — replaced at render time with an artboard-proportional\n // default (see `autoLabelFontSize` in the component). Callers can still\n // pin via `style.labelFontSize`.\n labelFontSize: 14,\n labelFontFamily:\n \"ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif\",\n};\n\n// ── Rendering helpers ─────────────────────────────────────────────────────────\n\nconst ANCHOR_TO_SVG: Record<\n NonNullable<GuideLabel[\"anchor\"]>,\n {\n textAnchor: React.SVGProps<SVGTextElement>[\"textAnchor\"];\n dominantBaseline: React.SVGProps<SVGTextElement>[\"dominantBaseline\"];\n }\n> = {\n // SVG `dominant-baseline` has no \"baseline\" keyword; \"alphabetic\" is the\n // bottom/baseline alignment (and React's typed union enforces this).\n tl: { textAnchor: \"start\", dominantBaseline: \"hanging\" },\n tc: { textAnchor: \"middle\", dominantBaseline: \"hanging\" },\n tr: { textAnchor: \"end\", dominantBaseline: \"hanging\" },\n cl: { textAnchor: \"start\", dominantBaseline: \"middle\" },\n c: { textAnchor: \"middle\", dominantBaseline: \"middle\" },\n cr: { textAnchor: \"end\", dominantBaseline: \"middle\" },\n bl: { textAnchor: \"start\", dominantBaseline: \"alphabetic\" },\n bc: { textAnchor: \"middle\", dominantBaseline: \"alphabetic\" },\n br: { textAnchor: \"end\", dominantBaseline: \"alphabetic\" },\n};\n\nfunction renderShape(\n path: SvgPath,\n commonProps: React.SVGProps<SVGElement>,\n): React.JSX.Element {\n if (path.kind === \"rect\") {\n return (\n <rect\n x={path.x}\n y={path.y}\n width={path.width}\n height={path.height}\n {...(commonProps as React.SVGProps<SVGRectElement>)}\n />\n );\n }\n if (path.kind === \"roundedRect\") {\n return (\n <rect\n x={path.x}\n y={path.y}\n width={path.width}\n height={path.height}\n rx={path.rx}\n ry={path.ry}\n {...(commonProps as React.SVGProps<SVGRectElement>)}\n />\n );\n }\n return (\n <path\n d={path.d}\n {...(commonProps as React.SVGProps<SVGPathElement>)}\n />\n );\n}\n\nfunction resolveBoundary(\n guide: PieceGuide | undefined,\n naturalW: number,\n naturalH: number,\n): SvgPath {\n // Authored boundaries live in the piece's NATURAL coord system (the\n // unrotated frame the layout was authored in). The fallback \"full\n // piece\" rect must therefore also be in natural dims, not the\n // displayed (post-rotation) `piece.width × piece.height`. Otherwise a\n // 90°/270° piece's fallback boundary lands sideways in the rotated\n // local frame. See PieceGuideRenderer.ts for the canvas-side mirror.\n return (\n guide?.boundary ?? {\n kind: \"rect\",\n x: 0,\n y: 0,\n width: naturalW,\n height: naturalH,\n }\n );\n}\n\n/**\n * Serialize an SvgPath to its SVG `d` attribute string. Used to build\n * evenodd compound paths (e.g. piece rect minus safe area) for the wash.\n */\nfunction svgPathD(path: SvgPath): string {\n if (path.kind === \"rect\") {\n return `M ${path.x} ${path.y} h ${path.width} v ${path.height} h ${-path.width} Z`;\n }\n if (path.kind === \"roundedRect\") {\n const rx = path.rx ?? path.ry ?? 0;\n const ry = path.ry ?? path.rx ?? 0;\n if (rx <= 0 && ry <= 0) {\n return `M ${path.x} ${path.y} h ${path.width} v ${path.height} h ${-path.width} Z`;\n }\n const { x, y, width: w, height: h } = path;\n return [\n `M ${x + rx} ${y}`,\n `h ${w - 2 * rx}`,\n `a ${rx} ${ry} 0 0 1 ${rx} ${ry}`,\n `v ${h - 2 * ry}`,\n `a ${rx} ${ry} 0 0 1 ${-rx} ${ry}`,\n `h ${-(w - 2 * rx)}`,\n `a ${rx} ${ry} 0 0 1 ${-rx} ${-ry}`,\n `v ${-(h - 2 * ry)}`,\n `a ${rx} ${ry} 0 0 1 ${rx} ${-ry}`,\n \"Z\",\n ].join(\" \");\n }\n return path.d;\n}\n\nfunction resolveSafeArea(\n guide: PieceGuide | undefined,\n piece: OverlayPiece,\n naturalW: number,\n naturalH: number,\n): SvgPath | null {\n if (guide?.safeArea) return guide.safeArea;\n if (piece.safeAreaInsetPx && piece.safeAreaInsetPx > 0) {\n const inset = piece.safeAreaInsetPx;\n return {\n kind: \"rect\",\n x: inset,\n y: inset,\n width: Math.max(0, naturalW - inset * 2),\n height: Math.max(0, naturalH - inset * 2),\n };\n }\n return null;\n}\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function VisualGuideOverlay({\n artboardWidth,\n artboardHeight,\n canvasPadding = 0,\n pieces,\n pieceGuides,\n show,\n style,\n className,\n}: VisualGuideOverlayProps): React.JSX.Element {\n const s = useMemo(() => {\n // Artboard-proportional default so labels stay legible at typical\n // viewport scales. Bumps with artboard size but clamped to a sane\n // floor. Callers can still override via `style.labelFontSize` to pin\n // an exact viewBox-unit value. 3% of the shorter axis reads clearly\n // at the zoom levels Snowcone's consumer editor typically renders at.\n const autoLabelFontSize = Math.max(\n 16,\n Math.round(Math.min(artboardWidth, artboardHeight) * 0.03),\n );\n const merged = {\n ...DEFAULT_STYLE,\n ...(style ?? {}),\n zoneFill: { ...DEFAULT_STYLE.zoneFill, ...(style?.zoneFill ?? {}) },\n zoneStroke: { ...DEFAULT_STYLE.zoneStroke, ...(style?.zoneStroke ?? {}) },\n };\n if (style?.labelFontSize === undefined) {\n merged.labelFontSize = autoLabelFontSize;\n }\n return merged;\n }, [style, artboardWidth, artboardHeight]);\n\n const visibility = {\n bleed: show?.bleed ?? true,\n zones: show?.zones ?? true,\n boundary: show?.boundary ?? true,\n safeArea: show?.safeArea ?? true,\n labels: show?.labels ?? true,\n };\n\n // Expand the viewBox to include SnowconeCanvas's padding on each side,\n // and translate all content by that padding so artboard-local coords\n // paint at the actual artboard position inside the padded canvas.\n // Without this, the SVG's painted rect matches the full padded canvas\n // (auto-centered by preserveAspectRatio) and drifts away from where\n // the canvas element paints the artboard — typically a few percent of\n // the smaller axis.\n const viewBoxWidth = artboardWidth + canvasPadding * 2;\n const viewBoxHeight = artboardHeight + canvasPadding * 2;\n\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox={`0 0 ${viewBoxWidth} ${viewBoxHeight}`}\n preserveAspectRatio=\"xMidYMid meet\"\n className={className}\n // Overlay mustn't swallow canvas interaction; individual elements\n // can opt back in with pointer-events if we later add hover/labels.\n style={{ pointerEvents: \"none\", width: \"100%\", height: \"100%\" }}\n >\n <g transform={`translate(${canvasPadding} ${canvasPadding})`}>\n {pieces.map((piece) => {\n const guide = pieceGuides?.[piece.id];\n const zones = guide?.zones ?? [];\n const labels = guide?.labels ?? [];\n\n // `piece.rotation` swaps the piece's displayed dim relative to\n // its natural (bbox) dim. Boundary / safeArea / wash etc. are\n // authored against the NATURAL coord system, so we need a\n // transform that maps natural (0, 0)→displayed corner exactly,\n // then draw at natural dims. The earlier `rotate(angle, cx, cy)`\n // around the *displayed* center offsets the rotated rect by\n // ((Nw−Dw)/2, (Nh−Dh)/2) — visible as a halo on rotated pieces.\n // Mirrors `PieceGuideRenderer.ts`'s canvas-side fix.\n //\n // SVG composes left-to-right; right-most transform applies\n // first. So for rotation = 90° CW the inner coord is rotated\n // first, then translated by (W, 0):\n // 90°: translate(piece.x piece.y) translate(W, 0) rotate(90)\n // 180°: translate(piece.x piece.y) translate(W, H) rotate(180)\n // 270°: translate(piece.x piece.y) translate(0, H) rotate(270)\n const rotation = piece.rotation ?? 0;\n const isOrtho = rotation === 90 || rotation === 270;\n const naturalW = isOrtho ? piece.height : piece.width;\n const naturalH = isOrtho ? piece.width : piece.height;\n const boundary = resolveBoundary(guide, naturalW, naturalH);\n const safeArea = resolveSafeArea(guide, piece, naturalW, naturalH);\n const originXform = `translate(${piece.x} ${piece.y})`;\n let rotationXform = \"\";\n if (rotation === 90) {\n rotationXform = `translate(${piece.width} 0) rotate(90)`;\n } else if (rotation === 180) {\n rotationXform = `translate(${piece.width} ${piece.height}) rotate(180)`;\n } else if (rotation === 270) {\n rotationXform = `translate(0 ${piece.height}) rotate(270)`;\n }\n const transform = rotationXform\n ? `${originXform} ${rotationXform}`\n : originXform;\n\n // Safe-area wash: fills the piece minus the safe area with a\n // subtle translucent color. This is the primary safe-area\n // indicator — replaces the old dashed outline for an Apple-y\n // \"fade out the edges\" feel. Only rendered when the safeArea is\n // rect/roundedRect (path-kind falls through until we parse SVG\n // path bounds in Phase 4).\n //\n // Wash outer uses a rounded rect by default so the fade hugs the\n // product's visual corners (phone cases, rounded cards) instead\n // of boxing sharp 90° corners. Consumers can set\n // `style.safeAreaWashCornerRadius = 0` for hard corners.\n const washRadius =\n s.safeAreaWashCornerRadius < 0\n ? Math.round(Math.min(naturalW, naturalH) * 0.02)\n : s.safeAreaWashCornerRadius;\n // Wash outer: prefer the authored boundary (it's the product's\n // actual design canvas — a rounded-rect phone case outline, a\n // book cover rectangle, etc.). Fall back to the piece rect only\n // when no boundary is authored. Apply the corner radius to a\n // plain-rect boundary at render time so the fade hugs the\n // product's visual curves even without explicit author geometry.\n const boundaryForWash: SvgPath | null =\n guide?.boundary &&\n (guide.boundary.kind === \"rect\" || guide.boundary.kind === \"roundedRect\")\n ? guide.boundary\n : null;\n const washOuter: SvgPath = boundaryForWash\n ? boundaryForWash.kind === \"rect\" && washRadius > 0\n ? {\n kind: \"roundedRect\",\n x: boundaryForWash.x,\n y: boundaryForWash.y,\n width: boundaryForWash.width,\n height: boundaryForWash.height,\n rx: washRadius,\n ry: washRadius,\n }\n : boundaryForWash\n : washRadius > 0\n ? {\n kind: \"roundedRect\",\n x: 0,\n y: 0,\n width: naturalW,\n height: naturalH,\n rx: washRadius,\n ry: washRadius,\n }\n : {\n kind: \"rect\",\n x: 0,\n y: 0,\n width: naturalW,\n height: naturalH,\n };\n // Bleed ring = piece rect ∖ boundary (the area OUTSIDE the\n // design canvas that physically wraps or trims). The inner\n // edge of the ring matches the boundary shape exactly, which\n // is the \"design canvas edge\" — where the sharp art stops.\n // No boundary stroke is drawn; the fade from bleed wash to\n // sharp art reads as the design-canvas edge on its own.\n const bleedFilterId = `bleed-blur-${piece.id}`;\n // Drawing happens in the natural coord system (post-rotation\n // transform), so the bleed-ring outer rect is sized at natural\n // dims. Same invariant as `washOuter` above.\n const pieceRect: SvgPath = {\n kind: \"rect\",\n x: 0,\n y: 0,\n width: naturalW,\n height: naturalH,\n };\n const canRenderBleed =\n washOuter.kind === \"rect\" || washOuter.kind === \"roundedRect\";\n\n return (\n <g key={piece.id} transform={transform}>\n {/* Bleed ring: (pieceRect ∖ boundary). Fills the wrap/trim\n zone outside the design canvas. Flat 55% bg-tertiary\n fill, softened with a big Gaussian blur so the inner\n edge fades smoothly out of the sharp design canvas —\n Apple vignette, no hard lines, no wash stroke. Clipped\n to the piece rect so the blur can't leak outside. */}\n {visibility.safeArea && canRenderBleed && (\n <>\n <defs>\n <filter\n id={bleedFilterId}\n x=\"-20%\"\n y=\"-20%\"\n width=\"140%\"\n height=\"140%\"\n >\n <feGaussianBlur\n in=\"SourceGraphic\"\n stdDeviation={\n // Scales with piece size so the fade feels\n // consistent across phones, notebook covers,\n // and tiny artboards alike. `Math.min` is\n // commutative, so the swap natural↔displayed\n // dim doesn't change the value here, but we\n // use natural dims for consistency with the\n // other piece-local geometry.\n Math.max(\n 12,\n Math.round(Math.min(naturalW, naturalH) * 0.025),\n )\n }\n />\n </filter>\n <clipPath id={`piece-clip-${piece.id}`}>\n <rect\n x={0}\n y={0}\n width={naturalW}\n height={naturalH}\n />\n </clipPath>\n </defs>\n <g\n clipPath={`url(#piece-clip-${piece.id})`}\n filter={`url(#${bleedFilterId})`}\n >\n <path\n d={`${svgPathD(pieceRect)} ${svgPathD(washOuter)}`}\n style={{\n // Honor `style.bleedFill` / `style.bleedOpacity`\n // overrides — previously hardcoded, which silently\n // dropped ADR-0054 Phase 5 `pieceGuidesStyle` edits\n // in the Loom authoring preview.\n fill: s.bleedFill || \"var(--color-bg-tertiary, #333)\",\n }}\n fillOpacity={s.bleedOpacity ?? 0.55}\n fillRule=\"evenodd\"\n />\n </g>\n </>\n )}\n\n {/* Non-die-cut zones render as a single filled+stroked shape.\n Die-cuts get a different treatment (blur + alpha) below\n so they share visual language with the canvas's own\n \"outside the artboard\" dim treatment. */}\n {visibility.zones &&\n zones\n .filter((z) => z.kind !== \"dieCut\")\n .map((zone, i) => (\n <g key={`zone-${i}`}>\n {renderShape(zone.path, {\n fill: s.zoneFill[zone.kind],\n stroke: s.zoneStroke[zone.kind],\n strokeWidth: 1.5,\n strokeDasharray: zone.kind === \"wrap\" ? \"4 3\" : undefined,\n vectorEffect: \"non-scaling-stroke\",\n } as unknown as React.SVGProps<SVGElement>)}\n </g>\n ))}\n\n {/* Die-cut zones: same visual language as elements that\n overflow the artboard in CanvasRenderer.ts (4px blur +\n ~30% white tint). Implemented via <foreignObject> so we\n can use CSS `backdrop-filter` to blur the canvas pixels\n beneath in place — no coordinate translation needed,\n since the foreignObject inherits the SVG viewBox\n coordinate system. Matches CanvasRenderer.ts:263-264. */}\n {visibility.zones &&\n zones\n .filter((z) => z.kind === \"dieCut\")\n .map((zone, i) => {\n const p = zone.path;\n if (p.kind !== \"rect\" && p.kind !== \"roundedRect\") {\n return null;\n }\n const rx =\n p.kind === \"roundedRect\" ? (p.rx ?? p.ry ?? 0) : 0;\n const ry =\n p.kind === \"roundedRect\" ? (p.ry ?? p.rx ?? 0) : 0;\n return (\n <foreignObject\n key={`diecut-${i}`}\n x={p.x}\n y={p.y}\n width={p.width}\n height={p.height}\n >\n <div\n // xmlns is required inside foreignObject so the\n // HTML child is parsed as HTML, not SVG. React's\n // HTMLDivElement prop types don't declare `xmlns`,\n // so spread it as a narrowly-typed extra attribute\n // rather than weaken the whole element's typing.\n {...({ xmlns: \"http://www.w3.org/1999/xhtml\" } as {\n xmlns: string;\n })}\n style={{\n width: \"100%\",\n height: \"100%\",\n borderRadius: `${rx}px / ${ry}px`,\n backdropFilter: \"blur(4px)\",\n WebkitBackdropFilter: \"blur(4px)\",\n // Match the canvas's own \"outside-artboard\"\n // tint (CanvasRenderer.ts uses globalAlpha=0.3\n // against `--color-bg-tertiary`). We stack the\n // bg-tertiary colour at 70% alpha on top of the\n // blurred artwork to achieve the same visual\n // blend. color-mix lets us stay theme-aware in\n // both light and dark modes.\n background:\n \"color-mix(in srgb, var(--color-bg-tertiary, #333) 70%, transparent)\",\n }}\n />\n </foreignObject>\n );\n })}\n\n {/* Boundary outline — only rendered when `boundaryStroke`\n is explicitly set by a consumer. Default is no stroke;\n the bleed wash's soft inner edge carries the\n design-canvas edge on its own. */}\n {visibility.boundary &&\n s.boundaryStroke &&\n renderShape(boundary, {\n fill: \"none\",\n stroke: s.boundaryStroke,\n strokeWidth: s.boundaryStrokeWidth,\n vectorEffect: \"non-scaling-stroke\",\n } as unknown as React.SVGProps<SVGElement>)}\n\n {/* Optional dashed safe-area outline (opt-in: empty by\n default so the wash alone carries the signal; consumers\n can set `style.safeAreaStroke` to a color to restore). */}\n {visibility.safeArea &&\n safeArea &&\n s.safeAreaStroke &&\n renderShape(safeArea, {\n fill: \"none\",\n stroke: s.safeAreaStroke,\n strokeWidth: s.safeAreaStrokeWidth,\n strokeDasharray: s.safeAreaDashArray,\n vectorEffect: \"non-scaling-stroke\",\n } as unknown as React.SVGProps<SVGElement>)}\n\n {/* Labels — font-size in CSS px via non-scaling approach.\n We draw at the piece-local coords but inside the viewBox\n the text honors its own font-size unchanged (fonts don't\n scale with SVG viewBox), so we bake the intended visual\n font size directly. */}\n {visibility.labels &&\n labels.map((label, i) => {\n const a = ANCHOR_TO_SVG[label.anchor ?? \"c\"];\n return (\n <text\n key={`label-${i}`}\n x={label.x}\n y={label.y}\n textAnchor={a.textAnchor}\n dominantBaseline={a.dominantBaseline}\n fontSize={s.labelFontSize}\n fontFamily={s.labelFontFamily}\n fill={s.labelColor}\n style={{ paintOrder: \"stroke\" }}\n stroke=\"#ffffff\"\n // Halo width tracks font size so labels stay legible\n // over any background at any zoom.\n strokeWidth={Math.max(2, s.labelFontSize * 0.25)}\n strokeLinejoin=\"round\"\n >\n {label.text}\n </text>\n );\n })}\n </g>\n );\n })}\n </g>\n </svg>\n );\n}\n\n","/**\n * @snowcone-app/canvas/advanced — Advanced API (Tier 2)\n *\n * May add new exports in minor versions. Breaking changes with migration\n * guide in minor versions. For developers building custom editor layouts\n * and integrations.\n *\n * @packageDocumentation\n */\n\n// Register all icons on module load\nimport { ensureIconsRegistered } from '../icons/registry.js';\nensureIconsRegistered();\n\n// =============================================================================\n// Individual Panel Components\n// =============================================================================\n\nexport { Canvas } from '../components/embed/Canvas.js';\nexport type { CanvasProps } from '../components/embed/Canvas.js';\n\nexport { LayersPanel } from '../components/embed/LayersPanel.js';\nexport type { LayersPanelProps } from '../components/embed/LayersPanel.js';\n\nexport { EffectsPanel } from '../components/embed/EffectsPanel.js';\nexport type { EffectsPanelProps } from '../components/embed/EffectsPanel.js';\n\nexport { ExportPanel } from '../components/embed/ExportPanel.js';\nexport type { ExportPanelProps } from '../components/embed/ExportPanel.js';\n\nexport { ImagePanel } from '../components/embed/ImagePanel.js';\nexport type { ImagePanelProps } from '../components/embed/ImagePanel.js';\n\nexport { GlyphPanel } from '../components/embed/GlyphPanel.js';\nexport type { GlyphPanelProps } from '../components/embed/GlyphPanel.js';\n\nexport { ArtboardTabs } from '../components/embed/ArtboardTabs.js';\nexport type { ArtboardTabsProps } from '../components/embed/ArtboardTabs.js';\n\nexport { ContextualToolbars } from '../components/ContextualToolbars.js';\nexport type { ContextualToolbarsProps } from '../components/ContextualToolbars.js';\n\nexport { default as ShapeTypeDrawer } from '../components/ShapeTypeDrawer.js';\n\nexport { MenuButton } from '../components/embed/MenuButton.js';\nexport type { MenuButtonProps } from '../components/embed/MenuButton.js';\n\nexport { ZoomControls } from '../components/embed/ZoomControls.js';\nexport type { ZoomControlsProps } from '../components/embed/ZoomControls.js';\n\nexport { CropPanel } from '../components/CropPanel.js';\nexport type { CropPanelProps } from '../components/CropPanel.js';\n\n// =============================================================================\n// Providers (for custom composition)\n// =============================================================================\n\nexport { EditorProvider } from '../contexts/EditorContext.js';\nexport type { EditorElement, PanOffset, ExpandedPanelType } from '../contexts/EditorContext.js';\n\nexport { ThemeProvider, useTheme } from '../contexts/ThemeContext.js';\nexport type { Theme } from '../contexts/ThemeContext.js';\n\nexport { KitProvider, useKit, useCapabilities } from '../contexts/KitContext.js';\nexport type { KitContextValue, KitProviderProps } from '../contexts/KitContext.js';\n\n// =============================================================================\n// Sub-Context Hooks\n// =============================================================================\n\nexport { useViewportContext } from '../contexts/ViewportContext.js';\nexport type { ViewportContextValue, ViewportProviderProps } from '../contexts/ViewportContext.js';\n\nexport { useSelectionContext } from '../contexts/SelectionContext.js';\nexport type { SelectionContextValue, SelectionProviderProps } from '../contexts/SelectionContext.js';\n\nexport { useHistoryContext } from '../contexts/HistoryContext.js';\nexport type { HistoryContextValue, HistoryProviderProps } from '../contexts/HistoryContext.js';\n\nexport { useToolStateContext } from '../contexts/ToolStateContext.js';\nexport type { ToolStateContextValue, ToolStateProviderProps } from '../contexts/ToolStateContext.js';\n\nexport { useElementsContext } from '../contexts/ElementsContext.js';\nexport type { ElementsContextValue, ElementsProviderProps } from '../contexts/ElementsContext.js';\n\nexport { useCommandContext } from '../contexts/CommandContext.js';\nexport type { CommandContextValue, CommandProviderProps } from '../contexts/CommandContext.js';\n\n// =============================================================================\n// Element Types (classes for instanceof checks)\n// =============================================================================\n\nexport { TextElement } from '../core/TextElement.js';\nexport { ImageElement } from '../core/ImageElement.js';\nexport { GroupElement } from '../core/GroupElement.js';\nexport { ShapeElement } from '../core/ShapeElement.js';\nexport { PathElement } from '../core/PathElement.js';\nexport { BaseElement } from '../core/BaseElement.js';\nexport { ArtboardElement } from '../core/ArtboardElement.js';\n\n// Config type guards\nexport {\n isTextElementConfig,\n isImageElementConfig,\n isCustomElementConfig,\n isCircleElementConfig,\n isGroupElementConfig,\n isShapeElementConfig,\n isPathElementConfig,\n} from '../types/index.js';\n\n// Instance type guards\nexport {\n isTextElement,\n isImageElement,\n isGroupElement,\n isShapeElement,\n isPathElement,\n} from '../types/guards.js';\n\n// Element config types\nexport type {\n BaseElementConfig,\n BaseTextElementConfig,\n TextOnlyElementConfig,\n CustomElementConfig,\n ImageElementConfig,\n GroupElementConfig,\n ShapeElementConfig,\n PathElementConfig,\n CircleElementConfig,\n ArchElementConfig,\n WaveElementConfig,\n FlagElementConfig,\n LeanElementConfig,\n AscendElementConfig,\n} from '../types/index.js';\n\n// =============================================================================\n// Advanced Hooks\n// =============================================================================\n\nexport { useProjectLoader } from '../hooks/useProjectLoader.js';\nexport type { ProjectTemplate, ProjectMetadata, UseProjectLoaderReturn } from '../hooks/useProjectLoader.js';\n\nexport { useTextToolbar } from '../hooks/useTextToolbar.js';\nexport type { UseTextToolbarOptions, UseTextToolbarReturn } from '../hooks/useTextToolbar.js';\n\nexport { useBreakpoint } from '../hooks/useBreakpoint.js';\nexport type { BreakpointConfig, Breakpoint, UseBreakpointReturn } from '../hooks/useBreakpoint.js';\n\nexport { usePerformance } from '../hooks/usePerformance.js';\nexport type { PerformanceMetrics, UsePerformanceReturn } from '../hooks/usePerformance.js';\n\nexport { useElementById } from '../hooks/useElementById.js';\n\nexport { useLayerPreview } from '../hooks/useLayerPreview.js';\nexport type { UseLayerPreviewOptions, UseLayerPreviewReturn } from '../hooks/useLayerPreview.js';\n\nexport { useLayerDragDrop } from '../hooks/useLayerDragDrop.js';\nexport type { UseLayerDragDropOptions, UseLayerDragDropReturn } from '../hooks/useLayerDragDrop.js';\n\nexport { useLayerSelection } from '../hooks/useLayerSelection.js';\nexport type { UseLayerSelectionOptions, UseLayerSelectionReturn } from '../hooks/useLayerSelection.js';\n\n// =============================================================================\n// UI Primitives\n// =============================================================================\n\nexport * from '../components/embed/primitives/index.js';\nexport * from '../components/embed/ui/index.js';\n\n// =============================================================================\n// Text Toolbar\n// =============================================================================\n\nexport { TextToolbar } from '../components/TextToolbar.js';\nexport type { TextToolbarProps } from '../components/TextToolbar.js';\nexport * from '../components/text-toolbar/index.js';\n\n// =============================================================================\n// Loading States\n// =============================================================================\n\nexport { CanvasSkeleton, LayersPanelSkeleton, ToolbarSkeleton, Spinner } from '../components/embed/LoadingStates.js';\nexport type { CanvasSkeletonProps, LayersPanelSkeletonProps, ToolbarSkeletonProps } from '../components/embed/LoadingStates.js';\n\n// =============================================================================\n// Auto Export Manager\n// =============================================================================\n\nexport { AutoExportManager, DEFAULT_AUTO_EXPORT_CONFIG } from '../services/AutoExportManager.js';\nexport type { AutoExportConfig, AutoExportStats } from '../services/AutoExportManager.js';\n\n// =============================================================================\n// Selection Preservation\n// =============================================================================\n\nexport {\n shouldPreserveSelection,\n preserveSelectionProps,\n PRESERVE_SELECTION_ATTR,\n} from '../utils/selectionPreservation.js';\n\n// =============================================================================\n// Advanced Types\n// =============================================================================\n\nexport type {\n AnyTransformData,\n CustomTransformData,\n CircleTransformData,\n ArchTransformData,\n WaveTransformData,\n FlagTransformData,\n LeanTransformData,\n AscendTransformData,\n ImageTransformData,\n GroupTransformData,\n ShapeTransformData,\n PathTransformData,\n StrokeConfig,\n MaskType,\n MaskDefinition,\n KnockoutScope,\n KnockoutConfig,\n DistressStyle,\n DistressEffect,\n CharacterStyle,\n TextSpan,\n GlyphOverride,\n GlyphAlternate,\n OpenTypeFeatures,\n ShapeType,\n PathPointType,\n PathPoint,\n InteractionMode,\n SelectionState,\n ResizeAnchor,\n RenderingContext,\n} from '../types/index.js';\n\nexport { RichText } from '../types/index.js';\n\n// Transform data type guards\nexport {\n isCustomTransform,\n isCircleTransform,\n isArchTransform,\n isWaveTransform,\n isFlagTransform,\n isImageTransform,\n isGroupTransform,\n isShapeTransform,\n isPathTransform,\n hasStroke,\n hasMasks,\n isKnockout,\n hasDistressEffect,\n} from '../types/index.js';\n\n// Kit registry\nexport { KIT_PRESETS } from '../kits/registry.js';\n\n// =============================================================================\n// Section Registry\n// =============================================================================\n\nexport {\n registerSection,\n getSection,\n getSectionIds,\n getAllSections,\n unregisterSection,\n registerBuiltinSections,\n registerBuiltinSectionsSync,\n} from '../kits/sections.js';\nexport type { SectionConfig, SectionComponent } from '../kits/sections.js';\n\n// =============================================================================\n// Kit Serialization\n// =============================================================================\n\nexport { serializeKit, loadKitFromJSON } from '../kits/serialization.js';\nexport type { KitJSON } from '../kits/serialization.js';\n\n// =============================================================================\n// Plugin System: Custom Element Types\n// =============================================================================\n\nexport {\n registerElementType,\n unregisterElementType,\n getCustomElementTypes,\n isRegisteredElementType,\n} from '../plugins/ElementTypePlugin.js';\nexport type { ElementTypeRegistration } from '../plugins/ElementTypePlugin.js';\n\n// =============================================================================\n// Event Bus\n// =============================================================================\n\nexport { CanvasEventBus, canvasEventBus } from '../core/EventBus.js';\nexport type { CanvasEventMap, CanvasEventHandler } from '../core/EventBus.js';\nexport { useCanvasEvents } from '../hooks/useCanvasEvents.js';\n\n// =============================================================================\n// ADR-0054: Visual Guide Overlay\n// =============================================================================\n\nexport { VisualGuideOverlay } from '../components/VisualGuideOverlay.js';\nexport type {\n VisualGuideOverlayProps,\n VisualGuideOverlayStyle,\n OverlayPiece,\n PieceGuide,\n SvgPath,\n ZoneShape,\n GuideLabel,\n} from '../components/VisualGuideOverlay.js';\n\n/**\n * Canvas-native piece-guide rendering (ADR-0054). Prefer the\n * `pieceGuides` prop on `SnowconeCanvas` over mounting a\n * `VisualGuideOverlay` — the canvas-native path draws inside the\n * render loop and avoids the coordinate-system drift of the DOM\n * overlay.\n */\nexport { renderPieceGuides } from '../rendering/PieceGuideRenderer.js';\nexport type {\n PieceGuideRenderParams,\n PieceGuideRendererPiece,\n PieceGuideRendererStyle,\n PieceGuideVisibility,\n} from '../rendering/PieceGuideRenderer.js';\nexport type { PieceGuidesConfig } from '../components/embed/SnowconeCanvas.js';\n\n/**\n * Fetch a product's printable-area guides (dead zones, safe areas) for a raw\n * `SnowconeCanvas` — the same overlay `<RealtimeEmbed>` shows automatically,\n * but for an AUTHORING surface (your own `SnowconeCanvas` + `useLayers` panel)\n * that doesn't go through the embed. Mirror of what www's route shell does:\n * fetch by gvid, then `pieceGuidesForArtboard(bundle, artboard)` (which gates\n * on the design's dims matching the print-file frame) → feed the result to the\n * canvas's `pieceGuides` prop. So the DESIGNER sees the camera cutout while\n * placing fields, not just the buyer afterward.\n */\nexport {\n usePieceGuideBundle,\n pieceGuidesForArtboard,\n PIECE_GUIDES_API_BASE,\n} from '../components/embed-realtime/usePieceGuideBundle.js';\nexport type { PieceGuideBundle } from '../components/embed-realtime/usePieceGuideBundle.js';\n"],"names":["Panel","title","icon","children","enableable","enabled","onEnabledChange","collapsible","expanded","onExpandChange","className","showContent","showExpandButton","handleToggle","handleExpandToggle","jsxs","jsx","ControlGroup","label","labelSuffix","inline","ButtonGroup","options","value","onChange","fullWidth","option","isActive","Toggle","checked","disabled","handleChange","e","Switch","SecondaryToolbar","isOpen","onToggle","trigger","Dropdown","Tooltip","TooltipTrigger","Button","Icon","TooltipContent","serializeKit","kit","json","loadKitFromJSON","overrides","base","resolveKit","extendKit","createKit","validation","validateKit","warning","logger","createLogger","useProjectLoader","handleLoadWorkspace","useEditor","isLoading","setIsLoading","useState","loadError","setLoadError","currentProject","setCurrentProject","projectMetadata","setProjectMetadata","loadFromJSON","useCallback","document","jsonString","blob","file","result","ImportManager","totalElements","sum","ab","_a","error","loadFromFile","text","loadTemplate","template","artboard","index","DEFAULT_BREAKPOINTS","getCurrentBreakpoint","width","config","useBreakpoint","breakpointConfig","setWidth","useEffect","handleResize","breakpoint","useMemo","isXs","isSm","isMd","isLg","isXl","isXxl","breakpointOrder","bp","currentIndex","targetIndex","DEFAULT_METRICS","usePerformance","metrics","setMetrics","isProfiling","setIsProfiling","frameTimesRef","useRef","renderTimesRef","exportTimesRef","lastFrameTimeRef","rafIdRef","calculateFPS","frameTimes","time","calculateAverage","values","val","getMemoryUsage","updateMetrics","newMetrics","measureFrame","now","frameTime","startProfiling","stopProfiling","clearMetrics","getReport","useElementById","id","getElementById","useLayerDragDrop","onReorder","onGroupReorder","onDropIntoGroup","onDropIntoGroupAtPosition","findElement","findParentGroup","draggedId","setDraggedId","dragOverId","setDragOverId","dropPosition","setDropPosition","handleDragStart","elementId","handleDragOver","rect","relativeY","height","isGroup","GroupElement","position","resetDragState","handleDrop","targetId","draggedParent","targetParent","arrayPosition","useLayerSelection","selectedId","multiSelection","onSelectionChange","onMultiSelectionChange","isSelected","handleClick","isInMultiSelection","isCurrentlySelected","newSelection","FontSizeDropdown","memo","fontSizes","handleSelect","size","Popover","PopoverTrigger","PopoverContent","FontSizeGroup","toolbar","fontSize","handleFontSizeIncrement","handleFontSizeChange","openDropdown","setOpenDropdown","clickProtection","el","handleClickOutside","event","target","FONT_SIZES","_customElementTypes","registerElementType","registration","registerTransform","unregisterElementType","type","unregisterTransform","getCustomElementTypes","isRegisteredElementType","getTransformById","CanvasEventBus","handler","handlers","wrapper","data","unsubscribe","err","canvasEventBus","useCanvasEvents","DEFAULT_STYLE","ANCHOR_TO_SVG","renderShape","path","commonProps","resolveBoundary","guide","naturalW","naturalH","svgPathD","rx","ry","x","y","w","h","resolveSafeArea","piece","inset","VisualGuideOverlay","artboardWidth","artboardHeight","canvasPadding","pieces","pieceGuides","show","style","s","autoLabelFontSize","merged","visibility","viewBoxWidth","viewBoxHeight","zones","labels","rotation","isOrtho","boundary","safeArea","originXform","rotationXform","transform","washRadius","boundaryForWash","washOuter","bleedFilterId","pieceRect","canRenderBleed","Fragment","z","zone","i","p","a","ensureIconsRegistered"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"advanced.mjs","sources":["../src/components/primitives/Panel.tsx","../src/components/primitives/ControlGroup.tsx","../src/components/primitives/ButtonGroup.tsx","../src/components/primitives/Toggle.tsx","../src/components/primitives/Switch.tsx","../src/components/primitives/SecondaryToolbar.tsx","../src/kits/serialization.ts","../src/hooks/useProjectLoader.ts","../src/hooks/useBreakpoint.ts","../src/hooks/usePerformance.ts","../src/hooks/useElementById.ts","../src/hooks/useLayerDragDrop.ts","../src/hooks/useLayerSelection.ts","../src/components/FontSizeDropdown.tsx","../src/components/text-toolbar/FontSizeGroup.tsx","../src/plugins/ElementTypePlugin.ts","../src/core/EventBus.ts","../src/hooks/useCanvasEvents.ts","../src/components/VisualGuideOverlay.tsx","../src/api/advanced.ts"],"sourcesContent":["/**\n * Panel - Collapsible panel with optional enable/disable toggle\n *\n * A reusable panel component that eliminates boilerplate for property panels.\n * Supports:\n * - Simple panel (no toggle, no expand)\n * - Collapsible panel (no toggle, with expand/collapse)\n * - Toggle panel (with enable/disable toggle and expand/collapse)\n *\n * @example\n * // Simple panel\n * <Panel title=\"Settings\">\n * <p>Panel content</p>\n * </Panel>\n *\n * @example\n * // Collapsible panel\n * <Panel title=\"Settings\" collapsible expanded={expanded} onExpandChange={setExpanded}>\n * <p>Panel content</p>\n * </Panel>\n *\n * @example\n * // Toggle panel (enable/disable + expand/collapse)\n * <Panel\n * title=\"Stroke\"\n * enableable\n * enabled={enabled}\n * onEnabledChange={setEnabled}\n * expanded={expanded}\n * onExpandChange={setExpanded}\n * >\n * <p>Panel content (only shown when enabled and expanded)</p>\n * </Panel>\n */\n\nimport React from 'react';\n\nexport interface PanelProps {\n /** Panel title */\n title: string;\n\n /** Panel icon (optional) */\n icon?: React.ReactNode;\n\n /** Panel content */\n children: React.ReactNode;\n\n /** Enable toggle functionality (checkbox to enable/disable) */\n enableable?: boolean;\n\n /** Enabled state (only used if enableable=true) */\n enabled?: boolean;\n\n /** Callback when enabled state changes */\n onEnabledChange?: (enabled: boolean) => void;\n\n /** Enable expand/collapse functionality */\n collapsible?: boolean;\n\n /** Expanded state (only used if collapsible=true or enableable=true) */\n expanded?: boolean;\n\n /** Callback when expanded state changes */\n onExpandChange?: (expanded: boolean) => void;\n\n /** Additional CSS classes */\n className?: string;\n}\n\nexport const Panel: React.FC<PanelProps> = ({\n title,\n icon,\n children,\n enableable = false,\n enabled = false,\n onEnabledChange,\n collapsible = false,\n expanded = true,\n onExpandChange,\n className = '',\n}) => {\n // Determine if content should be shown\n const showContent = enableable ? enabled && expanded : collapsible ? expanded : true;\n\n // Determine if expand button should be shown\n const showExpandButton = (enableable && enabled) || collapsible;\n\n const handleToggle = () => {\n if (onEnabledChange) {\n onEnabledChange(!enabled);\n }\n };\n\n const handleExpandToggle = () => {\n if (onExpandChange) {\n onExpandChange(!expanded);\n }\n };\n\n return (\n <div className={`sc:rounded-xl sc:border sc:border-border-primary sc:bg-bg-secondary sc:p-xl ${className}`}>\n {/* Header */}\n <div className=\"sc:flex sc:items-center sc:justify-between\">\n {/* Left side: toggle + title */}\n <div className=\"sc:flex sc:items-center sc:gap-md\">\n {enableable && (\n <label className=\"sc:flex sc:cursor-pointer sc:items-center\">\n <input\n type=\"checkbox\"\n checked={enabled}\n onChange={handleToggle}\n className=\"sc:h-4 sc:w-4 sc:cursor-pointer sc:rounded sc:border-border-primary sc:bg-bg-input sc:accent-accent-primary\"\n />\n </label>\n )}\n <div className=\"sc:flex sc:items-center sc:gap-md\">\n {icon && <span className=\"sc:flex sc:items-center sc:text-text-secondary\">{icon}</span>}\n <h3 className=\"sc:text-xl sc:font-semibold sc:text-text-primary\">{title}</h3>\n </div>\n </div>\n\n {/* Right side: expand button */}\n {showExpandButton && (\n <button\n onClick={handleExpandToggle}\n className=\"sc:rounded-lg sc:border-none sc:bg-transparent sc:px-md sc:py-xs sc:text-base sc:text-text-muted sc:transition-colors sc:duration-fast sc:hover:text-text-primary\"\n title={expanded ? 'Collapse' : 'Expand'}\n >\n {expanded ? '▼' : '▶'}\n </button>\n )}\n </div>\n\n {/* Content */}\n {showContent && <div className=\"sc:mt-xl sc:flex sc:flex-col sc:gap-xl\">{children}</div>}\n </div>\n );\n};\n\nexport default Panel;\n","/**\n * ControlGroup - Label + control wrapper with consistent spacing\n *\n * Provides a consistent layout for form controls with labels.\n * Eliminates repeated label + wrapper markup.\n *\n * @example\n * <ControlGroup label=\"Width\">\n * <input type=\"range\" ... />\n * </ControlGroup>\n *\n * @example\n * <ControlGroup label=\"Color\" inline>\n * <ColorPickerDropdown ... />\n * </ControlGroup>\n */\n\nimport React from 'react';\n\nexport interface ControlGroupProps {\n /** Label text */\n label: string;\n\n /** Optional label suffix (e.g., value display \"50px\") */\n labelSuffix?: React.ReactNode;\n\n /** Control element(s) */\n children: React.ReactNode;\n\n /** Inline layout (label and control side-by-side) */\n inline?: boolean;\n\n /** Additional CSS classes */\n className?: string;\n}\n\nexport const ControlGroup: React.FC<ControlGroupProps> = ({\n label,\n labelSuffix,\n children,\n inline = false,\n className = '',\n}) => {\n return (\n <div className={`sc:flex ${inline ? 'sc:items-center sc:justify-between' : 'sc:flex-col'} sc:gap-sm ${className}`}>\n {/* Label */}\n <label className=\"sc:flex sc:items-center sc:justify-between sc:text-base sc:font-medium sc:text-text-secondary\">\n <span>{label}</span>\n {labelSuffix && <span className=\"sc:font-semibold sc:text-accent-primary\">{labelSuffix}</span>}\n </label>\n\n {/* Control */}\n <div className={inline ? 'sc:flex-shrink-0' : ''}>{children}</div>\n </div>\n );\n};\n\nexport default ControlGroup;\n","/**\n * ButtonGroup - Group of buttons with active state\n *\n * A reusable button group for selecting one option from multiple choices.\n * Eliminates 10-15 lines of repeated button group markup.\n *\n * @example\n * <ButtonGroup\n * options={[\n * { value: 'butt', label: 'Butt', tooltip: 'Flat edges' },\n * { value: 'round', label: 'Round', tooltip: 'Rounded edges' },\n * { value: 'square', label: 'Square', tooltip: 'Extended square edges' }\n * ]}\n * value={lineCap}\n * onChange={setLineCap}\n * />\n */\n\nimport React from 'react';\n\nexport interface ButtonGroupOption<T = string> {\n /** Option value */\n value: T;\n\n /** Display label */\n label: string;\n\n /** Optional icon */\n icon?: React.ReactNode;\n\n /** Optional tooltip */\n tooltip?: string;\n\n /** Disabled state */\n disabled?: boolean;\n}\n\nexport interface ButtonGroupProps<T = string> {\n /** Available options */\n options: ButtonGroupOption<T>[];\n\n /** Current value */\n value: T;\n\n /** Callback when value changes */\n onChange: (value: T) => void;\n\n /** Full width buttons */\n fullWidth?: boolean;\n\n /** Additional CSS classes */\n className?: string;\n}\n\nexport function ButtonGroup<T = string>({\n options,\n value,\n onChange,\n fullWidth = true,\n className = '',\n}: ButtonGroupProps<T>) {\n return (\n <div className={`sc:flex sc:gap-sm ${className}`}>\n {options.map((option) => {\n const isActive = option.value === value;\n\n return (\n <button\n key={String(option.value)}\n onClick={() => !option.disabled && onChange(option.value)}\n disabled={option.disabled}\n title={option.tooltip}\n className={` ${fullWidth ? 'sc:flex-1' : ''} sc:flex sc:items-center sc:justify-center sc:gap-xs sc:rounded-lg sc:border sc:px-md sc:py-sm sc:text-base sc:font-medium sc:transition-all sc:duration-fast ${\n isActive\n ? 'sc:border-accent-primary sc:bg-accent-primary sc:text-text-on-accent'\n : 'sc:border-border-primary sc:bg-bg-tertiary sc:text-text-secondary sc:hover:border-border-secondary sc:hover:bg-bg-hover sc:hover:text-text-primary'\n } ${option.disabled ? 'sc:cursor-not-allowed sc:opacity-disabled' : 'sc:cursor-pointer'} `}\n >\n {option.icon && <span className=\"sc:flex sc:items-center\">{option.icon}</span>}\n <span>{option.label}</span>\n </button>\n );\n })}\n </div>\n );\n}\n\nexport default ButtonGroup;\n","/**\n * Toggle - Styled checkbox for enable/disable controls\n *\n * A consistent toggle/checkbox component with label.\n * Eliminates 5-10 lines of repeated toggle markup.\n *\n * @example\n * <Toggle\n * label=\"Enable Stroke\"\n * checked={enabled}\n * onChange={setEnabled}\n * />\n *\n * @example\n * <Toggle\n * label=\"Flip Horizontal\"\n * checked={flipH}\n * onChange={setFlipH}\n * icon={<LuFlipHorizontal2 />}\n * />\n */\n\nimport React from 'react';\n\nexport interface ToggleProps {\n /** Label text */\n label: string;\n\n /** Optional icon */\n icon?: React.ReactNode;\n\n /** Checked state */\n checked: boolean;\n\n /** Callback when checked state changes */\n onChange: (checked: boolean) => void;\n\n /** Disabled state */\n disabled?: boolean;\n\n /** Additional CSS classes */\n className?: string;\n}\n\nexport const Toggle: React.FC<ToggleProps> = ({ label, icon, checked, onChange, disabled = false, className = '' }) => {\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n onChange(e.target.checked);\n };\n\n return (\n <label\n className={`sc:flex sc:cursor-pointer sc:items-center sc:gap-md ${disabled ? 'sc:cursor-not-allowed sc:opacity-disabled' : ''} ${className}`}\n >\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={handleChange}\n disabled={disabled}\n className=\"sc:h-4 sc:w-4 sc:cursor-pointer sc:rounded sc:border-border-primary sc:bg-bg-input sc:accent-accent-primary sc:disabled:cursor-not-allowed\"\n />\n <span className=\"sc:flex sc:items-center sc:gap-sm sc:text-base sc:font-medium sc:text-text-primary\">\n {icon && <span className=\"sc:flex sc:items-center sc:text-text-secondary\">{icon}</span>}\n <span>{label}</span>\n </span>\n </label>\n );\n};\n\nexport default Toggle;\n","/**\n * Switch - Toggle switch component\n *\n * Features:\n * - Accessible checkbox input\n * - Smooth animation\n * - Fixed border radius (not affected by theme)\n * - Support for labels\n *\n * Usage:\n * ```tsx\n * <Switch\n * checked={isEnabled}\n * onChange={setIsEnabled}\n * label=\"Enable feature\"\n * />\n * ```\n */\n\nimport React from 'react';\n\nexport interface SwitchProps {\n checked: boolean;\n onChange: (checked: boolean) => void;\n label?: string;\n disabled?: boolean;\n className?: string;\n}\n\nconst Switch: React.FC<SwitchProps> = ({\n checked,\n onChange,\n label,\n disabled = false,\n className = '',\n}) => {\n const handleChange = () => {\n if (!disabled) {\n onChange(!checked);\n }\n };\n\n return (\n <label\n className={`sc:flex sc:items-center sc:gap-md sc:cursor-pointer sc:select-none ${disabled ? 'sc:opacity-50 sc:cursor-not-allowed' : ''} ${className}`.trim()}\n >\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={handleChange}\n disabled={disabled}\n className=\"sc:absolute sc:opacity-0 sc:w-0 sc:h-0 sc:peer\"\n />\n <span\n className={`\n sc:relative sc:inline-block sc:w-9 sc:h-5 sc:rounded-[999px] sc:transition-colors\n ${checked ? 'sc:bg-accent-primary' : 'sc:bg-bg-tertiary'}\n sc:after:content-[''] sc:after:absolute sc:after:top-[2px] sc:after:left-[2px]\n sc:after:w-4 sc:after:h-4 sc:after:bg-white sc:after:rounded-full\n sc:after:transition-transform sc:after:shadow-md\n ${checked ? 'sc:after:translate-x-4' : ''}\n `.trim()}\n />\n {label && <span className=\"sc:text-md sc:text-text-primary sc:font-medium\">{label}</span>}\n </label>\n );\n};\n\nexport default Switch;\n","/**\n * SecondaryToolbar - Overflow toolbar for uncommonly used controls\n *\n * Renders as a proper toolbar positioned below the primary toolbar,\n * right-aligned with a gap above it. Used for 3-dot overflow menus.\n *\n * Features:\n * - Same height and styling as primary toolbar\n * - Positioned below trigger, right-aligned\n * - Gap between primary and secondary toolbar\n * - Click-outside handling via Dropdown primitive\n *\n * Usage:\n * ```tsx\n * <SecondaryToolbar\n * isOpen={showMore}\n * onToggle={setShowMore}\n * trigger={<Button variant=\"toolbar\" icon={<DotsIcon />} />}\n * >\n * <Button variant=\"toolbar\" icon={<Icon />} onClick={handler} />\n * <Switch checked={value} onChange={handler} label=\"Label\" />\n * </SecondaryToolbar>\n * ```\n */\n\nimport React from 'react';\nimport { Icon } from '@iconify/react';\nimport { Button, Tooltip, TooltipTrigger, TooltipContent } from '../ui';\nimport { Dropdown } from './index';\n\nexport interface SecondaryToolbarProps {\n isOpen: boolean;\n onToggle: (open: boolean) => void;\n trigger?: React.ReactNode;\n children: React.ReactNode;\n}\n\nconst SecondaryToolbar: React.FC<SecondaryToolbarProps> = ({\n isOpen,\n onToggle,\n trigger,\n children,\n}) => {\n // Default trigger: 3-dot icon button with tooltip\n // The Button has its own onClick to ensure clicks are captured even when\n // wrapped by Radix's TooltipTrigger which can interfere with event bubbling\n const defaultTrigger = (\n <Tooltip delayDuration={0}>\n <TooltipTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"toolbar\"\n onClick={(e) => {\n // Stop propagation to prevent Dropdown's wrapper div from double-toggling\n e.stopPropagation();\n onToggle(!isOpen);\n }}\n >\n <Icon icon=\"lucide:ellipsis-vertical\" className=\"sc:size-6\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent>\n <p>More</p>\n </TooltipContent>\n </Tooltip>\n );\n\n return (\n <Dropdown\n isOpen={isOpen}\n onToggle={onToggle}\n trigger={trigger || defaultTrigger}\n position=\"bottom\"\n align=\"end\"\n offset={4}\n >\n {/* Secondary toolbar - matches primary toolbar styling */}\n <div className=\"sc:flex sc:items-center sc:gap-sm sc:bg-bg-primary sc:border sc:border-border-primary sc:rounded-xl sc:px-xl sc:py-md sc:shadow-sm sc:h-[52px]\">\n {children}\n </div>\n </Dropdown>\n );\n};\n\nexport default SecondaryToolbar;\n","/**\n * Kit Serialization - JSON import/export for kit definitions\n *\n * Provides functions to serialize a KitDefinition to a JSON-safe format\n * and to load a KitDefinition from JSON. Supports extending base presets\n * via the `extends` field, making it easy to persist and share kit\n * configurations.\n *\n * @example Serialize a kit to JSON\n * ```ts\n * import { serializeKit, COMPACT_CUSTOMIZER } from '@snowcone-app/canvas';\n *\n * const json = serializeKit(COMPACT_CUSTOMIZER);\n * const stored = JSON.stringify(json);\n * ```\n *\n * @example Load a kit from JSON\n * ```ts\n * import { loadKitFromJSON } from '@snowcone-app/canvas';\n *\n * const kit = loadKitFromJSON({\n * name: 'my-custom-kit',\n * extends: 'compact-customizer',\n * capabilities: { effects: ['stroke', 'mask'] },\n * behavior: { autoExport: true },\n * });\n * ```\n */\n\nimport type { EditorCapabilities } from '../types/capabilities.js';\nimport type { KitDefinition, KitPresetId } from './types.js';\nimport { extendKit, createKit } from './compose.js';\nimport { resolveKit } from './registry.js';\nimport { validateKit } from './validation.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * JSON-safe representation of a KitDefinition.\n *\n * When `extends` is provided, the kit is built by resolving the base\n * preset and applying the remaining fields as overrides. When `extends`\n * is omitted, a new kit is created from scratch using `createKit`.\n */\nexport interface KitJSON {\n /** Kit name (required) */\n name: string;\n /** Optional base preset to extend from */\n extends?: KitPresetId;\n /** Partial capability overrides */\n capabilities?: Partial<EditorCapabilities>;\n /** Partial behavior overrides */\n behavior?: Partial<KitDefinition['behavior']>;\n /** Partial layout overrides */\n layout?: Partial<KitDefinition['layout']>;\n /** Partial style overrides */\n style?: Partial<KitDefinition['style']>;\n /** Content configuration */\n content?: KitDefinition['content'];\n}\n\n// ---------------------------------------------------------------------------\n// Serialization\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize a KitDefinition to a JSON-safe format.\n *\n * The resulting object can be safely passed to `JSON.stringify()` and\n * later restored with `loadKitFromJSON()`. All fields from the kit\n * are included in the output for a complete, self-contained representation.\n *\n * @param kit - The KitDefinition to serialize\n * @returns A JSON-safe KitJSON object\n *\n * @example\n * ```ts\n * const json = serializeKit(myKit);\n * localStorage.setItem('kit', JSON.stringify(json));\n * ```\n */\nexport function serializeKit(kit: KitDefinition): KitJSON {\n const json: KitJSON = {\n name: kit.name,\n capabilities: {\n elements: [...kit.capabilities.elements],\n transforms: [...kit.capabilities.transforms],\n effects: [...kit.capabilities.effects],\n panels: [...kit.capabilities.panels],\n tools: [...kit.capabilities.tools],\n features: { ...kit.capabilities.features },\n },\n behavior: { ...kit.behavior },\n layout: {\n slots: {\n ...(kit.layout.slots.topbar && { topbar: [...kit.layout.slots.topbar] }),\n ...(kit.layout.slots.left && { left: [...kit.layout.slots.left] }),\n ...(kit.layout.slots.canvas && { canvas: [...kit.layout.slots.canvas] }),\n ...(kit.layout.slots.right && { right: [...kit.layout.slots.right] }),\n ...(kit.layout.slots.bottombar && { bottombar: [...kit.layout.slots.bottombar] }),\n },\n },\n style: { ...kit.style },\n };\n\n if (kit.content) {\n json.content = { ...kit.content };\n }\n\n return json;\n}\n\n// ---------------------------------------------------------------------------\n// Deserialization\n// ---------------------------------------------------------------------------\n\n/**\n * Load a KitDefinition from a JSON representation.\n *\n * If `extends` is provided, the corresponding preset is resolved and the\n * remaining fields are applied as overrides via `extendKit`. If `extends`\n * is omitted, a new kit is created from scratch via `createKit`.\n *\n * The result is validated with `validateKit`. If validation produces\n * errors (fatal issues), an error is thrown. Warnings are logged to\n * the console but do not prevent loading.\n *\n * @param json - The KitJSON object to deserialize\n * @returns A complete, validated KitDefinition\n * @throws {Error} If the kit name is missing\n * @throws {Error} If a preset ID in `extends` is invalid\n * @throws {Error} If the resulting kit fails validation (has errors)\n *\n * @example Extend a preset\n * ```ts\n * const kit = loadKitFromJSON({\n * name: 'my-pro-variant',\n * extends: 'pro-studio',\n * behavior: { autoExport: true },\n * });\n * ```\n *\n * @example Create from scratch\n * ```ts\n * const kit = loadKitFromJSON({\n * name: 'minimal-editor',\n * capabilities: { elements: ['text'] },\n * behavior: { toolbar: 'none', selection: 'single', panelMode: 'inline', onEmptyClick: 'noop', autoExport: false },\n * });\n * ```\n */\nexport function loadKitFromJSON(json: KitJSON): KitDefinition {\n if (!json.name || json.name.trim() === '') {\n throw new Error('[loadKitFromJSON] Kit name is required');\n }\n\n // Build overrides object from the JSON fields (excluding name and extends)\n const overrides: Record<string, unknown> = {};\n if (json.capabilities) overrides.capabilities = json.capabilities;\n if (json.behavior) overrides.behavior = json.behavior;\n if (json.layout) overrides.layout = json.layout;\n if (json.style) overrides.style = json.style;\n if (json.content) overrides.content = json.content;\n\n let kit: KitDefinition;\n\n if (json.extends) {\n // Resolve the base preset and apply overrides\n const base = resolveKit(json.extends);\n kit = extendKit(base, { name: json.name, ...overrides });\n } else {\n // Create from scratch with defaults\n kit = createKit({ name: json.name, ...overrides });\n }\n\n // Validate the result\n const validation = validateKit(kit);\n\n if (validation.warnings.length > 0) {\n for (const warning of validation.warnings) {\n console.warn(`[loadKitFromJSON] Warning: ${warning}`);\n }\n }\n\n if (!validation.valid) {\n throw new Error(\n `[loadKitFromJSON] Kit \"${json.name}\" failed validation:\\n${validation.errors.join('\\n')}`\n );\n }\n\n return kit;\n}\n","/**\n * useProjectLoader - Hook for loading saved projects programmatically\n * Supports loading from JSON, files, and templates\n */\n\nimport { useState, useCallback } from 'react';\nimport { useEditor } from '../contexts/EditorContext.js';\nimport { ImportManager } from '../utils/ImportManager.js';\nimport { createLogger } from '../utils/logger.js';\nimport type { CanvasDocument } from '../utils/ExportManager.js';\nimport type { AnyElementConfig } from '../types/index.js';\n\nconst logger = createLogger('useProjectLoader');\n\nexport interface ProjectTemplate {\n name: string;\n description?: string;\n artboards: Array<{\n name: string;\n width: number;\n height: number;\n backgroundColor: string;\n elements?: AnyElementConfig[];\n }>;\n}\n\nexport interface ProjectMetadata {\n version: string;\n timestamp: string;\n artboardCount: number;\n elementCount: number;\n}\n\nexport interface UseProjectLoaderReturn {\n // Load from JSON\n loadFromJSON: (json: string | CanvasDocument) => Promise<void>;\n\n // Load from file\n loadFromFile: (file: File) => Promise<void>;\n\n // Create from template\n loadTemplate: (template: ProjectTemplate) => Promise<void>;\n\n // State\n isLoading: boolean;\n loadError: Error | null;\n currentProject: CanvasDocument | null;\n\n // Metadata\n projectMetadata: ProjectMetadata | null;\n}\n\n/**\n * Hook for loading saved projects\n * Must be used within EditorProvider\n *\n * @example Load from API\n * ```tsx\n * function ProductCustomizer({ productId }: { productId: string }) {\n * const { loadFromJSON, isLoading } = useProjectLoader();\n *\n * useEffect(() => {\n * fetch(`/api/designs/${productId}`)\n * .then(res => res.json())\n * .then(design => loadFromJSON(design))\n * .catch(console.error);\n * }, [productId]);\n *\n * if (isLoading) return <div>Loading design...</div>;\n *\n * return <Canvas />;\n * }\n * ```\n *\n * @example Load from template\n * ```tsx\n * function TemplateGallery() {\n * const { loadTemplate } = useProjectLoader();\n *\n * const handleSelectTemplate = (template: ProjectTemplate) => {\n * loadTemplate(template);\n * };\n *\n * return <div>Template gallery...</div>;\n * }\n * ```\n */\nexport function useProjectLoader(): UseProjectLoaderReturn {\n const {\n handleLoadWorkspace,\n } = useEditor();\n\n const [isLoading, setIsLoading] = useState(false);\n const [loadError, setLoadError] = useState<Error | null>(null);\n const [currentProject, setCurrentProject] = useState<CanvasDocument | null>(null);\n const [projectMetadata, setProjectMetadata] = useState<ProjectMetadata | null>(null);\n\n /**\n * Load from JSON string or CanvasDocument object\n */\n const loadFromJSON = useCallback(async (json: string | CanvasDocument) => {\n setIsLoading(true);\n setLoadError(null);\n\n try {\n // Parse JSON if it's a string\n const document: CanvasDocument = typeof json === 'string' ? JSON.parse(json) : json;\n\n // Validate document structure\n if (!document.artboards || !Array.isArray(document.artboards)) {\n throw new Error('Invalid document format: missing artboards array');\n }\n\n // Import the document using ImportManager\n // Note: We need to create a JSON string and then parse it\n const jsonString = JSON.stringify(document);\n const blob = new Blob([jsonString], { type: 'application/json' });\n const file = new File([blob], 'project.json', { type: 'application/json' });\n\n const result = await ImportManager.importFromJSON(file);\n\n if (!result.success || !result.artboards || !result.elements) {\n throw new Error(result.error || 'Failed to import project');\n }\n\n // Load into editor\n handleLoadWorkspace(result.artboards, result.elements, result.activeArtboardId || null);\n\n // Update state\n setCurrentProject(document);\n\n // Calculate metadata\n const totalElements = document.artboards.reduce((sum, ab) => sum + (ab.elements?.length || 0), 0);\n setProjectMetadata({\n version: document.metadata.version,\n timestamp: document.metadata.timestamp,\n artboardCount: document.artboards.length,\n elementCount: totalElements,\n });\n\n } catch (error) {\n logger.error('[useProjectLoader] Failed to load project:', error);\n setLoadError(error as Error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n }, [handleLoadWorkspace]);\n\n /**\n * Load from file (File API)\n */\n const loadFromFile = useCallback(async (file: File) => {\n setIsLoading(true);\n setLoadError(null);\n\n try {\n // Read file as text\n const text = await file.text();\n\n // Load from JSON\n await loadFromJSON(text);\n } catch (error) {\n logger.error('[useProjectLoader] Failed to load file:', error);\n setLoadError(error as Error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n }, [loadFromJSON]);\n\n /**\n * Create from template\n * Templates define artboards with optional default elements\n */\n const loadTemplate = useCallback(async (template: ProjectTemplate) => {\n setIsLoading(true);\n setLoadError(null);\n\n try {\n // Convert template to CanvasDocument format\n const document: CanvasDocument = {\n metadata: {\n version: '2.0.0',\n timestamp: new Date().toISOString(),\n appVersion: '1.0.0',\n },\n artboards: template.artboards.map((artboard, index) => ({\n id: `artboard-${Date.now()}-${index}`,\n name: artboard.name,\n x: 0,\n y: 0,\n width: artboard.width,\n height: artboard.height,\n backgroundColor: artboard.backgroundColor,\n elements: artboard.elements || [],\n })),\n activeArtboardId: null, // First artboard will be active by default\n };\n\n // Load the document\n await loadFromJSON(document);\n\n } catch (error) {\n logger.error('[useProjectLoader] Failed to load template:', error);\n setLoadError(error as Error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n }, [loadFromJSON]);\n\n return {\n loadFromJSON,\n loadFromFile,\n loadTemplate,\n isLoading,\n loadError,\n currentProject,\n projectMetadata,\n };\n}\n","/**\n * useBreakpoint - Hook for responsive breakpoint detection\n * Provides responsive utilities for building adaptive UIs\n *\n * @example\n * ```tsx\n * function ResponsiveLayout() {\n * const { isAtLeast, breakpoint } = useBreakpoint();\n *\n * return (\n * <div>\n * {isAtLeast('md') ? (\n * <div className=\"desktop-layout\">\n * <LayersPanel />\n * <Canvas />\n * <ExportPanel />\n * </div>\n * ) : (\n * <div className=\"mobile-layout\">\n * <Canvas />\n * </div>\n * )}\n * <p>Current breakpoint: {breakpoint}</p>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useState, useEffect, useMemo } from 'react';\n\nexport interface BreakpointConfig {\n xs?: number; // Extra small (default: 320)\n sm?: number; // Small (default: 480)\n md?: number; // Medium (default: 768)\n lg?: number; // Large (default: 1024)\n xl?: number; // Extra large (default: 1280)\n xxl?: number; // Extra extra large (default: 1536)\n}\n\nexport type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';\n\nexport interface UseBreakpointReturn {\n breakpoint: Breakpoint;\n width: number;\n isXs: boolean;\n isSm: boolean;\n isMd: boolean;\n isLg: boolean;\n isXl: boolean;\n isXxl: boolean;\n isAtLeast: (bp: Breakpoint) => boolean;\n isAtMost: (bp: Breakpoint) => boolean;\n}\n\nconst DEFAULT_BREAKPOINTS: Required<BreakpointConfig> = {\n xs: 320,\n sm: 480,\n md: 768,\n lg: 1024,\n xl: 1280,\n xxl: 1536,\n};\n\n/**\n * Get current breakpoint based on window width\n */\nfunction getCurrentBreakpoint(width: number, config: Required<BreakpointConfig>): Breakpoint {\n if (width >= config.xxl) return 'xxl';\n if (width >= config.xl) return 'xl';\n if (width >= config.lg) return 'lg';\n if (width >= config.md) return 'md';\n if (width >= config.sm) return 'sm';\n return 'xs';\n}\n\n/**\n * Hook for responsive breakpoint detection\n */\nexport function useBreakpoint(config?: BreakpointConfig): UseBreakpointReturn {\n const breakpointConfig: Required<BreakpointConfig> = {\n ...DEFAULT_BREAKPOINTS,\n ...config,\n };\n\n const [width, setWidth] = useState<number>(() => {\n if (typeof window !== 'undefined') {\n return window.innerWidth;\n }\n return DEFAULT_BREAKPOINTS.lg; // Default for SSR\n });\n\n // Update width on window resize\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const handleResize = () => {\n setWidth(window.innerWidth);\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, []);\n\n // Calculate current breakpoint\n const breakpoint = useMemo(\n () => getCurrentBreakpoint(width, breakpointConfig),\n [width, breakpointConfig]\n );\n\n // Boolean helpers for each breakpoint\n const isXs = breakpoint === 'xs';\n const isSm = breakpoint === 'sm';\n const isMd = breakpoint === 'md';\n const isLg = breakpoint === 'lg';\n const isXl = breakpoint === 'xl';\n const isXxl = breakpoint === 'xxl';\n\n // Breakpoint ordering for comparisons\n const breakpointOrder: Breakpoint[] = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];\n\n // Check if current breakpoint is at least the specified breakpoint\n const isAtLeast = (bp: Breakpoint): boolean => {\n const currentIndex = breakpointOrder.indexOf(breakpoint);\n const targetIndex = breakpointOrder.indexOf(bp);\n return currentIndex >= targetIndex;\n };\n\n // Check if current breakpoint is at most the specified breakpoint\n const isAtMost = (bp: Breakpoint): boolean => {\n const currentIndex = breakpointOrder.indexOf(breakpoint);\n const targetIndex = breakpointOrder.indexOf(bp);\n return currentIndex <= targetIndex;\n };\n\n return {\n breakpoint,\n width,\n isXs,\n isSm,\n isMd,\n isLg,\n isXl,\n isXxl,\n isAtLeast,\n isAtMost,\n };\n}\n","/**\n * usePerformance - Hook for performance monitoring\n * Provides metrics for tracking editor performance\n *\n * @example\n * ```tsx\n * function PerformanceMonitor() {\n * const { metrics, startProfiling } = usePerformance();\n *\n * useEffect(() => {\n * startProfiling();\n * }, []);\n *\n * if (metrics.fps < 30) {\n * console.warn('Low FPS:', metrics.fps);\n * }\n *\n * return (\n * <div>\n * <p>FPS: {Math.round(metrics.fps)}</p>\n * <p>Render Time: {metrics.avgRenderTime.toFixed(2)}ms</p>\n * <p>Elements: {metrics.elementCount}</p>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useState, useCallback, useRef, useEffect } from 'react';\n\nexport interface PerformanceMetrics {\n fps: number;\n avgRenderTime: number;\n peakRenderTime: number;\n memoryUsage: number;\n elementCount: number;\n exportCount: number;\n avgExportTime: number;\n}\n\nexport interface UsePerformanceReturn {\n metrics: PerformanceMetrics;\n startProfiling: () => void;\n stopProfiling: () => void;\n clearMetrics: () => void;\n getReport: () => string;\n isProfiling: boolean;\n}\n\nconst DEFAULT_METRICS: PerformanceMetrics = {\n fps: 0,\n avgRenderTime: 0,\n peakRenderTime: 0,\n memoryUsage: 0,\n elementCount: 0,\n exportCount: 0,\n avgExportTime: 0,\n};\n\n/**\n * Hook for performance monitoring\n */\nexport function usePerformance(): UsePerformanceReturn {\n const [metrics, setMetrics] = useState<PerformanceMetrics>(DEFAULT_METRICS);\n const [isProfiling, setIsProfiling] = useState(false);\n\n const frameTimesRef = useRef<number[]>([]);\n const renderTimesRef = useRef<number[]>([]);\n const exportTimesRef = useRef<number[]>([]);\n const lastFrameTimeRef = useRef<number>(performance.now());\n const rafIdRef = useRef<number | null>(null);\n\n // Calculate FPS from frame times\n const calculateFPS = useCallback((frameTimes: number[]): number => {\n if (frameTimes.length === 0) return 0;\n const avg = frameTimes.reduce((sum, time) => sum + time, 0) / frameTimes.length;\n return 1000 / avg; // Convert to FPS\n }, []);\n\n // Calculate average\n const calculateAverage = useCallback((values: number[]): number => {\n if (values.length === 0) return 0;\n return values.reduce((sum, val) => sum + val, 0) / values.length;\n }, []);\n\n // Get memory usage (if available)\n const getMemoryUsage = useCallback((): number => {\n if ('memory' in performance && (performance as { memory?: { usedJSHeapSize: number } }).memory) {\n const memory = (performance as { memory: { usedJSHeapSize: number } }).memory;\n return memory.usedJSHeapSize / (1024 * 1024); // Convert to MB\n }\n return 0;\n }, []);\n\n // Update metrics\n const updateMetrics = useCallback(() => {\n const newMetrics: PerformanceMetrics = {\n fps: calculateFPS(frameTimesRef.current),\n avgRenderTime: calculateAverage(renderTimesRef.current),\n peakRenderTime: Math.max(...renderTimesRef.current, 0),\n memoryUsage: getMemoryUsage(),\n elementCount: 0, // Would need to be updated by editor context\n exportCount: exportTimesRef.current.length,\n avgExportTime: calculateAverage(exportTimesRef.current),\n };\n\n setMetrics(newMetrics);\n }, [calculateFPS, calculateAverage, getMemoryUsage]);\n\n // Frame measurement loop\n const measureFrame = useCallback(() => {\n const now = performance.now();\n const frameTime = now - lastFrameTimeRef.current;\n lastFrameTimeRef.current = now;\n\n // Add frame time\n frameTimesRef.current.push(frameTime);\n if (frameTimesRef.current.length > 60) {\n frameTimesRef.current.shift(); // Keep last 60 frames (1 second at 60fps)\n }\n\n // Update metrics every 30 frames (~500ms at 60fps)\n if (frameTimesRef.current.length % 30 === 0) {\n updateMetrics();\n }\n\n if (isProfiling) {\n rafIdRef.current = requestAnimationFrame(measureFrame);\n }\n }, [isProfiling, updateMetrics]);\n\n // Start profiling\n const startProfiling = useCallback(() => {\n setIsProfiling(true);\n lastFrameTimeRef.current = performance.now();\n rafIdRef.current = requestAnimationFrame(measureFrame);\n }, [measureFrame]);\n\n // Stop profiling\n const stopProfiling = useCallback(() => {\n setIsProfiling(false);\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n rafIdRef.current = null;\n }\n }, []);\n\n // Clear metrics\n const clearMetrics = useCallback(() => {\n frameTimesRef.current = [];\n renderTimesRef.current = [];\n exportTimesRef.current = [];\n setMetrics(DEFAULT_METRICS);\n }, []);\n\n // Get human-readable report\n const getReport = useCallback((): string => {\n return `\nPerformance Report\n==================\nFPS: ${metrics.fps.toFixed(1)}\nAvg Render Time: ${metrics.avgRenderTime.toFixed(2)}ms\nPeak Render Time: ${metrics.peakRenderTime.toFixed(2)}ms\nMemory Usage: ${metrics.memoryUsage.toFixed(1)}MB\nElement Count: ${metrics.elementCount}\nExport Count: ${metrics.exportCount}\nAvg Export Time: ${metrics.avgExportTime.toFixed(2)}ms\n `.trim();\n }, [metrics]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n };\n }, []);\n\n return {\n metrics,\n startProfiling,\n stopProfiling,\n clearMetrics,\n getReport,\n isProfiling,\n };\n}\n","import { useEditor } from '../contexts/EditorContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/**\n * Returns a specific element by its ID, or null if not found.\n *\n * Useful for components that need to display or react to a particular\n * element without subscribing to the full editor state.\n *\n * @param id - The element ID to look up, or null to skip the lookup.\n * @returns The matching element, or null if the ID is null or no element matches.\n *\n * @example\n * ```tsx\n * function ElementInspector({ elementId }: { elementId: string }) {\n * const element = useElementById(elementId);\n * if (!element) return <p>Element not found</p>;\n * return <p>Position: ({element.x}, {element.y})</p>;\n * }\n * ```\n */\nexport function useElementById(id: string | null): EditorElement | null {\n const { getElementById } = useEditor();\n if (!id) return null;\n return getElementById(id) ?? null;\n}\n","/**\n * useLayerDragDrop - Hook for layer drag-drop reordering\n *\n * Extracts drag-drop reordering logic from LayersPanel.tsx into a\n * reusable hook. Handles drag state management, drop position\n * calculation (before/after/into for groups), and dispatches to\n * the appropriate reorder callback.\n *\n * @example\n * ```tsx\n * function LayersList({ elements, onReorder }) {\n * const { draggedId, dragOverId, dropPosition, handlers } = useLayerDragDrop({\n * onReorder,\n * findElement: (id) => elements.find(e => e.id === id) ?? null,\n * findParentGroup: (id) => findGroupParent(elements, id),\n * });\n *\n * return elements.map(el => (\n * <div\n * key={el.id}\n * draggable\n * onDragStart={(e) => handlers.onDragStart(e, el.id)}\n * onDragOver={(e) => handlers.onDragOver(e, el.id)}\n * onDrop={(e) => handlers.onDrop(e, el.id)}\n * onDragEnd={handlers.onDragEnd}\n * />\n * ));\n * }\n * ```\n */\n\nimport { useState, useCallback } from 'react';\nimport { GroupElement } from '../core/GroupElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\nexport interface UseLayerDragDropOptions {\n /**\n * Called when reordering top-level elements or elements in different groups.\n * Position is already inverted for reversed display order.\n */\n onReorder: (draggedId: string, targetId: string, position: 'before' | 'after') => void;\n\n /**\n * Called when reordering elements within the same group.\n * Position is already inverted for reversed display order.\n */\n onGroupReorder?: (\n draggedId: string,\n targetId: string,\n position: 'before' | 'after',\n groupId: string\n ) => void;\n\n /**\n * Called when dropping an element into a group (center drop zone).\n */\n onDropIntoGroup?: (draggedId: string, groupId: string) => void;\n\n /**\n * Called when dropping a top-level element between children of a group.\n * Position is already inverted for reversed display order.\n */\n onDropIntoGroupAtPosition?: (\n draggedId: string,\n groupId: string,\n targetChildId: string,\n position: 'before' | 'after'\n ) => void;\n\n /**\n * Look up an element by ID. Required for determining if a target is a group.\n */\n findElement?: (id: string) => EditorElement | null;\n\n /**\n * Find the parent group of an element. Required for intra-group reordering.\n */\n findParentGroup?: (id: string) => GroupElement | null;\n}\n\nexport interface UseLayerDragDropReturn {\n /** ID of the currently dragged element, or null */\n draggedId: string | null;\n /** ID of the element being dragged over, or null */\n dragOverId: string | null;\n /** Where the dragged element will be dropped relative to the target */\n dropPosition: 'before' | 'after' | 'into' | null;\n /** Event handlers to attach to draggable layer items */\n handlers: {\n onDragStart: (e: React.DragEvent, elementId: string) => void;\n onDragOver: (e: React.DragEvent, elementId: string) => void;\n onDrop: (e: React.DragEvent, targetId: string) => void;\n onDragEnd: () => void;\n };\n}\n\n/**\n * Hook for managing drag-drop reordering in layer panels.\n *\n * Calculates drop position based on mouse position within the target element:\n * - For groups: top 25% = before, middle 50% = into, bottom 25% = after\n * - For non-groups: top/bottom split at 40%/60%\n *\n * Handles the display-order inversion: layers are displayed in reverse\n * order (topmost layer first), so visual \"before\" maps to array \"after\"\n * and vice versa. This inversion is applied before calling the callbacks.\n */\nexport function useLayerDragDrop(options: UseLayerDragDropOptions): UseLayerDragDropReturn {\n const {\n onReorder,\n onGroupReorder,\n onDropIntoGroup,\n onDropIntoGroupAtPosition,\n findElement,\n findParentGroup,\n } = options;\n\n const [draggedId, setDraggedId] = useState<string | null>(null);\n const [dragOverId, setDragOverId] = useState<string | null>(null);\n const [dropPosition, setDropPosition] = useState<'before' | 'after' | 'into' | null>(null);\n\n const handleDragStart = useCallback((e: React.DragEvent, elementId: string) => {\n e.dataTransfer.effectAllowed = 'move';\n // Delay setting dragged state until after browser captures drag image\n requestAnimationFrame(() => {\n setDraggedId(elementId);\n });\n }, []);\n\n const handleDragOver = useCallback(\n (e: React.DragEvent, elementId: string) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n\n const rect = e.currentTarget.getBoundingClientRect();\n const mouseY = e.clientY;\n const relativeY = mouseY - rect.top;\n const height = rect.height;\n\n // Check if target is a group\n const targetElement = findElement?.(elementId);\n const isGroup = targetElement instanceof GroupElement;\n\n let position: 'before' | 'after' | 'into';\n\n if (isGroup) {\n // For groups: top 25% = before, bottom 25% = after, middle 50% = into\n if (relativeY < height * 0.25) {\n position = 'before';\n } else if (relativeY > height * 0.75) {\n position = 'after';\n } else {\n position = 'into';\n }\n } else {\n // For non-groups: top 40% = before, bottom 40% = after, middle = split\n if (relativeY < height * 0.4) {\n position = 'before';\n } else if (relativeY > height * 0.6) {\n position = 'after';\n } else {\n position = relativeY < height / 2 ? 'before' : 'after';\n }\n }\n\n setDragOverId(elementId);\n setDropPosition(position);\n },\n [findElement]\n );\n\n const resetDragState = useCallback(() => {\n setDraggedId(null);\n setDragOverId(null);\n setDropPosition(null);\n }, []);\n\n const handleDrop = useCallback(\n (e: React.DragEvent, targetId: string) => {\n e.preventDefault();\n if (!draggedId || draggedId === targetId || !dropPosition) {\n resetDragState();\n return;\n }\n\n if (dropPosition === 'into') {\n // Dropping into a group\n if (onDropIntoGroup) {\n onDropIntoGroup(draggedId, targetId);\n }\n } else {\n // Reordering (before/after)\n // Check if both elements are children of the same group\n const draggedParent = findParentGroup?.(draggedId);\n const targetParent = findParentGroup?.(targetId);\n\n // Layers are displayed in reverse order, so invert the position\n // Visual \"before\" = Array \"after\", Visual \"after\" = Array \"before\"\n const arrayPosition = dropPosition === 'before' ? 'after' : 'before';\n\n if (draggedParent && targetParent && draggedParent.id === targetParent.id) {\n // Both are children of the same group - reorder within the group\n if (onGroupReorder) {\n onGroupReorder(draggedId, targetId, arrayPosition, draggedParent.id!);\n }\n } else if (!draggedParent && targetParent && onDropIntoGroupAtPosition) {\n // Dragging from top-level into between children of a group\n onDropIntoGroupAtPosition(draggedId, targetParent.id!, targetId, arrayPosition);\n } else {\n // Top-level reordering\n onReorder(draggedId, targetId, arrayPosition);\n }\n }\n\n // Clear drag state immediately since dragEnd may not fire when DOM changes\n resetDragState();\n },\n [\n draggedId,\n dropPosition,\n onReorder,\n onGroupReorder,\n onDropIntoGroup,\n onDropIntoGroupAtPosition,\n findParentGroup,\n resetDragState,\n ]\n );\n\n return {\n draggedId,\n dragOverId,\n dropPosition,\n handlers: {\n onDragStart: handleDragStart,\n onDragOver: handleDragOver,\n onDrop: handleDrop,\n onDragEnd: resetDragState,\n },\n };\n}\n","/**\n * useLayerSelection - Hook for layer selection logic\n *\n * Extracts the selection logic (shift-click multi-select, single-click,\n * deselection) from LayersPanel.tsx into a reusable hook.\n *\n * @example\n * ```tsx\n * function LayersList({ elements }) {\n * const [selectedId, setSelectedId] = useState<string | null>(null);\n * const [multiSelection, setMultiSelection] = useState<string[]>([]);\n *\n * const { isSelected, handleClick } = useLayerSelection({\n * selectedId,\n * multiSelection,\n * onSelectionChange: setSelectedId,\n * onMultiSelectionChange: setMultiSelection,\n * });\n *\n * return elements.map(el => (\n * <div\n * key={el.id}\n * className={isSelected(el.id) ? 'selected' : ''}\n * onClick={(e) => handleClick(e, el.id)}\n * />\n * ));\n * }\n * ```\n */\n\nimport { useCallback } from 'react';\n\nexport interface UseLayerSelectionOptions {\n /** Currently selected single element ID, or null */\n selectedId: string | null;\n /** Array of multi-selected element IDs */\n multiSelection: string[];\n /** Callback for single selection changes */\n onSelectionChange: (id: string | null) => void;\n /** Callback for multi-selection changes */\n onMultiSelectionChange: (ids: string[]) => void;\n}\n\nexport interface UseLayerSelectionReturn {\n /**\n * Check if an element is selected (either single or multi-selected).\n * Does not account for activeChildElement priority -- callers handling\n * group children should check that separately.\n */\n isSelected: (id: string) => boolean;\n\n /**\n * Handle a click on a layer element.\n * - Normal click: selects only this element, clears multi-selection\n * - Shift+click: toggles the element in/out of multi-selection\n */\n handleClick: (e: React.MouseEvent | React.KeyboardEvent, elementId: string) => void;\n}\n\n/**\n * Hook for managing layer selection state.\n *\n * Implements the following selection behaviors:\n *\n * **Normal click:**\n * - Selects only the clicked element\n * - Clears any multi-selection\n *\n * **Shift+click with existing multi-selection:**\n * - If element is in multi-selection: removes it\n * - If only 1 remains: converts back to single selection\n * - If 0 remain: clears selection\n * - If element is not in multi-selection: adds it\n *\n * **Shift+click with single selection:**\n * - If clicking the already-selected element: no-op\n * - If clicking a different element: starts multi-selection with both\n *\n * **Shift+click with no selection:**\n * - Selects the clicked element as a single selection\n */\nexport function useLayerSelection(options: UseLayerSelectionOptions): UseLayerSelectionReturn {\n const { selectedId, multiSelection, onSelectionChange, onMultiSelectionChange } = options;\n\n const isSelected = useCallback(\n (id: string): boolean => {\n return id === selectedId || multiSelection.includes(id);\n },\n [selectedId, multiSelection]\n );\n\n const handleClick = useCallback(\n (e: React.MouseEvent | React.KeyboardEvent, elementId: string) => {\n if (e.shiftKey) {\n // Shift-click: multi-selection behavior\n const isInMultiSelection = multiSelection.includes(elementId);\n const isCurrentlySelected = selectedId === elementId;\n\n if (multiSelection.length > 0) {\n // Already have multi-selection\n if (isInMultiSelection) {\n // Remove from multi-selection\n const newSelection = multiSelection.filter((id) => id !== elementId);\n if (newSelection.length === 1) {\n // Only one left, convert back to single selection\n onMultiSelectionChange([]);\n onSelectionChange(newSelection[0]);\n } else if (newSelection.length === 0) {\n // All deselected\n onMultiSelectionChange([]);\n onSelectionChange(null);\n } else {\n // Keep multi-selection\n onMultiSelectionChange(newSelection);\n }\n } else {\n // Add to multi-selection\n onMultiSelectionChange([...multiSelection, elementId]);\n }\n } else if (isCurrentlySelected) {\n // Single selection exists and shift-clicking it: do nothing\n return;\n } else if (selectedId !== null) {\n // Convert single selection to multi-selection\n onMultiSelectionChange([selectedId, elementId]);\n onSelectionChange(null);\n } else {\n // No selection, just select this element\n onSelectionChange(elementId);\n }\n } else {\n // Normal click: select only this element\n onSelectionChange(elementId);\n onMultiSelectionChange([]);\n }\n },\n [selectedId, multiSelection, onSelectionChange, onMultiSelectionChange]\n );\n\n return { isSelected, handleClick };\n}\n","/**\n * FontSizeDropdown - Dropdown for selecting font size\n */\n\nimport { memo } from 'react';\nimport { Icon } from '@iconify/react';\nimport { Popover, PopoverTrigger, PopoverContent } from './ui';\n\nexport interface FontSizeDropdownProps {\n value: number;\n onChange: (size: number) => void;\n fontSizes: number[];\n isOpen: boolean;\n onToggle: (open: boolean) => void;\n}\n\nconst FontSizeDropdown = memo(({ value, onChange, fontSizes, isOpen, onToggle }: FontSizeDropdownProps) => {\n const handleSelect = (size: number) => {\n onChange(size);\n onToggle(false);\n };\n\n return (\n <Popover open={isOpen} onOpenChange={onToggle}>\n <PopoverTrigger asChild>\n <button className=\"sc-toolbar-btn\" aria-label=\"Font size\">\n <Icon icon=\"radix-icons:font-size\" className=\"sc:size-5\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"sc:w-[120px] sc:max-h-[300px] sc:overflow-y-auto sc:p-1\" maxZIndex>\n <div className=\"sc:flex sc:flex-col sc:gap-0.5\">\n {fontSizes.map((size) => (\n <button\n key={size}\n onClick={() => handleSelect(size)}\n className={`sc:w-full sc:rounded-md sc:px-3 sc:py-1.5 sc:text-center sc:text-sm sc:transition-colors sc:hover:bg-muted ${\n value === size ? 'sc:bg-muted sc:font-medium' : ''\n }`}\n >\n {size}\n </button>\n ))}\n </div>\n </PopoverContent>\n </Popover>\n );\n});\n\nFontSizeDropdown.displayName = 'FontSizeDropdown';\n\nexport default FontSizeDropdown;\n","import { useState } from 'react';\nimport { Tooltip, TooltipTrigger, TooltipContent } from '../ui';\nimport { FONT_SIZES } from '../../constants.js';\nimport { clickProtection } from '../../utils/clickProtection.js';\nimport type { UseTextToolbarReturn } from '../../hooks/useTextToolbar.js';\n\nexport interface FontSizeGroupProps {\n toolbar: UseTextToolbarReturn;\n}\n\nexport function FontSizeGroup({ toolbar }: FontSizeGroupProps) {\n const { fontSize, handleFontSizeIncrement, handleFontSizeChange } = toolbar;\n const [openDropdown, setOpenDropdown] = useState(false);\n\n return (\n <div className=\"sc-toolbar-group\">\n <Tooltip delayDuration={300}>\n <TooltipTrigger asChild>\n <button className=\"sc-toolbar-btn-sm\" onClick={() => handleFontSizeIncrement(-1)}>\n -\n </button>\n </TooltipTrigger>\n <TooltipContent>Decrease Font Size</TooltipContent>\n </Tooltip>\n <Tooltip delayDuration={300}>\n <TooltipTrigger asChild>\n <div style={{ position: 'relative' }}>\n <button\n className=\"sc-toolbar-font-size-display\"\n onClick={() => {\n clickProtection.protectNextClick();\n setOpenDropdown(!openDropdown);\n }}\n >\n {fontSize}\n </button>\n {openDropdown && (\n <div\n className=\"font-dropdown\"\n ref={(el: HTMLDivElement | null) => {\n if (el) {\n const handleClickOutside = (event: MouseEvent) => {\n const target = event.target as HTMLElement;\n if (!el.contains(target) && !target.closest('.sc-toolbar-font-size-display')) {\n setOpenDropdown(false);\n }\n };\n setTimeout(() => {\n document.addEventListener('mousedown', handleClickOutside);\n }, 0);\n }\n }}\n >\n {FONT_SIZES.map((size) => (\n <button\n key={size}\n className={`font-dropdown-option ${fontSize === size ? 'selected' : ''}`}\n onClick={() => {\n handleFontSizeChange(size);\n setOpenDropdown(false);\n }}\n >\n {size}\n </button>\n ))}\n </div>\n )}\n </div>\n </TooltipTrigger>\n <TooltipContent>Font Size</TooltipContent>\n </Tooltip>\n <Tooltip delayDuration={300}>\n <TooltipTrigger asChild>\n <button className=\"sc-toolbar-btn-sm\" onClick={() => handleFontSizeIncrement(1)}>\n +\n </button>\n </TooltipTrigger>\n <TooltipContent>Increase Font Size</TooltipContent>\n </Tooltip>\n </div>\n );\n}\n","/**\n * ElementTypePlugin - Plugin API for registering custom element types\n *\n * Allows external developers to add new element types without modifying\n * core canvas files. Custom types integrate with the transform registry\n * so that GroupElement reconstruction, ElementFactory, and all other\n * registry consumers work seamlessly.\n *\n * @example\n * ```typescript\n * import { registerElementType } from '@snowcone-app/canvas/advanced';\n *\n * registerElementType({\n * type: 'custom-shape',\n * Component: CustomShapeElement,\n * label: 'Custom Shape',\n * icon: 'lucide:shapes',\n * category: 'custom',\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nimport {\n registerTransform,\n unregisterTransform,\n getTransformById,\n _resetDynamicTransforms,\n} from '../transforms/registry.js';\n\n/**\n * Registration descriptor for a custom element type.\n */\nexport interface ElementTypeRegistration {\n /** Unique type identifier (must not conflict with built-in types) */\n type: string;\n\n /**\n * Element class constructor.\n * Must accept a config object and produce a valid element instance\n * (i.e., something that extends BaseElement).\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Constructors have varying signatures\n Component: new (config: any) => any;\n\n /** Display label for UI (e.g., \"Custom Shape\") */\n label: string;\n\n /** Icon identifier in Iconify format (e.g., \"lucide:shapes\") */\n icon?: string;\n\n /** Category for grouping in the add-element menu */\n category?: 'text' | 'image' | 'shape' | 'custom';\n\n /** Default config for new elements of this type */\n defaultConfig?: Record<string, unknown>;\n}\n\n/**\n * Internal store for custom element type registrations.\n * Keyed by type string.\n */\nconst _customElementTypes = new Map<string, ElementTypeRegistration>();\n\n/**\n * Register a custom element type.\n *\n * - Validates the registration (type must be non-empty, Component must be a constructor)\n * - Prevents conflicts with built-in types and duplicate registrations\n * - Adds the type to the transform registry so GroupElement, ElementFactory,\n * and other registry consumers recognize it\n *\n * @param registration - The element type registration descriptor\n * @throws Error if type conflicts with a built-in type\n * @throws Error if type is already registered as a custom type\n * @throws Error if registration is invalid (missing type/Component/label)\n */\nexport function registerElementType(registration: ElementTypeRegistration): void {\n // Validate required fields\n if (!registration.type || typeof registration.type !== 'string') {\n throw new Error('ElementTypeRegistration requires a non-empty \"type\" string');\n }\n if (typeof registration.Component !== 'function') {\n throw new Error(\n `ElementTypeRegistration for \"${registration.type}\" requires a \"Component\" constructor function`\n );\n }\n if (!registration.label || typeof registration.label !== 'string') {\n throw new Error(\n `ElementTypeRegistration for \"${registration.type}\" requires a non-empty \"label\" string`\n );\n }\n\n // registerTransform will throw if the id conflicts with built-in or duplicate\n registerTransform(registration.type, {\n id: registration.type,\n label: registration.label,\n Component: registration.Component,\n });\n\n // Store the full registration (including icon, category, defaultConfig)\n _customElementTypes.set(registration.type, { ...registration });\n}\n\n/**\n * Unregister a custom element type.\n *\n * Removes the type from both the plugin registry and the transform registry.\n * Built-in types cannot be unregistered.\n *\n * @param type - The type identifier to unregister\n * @throws Error if attempting to unregister a built-in type\n */\nexport function unregisterElementType(type: string): void {\n // unregisterTransform throws if it's a built-in type\n unregisterTransform(type);\n _customElementTypes.delete(type);\n}\n\n/**\n * Get all registered custom element types.\n *\n * Returns only plugin-registered types, not built-in types.\n * The returned array is a snapshot; mutations do not affect the registry.\n *\n * @returns Array of ElementTypeRegistration descriptors\n */\nexport function getCustomElementTypes(): ElementTypeRegistration[] {\n return Array.from(_customElementTypes.values());\n}\n\n/**\n * Check if a type identifier is registered (built-in or custom).\n *\n * @param type - The type identifier to check\n * @returns true if the type exists in the transform registry\n */\nexport function isRegisteredElementType(type: string): boolean {\n return getTransformById(type) !== undefined;\n}\n\n/**\n * Reset all custom element type registrations (for testing only).\n * Also resets the underlying dynamic transforms in the registry.\n * @internal\n */\nexport function _resetCustomElementTypes(): void {\n _customElementTypes.clear();\n _resetDynamicTransforms();\n}\n","/**\n * Canvas Event Bus\n *\n * A typed publish/subscribe event system for cross-component communication\n * within the canvas editor. Components can emit and listen for events without\n * direct coupling, enabling loose coordination between panels, tools, and\n * the canvas itself.\n *\n * All event types are defined in {@link CanvasEventMap} for compile-time\n * type safety -- subscribers receive correctly-typed payloads and emitters\n * are checked against the event map at compile time.\n *\n * A singleton instance is available via {@link canvasEventBus} for use\n * across the editor. The {@link useCanvasEvents} hook provides React\n * integration.\n *\n * @example Subscribe to element creation\n * ```ts\n * import { canvasEventBus } from '@snowcone-app/canvas/advanced';\n *\n * const unsub = canvasEventBus.on('element:created', ({ elementId, type }) => {\n * console.log(`Created ${type} element: ${elementId}`);\n * });\n *\n * // Later: clean up\n * unsub();\n * ```\n *\n * @example One-shot listener\n * ```ts\n * canvasEventBus.once('export:completed', ({ durationMs }) => {\n * console.log(`Export finished in ${durationMs}ms`);\n * });\n * ```\n */\n\n// ---------------------------------------------------------------------------\n// Event Map\n// ---------------------------------------------------------------------------\n\n/**\n * Map of all canvas event names to their payload types.\n *\n * Extend this interface (via module augmentation) to add custom events:\n * ```ts\n * declare module '@snowcone-app/canvas/advanced' {\n * interface CanvasEventMap {\n * 'my-plugin:action': { value: number };\n * }\n * }\n * ```\n */\nexport interface CanvasEventMap {\n /** Fired after a new element is added to the canvas */\n 'element:created': { elementId: string; type: string };\n /** Fired after one or more properties of an element change */\n 'element:updated': { elementId: string; changes: string[] };\n /** Fired after an element is removed from the canvas */\n 'element:deleted': { elementId: string };\n /** Fired when the selected element changes (null = deselected) */\n 'element:selected': { elementId: string | null };\n /** Fired when the active artboard changes */\n 'artboard:changed': { artboardId: string; artboardName: string };\n /** Fired when an export operation begins */\n 'export:started': { artboardId: string };\n /** Fired when an export operation completes successfully */\n 'export:completed': { artboardId: string; durationMs: number };\n /** Fired when an export operation fails */\n 'export:error': { artboardId: string; error: Error };\n /** Fired after an undo operation */\n 'undo': { commandDescription?: string };\n /** Fired after a redo operation */\n 'redo': { commandDescription?: string };\n}\n\n// ---------------------------------------------------------------------------\n// Handler type\n// ---------------------------------------------------------------------------\n\n/** Strongly-typed event handler for a specific event name */\nexport type CanvasEventHandler<K extends keyof CanvasEventMap> = (\n event: CanvasEventMap[K],\n) => void;\n\n// ---------------------------------------------------------------------------\n// CanvasEventBus\n// ---------------------------------------------------------------------------\n\n/**\n * A typed event bus for the canvas editor.\n *\n * Provides subscribe ({@link on}, {@link once}), publish ({@link emit}),\n * and unsubscribe ({@link off}) operations with full type safety.\n *\n * Listeners for a given event are called in the order they were registered.\n * Errors thrown inside a handler are caught and logged to `console.error`\n * so that one failing handler does not prevent others from executing.\n */\nexport class CanvasEventBus {\n /** Map of event name to the set of registered handler functions */\n private listeners = new Map<string, Set<Function>>();\n\n // -----------------------------------------------------------------------\n // Subscribe\n // -----------------------------------------------------------------------\n\n /**\n * Subscribe to an event.\n *\n * @param event - The event name to listen for\n * @param handler - Callback invoked with the event payload\n * @returns An unsubscribe function -- call it to remove this handler\n */\n on<K extends keyof CanvasEventMap>(\n event: K,\n handler: CanvasEventHandler<K>,\n ): () => void {\n let handlers = this.listeners.get(event as string);\n if (!handlers) {\n handlers = new Set();\n this.listeners.set(event as string, handlers);\n }\n handlers.add(handler);\n\n // Return unsubscribe function\n return () => {\n handlers!.delete(handler);\n if (handlers!.size === 0) {\n this.listeners.delete(event as string);\n }\n };\n }\n\n /**\n * Subscribe to an event for a single invocation.\n *\n * The handler is automatically removed after the first time the event\n * fires. The returned unsubscribe function can also be used to cancel\n * before the event fires.\n *\n * @param event - The event name to listen for\n * @param handler - Callback invoked once with the event payload\n * @returns An unsubscribe function\n */\n once<K extends keyof CanvasEventMap>(\n event: K,\n handler: CanvasEventHandler<K>,\n ): () => void {\n const wrapper = ((data: CanvasEventMap[K]) => {\n unsubscribe();\n handler(data);\n }) as CanvasEventHandler<K>;\n\n const unsubscribe = this.on(event, wrapper);\n return unsubscribe;\n }\n\n // -----------------------------------------------------------------------\n // Publish\n // -----------------------------------------------------------------------\n\n /**\n * Emit an event, invoking all registered handlers for that event name.\n *\n * Handlers are called synchronously in registration order. If a handler\n * throws, the error is logged and remaining handlers still execute.\n *\n * @param event - The event name to emit\n * @param data - The payload to pass to each handler\n */\n emit<K extends keyof CanvasEventMap>(\n event: K,\n data: CanvasEventMap[K],\n ): void {\n const handlers = this.listeners.get(event as string);\n if (!handlers) return;\n\n // Iterate over a snapshot so that handlers can safely unsubscribe\n for (const handler of [...handlers]) {\n try {\n handler(data);\n } catch (err) {\n console.error(\n `[CanvasEventBus] Error in handler for \"${String(event)}\":`,\n err,\n );\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Unsubscribe\n // -----------------------------------------------------------------------\n\n /**\n * Remove listeners.\n *\n * - If called with an event name, removes all handlers for that event.\n * - If called with no arguments, removes all handlers for all events.\n *\n * @param event - Optional event name to clear. Omit to clear everything.\n */\n off<K extends keyof CanvasEventMap>(event?: K): void {\n if (event === undefined) {\n this.listeners.clear();\n } else {\n this.listeners.delete(event as string);\n }\n }\n\n // -----------------------------------------------------------------------\n // Introspection\n // -----------------------------------------------------------------------\n\n /**\n * Get the number of registered listeners for a specific event.\n *\n * @param event - The event name to query\n * @returns The number of handlers currently registered\n */\n listenerCount(event: keyof CanvasEventMap): number {\n return this.listeners.get(event as string)?.size ?? 0;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Singleton\n// ---------------------------------------------------------------------------\n\n/**\n * The singleton CanvasEventBus instance shared across the editor.\n *\n * Use this directly or via the {@link useCanvasEvents} hook in React\n * components.\n */\nexport const canvasEventBus = new CanvasEventBus();\n","/**\n * useCanvasEvents Hook\n *\n * Provides access to the singleton {@link CanvasEventBus} instance for\n * cross-component communication within the canvas editor.\n *\n * This is a thin convenience wrapper -- it simply returns the shared\n * singleton so that React components have a consistent hook-based API\n * for accessing the event bus.\n *\n * @example Subscribe to events in a component\n * ```tsx\n * import { useCanvasEvents } from '@snowcone-app/canvas/advanced';\n *\n * function MyPanel() {\n * const events = useCanvasEvents();\n *\n * useEffect(() => {\n * const unsub = events.on('element:selected', ({ elementId }) => {\n * console.log('Selected:', elementId);\n * });\n * return unsub;\n * }, [events]);\n *\n * return <div>My Panel</div>;\n * }\n * ```\n *\n * @example Emit events from a component\n * ```tsx\n * function ExportButton() {\n * const events = useCanvasEvents();\n *\n * const handleExport = () => {\n * events.emit('export:started', { artboardId: 'ab-1' });\n * };\n *\n * return <button onClick={handleExport}>Export</button>;\n * }\n * ```\n */\n\nimport { canvasEventBus, type CanvasEventBus } from '../core/EventBus.js';\n\n/**\n * Returns the singleton {@link CanvasEventBus} instance.\n *\n * The returned reference is stable across renders -- it always points to\n * the same singleton, so it is safe to include in dependency arrays.\n */\nexport function useCanvasEvents(): CanvasEventBus {\n return canvasEventBus;\n}\n","/**\n * ADR-0054: SVG-based visual-guide overlay.\n *\n * Duplicated intentionally between:\n * - snowcone-loom/app/components/VisualGuideOverlay.tsx (Loom editor)\n * - ui-components/packages/canvas/src/components/VisualGuideOverlay.tsx\n * (this file — consumed by the Snowcone consumer editor via\n * @snowcone-app/canvas)\n * Loom doesn't depend on @snowcone-app/canvas. Keep these two files\n * byte-identical below the header; if you change one, mirror the other.\n *\n * Renders structured piece guides (boundary, safe area, zones, labels) as\n * stacked SVG layers on top of an artboard preview. The entire overlay\n * lives inside a single `<svg viewBox=\"0 0 artboardWidth artboardHeight\">`\n * so piece-local coordinates compose directly and strokes can opt into\n * `vector-effect=\"non-scaling-stroke\"` to stay visually crisp at every\n * viewport scale.\n *\n * Falls back cleanly when no guides are authored:\n * - `boundary` → the piece's bounding rect (0,0 to pieceWidth, pieceHeight)\n * - `safeArea` → an inset rect derived from `safeAreaInsetPx` when present,\n * omitted otherwise\n */\n\nimport { useMemo } from \"react\";\n\n// ── Types (mirror the backend layouts.ts shape) ───────────────────────────────\n\nexport type SvgPath =\n | { kind: \"rect\"; x: number; y: number; width: number; height: number }\n | {\n kind: \"roundedRect\";\n x: number;\n y: number;\n width: number;\n height: number;\n rx?: number;\n ry?: number;\n }\n | { kind: \"path\"; d: string };\n\nexport interface ZoneShape {\n kind: \"wrap\" | \"binding\" | \"dieCut\" | \"warning\" | \"custom\";\n label?: string;\n path: SvgPath;\n}\n\nexport interface GuideLabel {\n text: string;\n x: number;\n y: number;\n anchor?: \"tl\" | \"tc\" | \"tr\" | \"cl\" | \"c\" | \"cr\" | \"bl\" | \"bc\" | \"br\";\n}\n\nexport interface PieceGuide {\n boundary?: SvgPath;\n safeArea?: SvgPath;\n zones?: ZoneShape[];\n labels?: GuideLabel[];\n /**\n * Corner radius of the piece's *physical outer shape* (e.g. a phone\n * case's outer curve) in piece-local pixels. When set, the bleed\n * ring's outer edge uses a rounded rectangle of `(pieceWidth,\n * pieceHeight)` with this radius, and the entire piece render is\n * clipped to that shape — so nothing bleeds into the sharp corners\n * of the piece's bounding rect. Omit / 0 = sharp corners (current\n * default behaviour).\n */\n outerRadius?: number;\n}\n\nexport interface OverlayPiece {\n /** Stable piece id (matches `placements[].name`). */\n id: string;\n /** Piece's top-left position on the artboard, in artboard pixel coords. */\n x: number;\n y: number;\n /** Piece-local dimensions in px. */\n width: number;\n height: number;\n /**\n * Fallback safe-area inset when no authored `safeArea` is present. In\n * piece-local pixels (caller converts from inches). Omit to skip the\n * fallback safe area entirely.\n */\n safeAreaInsetPx?: number;\n /** Clockwise rotation in degrees about the piece center. Default 0. */\n rotation?: number;\n}\n\nexport interface VisualGuideOverlayStyle {\n bleedFill?: string;\n bleedOpacity?: number;\n boundaryStroke?: string;\n boundaryStrokeWidth?: number;\n /**\n * Safe-area wash: fills the region outside the safe area (inside the\n * piece) with a semi-transparent color. Replaces the old dashed\n * safe-area outline as the default — de-emphasizes the bleed margin so\n * the safe area reads as the \"clean\" zone without adding line chrome.\n */\n safeAreaWashFill?: string;\n safeAreaWashOpacity?: number;\n /**\n * Corner radius (in viewBox px) for the wash's outer edge so it hugs\n * rounded products (phone cases, rounded cards, etc.) instead of\n * boxing sharp corners. Pass `0` for hard corners. Omit to use an\n * artboard-proportional default (~2% of the shorter axis).\n */\n safeAreaWashCornerRadius?: number;\n /**\n * Safe-area stroke settings — legacy dashed outline. Disabled by default\n * (wash does the work). Set `safeAreaStroke` to any color to re-enable.\n */\n safeAreaStroke?: string;\n safeAreaStrokeWidth?: number;\n safeAreaDashArray?: string;\n zoneFill?: Partial<Record<ZoneShape[\"kind\"], string>>;\n zoneStroke?: Partial<Record<ZoneShape[\"kind\"], string>>;\n labelColor?: string;\n labelFontSize?: number;\n labelFontFamily?: string;\n}\n\nexport interface VisualGuideOverlayProps {\n /** Artboard dimensions in px — defines the SVG viewBox. */\n artboardWidth: number;\n artboardHeight: number;\n /**\n * @deprecated Do not use — leave at 0 (the default).\n *\n * This was intended to mirror SnowconeCanvas's `fixedMargin`, but\n * `fixedMargin` is in CSS pixels (converted to world units via\n * `worldMargin = fixedMargin / zoom` in useCanvasLayout.ts) while\n * this prop treated the value as viewBox/world units. The two only\n * match at zoom === 1, so passing a non-zero value silently drifted\n * the overlay content by tens of pixels at typical editor zooms.\n *\n * Correct usage: keep the SVG viewBox equal to the artboard, and\n * position the wrapping div with `inset: ${fixedMargin}px` so the\n * overlay covers only the artboard region of the padded canvas.\n */\n canvasPadding?: number;\n /** Pieces to render guides for. */\n pieces: OverlayPiece[];\n /** Authored guides keyed by piece id. Pieces without a guide fall back. */\n pieceGuides?: Record<string, PieceGuide>;\n /** Per-layer visibility toggles. All default to true. */\n show?: {\n bleed?: boolean;\n zones?: boolean;\n boundary?: boolean;\n safeArea?: boolean;\n labels?: boolean;\n };\n /** Optional style overrides. */\n style?: VisualGuideOverlayStyle;\n /** `className` applied to the wrapping SVG element. */\n className?: string;\n}\n\n// ── Defaults ──────────────────────────────────────────────────────────────────\n\nconst DEFAULT_STYLE: Required<\n Omit<VisualGuideOverlayStyle, \"zoneFill\" | \"zoneStroke\">\n> & {\n zoneFill: Record<ZoneShape[\"kind\"], string>;\n zoneStroke: Record<ZoneShape[\"kind\"], string>;\n} = {\n bleedFill: \"#ffffff\",\n bleedOpacity: 0.5,\n // Subtle boundary — the wash already draws the eye to the safe area,\n // so the piece outline is just a quiet frame for context. Consumers\n // can dial it up to a harder blue / thicker stroke via style overrides.\n // Default to no boundary outline — the bleed wash's soft inner edge\n // carries the design-canvas edge on its own. Consumers can set any\n // colour via `style.boundaryStroke` to opt back in.\n boundaryStroke: \"\",\n boundaryStrokeWidth: 1,\n // New default: 25%-opacity white wash over the non-safe area. Apple-y\n // \"fade out the edges\" approach rather than a dashed line, so the safe\n // area reads as the clean canvas rather than a bordered box.\n safeAreaWashFill: \"#ffffff\",\n safeAreaWashOpacity: 0.25,\n // Sentinel: -1 means \"auto\" — the component computes an artboard-\n // proportional radius at render time. A real value overrides.\n safeAreaWashCornerRadius: -1,\n // Legacy dashed safe-area outline — disabled by default now. Set\n // `safeAreaStroke` to a color to re-enable for any consumer that\n // prefers the old look.\n safeAreaStroke: \"\",\n safeAreaStrokeWidth: 1.5,\n safeAreaDashArray: \"6 4\",\n zoneFill: {\n wrap: \"rgba(148, 163, 184, 0.25)\", // slate-400\n binding: \"rgba(234, 88, 12, 0.20)\", // orange-600\n // A die-cut is material *removed* from the product, so the fill reads\n // as a dark \"hole\" rather than a coloured region. 70% black gives\n // strong contrast over any artwork underneath.\n dieCut: \"rgba(0, 0, 0, 0.7)\",\n warning: \"rgba(245, 158, 11, 0.25)\", // amber-500\n custom: \"rgba(100, 100, 100, 0.15)\",\n },\n zoneStroke: {\n wrap: \"#64748b\",\n binding: \"#ea580c\",\n // White outline on die-cut pairs with the black fill to read as a\n // clean perforation. A red outline would compete with the fill.\n dieCut: \"#ffffff\",\n warning: \"#f59e0b\",\n custom: \"#6b7280\",\n },\n labelColor: \"#111827\",\n // Placeholder — replaced at render time with an artboard-proportional\n // default (see `autoLabelFontSize` in the component). Callers can still\n // pin via `style.labelFontSize`.\n labelFontSize: 14,\n labelFontFamily:\n \"ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif\",\n};\n\n// ── Rendering helpers ─────────────────────────────────────────────────────────\n\nconst ANCHOR_TO_SVG: Record<\n NonNullable<GuideLabel[\"anchor\"]>,\n {\n textAnchor: React.SVGProps<SVGTextElement>[\"textAnchor\"];\n dominantBaseline: React.SVGProps<SVGTextElement>[\"dominantBaseline\"];\n }\n> = {\n // SVG `dominant-baseline` has no \"baseline\" keyword; \"alphabetic\" is the\n // bottom/baseline alignment (and React's typed union enforces this).\n tl: { textAnchor: \"start\", dominantBaseline: \"hanging\" },\n tc: { textAnchor: \"middle\", dominantBaseline: \"hanging\" },\n tr: { textAnchor: \"end\", dominantBaseline: \"hanging\" },\n cl: { textAnchor: \"start\", dominantBaseline: \"middle\" },\n c: { textAnchor: \"middle\", dominantBaseline: \"middle\" },\n cr: { textAnchor: \"end\", dominantBaseline: \"middle\" },\n bl: { textAnchor: \"start\", dominantBaseline: \"alphabetic\" },\n bc: { textAnchor: \"middle\", dominantBaseline: \"alphabetic\" },\n br: { textAnchor: \"end\", dominantBaseline: \"alphabetic\" },\n};\n\nfunction renderShape(\n path: SvgPath,\n commonProps: React.SVGProps<SVGElement>,\n): React.JSX.Element {\n if (path.kind === \"rect\") {\n return (\n <rect\n x={path.x}\n y={path.y}\n width={path.width}\n height={path.height}\n {...(commonProps as React.SVGProps<SVGRectElement>)}\n />\n );\n }\n if (path.kind === \"roundedRect\") {\n return (\n <rect\n x={path.x}\n y={path.y}\n width={path.width}\n height={path.height}\n rx={path.rx}\n ry={path.ry}\n {...(commonProps as React.SVGProps<SVGRectElement>)}\n />\n );\n }\n return (\n <path\n d={path.d}\n {...(commonProps as React.SVGProps<SVGPathElement>)}\n />\n );\n}\n\nfunction resolveBoundary(\n guide: PieceGuide | undefined,\n naturalW: number,\n naturalH: number,\n): SvgPath {\n // Authored boundaries live in the piece's NATURAL coord system (the\n // unrotated frame the layout was authored in). The fallback \"full\n // piece\" rect must therefore also be in natural dims, not the\n // displayed (post-rotation) `piece.width × piece.height`. Otherwise a\n // 90°/270° piece's fallback boundary lands sideways in the rotated\n // local frame. See PieceGuideRenderer.ts for the canvas-side mirror.\n return (\n guide?.boundary ?? {\n kind: \"rect\",\n x: 0,\n y: 0,\n width: naturalW,\n height: naturalH,\n }\n );\n}\n\n/**\n * Serialize an SvgPath to its SVG `d` attribute string. Used to build\n * evenodd compound paths (e.g. piece rect minus safe area) for the wash.\n */\nfunction svgPathD(path: SvgPath): string {\n if (path.kind === \"rect\") {\n return `M ${path.x} ${path.y} h ${path.width} v ${path.height} h ${-path.width} Z`;\n }\n if (path.kind === \"roundedRect\") {\n const rx = path.rx ?? path.ry ?? 0;\n const ry = path.ry ?? path.rx ?? 0;\n if (rx <= 0 && ry <= 0) {\n return `M ${path.x} ${path.y} h ${path.width} v ${path.height} h ${-path.width} Z`;\n }\n const { x, y, width: w, height: h } = path;\n return [\n `M ${x + rx} ${y}`,\n `h ${w - 2 * rx}`,\n `a ${rx} ${ry} 0 0 1 ${rx} ${ry}`,\n `v ${h - 2 * ry}`,\n `a ${rx} ${ry} 0 0 1 ${-rx} ${ry}`,\n `h ${-(w - 2 * rx)}`,\n `a ${rx} ${ry} 0 0 1 ${-rx} ${-ry}`,\n `v ${-(h - 2 * ry)}`,\n `a ${rx} ${ry} 0 0 1 ${rx} ${-ry}`,\n \"Z\",\n ].join(\" \");\n }\n return path.d;\n}\n\nfunction resolveSafeArea(\n guide: PieceGuide | undefined,\n piece: OverlayPiece,\n naturalW: number,\n naturalH: number,\n): SvgPath | null {\n if (guide?.safeArea) return guide.safeArea;\n if (piece.safeAreaInsetPx && piece.safeAreaInsetPx > 0) {\n const inset = piece.safeAreaInsetPx;\n return {\n kind: \"rect\",\n x: inset,\n y: inset,\n width: Math.max(0, naturalW - inset * 2),\n height: Math.max(0, naturalH - inset * 2),\n };\n }\n return null;\n}\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function VisualGuideOverlay({\n artboardWidth,\n artboardHeight,\n canvasPadding = 0,\n pieces,\n pieceGuides,\n show,\n style,\n className,\n}: VisualGuideOverlayProps): React.JSX.Element {\n const s = useMemo(() => {\n // Artboard-proportional default so labels stay legible at typical\n // viewport scales. Bumps with artboard size but clamped to a sane\n // floor. Callers can still override via `style.labelFontSize` to pin\n // an exact viewBox-unit value. 3% of the shorter axis reads clearly\n // at the zoom levels Snowcone's consumer editor typically renders at.\n const autoLabelFontSize = Math.max(\n 16,\n Math.round(Math.min(artboardWidth, artboardHeight) * 0.03),\n );\n const merged = {\n ...DEFAULT_STYLE,\n ...(style ?? {}),\n zoneFill: { ...DEFAULT_STYLE.zoneFill, ...(style?.zoneFill ?? {}) },\n zoneStroke: { ...DEFAULT_STYLE.zoneStroke, ...(style?.zoneStroke ?? {}) },\n };\n if (style?.labelFontSize === undefined) {\n merged.labelFontSize = autoLabelFontSize;\n }\n return merged;\n }, [style, artboardWidth, artboardHeight]);\n\n const visibility = {\n bleed: show?.bleed ?? true,\n zones: show?.zones ?? true,\n boundary: show?.boundary ?? true,\n safeArea: show?.safeArea ?? true,\n labels: show?.labels ?? true,\n };\n\n // Expand the viewBox to include SnowconeCanvas's padding on each side,\n // and translate all content by that padding so artboard-local coords\n // paint at the actual artboard position inside the padded canvas.\n // Without this, the SVG's painted rect matches the full padded canvas\n // (auto-centered by preserveAspectRatio) and drifts away from where\n // the canvas element paints the artboard — typically a few percent of\n // the smaller axis.\n const viewBoxWidth = artboardWidth + canvasPadding * 2;\n const viewBoxHeight = artboardHeight + canvasPadding * 2;\n\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox={`0 0 ${viewBoxWidth} ${viewBoxHeight}`}\n preserveAspectRatio=\"xMidYMid meet\"\n className={className}\n // Overlay mustn't swallow canvas interaction; individual elements\n // can opt back in with pointer-events if we later add hover/labels.\n style={{ pointerEvents: \"none\", width: \"100%\", height: \"100%\" }}\n >\n <g transform={`translate(${canvasPadding} ${canvasPadding})`}>\n {pieces.map((piece) => {\n const guide = pieceGuides?.[piece.id];\n const zones = guide?.zones ?? [];\n const labels = guide?.labels ?? [];\n\n // `piece.rotation` swaps the piece's displayed dim relative to\n // its natural (bbox) dim. Boundary / safeArea / wash etc. are\n // authored against the NATURAL coord system, so we need a\n // transform that maps natural (0, 0)→displayed corner exactly,\n // then draw at natural dims. The earlier `rotate(angle, cx, cy)`\n // around the *displayed* center offsets the rotated rect by\n // ((Nw−Dw)/2, (Nh−Dh)/2) — visible as a halo on rotated pieces.\n // Mirrors `PieceGuideRenderer.ts`'s canvas-side fix.\n //\n // SVG composes left-to-right; right-most transform applies\n // first. So for rotation = 90° CW the inner coord is rotated\n // first, then translated by (W, 0):\n // 90°: translate(piece.x piece.y) translate(W, 0) rotate(90)\n // 180°: translate(piece.x piece.y) translate(W, H) rotate(180)\n // 270°: translate(piece.x piece.y) translate(0, H) rotate(270)\n const rotation = piece.rotation ?? 0;\n const isOrtho = rotation === 90 || rotation === 270;\n const naturalW = isOrtho ? piece.height : piece.width;\n const naturalH = isOrtho ? piece.width : piece.height;\n const boundary = resolveBoundary(guide, naturalW, naturalH);\n const safeArea = resolveSafeArea(guide, piece, naturalW, naturalH);\n const originXform = `translate(${piece.x} ${piece.y})`;\n let rotationXform = \"\";\n if (rotation === 90) {\n rotationXform = `translate(${piece.width} 0) rotate(90)`;\n } else if (rotation === 180) {\n rotationXform = `translate(${piece.width} ${piece.height}) rotate(180)`;\n } else if (rotation === 270) {\n rotationXform = `translate(0 ${piece.height}) rotate(270)`;\n }\n const transform = rotationXform\n ? `${originXform} ${rotationXform}`\n : originXform;\n\n // Safe-area wash: fills the piece minus the safe area with a\n // subtle translucent color. This is the primary safe-area\n // indicator — replaces the old dashed outline for an Apple-y\n // \"fade out the edges\" feel. Only rendered when the safeArea is\n // rect/roundedRect (path-kind falls through until we parse SVG\n // path bounds in Phase 4).\n //\n // Wash outer uses a rounded rect by default so the fade hugs the\n // product's visual corners (phone cases, rounded cards) instead\n // of boxing sharp 90° corners. Consumers can set\n // `style.safeAreaWashCornerRadius = 0` for hard corners.\n const washRadius =\n s.safeAreaWashCornerRadius < 0\n ? Math.round(Math.min(naturalW, naturalH) * 0.02)\n : s.safeAreaWashCornerRadius;\n // Wash outer: prefer the authored boundary (it's the product's\n // actual design canvas — a rounded-rect phone case outline, a\n // book cover rectangle, etc.). Fall back to the piece rect only\n // when no boundary is authored. Apply the corner radius to a\n // plain-rect boundary at render time so the fade hugs the\n // product's visual curves even without explicit author geometry.\n const boundaryForWash: SvgPath | null =\n guide?.boundary &&\n (guide.boundary.kind === \"rect\" || guide.boundary.kind === \"roundedRect\")\n ? guide.boundary\n : null;\n const washOuter: SvgPath = boundaryForWash\n ? boundaryForWash.kind === \"rect\" && washRadius > 0\n ? {\n kind: \"roundedRect\",\n x: boundaryForWash.x,\n y: boundaryForWash.y,\n width: boundaryForWash.width,\n height: boundaryForWash.height,\n rx: washRadius,\n ry: washRadius,\n }\n : boundaryForWash\n : washRadius > 0\n ? {\n kind: \"roundedRect\",\n x: 0,\n y: 0,\n width: naturalW,\n height: naturalH,\n rx: washRadius,\n ry: washRadius,\n }\n : {\n kind: \"rect\",\n x: 0,\n y: 0,\n width: naturalW,\n height: naturalH,\n };\n // Bleed ring = piece rect ∖ boundary (the area OUTSIDE the\n // design canvas that physically wraps or trims). The inner\n // edge of the ring matches the boundary shape exactly, which\n // is the \"design canvas edge\" — where the sharp art stops.\n // No boundary stroke is drawn; the fade from bleed wash to\n // sharp art reads as the design-canvas edge on its own.\n const bleedFilterId = `bleed-blur-${piece.id}`;\n // Drawing happens in the natural coord system (post-rotation\n // transform), so the bleed-ring outer rect is sized at natural\n // dims. Same invariant as `washOuter` above.\n const pieceRect: SvgPath = {\n kind: \"rect\",\n x: 0,\n y: 0,\n width: naturalW,\n height: naturalH,\n };\n const canRenderBleed =\n washOuter.kind === \"rect\" || washOuter.kind === \"roundedRect\";\n\n return (\n <g key={piece.id} transform={transform}>\n {/* Bleed ring: (pieceRect ∖ boundary). Fills the wrap/trim\n zone outside the design canvas. Flat 55% bg-tertiary\n fill, softened with a big Gaussian blur so the inner\n edge fades smoothly out of the sharp design canvas —\n Apple vignette, no hard lines, no wash stroke. Clipped\n to the piece rect so the blur can't leak outside. */}\n {visibility.safeArea && canRenderBleed && (\n <>\n <defs>\n <filter\n id={bleedFilterId}\n x=\"-20%\"\n y=\"-20%\"\n width=\"140%\"\n height=\"140%\"\n >\n <feGaussianBlur\n in=\"SourceGraphic\"\n stdDeviation={\n // Scales with piece size so the fade feels\n // consistent across phones, notebook covers,\n // and tiny artboards alike. `Math.min` is\n // commutative, so the swap natural↔displayed\n // dim doesn't change the value here, but we\n // use natural dims for consistency with the\n // other piece-local geometry.\n Math.max(\n 12,\n Math.round(Math.min(naturalW, naturalH) * 0.025),\n )\n }\n />\n </filter>\n <clipPath id={`piece-clip-${piece.id}`}>\n <rect\n x={0}\n y={0}\n width={naturalW}\n height={naturalH}\n />\n </clipPath>\n </defs>\n <g\n clipPath={`url(#piece-clip-${piece.id})`}\n filter={`url(#${bleedFilterId})`}\n >\n <path\n d={`${svgPathD(pieceRect)} ${svgPathD(washOuter)}`}\n style={{\n // Honor `style.bleedFill` / `style.bleedOpacity`\n // overrides — previously hardcoded, which silently\n // dropped ADR-0054 Phase 5 `pieceGuidesStyle` edits\n // in the Loom authoring preview.\n fill: s.bleedFill || \"var(--color-bg-tertiary, #333)\",\n }}\n fillOpacity={s.bleedOpacity ?? 0.55}\n fillRule=\"evenodd\"\n />\n </g>\n </>\n )}\n\n {/* Non-die-cut zones render as a single filled+stroked shape.\n Die-cuts get a different treatment (blur + alpha) below\n so they share visual language with the canvas's own\n \"outside the artboard\" dim treatment. */}\n {visibility.zones &&\n zones\n .filter((z) => z.kind !== \"dieCut\")\n .map((zone, i) => (\n <g key={`zone-${i}`}>\n {renderShape(zone.path, {\n fill: s.zoneFill[zone.kind],\n stroke: s.zoneStroke[zone.kind],\n strokeWidth: 1.5,\n strokeDasharray: zone.kind === \"wrap\" ? \"4 3\" : undefined,\n vectorEffect: \"non-scaling-stroke\",\n } as unknown as React.SVGProps<SVGElement>)}\n </g>\n ))}\n\n {/* Die-cut zones: same visual language as elements that\n overflow the artboard in CanvasRenderer.ts (4px blur +\n ~30% white tint). Implemented via <foreignObject> so we\n can use CSS `backdrop-filter` to blur the canvas pixels\n beneath in place — no coordinate translation needed,\n since the foreignObject inherits the SVG viewBox\n coordinate system. Matches CanvasRenderer.ts:263-264. */}\n {visibility.zones &&\n zones\n .filter((z) => z.kind === \"dieCut\")\n .map((zone, i) => {\n const p = zone.path;\n if (p.kind !== \"rect\" && p.kind !== \"roundedRect\") {\n return null;\n }\n const rx =\n p.kind === \"roundedRect\" ? (p.rx ?? p.ry ?? 0) : 0;\n const ry =\n p.kind === \"roundedRect\" ? (p.ry ?? p.rx ?? 0) : 0;\n return (\n <foreignObject\n key={`diecut-${i}`}\n x={p.x}\n y={p.y}\n width={p.width}\n height={p.height}\n >\n <div\n // xmlns is required inside foreignObject so the\n // HTML child is parsed as HTML, not SVG. React's\n // HTMLDivElement prop types don't declare `xmlns`,\n // so spread it as a narrowly-typed extra attribute\n // rather than weaken the whole element's typing.\n {...({ xmlns: \"http://www.w3.org/1999/xhtml\" } as {\n xmlns: string;\n })}\n style={{\n width: \"100%\",\n height: \"100%\",\n borderRadius: `${rx}px / ${ry}px`,\n backdropFilter: \"blur(4px)\",\n WebkitBackdropFilter: \"blur(4px)\",\n // Match the canvas's own \"outside-artboard\"\n // tint (CanvasRenderer.ts uses globalAlpha=0.3\n // against `--color-bg-tertiary`). We stack the\n // bg-tertiary colour at 70% alpha on top of the\n // blurred artwork to achieve the same visual\n // blend. color-mix lets us stay theme-aware in\n // both light and dark modes.\n background:\n \"color-mix(in srgb, var(--color-bg-tertiary, #333) 70%, transparent)\",\n }}\n />\n </foreignObject>\n );\n })}\n\n {/* Boundary outline — only rendered when `boundaryStroke`\n is explicitly set by a consumer. Default is no stroke;\n the bleed wash's soft inner edge carries the\n design-canvas edge on its own. */}\n {visibility.boundary &&\n s.boundaryStroke &&\n renderShape(boundary, {\n fill: \"none\",\n stroke: s.boundaryStroke,\n strokeWidth: s.boundaryStrokeWidth,\n vectorEffect: \"non-scaling-stroke\",\n } as unknown as React.SVGProps<SVGElement>)}\n\n {/* Optional dashed safe-area outline (opt-in: empty by\n default so the wash alone carries the signal; consumers\n can set `style.safeAreaStroke` to a color to restore). */}\n {visibility.safeArea &&\n safeArea &&\n s.safeAreaStroke &&\n renderShape(safeArea, {\n fill: \"none\",\n stroke: s.safeAreaStroke,\n strokeWidth: s.safeAreaStrokeWidth,\n strokeDasharray: s.safeAreaDashArray,\n vectorEffect: \"non-scaling-stroke\",\n } as unknown as React.SVGProps<SVGElement>)}\n\n {/* Labels — font-size in CSS px via non-scaling approach.\n We draw at the piece-local coords but inside the viewBox\n the text honors its own font-size unchanged (fonts don't\n scale with SVG viewBox), so we bake the intended visual\n font size directly. */}\n {visibility.labels &&\n labels.map((label, i) => {\n const a = ANCHOR_TO_SVG[label.anchor ?? \"c\"];\n return (\n <text\n key={`label-${i}`}\n x={label.x}\n y={label.y}\n textAnchor={a.textAnchor}\n dominantBaseline={a.dominantBaseline}\n fontSize={s.labelFontSize}\n fontFamily={s.labelFontFamily}\n fill={s.labelColor}\n style={{ paintOrder: \"stroke\" }}\n stroke=\"#ffffff\"\n // Halo width tracks font size so labels stay legible\n // over any background at any zoom.\n strokeWidth={Math.max(2, s.labelFontSize * 0.25)}\n strokeLinejoin=\"round\"\n >\n {label.text}\n </text>\n );\n })}\n </g>\n );\n })}\n </g>\n </svg>\n );\n}\n\n","/**\n * @snowcone-app/canvas/advanced — Advanced API (Tier 2)\n *\n * May add new exports in minor versions. Breaking changes with migration\n * guide in minor versions. For developers building custom editor layouts\n * and integrations.\n *\n * @packageDocumentation\n */\n\n// Register all icons on module load\nimport { ensureIconsRegistered } from '../icons/registry.js';\nensureIconsRegistered();\n\n// =============================================================================\n// Individual Panel Components\n// =============================================================================\n\nexport { Canvas } from '../components/embed/Canvas.js';\nexport type { CanvasProps } from '../components/embed/Canvas.js';\n\nexport { LayersPanel } from '../components/embed/LayersPanel.js';\nexport type { LayersPanelProps } from '../components/embed/LayersPanel.js';\n\nexport { EffectsPanel } from '../components/embed/EffectsPanel.js';\nexport type { EffectsPanelProps } from '../components/embed/EffectsPanel.js';\n\nexport { ExportPanel } from '../components/embed/ExportPanel.js';\nexport type { ExportPanelProps } from '../components/embed/ExportPanel.js';\n\nexport { ImagePanel } from '../components/embed/ImagePanel.js';\nexport type { ImagePanelProps } from '../components/embed/ImagePanel.js';\n\nexport { GlyphPanel } from '../components/embed/GlyphPanel.js';\nexport type { GlyphPanelProps } from '../components/embed/GlyphPanel.js';\n\nexport { ArtboardTabs } from '../components/embed/ArtboardTabs.js';\nexport type { ArtboardTabsProps } from '../components/embed/ArtboardTabs.js';\n\nexport { ContextualToolbars } from '../components/ContextualToolbars.js';\nexport type { ContextualToolbarsProps } from '../components/ContextualToolbars.js';\n\nexport { default as ShapeTypeDrawer } from '../components/ShapeTypeDrawer.js';\n\nexport { MenuButton } from '../components/embed/MenuButton.js';\nexport type { MenuButtonProps } from '../components/embed/MenuButton.js';\n\nexport { ZoomControls } from '../components/embed/ZoomControls.js';\nexport type { ZoomControlsProps } from '../components/embed/ZoomControls.js';\n\nexport { CropPanel } from '../components/CropPanel.js';\nexport type { CropPanelProps } from '../components/CropPanel.js';\n\n// =============================================================================\n// Providers (for custom composition)\n// =============================================================================\n\nexport { EditorProvider } from '../contexts/EditorContext.js';\nexport type { EditorElement, PanOffset, ExpandedPanelType } from '../contexts/EditorContext.js';\n\nexport { ThemeProvider, useTheme } from '../contexts/ThemeContext.js';\nexport type { Theme } from '../contexts/ThemeContext.js';\n\nexport { KitProvider, useKit, useCapabilities } from '../contexts/KitContext.js';\nexport type { KitContextValue, KitProviderProps } from '../contexts/KitContext.js';\n\n// =============================================================================\n// Sub-Context Hooks\n// =============================================================================\n\nexport { useViewportContext } from '../contexts/ViewportContext.js';\nexport type { ViewportContextValue, ViewportProviderProps } from '../contexts/ViewportContext.js';\n\nexport { useSelectionContext } from '../contexts/SelectionContext.js';\nexport type { SelectionContextValue, SelectionProviderProps } from '../contexts/SelectionContext.js';\n\nexport { useHistoryContext } from '../contexts/HistoryContext.js';\nexport type { HistoryContextValue, HistoryProviderProps } from '../contexts/HistoryContext.js';\n\nexport { useToolStateContext } from '../contexts/ToolStateContext.js';\nexport type { ToolStateContextValue, ToolStateProviderProps } from '../contexts/ToolStateContext.js';\n\nexport { useElementsContext } from '../contexts/ElementsContext.js';\nexport type { ElementsContextValue, ElementsProviderProps } from '../contexts/ElementsContext.js';\n\nexport { useCommandContext } from '../contexts/CommandContext.js';\nexport type { CommandContextValue, CommandProviderProps } from '../contexts/CommandContext.js';\n\n// =============================================================================\n// Element Types (classes for instanceof checks)\n// =============================================================================\n\nexport { TextElement } from '../core/TextElement.js';\nexport { ImageElement } from '../core/ImageElement.js';\nexport { GroupElement } from '../core/GroupElement.js';\nexport { ShapeElement } from '../core/ShapeElement.js';\nexport { PathElement } from '../core/PathElement.js';\nexport { BaseElement } from '../core/BaseElement.js';\nexport { ArtboardElement } from '../core/ArtboardElement.js';\n\n// Config type guards\nexport {\n isTextElementConfig,\n isImageElementConfig,\n isCustomElementConfig,\n isCircleElementConfig,\n isGroupElementConfig,\n isShapeElementConfig,\n isPathElementConfig,\n} from '../types/index.js';\n\n// Instance type guards\nexport {\n isTextElement,\n isImageElement,\n isGroupElement,\n isShapeElement,\n isPathElement,\n} from '../types/guards.js';\n\n// Element config types\nexport type {\n BaseElementConfig,\n BaseTextElementConfig,\n TextOnlyElementConfig,\n CustomElementConfig,\n ImageElementConfig,\n GroupElementConfig,\n ShapeElementConfig,\n PathElementConfig,\n CircleElementConfig,\n ArchElementConfig,\n WaveElementConfig,\n FlagElementConfig,\n LeanElementConfig,\n AscendElementConfig,\n} from '../types/index.js';\n\n// =============================================================================\n// Advanced Hooks\n// =============================================================================\n\nexport { useProjectLoader } from '../hooks/useProjectLoader.js';\nexport type { ProjectTemplate, ProjectMetadata, UseProjectLoaderReturn } from '../hooks/useProjectLoader.js';\n\nexport { useTextToolbar } from '../hooks/useTextToolbar.js';\nexport type { UseTextToolbarOptions, UseTextToolbarReturn } from '../hooks/useTextToolbar.js';\n\nexport { useBreakpoint } from '../hooks/useBreakpoint.js';\nexport type { BreakpointConfig, Breakpoint, UseBreakpointReturn } from '../hooks/useBreakpoint.js';\n\nexport { usePerformance } from '../hooks/usePerformance.js';\nexport type { PerformanceMetrics, UsePerformanceReturn } from '../hooks/usePerformance.js';\n\nexport { useElementById } from '../hooks/useElementById.js';\n\nexport { useLayerPreview } from '../hooks/useLayerPreview.js';\nexport type { UseLayerPreviewOptions, UseLayerPreviewReturn } from '../hooks/useLayerPreview.js';\n\nexport { useLayerDragDrop } from '../hooks/useLayerDragDrop.js';\nexport type { UseLayerDragDropOptions, UseLayerDragDropReturn } from '../hooks/useLayerDragDrop.js';\n\nexport { useLayerSelection } from '../hooks/useLayerSelection.js';\nexport type { UseLayerSelectionOptions, UseLayerSelectionReturn } from '../hooks/useLayerSelection.js';\n\n// =============================================================================\n// UI Primitives\n// =============================================================================\n\nexport * from '../components/embed/primitives/index.js';\nexport * from '../components/embed/ui/index.js';\n\n// =============================================================================\n// Text Toolbar\n// =============================================================================\n\nexport { TextToolbar } from '../components/TextToolbar.js';\nexport type { TextToolbarProps } from '../components/TextToolbar.js';\nexport * from '../components/text-toolbar/index.js';\n\n// =============================================================================\n// Loading States\n// =============================================================================\n\nexport { CanvasSkeleton, LayersPanelSkeleton, ToolbarSkeleton, Spinner } from '../components/embed/LoadingStates.js';\nexport type { CanvasSkeletonProps, LayersPanelSkeletonProps, ToolbarSkeletonProps } from '../components/embed/LoadingStates.js';\n\n// =============================================================================\n// Auto Export Manager\n// =============================================================================\n\nexport { AutoExportManager, DEFAULT_AUTO_EXPORT_CONFIG } from '../services/AutoExportManager.js';\nexport type { AutoExportConfig, AutoExportStats } from '../services/AutoExportManager.js';\n\n// =============================================================================\n// Selection Preservation\n// =============================================================================\n\nexport {\n shouldPreserveSelection,\n preserveSelectionProps,\n PRESERVE_SELECTION_ATTR,\n} from '../utils/selectionPreservation.js';\n\n// =============================================================================\n// Advanced Types\n// =============================================================================\n\nexport type {\n AnyTransformData,\n CustomTransformData,\n CircleTransformData,\n ArchTransformData,\n WaveTransformData,\n FlagTransformData,\n LeanTransformData,\n AscendTransformData,\n ImageTransformData,\n GroupTransformData,\n ShapeTransformData,\n PathTransformData,\n StrokeConfig,\n MaskType,\n MaskDefinition,\n KnockoutScope,\n KnockoutConfig,\n DistressStyle,\n DistressEffect,\n CharacterStyle,\n TextSpan,\n GlyphOverride,\n GlyphAlternate,\n OpenTypeFeatures,\n ShapeType,\n PathPointType,\n PathPoint,\n InteractionMode,\n SelectionState,\n ResizeAnchor,\n RenderingContext,\n} from '../types/index.js';\n\nexport { RichText } from '../types/index.js';\n\n// Transform data type guards\nexport {\n isCustomTransform,\n isCircleTransform,\n isArchTransform,\n isWaveTransform,\n isFlagTransform,\n isImageTransform,\n isGroupTransform,\n isShapeTransform,\n isPathTransform,\n hasStroke,\n hasMasks,\n isKnockout,\n hasDistressEffect,\n} from '../types/index.js';\n\n// Kit registry\nexport { KIT_PRESETS } from '../kits/registry.js';\n\n// =============================================================================\n// Section Registry\n// =============================================================================\n\nexport {\n registerSection,\n getSection,\n getSectionIds,\n getAllSections,\n unregisterSection,\n registerBuiltinSections,\n registerBuiltinSectionsSync,\n} from '../kits/sections.js';\nexport type { SectionConfig, SectionComponent } from '../kits/sections.js';\n\n// =============================================================================\n// Kit Serialization\n// =============================================================================\n\nexport { serializeKit, loadKitFromJSON } from '../kits/serialization.js';\nexport type { KitJSON } from '../kits/serialization.js';\n\n// =============================================================================\n// Plugin System: Custom Element Types\n// =============================================================================\n\nexport {\n registerElementType,\n unregisterElementType,\n getCustomElementTypes,\n isRegisteredElementType,\n} from '../plugins/ElementTypePlugin.js';\nexport type { ElementTypeRegistration } from '../plugins/ElementTypePlugin.js';\n\n// =============================================================================\n// Event Bus\n// =============================================================================\n\nexport { CanvasEventBus, canvasEventBus } from '../core/EventBus.js';\nexport type { CanvasEventMap, CanvasEventHandler } from '../core/EventBus.js';\nexport { useCanvasEvents } from '../hooks/useCanvasEvents.js';\n\n// =============================================================================\n// ADR-0054: Visual Guide Overlay\n// =============================================================================\n\nexport { VisualGuideOverlay } from '../components/VisualGuideOverlay.js';\nexport type {\n VisualGuideOverlayProps,\n VisualGuideOverlayStyle,\n OverlayPiece,\n PieceGuide,\n SvgPath,\n ZoneShape,\n GuideLabel,\n} from '../components/VisualGuideOverlay.js';\n\n/**\n * Canvas-native piece-guide rendering (ADR-0054). Prefer the\n * `pieceGuides` prop on `SnowconeCanvas` over mounting a\n * `VisualGuideOverlay` — the canvas-native path draws inside the\n * render loop and avoids the coordinate-system drift of the DOM\n * overlay.\n */\nexport { renderPieceGuides } from '../rendering/PieceGuideRenderer.js';\nexport type {\n PieceGuideRenderParams,\n PieceGuideRendererPiece,\n PieceGuideRendererStyle,\n PieceGuideVisibility,\n} from '../rendering/PieceGuideRenderer.js';\nexport type { PieceGuidesConfig } from '../components/embed/SnowconeCanvas.js';\n\n/**\n * Fetch a product's printable-area guides (dead zones, safe areas) for a raw\n * `SnowconeCanvas` — the same overlay `<RealtimeEmbed>` shows automatically,\n * but for an AUTHORING surface (your own `SnowconeCanvas` + `useLayers` panel)\n * that doesn't go through the embed. Mirror of what www's route shell does:\n * fetch by gvid, then `pieceGuidesForArtboard(bundle, artboard)` (which gates\n * on the design's dims matching the print-file frame) → feed the result to the\n * canvas's `pieceGuides` prop. So the DESIGNER sees the camera cutout while\n * placing fields, not just the buyer afterward.\n */\nexport {\n usePieceGuideBundle,\n pieceGuidesForArtboard,\n PIECE_GUIDES_API_BASE,\n} from '../components/embed-realtime/usePieceGuideBundle.js';\nexport type { PieceGuideBundle } from '../components/embed-realtime/usePieceGuideBundle.js';\n"],"names":["Panel","title","icon","children","enableable","enabled","onEnabledChange","collapsible","expanded","onExpandChange","className","showContent","showExpandButton","handleToggle","handleExpandToggle","jsxs","jsx","ControlGroup","label","labelSuffix","inline","ButtonGroup","options","value","onChange","fullWidth","option","isActive","Toggle","checked","disabled","handleChange","e","Switch","SecondaryToolbar","isOpen","onToggle","trigger","Dropdown","Tooltip","TooltipTrigger","Button","Icon","TooltipContent","serializeKit","kit","json","loadKitFromJSON","overrides","base","resolveKit","extendKit","createKit","validation","validateKit","warning","logger","createLogger","useProjectLoader","handleLoadWorkspace","useEditor","isLoading","setIsLoading","useState","loadError","setLoadError","currentProject","setCurrentProject","projectMetadata","setProjectMetadata","loadFromJSON","useCallback","document","jsonString","blob","file","result","ImportManager","totalElements","sum","ab","_a","error","loadFromFile","text","loadTemplate","template","artboard","index","DEFAULT_BREAKPOINTS","getCurrentBreakpoint","width","config","useBreakpoint","breakpointConfig","setWidth","useEffect","handleResize","breakpoint","useMemo","isXs","isSm","isMd","isLg","isXl","isXxl","breakpointOrder","bp","currentIndex","targetIndex","DEFAULT_METRICS","usePerformance","metrics","setMetrics","isProfiling","setIsProfiling","frameTimesRef","useRef","renderTimesRef","exportTimesRef","lastFrameTimeRef","rafIdRef","calculateFPS","frameTimes","time","calculateAverage","values","val","getMemoryUsage","updateMetrics","newMetrics","measureFrame","now","frameTime","startProfiling","stopProfiling","clearMetrics","getReport","useElementById","id","getElementById","useLayerDragDrop","onReorder","onGroupReorder","onDropIntoGroup","onDropIntoGroupAtPosition","findElement","findParentGroup","draggedId","setDraggedId","dragOverId","setDragOverId","dropPosition","setDropPosition","handleDragStart","elementId","handleDragOver","rect","relativeY","height","isGroup","GroupElement","position","resetDragState","handleDrop","targetId","draggedParent","targetParent","arrayPosition","useLayerSelection","selectedId","multiSelection","onSelectionChange","onMultiSelectionChange","isSelected","handleClick","isInMultiSelection","isCurrentlySelected","newSelection","FontSizeDropdown","memo","fontSizes","handleSelect","size","Popover","PopoverTrigger","PopoverContent","FontSizeGroup","toolbar","fontSize","handleFontSizeIncrement","handleFontSizeChange","openDropdown","setOpenDropdown","clickProtection","el","handleClickOutside","event","target","FONT_SIZES","_customElementTypes","registerElementType","registration","registerTransform","unregisterElementType","type","unregisterTransform","getCustomElementTypes","isRegisteredElementType","getTransformById","CanvasEventBus","handler","handlers","wrapper","data","unsubscribe","err","canvasEventBus","useCanvasEvents","DEFAULT_STYLE","ANCHOR_TO_SVG","renderShape","path","commonProps","resolveBoundary","guide","naturalW","naturalH","svgPathD","rx","ry","x","y","w","h","resolveSafeArea","piece","inset","VisualGuideOverlay","artboardWidth","artboardHeight","canvasPadding","pieces","pieceGuides","show","style","s","autoLabelFontSize","merged","visibility","viewBoxWidth","viewBoxHeight","zones","labels","rotation","isOrtho","boundary","safeArea","originXform","rotationXform","transform","washRadius","boundaryForWash","washOuter","bleedFilterId","pieceRect","canRenderBleed","Fragment","z","zone","i","p","a","ensureIconsRegistered"],"mappings":"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"}
|