@snowcone-app/canvas 0.38.0 → 0.39.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.
Files changed (33) hide show
  1. package/dist/{CanvasRenderer-D-E5WWLm.js → CanvasRenderer-COO5UXtZ.js} +704 -669
  2. package/dist/CanvasRenderer-COO5UXtZ.js.map +1 -0
  3. package/dist/{CanvasStateV1-C9LQt2J0.js → CanvasStateV1-CJJff7Ci.js} +315 -314
  4. package/dist/{CanvasStateV1-C9LQt2J0.js.map → CanvasStateV1-CJJff7Ci.js.map} +1 -1
  5. package/dist/{EditorCanvasImpl-BnxEbr2p.js → EditorCanvasImpl-C_72UKu3.js} +3 -3
  6. package/dist/{EditorCanvasImpl-BnxEbr2p.js.map → EditorCanvasImpl-C_72UKu3.js.map} +1 -1
  7. package/dist/FontAnalyzer-DWHS41ba.js.map +1 -1
  8. package/dist/{SnowconeCanvas-CWSPyqqj.js → SnowconeCanvas-C-YbGCcQ.js} +4 -4
  9. package/dist/{SnowconeCanvas-CWSPyqqj.js.map → SnowconeCanvas-C-YbGCcQ.js.map} +1 -1
  10. package/dist/advanced.mjs +358 -360
  11. package/dist/advanced.mjs.map +1 -1
  12. package/dist/components/VisualGuideOverlay.d.ts +4 -1
  13. package/dist/components/embed-realtime/usePieceGuideBundle.d.ts +2 -0
  14. package/dist/{embed-SOFUZlGB.js → embed-DtlgX9iG.js} +5 -5
  15. package/dist/{embed-SOFUZlGB.js.map → embed-DtlgX9iG.js.map} +1 -1
  16. package/dist/embed.mjs +3 -3
  17. package/dist/index.mjs +4 -4
  18. package/dist/internals.mjs +2 -2
  19. package/dist/rendering/PieceGuideRenderer.d.ts +4 -1
  20. package/dist/state/CanvasStateV1.d.ts +3 -3
  21. package/dist/{template-fields-CBLHgW7G.js → template-fields-Dltc4Ztb.js} +2 -2
  22. package/dist/{template-fields-CBLHgW7G.js.map → template-fields-Dltc4Ztb.js.map} +1 -1
  23. package/dist/types/index.d.ts +2 -1
  24. package/dist/{usePieceGuideBundle-C7r06a_x.js → usePieceGuideBundle-Cab2gRnn.js} +19 -16
  25. package/dist/usePieceGuideBundle-Cab2gRnn.js.map +1 -0
  26. package/dist/utils/pieceRotation.d.ts +35 -0
  27. package/dist/{validation-BG-V98h0.js → validation-Dh9sVEb9.js} +1428 -1430
  28. package/dist/validation-Dh9sVEb9.js.map +1 -0
  29. package/example/package.json +1 -1
  30. package/package.json +3 -3
  31. package/dist/CanvasRenderer-D-E5WWLm.js.map +0 -1
  32. package/dist/usePieceGuideBundle-C7r06a_x.js.map +0 -1
  33. package/dist/validation-BG-V98h0.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-Dh9sVEb9.js","sources":["../src/types/capabilities.ts","../src/kits/presets.ts","../src/kits/registry.ts","../src/contexts/KitContext.tsx","../src/hooks/useKeyboardShortcuts.ts","../src/components/CanvasEditor/hooks/useAnimatedFocusRect.ts","../src/utils/textCursorUtils.ts","../src/components/CanvasEditor/hooks/useMarqueeSelection.ts","../src/core/PenToolManager.ts","../src/components/CanvasEditor/hooks/usePenTool.tsx","../src/components/CanvasEditor/hooks/useActiveChild.ts","../src/components/CanvasEditor/hooks/useHoverState.ts","../src/core/CoordinateTransform.ts","../src/core/CropModeController.ts","../src/components/CanvasEditor/hooks/useCropMode.ts","../src/components/CanvasEditor/hooks/useMultiSelection.ts","../src/components/CanvasEditor/hooks/useTextEditing.ts","../src/components/CanvasEditor/hooks/useCanvasLayout.ts","../src/components/CanvasEditor/hooks/useInteractionState.ts","../src/components/CanvasEditor/hooks/useRenderState.ts","../src/rendering/PieceGuideRenderer.ts","../src/components/CanvasEditor/renderers/multiSelectionRenderer.ts","../src/components/CanvasEditor/renderers/marqueeRenderer.ts","../src/components/CanvasEditor/renderers/hoverRenderer.ts","../src/utils/PerformanceMonitor.ts","../src/components/CanvasEditor/hooks/buildSpreadClipShape.ts","../src/components/CanvasEditor/hooks/useCanvasRenderLoop.ts","../src/core/PinchHandler.ts","../src/components/CanvasEditor/handlers/multiSelectionHandlers.ts","../src/components/CanvasEditor/handlers/cropModeHandlers.ts","../src/components/CanvasEditor/handlers/groupChildHandlers.ts","../src/components/CanvasEditor/hooks/useCanvasInteraction.ts","../src/components/CanvasEditor/hooks/useTextEditingHandlers.ts","../src/components/CanvasEditor.tsx","../src/vendor/ui/primitives/badge.tsx","../src/vendor/ui/primitives/fieldset.tsx","../src/vendor/ui/primitives/radio-group.tsx","../src/vendor/ui/primitives/spinner.tsx","../src/components/CanvasContextMenu.tsx","../src/components/embed/Canvas.tsx","../src/kits/sections.ts","../src/components/StrokePanel.tsx","../src/components/MaskItem.tsx","../src/components/MasksPanel.tsx","../src/effects/distress-presets.ts","../src/components/DistressPanel.tsx","../src/rendering/knockout-utils.ts","../src/components/CompositingPanel.tsx","../src/components/GlyphPicker.tsx","../src/components/OpenTypeFeaturesPanel.tsx","../src/components/EffectsPanel.tsx","../src/components/embed/EffectsPanel.tsx","../src/components/embed/ExportPanel.tsx","../src/components/GlyphBrowserDrawer.tsx","../src/components/embed/GlyphPanel.tsx","../src/components/embed/ArtboardTabs.tsx","../src/components/embed/ZoomControls.tsx","../src/services/AutoExportManager.ts","../src/components/embed/LoadingStates.tsx","../src/kits/validation.ts"],"sourcesContent":["/**\n * EditorCapabilities - Feature flags for editor components\n *\n * Controls which elements, transforms, effects, panels, and tools\n * are available in a given editor instance. Used by KitDefinition\n * and KitRenderer to configure the editor for different use cases.\n */\n\nimport type { TransformType } from './index.js';\n\n/** Types of elements that can be created in the editor */\nexport type ElementType = 'text' | 'image' | 'shape' | 'path' | 'group';\n\n/** Types of visual effects available */\nexport type EffectType = 'stroke' | 'mask' | 'distress' | 'knockout';\n\n/** Types of UI panels available */\nexport type PanelType = 'layers' | 'effects' | 'export' | 'images' | 'artboards';\n\n/** Types of editing tools available */\nexport type ToolType = 'select' | 'pen' | 'crop' | 'zoom';\n\n/**\n * Defines the full set of capabilities available in an editor instance.\n *\n * Each field controls a specific feature set. Kit presets use these\n * to restrict or expand what the editor can do.\n */\nexport interface EditorCapabilities {\n /** Allowed element types that can be created */\n elements: ElementType[];\n\n /** Allowed transform types for text elements */\n transforms: TransformType[];\n\n /** Allowed visual effect types */\n effects: EffectType[];\n\n /** UI panels that are available */\n panels: PanelType[];\n\n /** Editing tools available in the toolbar */\n tools: ToolType[];\n\n /** Boolean feature flags for editor behavior */\n features: {\n /** Whether exporting artboards is enabled */\n export: boolean;\n /** Whether the user can create and manage multiple artboards */\n multiArtboard: boolean;\n /** Whether rich text (per-character styling) is enabled */\n richText: boolean;\n /** Whether undo/redo is enabled */\n undo: boolean;\n /** Whether keyboard shortcuts are active */\n keyboardShortcuts: boolean;\n };\n}\n\n/**\n * All capabilities enabled -- for the full pro editor.\n * Includes every element type, transform, effect, panel, and tool.\n */\nexport const ALL_CAPABILITIES: EditorCapabilities = {\n elements: ['text', 'image', 'shape', 'path', 'group'],\n transforms: ['custom', 'circle', 'lean', 'arch', 'ascend', 'wave', 'flag', 'image', 'group', 'shape', 'path'],\n effects: ['stroke', 'mask', 'distress', 'knockout'],\n panels: ['layers', 'effects', 'export', 'images', 'artboards'],\n tools: ['select', 'pen', 'crop', 'zoom'],\n features: {\n export: true,\n multiArtboard: true,\n richText: true,\n undo: true,\n keyboardShortcuts: true,\n },\n};\n\n/**\n * Minimal capabilities -- text and image only, single artboard.\n * Suitable for simple product customizers and embedded editors.\n */\nexport const MINIMAL_CAPABILITIES: EditorCapabilities = {\n elements: ['text', 'image'],\n transforms: ['custom', 'circle', 'arch', 'wave', 'image'],\n effects: ['stroke'],\n panels: [],\n tools: ['select', 'zoom'],\n features: {\n export: true,\n multiArtboard: false,\n richText: false,\n undo: true,\n keyboardShortcuts: true,\n },\n};\n","/**\n * Kit Presets - Reference implementations\n *\n * Three preset kits for common use cases:\n * - COMPACT_CUSTOMIZER: Canvas + contextual toolbar, text/image only\n * - PRO_STUDIO: Full panels, all features, multi-artboard\n * - EMBED_ONLY: Canvas only, auto-export, no UI chrome\n */\n\nimport { ALL_CAPABILITIES, MINIMAL_CAPABILITIES } from '../types/capabilities.js';\nimport type { KitDefinition } from './types.js';\n\n/**\n * Compact customizer -- minimal UI for product customization.\n *\n * Includes a contextual toolbar and single-selection mode.\n * Best for e-commerce product customization flows where the user\n * adds text or uploads an image onto a product.\n *\n * @example\n * ```tsx\n * <KitRenderer kit={COMPACT_CUSTOMIZER} />\n * ```\n */\nexport const COMPACT_CUSTOMIZER: KitDefinition = {\n name: 'compact-customizer',\n layout: {\n slots: {\n topbar: ['contextual-toolbar'],\n canvas: ['canvas'],\n },\n },\n capabilities: {\n ...MINIMAL_CAPABILITIES,\n effects: ['stroke'],\n panels: [],\n tools: ['select', 'zoom'],\n },\n behavior: {\n toolbar: 'contextual',\n selection: 'single',\n panelMode: 'drawer',\n onEmptyClick: 'deselect',\n autoExport: false,\n },\n style: {\n density: 'compact',\n toolbarVariant: 'pill',\n panelVariant: 'floating',\n },\n content: {\n sampleText: 'Your Text',\n defaultFontFamily: 'Poppins',\n },\n};\n\n/**\n * Pro studio -- full editor with all panels and features.\n *\n * Includes layers panel, effects panel, multi-selection, and all\n * transform/effect types. Best for design professionals or admin\n * interfaces where full editing power is needed.\n *\n * @example\n * ```tsx\n * <KitRenderer kit={PRO_STUDIO} />\n * ```\n */\nexport const PRO_STUDIO: KitDefinition = {\n name: 'pro-studio',\n layout: {\n slots: {\n // D6 (ADR-0085 P4): slots resolve to the public `Editor.*` parts —\n // `add-element-menu` is `Editor.Add` (the ⊕ catalog only), so the\n // save/open/export overflow now comes from `menu-button`\n // (`Editor.Menu`), which this preset previously leaned on\n // `add-element-menu`'s MenuButton hack for.\n topbar: ['add-element-menu', 'undo-redo-controls', 'menu-button', 'contextual-toolbar'],\n left: ['layers-panel'],\n canvas: ['canvas'],\n right: ['effects-panel'],\n },\n },\n capabilities: ALL_CAPABILITIES,\n behavior: {\n toolbar: 'contextual',\n selection: 'multi',\n panelMode: 'sidebar',\n onEmptyClick: 'deselect',\n autoExport: false,\n },\n style: {\n density: 'comfortable',\n toolbarVariant: 'flat',\n panelVariant: 'card',\n },\n content: {\n sampleText: 'New Text',\n defaultFontFamily: 'Poppins',\n },\n};\n\n/**\n * Embed only -- canvas with no UI chrome, for embedding in other apps.\n *\n * No toolbar, no panels, auto-export enabled. Best for headless\n * integration where the host app provides all UI and just needs\n * the canvas rendering engine.\n *\n * @example\n * ```tsx\n * <KitRenderer kit={EMBED_ONLY} />\n * ```\n */\nexport const EMBED_ONLY: KitDefinition = {\n name: 'embed-only',\n layout: {\n slots: {\n canvas: ['canvas'],\n },\n },\n capabilities: {\n ...MINIMAL_CAPABILITIES,\n panels: [],\n tools: ['select'],\n features: {\n export: true,\n multiArtboard: false,\n richText: false,\n undo: true,\n keyboardShortcuts: false,\n },\n },\n behavior: {\n toolbar: 'none',\n selection: 'single',\n panelMode: 'inline',\n onEmptyClick: 'noop',\n autoExport: true,\n },\n style: {\n density: 'compact',\n toolbarVariant: 'minimal',\n panelVariant: 'flush',\n },\n};\n","/**\n * Kit Preset Registry\n *\n * Central registry that maps preset IDs to their KitDefinition objects.\n * Provides `resolveKit()` to accept either a preset ID string or a\n * full KitDefinition, making it easy for consumers to use shorthand\n * names while still supporting custom kit definitions.\n */\n\nimport type { KitDefinition, KitPresetId } from './types.js';\nimport { COMPACT_CUSTOMIZER, PRO_STUDIO, EMBED_ONLY } from './presets.js';\n\n/**\n * Map of all registered kit presets, keyed by their preset ID.\n *\n * @example\n * ```ts\n * const kit = KIT_PRESETS.get('compact-customizer');\n * // kit === COMPACT_CUSTOMIZER\n * ```\n */\nexport const KIT_PRESETS: ReadonlyMap<KitPresetId, KitDefinition> = new Map<KitPresetId, KitDefinition>([\n ['compact-customizer', COMPACT_CUSTOMIZER],\n ['pro-studio', PRO_STUDIO],\n ['embed-only', EMBED_ONLY],\n]);\n\n/**\n * Resolve a kit from either a preset ID string or a full KitDefinition.\n *\n * This is the primary entry point for consumers. It allows components\n * like `<KitRenderer>` to accept a `kit` prop that is either a string\n * shorthand or a fully custom definition.\n *\n * @param kit - A preset ID string (e.g., 'compact-customizer') or a full KitDefinition object\n * @returns The resolved KitDefinition\n * @throws {Error} If a string is passed that does not match any registered preset\n *\n * @example\n * ```ts\n * // Using a preset ID\n * const kit = resolveKit('pro-studio');\n *\n * // Using a custom definition (passed through unchanged)\n * const custom: KitDefinition = { name: 'my-kit', ... };\n * const kit = resolveKit(custom);\n * ```\n */\nexport function resolveKit(kit: KitPresetId | KitDefinition): KitDefinition {\n if (typeof kit === 'string') {\n const preset = KIT_PRESETS.get(kit);\n if (!preset) {\n const validIds = Array.from(KIT_PRESETS.keys()).join(', ');\n throw new Error(\n `[resolveKit] Unknown kit preset \"${kit}\". Valid presets: ${validIds}`\n );\n }\n return preset;\n }\n return kit;\n}\n","/**\n * KitContext - Provides the resolved KitDefinition to child components\n *\n * Allows any component in the tree to access the current kit configuration,\n * including capabilities, behavior settings, layout, and style preferences.\n * Supports runtime kit switching via `setKit`.\n *\n * @example\n * ```tsx\n * import { useKit, useCapabilities } from '@snowcone-app/canvas';\n *\n * function MyToolbar() {\n * const capabilities = useCapabilities();\n * if (!capabilities.features.richText) return null;\n * return <RichTextToolbar />;\n * }\n *\n * function KitSwitcher() {\n * const { kit, setKit } = useKit();\n * return (\n * <button onClick={() => setKit('pro-studio')}>\n * Switch to Pro Studio (current: {kit.name})\n * </button>\n * );\n * }\n * ```\n */\n\nimport { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';\nimport type { KitDefinition, KitPresetId } from '../kits/types.js';\nimport { resolveKit } from '../kits/registry.js';\n\n/**\n * The value provided by KitContext, including the current kit\n * and a function to switch kits at runtime.\n */\nexport interface KitContextValue {\n /** The current resolved KitDefinition */\n kit: KitDefinition;\n /** Switch the active kit. Accepts a preset ID string or a full KitDefinition. */\n setKit: (kit: KitPresetId | KitDefinition) => void;\n}\n\nconst KitContext = createContext<KitContextValue | null>(null);\n\n/**\n * Props for the KitProvider component.\n */\nexport interface KitProviderProps {\n /** The initial kit definition to provide. Updates to this prop will sync the internal state. */\n kit: KitDefinition;\n children: React.ReactNode;\n}\n\n/**\n * Provides a resolved KitDefinition to all child components via context.\n * Supports runtime kit switching: children can call `setKit` from `useKit()`\n * to dynamically change the active kit. If the parent passes a new `kit` prop,\n * the internal state is synced to match.\n */\nexport function KitProvider({ kit: initialKit, children }: KitProviderProps) {\n const [currentKit, setCurrentKit] = useState<KitDefinition>(initialKit);\n\n // Sync if parent passes a new kit prop\n useEffect(() => {\n setCurrentKit(initialKit);\n }, [initialKit]);\n\n const setKit = useCallback((kit: KitPresetId | KitDefinition) => {\n const resolved = typeof kit === 'string' ? resolveKit(kit) : kit;\n setCurrentKit(resolved);\n }, []);\n\n const value = useMemo<KitContextValue>(\n () => ({ kit: currentKit, setKit }),\n [currentKit, setKit],\n );\n\n return <KitContext.Provider value={value}>{children}</KitContext.Provider>;\n}\n\n/**\n * Returns the current kit context value, including the active KitDefinition\n * and a `setKit` function for runtime kit switching.\n *\n * Must be called within a KitProvider.\n *\n * @throws {Error} If called outside of a KitProvider\n *\n * @example\n * ```tsx\n * const { kit, setKit } = useKit();\n * console.log(kit.name);\n * setKit('pro-studio'); // switch to a preset\n * setKit(myCustomKit); // switch to a custom kit\n * ```\n */\nexport function useKit(): KitContextValue {\n const ctx = useContext(KitContext);\n if (!ctx) throw new Error('useKit must be used within a KitProvider');\n return ctx;\n}\n\n/**\n * Convenience hook that returns just the capabilities from the current kit.\n * Equivalent to `useKit().kit.capabilities`.\n *\n * @throws {Error} If called outside of a KitProvider\n */\nexport function useCapabilities() {\n const { kit } = useKit();\n return kit.capabilities;\n}\n","/**\n * useKeyboardShortcuts - Hook for handling keyboard shortcuts in the editor\n * Provides copy/paste and other keyboard shortcuts\n */\n\nimport { useEffect } from 'react';\nimport { useEditor } from '../contexts/EditorContext.js';\nimport { useIsLayerBound } from '../contexts/BoundLayerGuardContext.js';\nimport { useLayers } from './useLayers.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport { GroupElement } from '../core/GroupElement.js';\n\n/**\n * Check if any image is currently in crop mode\n * Handles both standalone images and images inside groups\n */\nfunction isAnyCroppingActive(\n selectedElement: ReturnType<typeof useEditor>['selectedElement'],\n activeChildElement: ReturnType<typeof useEditor>['activeChildElement']\n): boolean {\n // Check if active child element is an image in crop mode\n if (activeChildElement instanceof ImageElement && activeChildElement.isCropping) {\n return true;\n }\n\n // Check if selected element is an image in crop mode\n if (selectedElement instanceof ImageElement && selectedElement.isCropping) {\n return true;\n }\n\n // Check if selected element is a group with a child image in crop mode\n if (selectedElement instanceof GroupElement) {\n for (const child of selectedElement.children) {\n if (child instanceof ImageElement && child.isCropping) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Select every element on the ACTIVE artboard (multi-selection).\n *\n * Shared implementation behind Cmd/Ctrl+A AND the canvas context menu's\n * \"Select All\" entry — one definition so the two surfaces can't drift.\n * Locked elements are included — locking gates manipulation, not selection\n * (gotcha #20); hidden elements are skipped to mirror marquee selection.\n * Same state path as marquee selection: the multi-selection array is the\n * source of truth; single selection is cleared alongside it.\n */\nexport function selectAllOnActiveArtboard(opts: {\n elements: ReturnType<typeof useEditor>['elements'];\n artboardManager: ReturnType<typeof useEditor>['artboardManager'];\n handleSelectionChange: (id: string | null) => void;\n setMultiSelection: (ids: string[]) => void;\n}): void {\n const { elements, artboardManager, handleSelectionChange, setMultiSelection } = opts;\n const activeArtboardId = artboardManager.getActiveArtboardId();\n if (!activeArtboardId) return;\n const ids = elements\n .filter((el) => el.visible !== false)\n .filter((el) => artboardManager.getArtboardIdForElement(el.id) === activeArtboardId)\n .map((el) => el.id);\n if (ids.length === 0) return;\n handleSelectionChange(null);\n setMultiSelection(ids);\n}\n\n/**\n * Hook that sets up keyboard shortcuts for the editor\n * Handles copy/paste, delete, undo/redo, and other shortcuts\n *\n * @param options.enabled - Whether keyboard shortcuts are active (default: true)\n */\nexport function useKeyboardShortcuts(options?: { enabled?: boolean }) {\n const enabled = options?.enabled ?? true;\n const {\n selectedElement,\n activeChildElement,\n multiSelection,\n elements,\n handleElementUpdate,\n handleCopyElements,\n handlePasteElements,\n executeRemoveElement,\n handleSelectionChange,\n setMultiSelection,\n // Use per-artboard undo/redo for keyboard shortcuts\n undoActiveArtboard,\n redoActiveArtboard,\n setHideHandles,\n handleAddElement,\n isToolbarMenuOpen,\n artboardManager,\n } = useEditor();\n\n // Bound-layer suppression (ADR-0085 §3): a layer a mounted\n // <Editor.TextField> is bound to is not keyboard-deletable — the gesture is\n // silently skipped, mirroring the hidden affordance in LayersPanel (the\n // command path would otherwise throw E_BOUND_LAYER_MUTATION from a key\n // handler). No-op outside <Editor.Root>.\n const isLayerBound = useIsLayerBound();\n\n const { duplicateLayer } = useLayers();\n\n useEffect(() => {\n if (!enabled) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n // Check if user is interacting with a UI control that needs keyboard input\n const target = e.target as HTMLElement;\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') return;\n // contentEditable regions (host-page rich text, etc.) own their keys too\n if (target.isContentEditable) return;\n // Skip when focus is inside a listbox (e.g. preset carousels) or other\n // interactive toolbar controls that use arrow keys for navigation\n if (target.closest?.('[role=\"listbox\"], [role=\"menu\"], [role=\"tablist\"]')) return;\n\n // Cmd/Ctrl+Z: Undo (per-artboard)\n if ((e.metaKey || e.ctrlKey) && e.key === 'z' && !e.shiftKey) {\n e.preventDefault();\n undoActiveArtboard();\n return;\n }\n\n // Cmd/Ctrl+Shift+Z or Ctrl+Y: Redo (per-artboard)\n if (((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'z') || (e.ctrlKey && e.key === 'y')) {\n e.preventDefault();\n redoActiveArtboard();\n return;\n }\n\n // Cmd/Ctrl+A: Select all elements on the ACTIVE artboard (multi-selection).\n // Locked elements are included — locking gates manipulation, not\n // selection (gotcha #20); hidden elements are skipped to mirror\n // marquee selection. preventDefault stops the browser selecting the page.\n if ((e.metaKey || e.ctrlKey) && e.key === 'a') {\n e.preventDefault();\n selectAllOnActiveArtboard({\n elements,\n artboardManager,\n handleSelectionChange,\n setMultiSelection,\n });\n return;\n }\n\n // Cmd/Ctrl+C: Copy selected element(s)\n if ((e.metaKey || e.ctrlKey) && e.key === 'c') {\n e.preventDefault();\n handleCopyElements();\n return;\n }\n\n // Cmd/Ctrl+V: Paste element(s) from clipboard\n if ((e.metaKey || e.ctrlKey) && e.key === 'v') {\n e.preventDefault();\n handlePasteElements();\n return;\n }\n\n // Cmd/Ctrl+D: Duplicate selected element(s)\n if ((e.metaKey || e.ctrlKey) && e.key === 'd') {\n e.preventDefault();\n if (selectedElement) {\n duplicateLayer(selectedElement.id);\n } else if (multiSelection.length > 0) {\n // Duplicate all selected elements\n multiSelection.forEach((id) => {\n duplicateLayer(id);\n });\n }\n return;\n }\n\n // Escape: Deselect element (text edit exit is handled by CanvasEditor)\n if (e.key === 'Escape') {\n e.preventDefault();\n // Deselect current selection\n if (selectedElement || multiSelection.length > 0) {\n handleSelectionChange(null);\n setMultiSelection([]);\n }\n return;\n }\n\n // T: Add new text element (like Figma/Sketch)\n if (e.key === 't' && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey) {\n e.preventDefault();\n handleAddElement('text');\n return;\n }\n\n // Delete or Backspace: Delete selected element(s)\n // Only works when element is selected and no tools/panels are open\n if (e.key === 'Delete' || e.key === 'Backspace') {\n // Don't delete when any toolbar menu/panel/dropdown is open\n if (isToolbarMenuOpen) {\n return;\n }\n\n // Don't delete when any image is in crop mode (standalone or in group)\n if (isAnyCroppingActive(selectedElement, activeChildElement)) {\n return;\n }\n\n e.preventDefault();\n // Handle multi-selection delete. Locked elements are deletable\n // — locking gates *manipulation* (drag/resize/rotate) but\n // delete is a deliberate, single-shortcut action so the user\n // doesn't have to first toggle the lock to remove an element.\n if (multiSelection.length > 0) {\n const elementsToDelete = elements.filter(\n (s) => multiSelection.includes(s.id) && !isLayerBound(s.name)\n );\n elementsToDelete.forEach((element) => {\n executeRemoveElement(element);\n });\n setMultiSelection([]);\n handleSelectionChange(null);\n return;\n }\n\n // Handle single selection delete\n if (selectedElement && !isLayerBound(selectedElement.name)) {\n executeRemoveElement(selectedElement);\n handleSelectionChange(null);\n }\n return;\n }\n\n // Handle arrow keys for nudging or multi-selection nudging\n if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n // If multi-selection is active, nudge all selected elements\n if (multiSelection.length > 0) {\n e.preventDefault();\n\n // Hide handles during nudging\n setHideHandles(true);\n\n // Use 10px increment if Shift is held, otherwise 1px\n const increment = e.shiftKey ? 10 : 1;\n\n let dx = 0;\n let dy = 0;\n\n if (e.key === 'ArrowLeft') dx = -increment;\n if (e.key === 'ArrowRight') dx = increment;\n if (e.key === 'ArrowUp') dy = -increment;\n if (e.key === 'ArrowDown') dy = increment;\n\n // Update all selected elements\n const selectedElements = elements.filter((s) => multiSelection.includes(s.id));\n selectedElements.forEach((element) => {\n const updatedElement = element.clone();\n updatedElement.x += dx;\n updatedElement.y += dy;\n handleElementUpdate(updatedElement);\n });\n\n return;\n }\n\n // If single element is selected, nudge it\n if (selectedElement) {\n e.preventDefault();\n\n // Hide handles during nudging\n setHideHandles(true);\n\n // Use 10px increment if Shift is held, otherwise 1px\n const increment = e.shiftKey ? 10 : 1;\n\n let dx = 0;\n let dy = 0;\n\n if (e.key === 'ArrowLeft') dx = -increment;\n if (e.key === 'ArrowRight') dx = increment;\n if (e.key === 'ArrowUp') dy = -increment;\n if (e.key === 'ArrowDown') dy = increment;\n\n const updatedElement = selectedElement.clone();\n updatedElement.x += dx;\n updatedElement.y += dy;\n handleElementUpdate(updatedElement);\n\n return;\n }\n }\n };\n\n const handleKeyUp = (e: KeyboardEvent) => {\n // Restore selection handles after nudge ends\n if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n setHideHandles(false);\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n window.addEventListener('keyup', handleKeyUp);\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n window.removeEventListener('keyup', handleKeyUp);\n };\n }, [\n enabled,\n selectedElement,\n activeChildElement,\n multiSelection,\n elements,\n handleElementUpdate,\n handleCopyElements,\n handlePasteElements,\n executeRemoveElement,\n handleSelectionChange,\n setMultiSelection,\n undoActiveArtboard,\n redoActiveArtboard,\n setHideHandles,\n duplicateLayer,\n isToolbarMenuOpen,\n isLayerBound,\n artboardManager,\n ]);\n}\n\n","/**\n * useAnimatedFocusRect — smoothly interpolates a focus rect when the\n * target changes. Used by ADR-0060 multi-piece focus to \"dolly\"\n * between pieces (Back ↔ Spine ↔ Front) instead of snapping.\n *\n * Snaps (no animation) on first mount and when target becomes null,\n * since there's no meaningful \"from\" value in those cases. Cancels\n * the in-flight tween on every target change so rapid switches feel\n * responsive instead of queueing.\n */\n\nimport { useEffect, useRef, useState } from 'react';\n\nexport interface FocusRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nconst DEFAULT_DURATION_MS = 280;\nconst easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3);\n\nexport function useAnimatedFocusRect(\n target: FocusRect | null,\n durationMs: number = DEFAULT_DURATION_MS,\n): FocusRect | null {\n const [rect, setRect] = useState<FocusRect | null>(target);\n const rectRef = useRef<FocusRect | null>(rect);\n rectRef.current = rect;\n const rafRef = useRef<number | null>(null);\n\n const targetKey = target\n ? `${target.x},${target.y},${target.width},${target.height}`\n : 'none';\n\n useEffect(() => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n\n if (!target) {\n setRect(null);\n return;\n }\n\n const from = rectRef.current;\n if (!from) {\n setRect(target);\n return;\n }\n\n if (\n from.x === target.x &&\n from.y === target.y &&\n from.width === target.width &&\n from.height === target.height\n ) {\n return;\n }\n\n const start = performance.now();\n const tick = (now: number) => {\n const t = Math.min(1, (now - start) / durationMs);\n const eased = easeOutCubic(t);\n setRect({\n x: from.x + (target.x - from.x) * eased,\n y: from.y + (target.y - from.y) * eased,\n width: from.width + (target.width - from.width) * eased,\n height: from.height + (target.height - from.height) * eased,\n });\n if (t < 1) {\n rafRef.current = requestAnimationFrame(tick);\n } else {\n rafRef.current = null;\n }\n };\n rafRef.current = requestAnimationFrame(tick);\n\n return () => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n };\n }, [targetKey, durationMs]);\n\n return rect;\n}\n","import { TextElement } from '../core/TextElement';\nimport { wrapText, measureTextWidth } from '../core/TextMetrics';\nimport { HORIZONTAL_PADDING } from '../constants';\n\n/**\n * Get word boundaries at a given character index.\n * Returns the start and end indices of the word containing the given index.\n * Words are separated by whitespace.\n *\n * @param text The text to search in\n * @param index The character index to find the word at\n * @returns Object with start and end indices of the word\n */\nexport function getWordBoundaryAt(\n text: string,\n index: number\n): { start: number; end: number } {\n if (text.length === 0) {\n return { start: 0, end: 0 };\n }\n\n // Clamp index to valid range\n const clampedIndex = Math.max(0, Math.min(index, text.length - 1));\n\n // Word boundary pattern - non-whitespace characters\n const wordChars = /[^\\s]/;\n\n // If clicking on whitespace, find adjacent word\n if (!wordChars.test(text[clampedIndex])) {\n // Look for word to the right first\n let rightStart = clampedIndex;\n while (rightStart < text.length && !wordChars.test(text[rightStart])) {\n rightStart++;\n }\n if (rightStart < text.length) {\n // Found word to the right\n let rightEnd = rightStart;\n while (rightEnd < text.length && wordChars.test(text[rightEnd])) {\n rightEnd++;\n }\n return { start: rightStart, end: rightEnd };\n }\n\n // Look for word to the left\n let leftEnd = clampedIndex;\n while (leftEnd > 0 && !wordChars.test(text[leftEnd - 1])) {\n leftEnd--;\n }\n if (leftEnd > 0) {\n let leftStart = leftEnd;\n while (leftStart > 0 && wordChars.test(text[leftStart - 1])) {\n leftStart--;\n }\n return { start: leftStart, end: leftEnd };\n }\n\n // No words found, return current position\n return { start: clampedIndex, end: clampedIndex };\n }\n\n // Find word start (scan backwards)\n let start = clampedIndex;\n while (start > 0 && wordChars.test(text[start - 1])) {\n start--;\n }\n\n // Find word end (scan forwards)\n let end = clampedIndex;\n while (end < text.length && wordChars.test(text[end])) {\n end++;\n }\n\n return { start, end };\n}\n\n/**\n * Get line info for a cursor position - which line it's on and the X position within that line\n */\nexport function getCursorLineInfo(\n element: TextElement,\n cursorIndex: number\n): { lineIndex: number; lineCount: number; xPositionInLine: number; lineStartIndex: number } {\n const { fontSize, fontFamily, bold, italic, textAlign } = element;\n const elementLetterSpacing = element.letterSpacing ?? 0;\n const richText = element.getRichText();\n\n const visualBbox = element.getVisualBoundingBox();\n const maxWidth = visualBbox.width;\n\n let wrappingWidth = maxWidth;\n let horizontalPadding = 0;\n\n if (element.transformType === 'custom') {\n horizontalPadding = HORIZONTAL_PADDING;\n wrappingWidth = maxWidth - (horizontalPadding * 2);\n }\n\n const lines = wrapText(\n element.getText(),\n wrappingWidth,\n fontSize,\n fontFamily,\n bold,\n italic,\n undefined,\n true,\n { letterSpacing: elementLetterSpacing }\n );\n\n // Find which line the cursor is on\n let charStartIndex = 0;\n let lineIndex = 0;\n\n for (let i = 0; i < lines.length; i++) {\n const lineEndIndex = charStartIndex + lines[i].length;\n if (cursorIndex <= lineEndIndex) {\n lineIndex = i;\n break;\n }\n charStartIndex += lines[i].length + 1; // +1 for newline\n if (i === lines.length - 1) {\n lineIndex = i;\n }\n }\n\n // Calculate X position within the line\n const line = lines[lineIndex];\n const posInLine = cursorIndex - charStartIndex;\n\n // Handle space collapsing\n let visibleLineText = line;\n let leadingSpacesCount = 0;\n\n const isParagraphStart = lineIndex === 0;\n const isParagraphEnd = lineIndex === lines.length - 1;\n\n if (!isParagraphStart) {\n const leadingSpacesMatch = line.match(/^ +/);\n leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n if (leadingSpacesCount > 0) {\n visibleLineText = visibleLineText.substring(leadingSpacesCount);\n }\n }\n\n if (!isParagraphEnd) {\n const trailingSpacesMatch = visibleLineText.match(/ +$/);\n const trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n if (trailingSpacesCount > 0) {\n visibleLineText = visibleLineText.substring(0, visibleLineText.length - trailingSpacesCount);\n }\n }\n\n const lineWidth = measureTextWidth(visibleLineText, fontSize, fontFamily, bold, italic, elementLetterSpacing);\n\n let lineStartX = horizontalPadding;\n if (textAlign === 'center') {\n lineStartX = (maxWidth - lineWidth) / 2;\n } else if (textAlign === 'right') {\n lineStartX = maxWidth - horizontalPadding - lineWidth;\n }\n\n // Measure X position up to cursor within line\n let xPosition = lineStartX;\n const measureEnd = Math.min(posInLine, line.length);\n\n for (let i = 0; i < measureEnd; i++) {\n if (!isParagraphStart && i < leadingSpacesCount) continue;\n\n const char = line[i];\n const globalIndex = charStartIndex + i;\n const style = richText.getStyleAt(globalIndex);\n\n const charFontSize = style.fontSize || fontSize;\n const charFontFamily = style.fontFamily || fontFamily;\n const charBold = style.bold !== undefined ? style.bold : bold;\n const charItalic = style.italic !== undefined ? style.italic : italic;\n // Same advance model as paint: every character gets +letterSpacing\n const charSpacing = style.letterSpacing !== undefined ? style.letterSpacing : elementLetterSpacing;\n\n xPosition += measureTextWidth(char, charFontSize, charFontFamily, charBold, charItalic, charSpacing);\n }\n\n return {\n lineIndex,\n lineCount: lines.length,\n xPositionInLine: xPosition,\n lineStartIndex: charStartIndex\n };\n}\n\n/**\n * Move cursor up or down by one line, trying to maintain the same X position\n */\nexport function moveCursorVertically(\n element: TextElement,\n cursorIndex: number,\n direction: 'up' | 'down'\n): number {\n const { fontSize } = element;\n const lineHeight = element.lineHeight ?? 1.2;\n const lineSpacing = fontSize * lineHeight;\n\n const info = getCursorLineInfo(element, cursorIndex);\n\n // Calculate target line\n const targetLineIndex = direction === 'up'\n ? Math.max(0, info.lineIndex - 1)\n : Math.min(info.lineCount - 1, info.lineIndex + 1);\n\n // If already at first/last line, stay at same position\n if (targetLineIndex === info.lineIndex) {\n return cursorIndex;\n }\n\n // Use getCursorIndexFromPoint with the target line's Y and same X\n const targetY = targetLineIndex * lineSpacing + lineSpacing / 2; // Middle of target line\n\n return getCursorIndexFromPoint(element, {\n x: info.xPositionInLine,\n y: targetY\n });\n}\n\n/**\n * Calculates the cursor character index based on a point relative to the text element's\n * top-left corner (local coordinates).\n * \n * @param element The text element\n * @param point Local coordinates {x, y} relative to the element's visual bounding box top-left\n * @returns The character index closest to the point\n */\nexport function getCursorIndexFromPoint(\n element: TextElement,\n point: { x: number; y: number }\n): number {\n const { fontSize, fontFamily, bold, italic, textAlign } = element;\n const elementLetterSpacing = element.letterSpacing ?? 0;\n const richText = element.getRichText();\n \n // Determine wrapping configuration\n // For CustomTransform, width is fixed and we have padding\n const visualBbox = element.getVisualBoundingBox();\n const maxWidth = visualBbox.width;\n \n let wrappingWidth = maxWidth;\n let horizontalPadding = 0;\n \n // Check transform type - currently focusing on 'custom' which has padding\n if (element.transformType === 'custom') {\n horizontalPadding = HORIZONTAL_PADDING;\n wrappingWidth = maxWidth - (horizontalPadding * 2);\n }\n\n // Get wrapped lines\n // Note: This must match getWrappedTextForEditing logic in CanvasEditor\n // and rendering logic in CanvasRenderer to ensure consistency.\n const lines = wrapText(\n element.getText(),\n wrappingWidth,\n fontSize,\n fontFamily,\n bold,\n italic,\n undefined, // No locked line count\n true, // strict: true to match rendering logic\n { letterSpacing: elementLetterSpacing }\n );\n\n // Calculate layout metrics\n const lineHeight = element.lineHeight ?? 1.2; // Default line height multiplier\n const lineSpacing = fontSize * lineHeight;\n \n // Adjust input point for padding if necessary\n // We treat the point as relative to the Visual Bounding Box.\n // Text rendering usually starts at (padding, 0) relative to bbox for CustomTransform?\n // CustomTransform getBoundingBox returns y = center - height/2.\n // Text is rendered starting at that top-y.\n // So Y is fine. X needs padding adjustment.\n \n // Find which line the Y coordinate falls into\n let lineIndex = 0;\n let currentY = 0;\n \n // If above the first line\n if (point.y < 0) {\n return 0;\n }\n\n for (let i = 0; i < lines.length; i++) {\n // Check if point is within this line's vertical span\n // Using lineSpacing as the height per line\n if (point.y >= currentY && point.y < currentY + lineSpacing) {\n lineIndex = i;\n break;\n }\n \n currentY += lineSpacing;\n \n // If we reached the last line and haven't found it (y is below text),\n // stick to the last line\n if (i === lines.length - 1) {\n lineIndex = i;\n }\n }\n \n // Calculate the starting character index for this line\n // Since lines are joined by '\\n' in the editor, we count line length + 1 for newline\n let charStartIndex = 0;\n for (let i = 0; i < lineIndex; i++) {\n charStartIndex += lines[i].length + 1; // +1 for the newline character\n }\n \n // Now find the character within the line\n const line = lines[lineIndex];\n \n // Handle space collapsing logic to determine visual line width\n // This matches renderTextStroke logic\n let visibleLineText = line;\n let leadingSpacesCount = 0;\n let trailingSpacesCount = 0;\n\n const isParagraphStart = lineIndex === 0;\n const isParagraphEnd = lineIndex === lines.length - 1;\n\n // Hide leading spaces when NOT at paragraph start\n if (!isParagraphStart) {\n const leadingSpacesMatch = line.match(/^ +/);\n leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n if (leadingSpacesCount > 0) {\n visibleLineText = visibleLineText.substring(leadingSpacesCount);\n }\n }\n\n // Hide trailing spaces when NOT at paragraph end\n if (!isParagraphEnd) {\n const trailingSpacesMatch = visibleLineText.match(/ +$/);\n trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n if (trailingSpacesCount > 0) {\n visibleLineText = visibleLineText.substring(0, visibleLineText.length - trailingSpacesCount);\n }\n }\n\n const lineWidth = measureTextWidth(visibleLineText, fontSize, fontFamily, bold, italic, elementLetterSpacing);\n\n // Calculate line start X based on alignment\n let lineStartX = horizontalPadding;\n if (textAlign === 'center') {\n lineStartX = (maxWidth - lineWidth) / 2;\n } else if (textAlign === 'right') {\n lineStartX = maxWidth - horizontalPadding - lineWidth;\n }\n \n // Relative X on the line\n const relativeX = point.x - lineStartX;\n \n // If clicked before the start of line\n if (relativeX <= 0) {\n // Return start of visual content\n return charStartIndex + leadingSpacesCount;\n }\n \n // If clicked after the end of line\n if (relativeX >= lineWidth) {\n // Return end of visual content\n return charStartIndex + line.length - trailingSpacesCount;\n }\n \n // Iterate characters to find the split point\n let currentX = 0;\n \n // We need to iterate over the ORIGINAL line characters to correctly map to indices\n for (let i = 0; i < line.length; i++) {\n // Skip collapsed characters (effectively 0 width)\n if (!isParagraphStart && i < leadingSpacesCount) continue;\n if (!isParagraphEnd && i >= line.length - trailingSpacesCount) continue;\n\n const char = line[i];\n const globalIndex = charStartIndex + i;\n \n // Get style for this character from RichText\n const style = richText.getStyleAt(globalIndex);\n \n // Use character-specific style or fallback to element defaults\n const charFontSize = style.fontSize || fontSize;\n const charFontFamily = style.fontFamily || fontFamily;\n const charBold = style.bold !== undefined ? style.bold : bold;\n const charItalic = style.italic !== undefined ? style.italic : italic;\n // Same advance model as paint: every character gets +letterSpacing\n const charSpacing = style.letterSpacing !== undefined ? style.letterSpacing : elementLetterSpacing;\n\n const charWidth = measureTextWidth(char, charFontSize, charFontFamily, charBold, charItalic, charSpacing);\n \n // Check if the point is within this character's horizontal span\n // We use midpoint to decide whether to place cursor before or after\n if (relativeX < currentX + charWidth / 2) {\n return globalIndex;\n }\n \n currentX += charWidth;\n }\n \n // If we fall through, it's at the end of the line\n return charStartIndex + line.length;\n}\n\n","/**\n * useMarqueeSelection - Manages marquee selection box state and logic\n */\n\nimport { useState, useEffect } from 'react';\nimport type { TextElement } from '../../../core/TextElement.js';\nimport type { ImageElement } from '../../../core/ImageElement.js';\nimport type { GroupElement } from '../../../core/GroupElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { PathElement } from '../../../core/PathElement.js';\n\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\nexport function useMarqueeSelection() {\n const [isMarqueeSelecting, setIsMarqueeSelecting] = useState<boolean>(false);\n const [marqueeStart, setMarqueeStart] = useState<{ x: number; y: number }>({\n x: 0,\n y: 0,\n });\n const [marqueeEnd, setMarqueeEnd] = useState<{ x: number; y: number }>({\n x: 0,\n y: 0,\n });\n const [marqueePreviewSelection, setMarqueePreviewSelection] = useState<string[]>([]);\n\n // Debug marquee state changes\n useEffect(() => {\n }, [isMarqueeSelecting]);\n\n const startMarqueeSelection = (x: number, y: number) => {\n setIsMarqueeSelecting(true);\n setMarqueeStart({ x, y });\n setMarqueeEnd({ x, y });\n setMarqueePreviewSelection([]);\n };\n\n const updateMarqueeSelection = (\n x: number,\n y: number,\n elements: CanvasElement[]\n ) => {\n setMarqueeEnd({ x, y });\n\n // Calculate which elements are currently overlapping with the marquee\n const minX = Math.min(marqueeStart.x, x);\n const maxX = Math.max(marqueeStart.x, x);\n const minY = Math.min(marqueeStart.y, y);\n const maxY = Math.max(marqueeStart.y, y);\n\n const previewSelectedIds: string[] = [];\n elements.forEach((element) => {\n // Skip invisible elements\n if (element.visible === false) return;\n\n const bbox = element.getVisualBoundingBox();\n\n // Check if element's bounding box overlaps with marquee\n const elementLeft = bbox.x;\n const elementRight = bbox.x + bbox.width;\n const elementTop = bbox.y;\n const elementBottom = bbox.y + bbox.height;\n\n const overlaps = !(\n elementRight < minX || // element is left of marquee\n elementLeft > maxX || // element is right of marquee\n elementBottom < minY || // element is above marquee\n elementTop > maxY // element is below marquee\n );\n\n if (overlaps) {\n previewSelectedIds.push(element.id);\n }\n });\n\n setMarqueePreviewSelection(previewSelectedIds);\n };\n\n const finishMarqueeSelection = (\n onMultiSelectionChange: (ids: string[]) => void\n ) => {\n\n // Capture the selection before clearing state\n const selectedIds = [...marqueePreviewSelection];\n\n setIsMarqueeSelecting(false);\n\n // Use the preview selection as the final selection\n if (selectedIds.length > 0) {\n onMultiSelectionChange(selectedIds);\n }\n\n // Clear preview\n setMarqueePreviewSelection([]);\n };\n\n return {\n isMarqueeSelecting,\n marqueeStart,\n marqueeEnd,\n marqueePreviewSelection,\n startMarqueeSelection,\n updateMarqueeSelection,\n finishMarqueeSelection,\n };\n}\n","/**\n * PenToolManager - Manages pen tool state for creating custom paths\n * Handles:\n * - Click to add anchor points\n * - Drag after click to create bezier handles\n * - Path preview rendering\n * - Path completion (close or finish)\n */\n\nimport type { PathPoint, Point } from '../types/index.js';\nimport { PathElement } from './PathElement.js';\nimport { getThemeAccentColor, getThemePenAnchorFillColor, getThemePenHandleColor, getThemePenPathColor, getThemeShapeFillColor } from '../constants.js';\n\nexport interface PenToolState {\n active: boolean;\n pathElement: PathElement | null;\n points: PathPoint[];\n currentPoint: PathPoint | null;\n isDraggingHandle: boolean;\n previewPoint: Point | null;\n isClosing: boolean; // Track if we're in the process of closing the path\n closingClickPos: Point | null; // Position where user clicked to close (for handle calculation)\n}\n\nexport class PenToolManager {\n private state: PenToolState = {\n active: false,\n pathElement: null,\n points: [],\n currentPoint: null,\n isDraggingHandle: false,\n previewPoint: null,\n isClosing: false,\n closingClickPos: null,\n };\n\n /**\n * Start pen tool mode with a new path element\n */\n startPath(pathElement: PathElement): void {\n this.state.active = true;\n this.state.pathElement = pathElement;\n this.state.points = [];\n this.state.currentPoint = null;\n this.state.isDraggingHandle = false;\n this.state.previewPoint = null;\n this.state.isClosing = false;\n this.state.closingClickPos = null;\n }\n\n /**\n * Edit an existing path element\n * Loads the existing points and enters edit mode\n */\n editPath(pathElement: PathElement): void {\n this.state.active = true;\n this.state.pathElement = pathElement;\n // Load existing points from the path element\n this.state.points = pathElement.transformData.points.map((p) => ({ ...p }));\n this.state.currentPoint = null;\n this.state.isDraggingHandle = false;\n this.state.previewPoint = null;\n this.state.isClosing = false;\n this.state.closingClickPos = null;\n }\n\n /**\n * Check if pen tool is active\n */\n isActive(): boolean {\n return this.state.active;\n }\n\n /**\n * Get current state\n */\n getState(): PenToolState {\n return { ...this.state };\n }\n\n /**\n * Update preview point (for cursor following)\n */\n setPreviewPoint(x: number, y: number): void {\n this.state.previewPoint = { x, y };\n }\n\n /**\n * Clear preview point\n */\n clearPreviewPoint(): void {\n this.state.previewPoint = null;\n }\n\n /**\n * Add a new anchor point at the given position (relative to path element)\n * Returns the created point\n */\n addPoint(x: number, y: number): PathPoint {\n const newPoint: PathPoint = {\n id: `point-${Date.now()}-${Math.random()}`,\n x,\n y,\n type: 'corner', // Default to corner, will convert to bezier if handle is dragged\n };\n\n this.state.points.push(newPoint);\n this.state.currentPoint = newPoint;\n this.state.isDraggingHandle = false;\n\n return newPoint;\n }\n\n /**\n * Start dragging handle for the current point\n */\n startDraggingHandle(): void {\n if (!this.state.currentPoint) return;\n this.state.isDraggingHandle = true;\n\n // Convert point to bezier type if not already\n if (this.state.currentPoint.type === 'corner') {\n this.state.currentPoint.type = 'bezier';\n }\n }\n\n /**\n * Update handle position for current point (relative to point position)\n */\n updateHandle(handleX: number, handleY: number): void {\n if (!this.state.currentPoint || !this.state.isDraggingHandle) return;\n\n if (this.state.isClosing && this.state.closingClickPos) {\n // When closing, calculate offset from where the user clicked (not from first point)\n const offsetX = handleX - this.state.closingClickPos.x;\n const offsetY = handleY - this.state.closingClickPos.y;\n\n const firstPoint = this.state.points[0];\n\n // Set first point's handleIn (curve arriving at first point from last point)\n firstPoint.handleIn = { x: offsetX, y: offsetY };\n\n // Also set handleOut as the mirror so both handles are visible\n firstPoint.handleOut = { x: -offsetX, y: -offsetY };\n\n if (firstPoint.type === 'corner') {\n firstPoint.type = 'bezier';\n }\n } else {\n // Normal point: Calculate handle offset from point\n const offsetX = handleX - this.state.currentPoint.x;\n const offsetY = handleY - this.state.currentPoint.y;\n\n // Set handleOut (control point for outgoing curve)\n this.state.currentPoint.handleOut = { x: offsetX, y: offsetY };\n\n // For smooth points, mirror the handle\n if (this.state.currentPoint.type === 'smooth') {\n this.state.currentPoint.handleIn = { x: -offsetX, y: -offsetY };\n } else if (this.state.currentPoint.type === 'bezier') {\n // For bezier, set handleIn to opposite direction (mirrored)\n // User can adjust independently later in edit mode\n this.state.currentPoint.handleIn = { x: -offsetX, y: -offsetY };\n }\n }\n }\n\n /**\n * Finish dragging handle\n * If closing the path, this will complete the closing operation\n */\n finishDraggingHandle(): void {\n this.state.isDraggingHandle = false;\n\n // If we were closing the path, complete it now\n if (this.state.isClosing) {\n this.closePath();\n } else {\n this.state.currentPoint = null;\n }\n }\n\n /**\n * Check if we can close the path (clicking near first point)\n */\n canClosePath(x: number, y: number, threshold: number = 10): boolean {\n if (this.state.points.length < 3) return false;\n\n const firstPoint = this.state.points[0];\n const dx = x - firstPoint.x;\n const dy = y - firstPoint.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n return distance <= threshold;\n }\n\n /**\n * Start closing the path (allows handle dragging for the closing point)\n * Records where the user clicked so we can calculate handle offsets correctly\n */\n startClosing(clickX: number, clickY: number): void {\n if (this.state.points.length < 3) return;\n\n this.state.isClosing = true;\n this.state.closingClickPos = { x: clickX, y: clickY };\n // Set the first point as current (for reference, though we'll use closingClickPos for offset calc)\n this.state.currentPoint = this.state.points[0];\n }\n\n /**\n * Check if currently in closing mode\n */\n isClosingPath(): boolean {\n return this.state.isClosing;\n }\n\n /**\n * Recenter the path element to match where the path was actually drawn\n * Adjusts element position and converts points to be relative to the actual center\n */\n private recenterPath(): void {\n if (!this.state.pathElement || this.state.points.length === 0) return;\n\n // Calculate the bounding box of the drawn points\n let minX = Infinity,\n minY = Infinity,\n maxX = -Infinity,\n maxY = -Infinity;\n for (const point of this.state.points) {\n minX = Math.min(minX, point.x);\n minY = Math.min(minY, point.y);\n maxX = Math.max(maxX, point.x);\n maxY = Math.max(maxY, point.y);\n }\n\n // Calculate the center of the drawn points\n const centerX = (minX + maxX) / 2;\n const centerY = (minY + maxY) / 2;\n\n // Update element position to the actual drawn center\n this.state.pathElement.x += centerX;\n this.state.pathElement.y += centerY;\n\n // Adjust all points to be relative to the new center\n for (const point of this.state.points) {\n point.x -= centerX;\n point.y -= centerY;\n }\n }\n\n /**\n * Close the path (connect last point to first)\n */\n closePath(): void {\n if (!this.state.pathElement) return;\n\n // Recenter the path to match where it was drawn\n this.recenterPath();\n\n // Update path element with points and set closed = true\n this.state.pathElement.transformData.points = [...this.state.points];\n this.state.pathElement.transformData.closed = true;\n\n // Enable fill with default color for closed shapes\n this.state.pathElement.transformData.fillEnabled = true;\n this.state.pathElement.transformData.fillColor = getThemeShapeFillColor();\n\n this.state.pathElement.updateBounds();\n\n // Exit pen mode\n this.endPath();\n }\n\n /**\n * Finish the path without closing (leave open)\n */\n finishPath(): void {\n if (!this.state.pathElement) return;\n\n // Recenter the path to match where it was drawn\n this.recenterPath();\n\n // Update path element with points and set closed = false\n this.state.pathElement.transformData.points = [...this.state.points];\n this.state.pathElement.transformData.closed = false;\n this.state.pathElement.updateBounds();\n\n // Exit pen mode\n this.endPath();\n }\n\n /**\n * Delete the last added point (undo last point)\n */\n deleteLastPoint(): void {\n if (this.state.points.length === 0) return;\n this.state.points.pop();\n this.state.currentPoint = null;\n }\n\n /**\n * Cancel path creation entirely\n */\n cancelPath(): PathElement | null {\n const element = this.state.pathElement;\n this.state.active = false;\n this.state.pathElement = null;\n this.state.points = [];\n this.state.currentPoint = null;\n this.state.isDraggingHandle = false;\n this.state.previewPoint = null;\n this.state.isClosing = false;\n this.state.closingClickPos = null;\n return element; // Return element so it can be removed\n }\n\n /**\n * End path creation mode\n */\n private endPath(): void {\n this.state.active = false;\n this.state.pathElement = null;\n this.state.points = [];\n this.state.currentPoint = null;\n this.state.isDraggingHandle = false;\n this.state.previewPoint = null;\n this.state.isClosing = false;\n this.state.closingClickPos = null;\n }\n\n /**\n * Get points for rendering preview\n */\n getPoints(): PathPoint[] {\n return this.state.points;\n }\n\n /**\n * Get current point being edited\n */\n getCurrentPoint(): PathPoint | null {\n return this.state.currentPoint;\n }\n\n /**\n * Check if currently dragging a handle\n */\n isDraggingHandle(): boolean {\n return this.state.isDraggingHandle;\n }\n\n /**\n * Render pen tool preview on canvas\n */\n renderPreview(ctx: CanvasRenderingContext2D, pathElement: PathElement): void {\n if (!this.state.active || this.state.points.length === 0) return;\n\n const { points, previewPoint, currentPoint: _currentPoint } = this.state;\n\n ctx.save();\n\n // Translate to path element position\n ctx.translate(pathElement.x, pathElement.y);\n\n // Draw existing path segments\n ctx.strokeStyle = getThemePenPathColor(); // Theme-aware path stroke\n ctx.lineWidth = 2;\n ctx.setLineDash([]); // Solid line (not dashed)\n ctx.beginPath();\n\n // Draw path\n if (points.length > 0) {\n const firstPoint = points[0];\n ctx.moveTo(firstPoint.x, firstPoint.y);\n\n for (let i = 1; i < points.length; i++) {\n const prevPoint = points[i - 1];\n const currentPoint = points[i];\n\n // Draw bezier curve if handles exist\n const hasCurve = prevPoint.handleOut || currentPoint.handleIn;\n\n if (hasCurve) {\n const cp1x = prevPoint.x + (prevPoint.handleOut?.x || 0);\n const cp1y = prevPoint.y + (prevPoint.handleOut?.y || 0);\n const cp2x = currentPoint.x + (currentPoint.handleIn?.x || 0);\n const cp2y = currentPoint.y + (currentPoint.handleIn?.y || 0);\n\n ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, currentPoint.x, currentPoint.y);\n } else {\n ctx.lineTo(currentPoint.x, currentPoint.y);\n }\n }\n\n // Draw closing segment if in closing mode OR if editing a closed path\n const isClosed = this.state.isClosing || pathElement.transformData.closed;\n if (isClosed && points.length > 2) {\n const lastPoint = points[points.length - 1];\n const firstPoint = points[0];\n\n // Draw bezier curve from last to first using their handles\n const hasCurve = lastPoint.handleOut || firstPoint.handleIn;\n\n if (hasCurve) {\n const cp1x = lastPoint.x + (lastPoint.handleOut?.x || 0);\n const cp1y = lastPoint.y + (lastPoint.handleOut?.y || 0);\n const cp2x = firstPoint.x + (firstPoint.handleIn?.x || 0);\n const cp2y = firstPoint.y + (firstPoint.handleIn?.y || 0);\n\n ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, firstPoint.x, firstPoint.y);\n } else {\n ctx.lineTo(firstPoint.x, firstPoint.y);\n }\n } else if (previewPoint && !this.state.isDraggingHandle) {\n // Draw line to preview point if present (only when not closing)\n ctx.lineTo(previewPoint.x, previewPoint.y);\n }\n\n ctx.stroke();\n\n // Draw fill for closed paths\n if (isClosed && pathElement.transformData.fillEnabled) {\n ctx.fillStyle = pathElement.transformData.fillColor || getThemeShapeFillColor();\n ctx.globalAlpha = pathElement.transformData.fillOpacity ?? 1;\n ctx.fill();\n ctx.globalAlpha = 1; // Reset alpha\n }\n }\n\n ctx.setLineDash([]); // Reset dash\n\n // Draw anchor points\n for (let i = 0; i < points.length; i++) {\n const point = points[i];\n const isFirstPoint = i === 0;\n\n // Draw special ring indicator for first point (to show where path can be closed)\n if (isFirstPoint && points.length > 1) {\n ctx.strokeStyle = getThemeAccentColor(); // Theme color ring\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.arc(point.x, point.y, 8, 0, Math.PI * 2);\n ctx.stroke();\n }\n\n // Draw point\n ctx.fillStyle = getThemePenAnchorFillColor(); // Theme background color so anchor is visible on light/dark themes\n ctx.strokeStyle = getThemeAccentColor(); // Use theme color for all points\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.arc(point.x, point.y, 4, 0, Math.PI * 2);\n ctx.fill();\n ctx.stroke();\n\n // Draw handles if point has them\n if (point.handleIn) {\n const handleInX = point.x + point.handleIn.x;\n const handleInY = point.y + point.handleIn.y;\n\n // Handle line\n ctx.strokeStyle = getThemePenHandleColor();\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(point.x, point.y);\n ctx.lineTo(handleInX, handleInY);\n ctx.stroke();\n\n // Handle point\n ctx.fillStyle = getThemePenHandleColor();\n ctx.beginPath();\n ctx.arc(handleInX, handleInY, 3, 0, Math.PI * 2);\n ctx.fill();\n }\n\n if (point.handleOut) {\n const handleOutX = point.x + point.handleOut.x;\n const handleOutY = point.y + point.handleOut.y;\n\n // Handle line\n ctx.strokeStyle = getThemePenHandleColor();\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(point.x, point.y);\n ctx.lineTo(handleOutX, handleOutY);\n ctx.stroke();\n\n // Handle point\n ctx.fillStyle = getThemePenHandleColor();\n ctx.beginPath();\n ctx.arc(handleOutX, handleOutY, 3, 0, Math.PI * 2);\n ctx.fill();\n }\n }\n\n // Highlight first point if we can close the path\n if (previewPoint && this.canClosePath(previewPoint.x, previewPoint.y)) {\n const firstPoint = points[0];\n ctx.strokeStyle = getThemeAccentColor(); // Theme color to indicate closeable\n ctx.lineWidth = 3;\n ctx.beginPath();\n ctx.arc(firstPoint.x, firstPoint.y, 6, 0, Math.PI * 2);\n ctx.stroke();\n }\n\n // Draw preview point\n if (previewPoint && !this.state.isDraggingHandle) {\n const themeColor = getThemeAccentColor();\n // Convert hex to rgba\n const hex = themeColor.replace('#', '');\n const r = parseInt(hex.substring(0, 2), 16);\n const g = parseInt(hex.substring(2, 4), 16);\n const b = parseInt(hex.substring(4, 6), 16);\n ctx.fillStyle = `rgba(${r}, ${g}, ${b}, 0.3)`;\n ctx.strokeStyle = themeColor;\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.arc(previewPoint.x, previewPoint.y, 4, 0, Math.PI * 2);\n ctx.fill();\n ctx.stroke();\n }\n\n ctx.restore();\n }\n}\n","/**\n * usePenTool - Manages pen tool mode for path elements\n */\n\nimport { useState, useEffect } from 'react';\nimport { PenToolManager } from '../../../core/PenToolManager.js';\nimport { PathElement } from '../../../core/PathElement.js';\nimport type { BaseElement } from '../../../core/BaseElement.js';\nimport { Icon } from '@iconify/react';\nimport { renderToStaticMarkup } from 'react-dom/server';\nimport { createLogger } from '../../../utils/logger.js';\n\nconst logger = createLogger('usePenTool');\n\ninterface UsePenToolProps {\n selectedElement: BaseElement | null | undefined;\n}\n\nexport function usePenTool({ selectedElement }: UsePenToolProps) {\n // Create pen tool manager\n const [penTool] = useState(() => new PenToolManager());\n\n // Custom pen cursor\n const [penCursor, setPenCursor] = useState<string>('crosshair');\n\n // Create custom pen cursor from icon\n useEffect(() => {\n try {\n // Get the pen icon SVG content\n const penIconContent = renderToStaticMarkup(<Icon icon=\"lucide:pen-tool\" width={24} height={24} />).replace(\n /<svg[^>]*>|<\\/svg>/g,\n ''\n );\n\n // Create SVG with white stroke behind the black stroke\n const cursorSvg = `\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <!-- White background stroke (thicker) -->\n <g stroke=\"white\" stroke-width=\"4\" fill=\"none\" stroke-linejoin=\"round\" stroke-linecap=\"round\">\n ${penIconContent}\n </g>\n <!-- Black pen icon stroke on top (thinner) -->\n <g stroke=\"#000000\" stroke-width=\"2\" fill=\"none\" stroke-linejoin=\"round\" stroke-linecap=\"round\">\n ${penIconContent}\n </g>\n </svg>\n `.trim();\n\n // Create a data URL from the SVG\n const encodedSvg = encodeURIComponent(cursorSvg);\n const dataUrl = `data:image/svg+xml,${encodedSvg}`;\n\n // Set cursor with hotspot at pen tip (lower-left corner where tip touches)\n setPenCursor(`url('${dataUrl}') 0 0, crosshair`);\n } catch (error) {\n logger.error('Failed to create pen cursor:', error);\n setPenCursor('crosshair');\n }\n }, []);\n\n // Auto-enter pen mode when path element with no points is selected\n useEffect(() => {\n if (\n selectedElement instanceof PathElement &&\n selectedElement.transformData.points.length === 0 &&\n !penTool.isActive()\n ) {\n penTool.startPath(selectedElement);\n }\n }, [selectedElement, penTool]);\n\n return {\n penTool,\n penCursor,\n };\n}\n","/**\n * useActiveChild - Manages active child element for groups\n */\n\nimport { useState, useCallback, useEffect, useRef } from 'react';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport type { TextElement } from '../../../core/TextElement.js';\nimport type { ImageElement } from '../../../core/ImageElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { PathElement } from '../../../core/PathElement.js';\nimport type { TransformHandles } from '../../../core/TransformHandles.js';\n// Note: GroupElement is imported above as a value, used in type here\n\n/** Elements that can be on the canvas */\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\n/** Elements that can be children of a group (including nested groups) */\ntype ChildElement = TextElement | ImageElement | ShapeElement | PathElement | GroupElement;\n\ninterface UseActiveChildProps {\n elements: CanvasElement[];\n selectedElement: CanvasElement | null | undefined;\n transformHandles: TransformHandles;\n onActiveChildChange?: (child: CanvasElement | null) => void;\n}\n\nexport function useActiveChild({\n elements,\n selectedElement,\n transformHandles,\n onActiveChildChange,\n}: UseActiveChildProps) {\n // Active child for editing (when editing a child within a group)\n const [activeChildElement, setActiveChildElement] = useState<ChildElement | null>(null);\n\n // Track which child ID is being edited (for text editing in groups)\n const editingChildIdRef = useRef<string | null>(null);\n\n // Helper to update active child and notify parent\n const updateActiveChild = useCallback(\n (child: ChildElement | null) => {\n setActiveChildElement(child);\n onActiveChildChange?.(child);\n },\n [onActiveChildChange]\n );\n\n // Sync activeChildElement when elements array updates\n // When a child in a group is modified, we need to get the fresh reference from the updated group\n useEffect(() => {\n if (activeChildElement && selectedElement instanceof GroupElement) {\n const updatedChild = selectedElement.children.find((c) => c.id === activeChildElement.id);\n if (updatedChild && updatedChild !== activeChildElement) {\n // Child was updated, refresh our reference (exclude GroupElement for active child)\n if (!(updatedChild instanceof GroupElement)) {\n setActiveChildElement(updatedChild);\n // Notify parent that active child has been updated\n onActiveChildChange?.(updatedChild);\n transformHandles.update(updatedChild);\n }\n }\n }\n }, [elements, activeChildElement, selectedElement, transformHandles, onActiveChildChange]);\n\n return {\n activeChildElement,\n setActiveChildElement,\n updateActiveChild,\n editingChildIdRef,\n };\n}\n","/**\n * useHoverState - Manages hover detection and cursor management with animation timing\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport type { GroupElement } from '../../../core/GroupElement.js';\nimport type { LocalHoverState } from '../types/index.js';\nimport type { TextElement } from '../../../core/TextElement.js';\nimport type { ImageElement } from '../../../core/ImageElement.js';\n\ninterface UseHoverStateProps {\n onHoverChange?: (elementId: string | null) => void;\n}\n\nexport function useHoverState({ onHoverChange }: UseHoverStateProps) {\n // Hover state with timing for animation\n const [hoverState, setHoverStateInternal] = useState<LocalHoverState>({\n type: null,\n data: null,\n startTime: null,\n });\n\n // Wrapped setter that automatically adds startTime for new hovers\n const setHoverState = useCallback((newState: Omit<LocalHoverState, 'startTime'>) => {\n setHoverStateInternal((prevState) => {\n // If transitioning to a new hover (type or element changed), record start time\n const isNewHover =\n newState.type !== prevState.type ||\n (newState.type === 'element' && (newState.data as { id?: string })?.id !== (prevState.data as { id?: string })?.id) ||\n (newState.type === 'group-sibling' && (newState.data as { id?: string })?.id !== (prevState.data as { id?: string })?.id);\n\n return {\n ...newState,\n startTime: isNewHover && newState.type !== null ? performance.now() : prevState.startTime,\n };\n });\n }, []);\n\n // Notify parent of hover changes for layers panel synchronization\n useEffect(() => {\n if (!onHoverChange) return;\n\n let hoveredElementId: string | null = null;\n\n // Extract element ID from hover state\n if (hoverState.type === 'element' || hoverState.type === 'group-sibling') {\n const hoveredElement = hoverState.data as TextElement | ImageElement | GroupElement | null;\n hoveredElementId = hoveredElement?.id ?? null;\n }\n\n onHoverChange(hoveredElementId);\n }, [hoverState, onHoverChange]);\n\n return {\n hoverState,\n setHoverState,\n };\n}\n","import { RotationUtils } from './RotationUtils.js';\n\n/** Minimal interface for objects that can be used with CoordinateTransform */\ninterface TransformableElement {\n x: number;\n y: number;\n rotation?: number;\n flipH?: boolean;\n flipV?: boolean;\n}\n\n/**\n * CoordinateTransform - Handles coordinate transformations between world and local space\n *\n * Manages transformations including:\n * - Translation (x, y position)\n * - Rotation\n * - Horizontal and vertical flips\n */\nexport class CoordinateTransform {\n x: number;\n y: number;\n rotation: number;\n flipH: boolean;\n flipV: boolean;\n\n /**\n * @param x - Center X position in world space\n * @param y - Center Y position in world space\n * @param rotation - Rotation in degrees (positive = clockwise)\n * @param flipH - Horizontal flip\n * @param flipV - Vertical flip\n */\n constructor(x: number, y: number, rotation: number, flipH: boolean, flipV: boolean) {\n this.x = x;\n this.y = y;\n this.rotation = rotation;\n this.flipH = flipH;\n this.flipV = flipV;\n }\n\n /**\n * Convert world coordinates to local coordinates\n * @param worldX - X coordinate in world space\n * @param worldY - Y coordinate in world space\n * @returns Local coordinates\n */\n worldToLocal(worldX: number, worldY: number) {\n // Translate to origin\n const dx = worldX - this.x;\n const dy = worldY - this.y;\n\n // Rotate (inverse rotation)\n const rotRad = RotationUtils.toRadiansInverse(this.rotation);\n const cos = Math.cos(rotRad);\n const sin = Math.sin(rotRad);\n\n let localX = dx * cos + dy * sin;\n let localY = -dx * sin + dy * cos;\n\n // Apply flips\n if (this.flipH) localX = -localX;\n if (this.flipV) localY = -localY;\n\n return { x: localX, y: localY };\n }\n\n /**\n * Convert local coordinates to world coordinates\n * @param localX - X coordinate in local space\n * @param localY - Y coordinate in local space\n * @returns World coordinates\n */\n localToWorld(localX: number, localY: number) {\n // Unapply flips\n let x = this.flipH ? -localX : localX;\n let y = this.flipV ? -localY : localY;\n\n // Rotate (forward rotation)\n const rotRad = RotationUtils.toRadians(this.rotation);\n const cos = Math.cos(rotRad);\n const sin = Math.sin(rotRad);\n\n const rotatedX = x * cos - y * sin;\n const rotatedY = x * sin + y * cos;\n\n // Translate from origin\n return {\n x: rotatedX + this.x,\n y: rotatedY + this.y,\n };\n }\n\n /**\n * Transform a vector (direction/offset) without translation\n * @param dx - X component of vector\n * @param dy - Y component of vector\n * @param inverse - If true, apply inverse transform\n * @returns Transformed vector\n */\n transformVector(dx: number, dy: number, inverse: boolean = false) {\n // Apply flips\n let x = this.flipH ? -dx : dx;\n let y = this.flipV ? -dy : dy;\n\n // Rotate\n const rotRad = inverse ? RotationUtils.toRadiansInverse(this.rotation) : RotationUtils.toRadians(this.rotation);\n\n const cos = Math.cos(rotRad);\n const sin = Math.sin(rotRad);\n\n if (inverse) {\n // Inverse rotation\n return {\n dx: x * cos + y * sin,\n dy: -x * sin + y * cos,\n };\n } else {\n // Forward rotation\n return {\n dx: x * cos - y * sin,\n dy: x * sin + y * cos,\n };\n }\n }\n\n /**\n * Create a CoordinateTransform from an element object\n * @param element - Element with x, y, rotation, flipH, flipV properties\n * @returns CoordinateTransform\n */\n static fromElement(element: TransformableElement) {\n return new CoordinateTransform(\n element.x,\n element.y,\n element.rotation || 0,\n element.flipH || false,\n element.flipV || false\n );\n }\n}\n","import { CoordinateTransform } from './CoordinateTransform.js';\nimport { Transform } from './Transform.js';\nimport { MIN_CROP_SIZE } from '../constants.js';\nimport type { BaseElement } from './BaseElement.js';\nimport type { TransformStartData, ImageTransformData } from '../types/index.js';\n\n/** Shape of the context object from the interaction state machine */\ninterface DragContext {\n startX: number;\n startY: number;\n startData: TransformStartData;\n}\n\n/** Shape of the resize context from the interaction state machine */\ninterface ResizeContext {\n startData: TransformStartData;\n}\n\n/** Result of a crop image drag operation */\ninterface CropDragResult {\n cropX: number;\n cropY: number;\n cropWidth: number;\n cropHeight: number;\n x: number;\n y: number;\n}\n\n/** Result of a crop box resize operation */\ninterface CropResizeResult {\n cropX: number;\n cropY: number;\n cropWidth: number;\n cropHeight: number;\n x: number;\n y: number;\n rotation: number;\n width: number;\n height: number;\n cursor: string;\n}\n\n/** Result of constrained crop values */\ninterface CropValues {\n cropX: number;\n cropY: number;\n cropWidth: number;\n cropHeight: number;\n}\n\n/**\n * CropModeController - Handles all crop mode interactions\n *\n * Manages:\n * - Dragging the image behind the crop window\n * - Resizing the crop box\n * - Coordinate transformations for crop operations\n */\nexport class CropModeController {\n /**\n * Handle dragging the image in crop mode\n */\n handleCropImageDrag(_element: BaseElement, context: DragContext, currentX: number, currentY: number): CropDragResult {\n const dx = currentX - context.startX;\n const dy = currentY - context.startY;\n\n // Use START rotation and dimensions for consistent coordinate conversion\n const startX = context.startData.x;\n const startY = context.startData.y;\n const td = context.startData.transformData as ImageTransformData;\n const startWidth = td.width;\n const startHeight = td.height;\n\n // Get start crop values\n const startCropX = context.startData.cropX ?? 0;\n const startCropY = context.startData.cropY ?? 0;\n const startCropWidth = context.startData.cropWidth ?? 1;\n const startCropHeight = context.startData.cropHeight ?? 1;\n\n // Convert the position delta to local coordinates (inverse transform: world → local)\n const startTransform = Transform.fromSnapshot(context.startData);\n const localDelta = startTransform.worldDeltaToLocal(dx, dy);\n let localDx = localDelta.dx;\n let localDy = localDelta.dy;\n\n // Account for flips\n const flipH = td.flipHorizontal ? -1 : 1;\n const flipV = td.flipVertical ? -1 : 1;\n localDx *= flipH;\n localDy *= flipV;\n\n // Convert delta to normalized coordinates (move crop in opposite direction)\n const normalizedDx = -localDx / startWidth;\n const normalizedDy = -localDy / startHeight;\n\n // Calculate desired crop coordinates\n let newCropX = startCropX + normalizedDx;\n let newCropY = startCropY + normalizedDy;\n\n // Constrain: Crop box must stay within image bounds (0-1 range)\n const maxCropX = 1 - startCropWidth;\n const maxCropY = 1 - startCropHeight;\n newCropX = Math.max(0, Math.min(maxCropX, newCropX));\n newCropY = Math.max(0, Math.min(maxCropY, newCropY));\n\n return {\n cropX: newCropX,\n cropY: newCropY,\n cropWidth: startCropWidth,\n cropHeight: startCropHeight,\n x: startX, // Position stays the same\n y: startY,\n };\n }\n\n /**\n * Handle resizing the crop box\n */\n handleCropBoxResize(\n _element: BaseElement,\n anchor: string,\n resizeContext: ResizeContext,\n stateMachineContext: DragContext,\n currentX: number,\n currentY: number\n ): CropResizeResult {\n const td = resizeContext.startData.transformData as ImageTransformData;\n const flipH = td.flipHorizontal;\n const flipV = td.flipVertical;\n\n // Get cursor for this anchor (accounting for flips)\n const cursor = this._getCropResizeCursor(anchor, !!flipH, !!flipV);\n\n // Use START element's rotation and position for coordinate conversions\n const startRotation = resizeContext.startData.rotation;\n const startX = resizeContext.startData.x;\n const startY = resizeContext.startData.y;\n\n // Create transform for inverse operations (world → local)\n const startTransform = Transform.fromSnapshot(resizeContext.startData);\n const { cos, sin } = startTransform.getInverseCosSin();\n\n // Flip multipliers\n const flipHMult = flipH ? -1 : 1;\n const flipVMult = flipV ? -1 : 1;\n\n // Convert current mouse position to local coordinates\n const dxCurrent = currentX - startX;\n const dyCurrent = currentY - startY;\n let localCurrentX = dxCurrent * cos - dyCurrent * sin;\n let localCurrentY = dxCurrent * sin + dyCurrent * cos;\n localCurrentX *= flipHMult;\n localCurrentY *= flipVMult;\n\n // Convert start mouse position to local coordinates\n const dxStart = stateMachineContext.startX - startX;\n const dyStart = stateMachineContext.startY - startY;\n let localStartX = dxStart * cos - dyStart * sin;\n let localStartY = dxStart * sin + dyStart * cos;\n localStartX *= flipHMult;\n localStartY *= flipVMult;\n\n // Calculate delta in local space\n const localDx = localCurrentX - localStartX;\n const localDy = localCurrentY - localStartY;\n\n // Get original crop values\n const startCropX = td.cropX;\n const startCropY = td.cropY;\n const startCropWidth = td.cropWidth;\n const startCropHeight = td.cropHeight;\n const startWidth = td.width;\n const startHeight = td.height;\n\n // Convert to normalized coordinates\n const normalizedDx = localDx / startWidth;\n const normalizedDy = localDy / startHeight;\n\n // Calculate new crop values based on anchor\n const result = this._calculateNewCropValues(\n anchor,\n startCropX,\n startCropY,\n startCropWidth,\n startCropHeight,\n normalizedDx,\n normalizedDy\n );\n\n // Calculate position offset to keep crop center in same world position\n const positionOffset = this._calculateCropCenterOffset(\n startCropX,\n startCropY,\n startCropWidth,\n startCropHeight,\n result.cropX,\n result.cropY,\n result.cropWidth,\n result.cropHeight,\n startWidth,\n startHeight,\n startRotation,\n flipHMult,\n flipVMult\n );\n\n return {\n ...result,\n x: startX + positionOffset.worldOffsetX,\n y: startY + positionOffset.worldOffsetY,\n rotation: startRotation,\n width: startWidth,\n height: startHeight,\n cursor,\n };\n }\n\n /**\n * Get cursor for crop resize handle\n * @private\n */\n _getCropResizeCursor(anchor: string, flipH: boolean, flipV: boolean): string {\n // Adjust anchor for flips\n let cursorAnchor = anchor;\n if (flipH) {\n if (cursorAnchor.includes('left')) {\n cursorAnchor = cursorAnchor.replace('left', 'right');\n } else if (cursorAnchor.includes('right')) {\n cursorAnchor = cursorAnchor.replace('right', 'left');\n }\n }\n if (flipV) {\n if (cursorAnchor.includes('top')) {\n cursorAnchor = cursorAnchor.replace('top', 'bottom');\n } else if (cursorAnchor.includes('bottom')) {\n cursorAnchor = cursorAnchor.replace('bottom', 'top');\n }\n }\n\n if (cursorAnchor === 'top-left' || cursorAnchor === 'bottom-right') {\n return 'nwse-resize';\n } else if (cursorAnchor === 'top-right' || cursorAnchor === 'bottom-left') {\n return 'nesw-resize';\n } else if (cursorAnchor === 'top' || cursorAnchor === 'bottom') {\n return 'ns-resize';\n } else if (cursorAnchor === 'left' || cursorAnchor === 'right') {\n return 'ew-resize';\n }\n return 'default';\n }\n\n /**\n * Calculate new crop values based on anchor and delta\n * @private\n */\n _calculateNewCropValues(\n anchor: string,\n startCropX: number,\n startCropY: number,\n startCropWidth: number,\n startCropHeight: number,\n normalizedDx: number,\n normalizedDy: number\n ): CropValues {\n const minSize = MIN_CROP_SIZE; // Minimum crop size\n\n let newCropX = startCropX;\n let newCropY = startCropY;\n let newCropWidth = startCropWidth;\n let newCropHeight = startCropHeight;\n\n // Adjust crop based on handle\n if (anchor === 'top-left') {\n newCropX = startCropX + normalizedDx;\n newCropY = startCropY + normalizedDy;\n newCropWidth = startCropWidth - normalizedDx;\n newCropHeight = startCropHeight - normalizedDy;\n } else if (anchor === 'top-right') {\n newCropY = startCropY + normalizedDy;\n newCropWidth = startCropWidth + normalizedDx;\n newCropHeight = startCropHeight - normalizedDy;\n } else if (anchor === 'bottom-left') {\n newCropX = startCropX + normalizedDx;\n newCropWidth = startCropWidth - normalizedDx;\n newCropHeight = startCropHeight + normalizedDy;\n } else if (anchor === 'bottom-right') {\n newCropWidth = startCropWidth + normalizedDx;\n newCropHeight = startCropHeight + normalizedDy;\n } else if (anchor === 'top') {\n newCropY = startCropY + normalizedDy;\n newCropHeight = startCropHeight - normalizedDy;\n } else if (anchor === 'bottom') {\n newCropHeight = startCropHeight + normalizedDy;\n } else if (anchor === 'left') {\n newCropX = startCropX + normalizedDx;\n newCropWidth = startCropWidth - normalizedDx;\n } else if (anchor === 'right') {\n newCropWidth = startCropWidth + normalizedDx;\n }\n\n // Apply constraints\n return this._constrainCropValues(\n anchor,\n newCropX,\n newCropY,\n newCropWidth,\n newCropHeight,\n startCropX,\n startCropY,\n startCropWidth,\n startCropHeight,\n minSize\n );\n }\n\n /**\n * Constrain crop values to valid bounds\n * @private\n */\n _constrainCropValues(\n anchor: string,\n cropX: number,\n cropY: number,\n cropWidth: number,\n cropHeight: number,\n startCropX: number,\n startCropY: number,\n startCropWidth: number,\n startCropHeight: number,\n minSize: number\n ): CropValues {\n let newCropX = cropX;\n let newCropY = cropY;\n let newCropWidth = cropWidth;\n let newCropHeight = cropHeight;\n\n // Handle width constraints\n if (newCropWidth < minSize) {\n newCropWidth = minSize;\n if (anchor.includes('left')) {\n newCropX = startCropX + startCropWidth - minSize;\n }\n }\n if (newCropX + newCropWidth > 1) {\n if (anchor.includes('left')) {\n newCropX = 1 - newCropWidth;\n } else {\n newCropWidth = 1 - newCropX;\n }\n }\n if (newCropX < 0) {\n newCropWidth = newCropWidth + newCropX;\n newCropX = 0;\n }\n\n // Handle height constraints\n if (newCropHeight < minSize) {\n newCropHeight = minSize;\n if (anchor.includes('top')) {\n newCropY = startCropY + startCropHeight - minSize;\n }\n }\n if (newCropY + newCropHeight > 1) {\n if (anchor.includes('top')) {\n newCropY = 1 - newCropHeight;\n } else {\n newCropHeight = 1 - newCropY;\n }\n }\n if (newCropY < 0) {\n newCropHeight = newCropHeight + newCropY;\n newCropY = 0;\n }\n\n return { cropX: newCropX, cropY: newCropY, cropWidth: newCropWidth, cropHeight: newCropHeight };\n }\n\n /**\n * Calculate offset to keep crop center at same world position\n * @private\n */\n _calculateCropCenterOffset(\n oldCropX: number,\n oldCropY: number,\n oldCropWidth: number,\n oldCropHeight: number,\n newCropX: number,\n newCropY: number,\n newCropWidth: number,\n newCropHeight: number,\n width: number,\n height: number,\n rotation: number,\n flipHMult: number,\n flipVMult: number\n ): { worldOffsetX: number; worldOffsetY: number } {\n // Calculate old crop center\n const oldCropCenterX = oldCropX * width + (oldCropWidth * width) / 2;\n const oldCropCenterY = oldCropY * height + (oldCropHeight * height) / 2;\n\n // Calculate new crop center\n const newCropCenterX = newCropX * width + (newCropWidth * width) / 2;\n const newCropCenterY = newCropY * height + (newCropHeight * height) / 2;\n\n // Calculate offset in local space\n let localOffsetX = newCropCenterX - oldCropCenterX;\n let localOffsetY = newCropCenterY - oldCropCenterY;\n\n // Apply flip transformation\n localOffsetX *= flipHMult;\n localOffsetY *= flipVMult;\n\n // Rotate to world space (forward transform: local → world)\n const transform = new Transform({ rotation, x: 0, y: 0 }); // Only need rotation\n const worldOffset = transform.localDeltaToWorld(localOffsetX, localOffsetY);\n const worldOffsetX = worldOffset.dx;\n const worldOffsetY = worldOffset.dy;\n\n return { worldOffsetX, worldOffsetY };\n }\n\n /**\n * Get the world position of a crop box handle\n */\n getCropBoxHandlePosition(element: BaseElement, anchor: string): { x: number; y: number } {\n const transform = CoordinateTransform.fromElement(element);\n const td = element.transformData as ImageTransformData;\n const width = td.width;\n const height = td.height;\n\n // Get crop dimensions\n const cropX = td.cropX || 0;\n const cropY = td.cropY || 0;\n const cropWidth = td.cropWidth || 1;\n const cropHeight = td.cropHeight || 1;\n\n // Calculate handle position in local coordinates\n let localX = 0;\n let localY = 0;\n\n if (anchor.includes('left')) {\n localX = (cropX - 0.5) * width;\n } else if (anchor.includes('right')) {\n localX = (cropX + cropWidth - 0.5) * width;\n } else {\n localX = (cropX + cropWidth / 2 - 0.5) * width;\n }\n\n if (anchor.includes('top')) {\n localY = (cropY - 0.5) * height;\n } else if (anchor.includes('bottom')) {\n localY = (cropY + cropHeight - 0.5) * height;\n } else {\n localY = (cropY + cropHeight / 2 - 0.5) * height;\n }\n\n return transform.localToWorld(localX, localY);\n }\n}\n","/**\n * useCropMode - Manages image crop mode state and logic\n */\n\nimport { useState } from 'react';\nimport { CropModeController } from '../../../core/CropModeController.js';\n\nexport function useCropMode() {\n // Create crop mode controller\n const [cropController] = useState(() => new CropModeController());\n\n return {\n cropController,\n };\n}\n","/**\n * useMultiSelection - Manages multi-element selection state\n */\n\nimport { useEffect, useRef } from 'react';\nimport type { MultiSelectionOBB, MultiSelectionGroupBounds } from '../renderers/index.js';\n\nexport function useMultiSelection(multiSelection: string[]) {\n // Multi-selection group bounds (for resize and rotation handles)\n const multiSelectionGroupBoundsRef = useRef<MultiSelectionGroupBounds | null>(null);\n\n // Persistent oriented bounding box for multi-selection (preserves rotation).\n // Stored as a ref instead of state so the RAF render loop can write the computed\n // OBB directly without triggering React re-renders.\n const multiSelectionOBBRef = useRef<MultiSelectionOBB | null>(null);\n\n // Reset OBB when multi-selection changes\n useEffect(() => {\n multiSelectionOBBRef.current = null;\n }, [multiSelection]);\n\n return {\n multiSelectionGroupBoundsRef,\n multiSelectionOBBRef,\n };\n}\n","/**\n * useTextEditing - Manages rich text editing state and formatting\n */\n\nimport { useState, useRef, useCallback, useImperativeHandle, type Ref } from 'react';\nimport type { RichText, CharacterStyle } from '../../../types/index.js';\nimport { TextElement } from '../../../core/TextElement.js';\nimport type { ImageElement } from '../../../core/ImageElement.js';\nimport type { GroupElement } from '../../../core/GroupElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { PathElement } from '../../../core/PathElement.js';\n\n/** Elements that can be selected in the canvas */\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\n/** Helper to check if an element has text editing capabilities (getDefaultStyle method) */\nfunction hasTextCapabilities(element: CanvasElement | null | undefined): element is TextElement {\n return element !== null && element !== undefined && element instanceof TextElement;\n}\n\nexport interface TextEditingHandle {\n applyTextFormatting: (style: CharacterStyle) => void;\n toggleFormattingProperty: (property: 'bold' | 'italic' | 'underline' | 'strikethrough') => void;\n isEditingText: () => boolean;\n getSelectionStyle: () => CharacterStyle | null;\n}\n\ninterface UseTextEditingProps {\n selectedElement: CanvasElement | null | undefined;\n ref?: Ref<TextEditingHandle>;\n}\n\nexport function useTextEditing({ selectedElement, ref }: UseTextEditingProps) {\n // Edit state\n const [isEditing, setIsEditing] = useState<boolean>(false);\n const [editText, setEditText] = useState<string>('');\n const [editPosition, setEditPosition] = useState<{\n x: number;\n y: number;\n width: number;\n height?: number;\n }>({ x: 0, y: 0, width: 200, height: 50 });\n const [cursorPosition, setCursorPosition] = useState<number>(0);\n const [selectionStart, setSelectionStart] = useState<number>(0);\n const [selectionEnd, setSelectionEnd] = useState<number>(0);\n const cursorOpacityRef = useRef<number>(1); // Use ref instead of state to avoid triggering re-renders\n const cursorStartTimeRef = useRef<number>(performance.now()); // Track animation start time\n const selectionAnchorRef = useRef<number>(0); // Immediate anchor for drag selection (avoids async state issues)\n const didDragSelectRef = useRef<boolean>(false); // Track if drag selection occurred (to skip onClick)\n\n // Rich text editing state\n const [editRichText, setEditRichText] = useState<RichText | null>(null);\n const [currentFormattingStyle, setCurrentFormattingStyle] = useState<CharacterStyle | null>(null);\n\n // Ref to track latest editRichText synchronously (for handleTextEditComplete to read\n // immediately after formatting is applied, before React state update completes)\n const editRichTextRef = useRef<RichText | null>(null);\n\n // Apply formatting to selected text\n const applyFormattingToSelection = useCallback(\n (style: CharacterStyle) => {\n\n if (!isEditing || !editRichText) {\n return;\n }\n\n const start = Math.min(selectionStart, selectionEnd);\n const end = Math.max(selectionStart, selectionEnd);\n\n\n if (start === end) {\n // No selection - apply to ALL characters\n\n const updated = editRichText.clone();\n const textLength = updated.getText().length;\n updated.applyStyle(0, textLength, style);\n\n // Update ref synchronously so handleTextEditComplete can read latest value\n editRichTextRef.current = updated;\n setEditRichText(updated);\n setEditText(updated.getText());\n\n // Also update current formatting style so next character uses the same formatting\n setCurrentFormattingStyle((prevStyle) => {\n const newStyle = { ...prevStyle, ...style };\n return newStyle;\n });\n } else {\n // Apply to selection\n\n const updated = editRichText.clone();\n updated.applyStyle(start, end, style);\n\n // Update ref synchronously so handleTextEditComplete can read latest value\n editRichTextRef.current = updated;\n setEditRichText(updated);\n setEditText(updated.getText());\n\n // Also update current formatting style so next character uses the same formatting\n setCurrentFormattingStyle((prevStyle) => {\n const newStyle = { ...prevStyle, ...style };\n return newStyle;\n });\n }\n },\n [isEditing, editRichText, selectionStart, selectionEnd]\n );\n\n // Get the style of the current selection (for toolbar state)\n const getSelectionStyle = useCallback((): CharacterStyle | null => {\n if (!isEditing || !editRichText || !selectedElement) return null;\n\n const start = Math.min(selectionStart, selectionEnd);\n const end = Math.max(selectionStart, selectionEnd);\n\n // If no selection, get style at cursor position\n if (start === end) {\n if (start === 0) {\n // At beginning, use element defaults (only for elements with text capabilities)\n return hasTextCapabilities(selectedElement) ? selectedElement.getDefaultStyle() : {};\n }\n // Use style of character before cursor\n return editRichText.getStyleAt(Math.max(0, start - 1));\n }\n\n // Get style of first character in selection\n return editRichText.getStyleAt(start);\n }, [isEditing, editRichText, selectionStart, selectionEnd, selectedElement]);\n\n // Helper function to toggle boolean formatting properties\n const toggleFormattingProperty = useCallback(\n (property: 'bold' | 'italic' | 'underline' | 'strikethrough') => {\n if (!isEditing || !editRichText || !selectedElement) return;\n\n // Get current style at cursor/selection\n const currentStyle = getSelectionStyle();\n const defaultStyle = hasTextCapabilities(selectedElement) ? selectedElement.getDefaultStyle() : {};\n\n // Merge with defaults to get effective style\n const effectiveStyle = { ...defaultStyle, ...currentStyle };\n\n // Toggle the property\n const newValue = !effectiveStyle[property];\n\n\n // Apply the toggled value\n applyFormattingToSelection({ [property]: newValue });\n },\n [isEditing, editRichText, selectedElement, getSelectionStyle, applyFormattingToSelection]\n );\n\n // Expose formatting methods to parent component via ref\n useImperativeHandle(\n ref,\n () => ({\n applyTextFormatting: (style: CharacterStyle) => {\n applyFormattingToSelection(style);\n },\n toggleFormattingProperty: (property: 'bold' | 'italic' | 'underline' | 'strikethrough') => {\n toggleFormattingProperty(property);\n },\n isEditingText: () => isEditing,\n getSelectionStyle: () => getSelectionStyle(),\n }),\n [applyFormattingToSelection, toggleFormattingProperty, isEditing, getSelectionStyle]\n );\n\n return {\n isEditing,\n setIsEditing,\n editText,\n setEditText,\n editPosition,\n setEditPosition,\n cursorPosition,\n setCursorPosition,\n selectionStart,\n setSelectionStart,\n selectionEnd,\n setSelectionEnd,\n cursorOpacityRef, // Export ref instead of state\n cursorStartTimeRef, // Export animation start time ref\n selectionAnchorRef, // Export anchor ref for drag selection\n didDragSelectRef, // Export drag selection flag ref\n editRichText,\n setEditRichText,\n editRichTextRef, // Export ref for synchronous access in handleTextEditComplete\n currentFormattingStyle,\n setCurrentFormattingStyle,\n applyFormattingToSelection,\n getSelectionStyle,\n toggleFormattingProperty,\n };\n}\n","/**\n * useCanvasLayout - Composite hook for canvas sizing, touch detection, and view calculations\n *\n * Groups related state:\n * - Canvas size (responsive to props, artboard changes, and window resize)\n * - Touch device detection\n * - Touch target tracking for iOS Safari blur handling\n * - View calculations (padding, margins, zoom-invariant border radius)\n */\n\nimport { useState, useEffect, useRef } from 'react';\nimport type { ArtboardElement } from '../../../core/ArtboardElement.js';\nimport type { ArtboardManager } from '../../../core/ArtboardManager.js';\n\nexport interface UseCanvasLayoutParams {\n width: number | undefined;\n height: number | undefined;\n zoom: number;\n viewPadding: number;\n artboardBorderRadius: number;\n fixedMargin: number | undefined;\n fixedMarginX: number | undefined;\n fixedMarginY: number | undefined;\n artboards: ArtboardElement[];\n artboardManager: ArtboardManager;\n /**\n * ADR-0060: when present, fit the viewport to this piece (in artboard\n * / spread coordinates) plus a peek margin so neighbouring pieces\n * stay visible at the edges. When `null` / `undefined`, the viewport\n * fits the active artboard (historical behaviour).\n */\n focusedPieceRect?: { x: number; y: number; width: number; height: number } | null;\n}\n\nexport interface UseCanvasLayoutReturn {\n /** Whether the device supports touch input */\n isTouchDevice: boolean;\n /** Current canvas dimensions */\n canvasSize: { width: number; height: number };\n /** Ref tracking the last touch/pointer target for iOS Safari blur handling */\n lastTouchTargetRef: React.RefObject<HTMLElement | null>;\n\n // View calculations\n /** View padding clamped to [0.1, 1.0] */\n effectiveViewPadding: number;\n /** Canvas width including padding/margins, in world space */\n paddedCanvasWidth: number;\n /** Canvas height including padding/margins, in world space */\n paddedCanvasHeight: number;\n /** Horizontal padding offset in world space */\n paddingOffsetX: number;\n /** Vertical padding offset in world space */\n paddingOffsetY: number;\n /** Border radius adjusted for current zoom level */\n zoomInvariantBorderRadius: number;\n}\n\nexport function useCanvasLayout({\n width,\n height,\n zoom,\n viewPadding,\n artboardBorderRadius,\n fixedMargin,\n fixedMarginX,\n fixedMarginY,\n artboards,\n artboardManager,\n focusedPieceRect,\n}: UseCanvasLayoutParams): UseCanvasLayoutReturn {\n // ── Touch device detection ──────────────────────────────────────────────\n const [isTouchDevice, setIsTouchDevice] = useState(false);\n\n useEffect(() => {\n const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;\n setIsTouchDevice(hasTouch);\n }, []);\n\n // ── Touch target tracking (iOS Safari blur handling) ────────────────────\n const lastTouchTargetRef = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n const handleTouchStart = (e: TouchEvent | PointerEvent) => {\n lastTouchTargetRef.current = e.target as HTMLElement;\n setTimeout(() => { lastTouchTargetRef.current = null; }, 500);\n };\n document.addEventListener('touchstart', handleTouchStart, { capture: true, passive: true });\n document.addEventListener('pointerdown', handleTouchStart, { capture: true, passive: true });\n return () => {\n document.removeEventListener('touchstart', handleTouchStart, { capture: true });\n document.removeEventListener('pointerdown', handleTouchStart, { capture: true });\n };\n }, []);\n\n // ── Canvas size ─────────────────────────────────────────────────────────\n const [canvasSize, setCanvasSize] = useState({\n width: width ?? window.innerWidth,\n height: height ?? window.innerHeight,\n });\n\n // Update canvas size when width/height props change. When a focused\n // piece is provided (ADR-0060), the canvas resizes to that piece\n // instead of the full artboard so the piece fills most of the\n // display; the surrounding render-loop translate puts neighbouring\n // pieces just past the canvas bounds where the peek margin makes\n // them visible.\n //\n // Order matters: `focusedPieceRect` MUST win over the explicit\n // `width` / `height` props. The Canvas.tsx wrapper passes the\n // active artboard's dims as width/height — which would otherwise\n // override the focused piece and put us back at full-artboard\n // sizing. Focus is a more specific, intentional signal.\n useEffect(() => {\n if (focusedPieceRect) {\n setCanvasSize({ width: focusedPieceRect.width, height: focusedPieceRect.height });\n return;\n }\n if (width !== undefined && height !== undefined) {\n setCanvasSize({ width, height });\n return;\n }\n const activeArtboard = artboardManager.getActiveArtboard();\n if (activeArtboard) {\n setCanvasSize({ width: activeArtboard.width, height: activeArtboard.height });\n }\n }, [width, height, artboards, artboardManager, focusedPieceRect]);\n\n // Window resize handler\n useEffect(() => {\n const handleResize = () => {\n if (focusedPieceRect) {\n setCanvasSize({ width: focusedPieceRect.width, height: focusedPieceRect.height });\n return;\n }\n if (width === undefined && height === undefined) {\n const activeArtboard = artboardManager.getActiveArtboard();\n if (!activeArtboard) {\n setCanvasSize({ width: window.innerWidth, height: window.innerHeight });\n } else {\n setCanvasSize({ width: activeArtboard.width, height: activeArtboard.height });\n }\n }\n };\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [width, height, artboardManager, focusedPieceRect]);\n\n // ── View calculations ───────────────────────────────────────────────────\n const effectiveViewPadding = Math.max(0.1, Math.min(1.0, viewPadding));\n const effectiveMarginX = fixedMarginX ?? fixedMargin;\n const effectiveMarginY = fixedMarginY ?? fixedMargin;\n const worldMarginX = effectiveMarginX !== undefined && zoom > 0 ? effectiveMarginX / zoom : 0;\n const worldMarginY = effectiveMarginY !== undefined && zoom > 0 ? effectiveMarginY / zoom : 0;\n\n const paddedCanvasWidth = effectiveMarginX !== undefined\n ? canvasSize.width + (worldMarginX * 2)\n : canvasSize.width / effectiveViewPadding;\n const paddedCanvasHeight = effectiveMarginY !== undefined\n ? canvasSize.height + (worldMarginY * 2)\n : canvasSize.height / effectiveViewPadding;\n\n // ADR-0060: in focused mode, shift the world transform so the\n // focused piece's `(x, y)` origin in artboard / spread coords lands\n // at `(worldMarginX, worldMarginY)` on the canvas. Elements still\n // live at their spread coordinates; the render-loop translate just\n // recentres what the user sees. Anything outside the focused piece's\n // rect (other pieces, element overflow) draws beyond the canvas\n // bounds and is naturally clipped — neighbours are visible only via\n // the peek margin baked into `fixedMargin`.\n const baseOffsetX = effectiveMarginX !== undefined\n ? worldMarginX\n : (paddedCanvasWidth - canvasSize.width) / 2;\n const baseOffsetY = effectiveMarginY !== undefined\n ? worldMarginY\n : (paddedCanvasHeight - canvasSize.height) / 2;\n const paddingOffsetX = focusedPieceRect ? baseOffsetX - focusedPieceRect.x : baseOffsetX;\n const paddingOffsetY = focusedPieceRect ? baseOffsetY - focusedPieceRect.y : baseOffsetY;\n\n const zoomInvariantBorderRadius = zoom > 0 ? artboardBorderRadius / zoom : artboardBorderRadius;\n\n return {\n isTouchDevice,\n canvasSize,\n lastTouchTargetRef,\n effectiveViewPadding,\n paddedCanvasWidth,\n paddedCanvasHeight,\n paddingOffsetX,\n paddingOffsetY,\n zoomInvariantBorderRadius,\n };\n}\n","/**\n * useInteractionState - Composite hook for interaction overlay state\n *\n * Groups state that drives visual overlays during user interaction:\n * - Snap guides (alignment lines shown during drag/resize)\n * - Spacing indicators (distance labels shown when Alt is pressed)\n * - Alt key tracking\n * - Rotation state (angle tracking during rotation gesture)\n */\n\nimport { useState } from 'react';\nimport type { SnapGuide, SpacingIndicator } from '../../../types/index.js';\n\nexport interface UseInteractionStateReturn {\n /** Active snap guide lines */\n snapGuides: SnapGuide[];\n setSnapGuides: React.Dispatch<React.SetStateAction<SnapGuide[]>>;\n\n /** Spacing measurement indicators (shown with Alt key) */\n spacingIndicators: SpacingIndicator[];\n setSpacingIndicators: React.Dispatch<React.SetStateAction<SpacingIndicator[]>>;\n\n /** Whether the Alt key is currently pressed */\n isAltKeyPressed: boolean;\n setIsAltKeyPressed: React.Dispatch<React.SetStateAction<boolean>>;\n\n /** Whether the user is currently rotating an element */\n isRotating: boolean;\n setIsRotating: React.Dispatch<React.SetStateAction<boolean>>;\n\n /** The angle at which rotation started */\n setRotationStartAngle: React.Dispatch<React.SetStateAction<number>>;\n\n /** Current rotation angle during a rotation gesture */\n currentRotation: number;\n setCurrentRotation: React.Dispatch<React.SetStateAction<number>>;\n\n /** Version counter to trigger re-renders when rotating element updates */\n setRotatingElementVersion: React.Dispatch<React.SetStateAction<number>>;\n}\n\nexport function useInteractionState(): UseInteractionStateReturn {\n const [snapGuides, setSnapGuides] = useState<SnapGuide[]>([]);\n const [spacingIndicators, setSpacingIndicators] = useState<SpacingIndicator[]>([]);\n const [isAltKeyPressed, setIsAltKeyPressed] = useState(false);\n const [isRotating, setIsRotating] = useState<boolean>(false);\n const [_rotationStartAngle, setRotationStartAngle] = useState<number>(0);\n const [currentRotation, setCurrentRotation] = useState<number>(0);\n const [, setRotatingElementVersion] = useState<number>(0);\n\n return {\n snapGuides,\n setSnapGuides,\n spacingIndicators,\n setSpacingIndicators,\n isAltKeyPressed,\n setIsAltKeyPressed,\n isRotating,\n setIsRotating,\n setRotationStartAngle,\n currentRotation,\n setCurrentRotation,\n setRotatingElementVersion,\n };\n}\n","/**\n * useRenderState - Composite hook for render versioning and triggers\n *\n * Groups state that controls when the canvas re-renders:\n * - renderVersion: incremented when external events (image loads) require a re-render\n * - handlesVersion: incremented when transform handles need recalculation\n * - forceUpdate: generic trigger for component re-render\n * - cursorAnimationFrame: drives text cursor blink animation\n * - Image load subscription: listens for async image loads to trigger re-renders\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { subscribeToImageLoads } from '../../../core/ImageLoadEvents.js';\n\nexport interface UseRenderStateParams {\n /** Current elements array, used to check if loaded image belongs to an active element */\n elements: Array<{ id: string }>;\n}\n\nexport interface UseRenderStateReturn {\n /** Version counter for render-triggering events */\n renderVersion: number;\n\n /** Force a render by incrementing the render version */\n forceRender: () => void;\n\n /** Version counter for transform handle recalculation */\n handlesVersion: number;\n setHandlesVersion: React.Dispatch<React.SetStateAction<number>>;\n\n /** Generic force re-render trigger */\n forceUpdate: React.Dispatch<React.SetStateAction<object>>;\n\n /** Frame counter for cursor blink animation */\n cursorAnimationFrame: number;\n setCursorAnimationFrame: React.Dispatch<React.SetStateAction<number>>;\n}\n\nexport function useRenderState({ elements }: UseRenderStateParams): UseRenderStateReturn {\n const [, forceUpdate] = useState<object>({});\n const [handlesVersion, setHandlesVersion] = useState(0);\n const [renderVersion, setRenderVersion] = useState(0);\n const [cursorAnimationFrame, setCursorAnimationFrame] = useState(0);\n\n const forceRender = useCallback(() => {\n setRenderVersion((v) => v + 1);\n }, []);\n\n // Subscribe to image load events to trigger re-renders\n useEffect(() => {\n const unsubscribe = subscribeToImageLoads((elementId) => {\n const hasElement = elements.some(e => e.id === elementId);\n if (hasElement) { setRenderVersion(v => v + 1); }\n });\n return unsubscribe;\n }, [elements]);\n\n return {\n renderVersion,\n forceRender,\n handlesVersion,\n setHandlesVersion,\n forceUpdate,\n cursorAnimationFrame,\n setCursorAnimationFrame,\n };\n}\n","/**\n * ADR-0054: canvas-native piece-guide rendering.\n *\n * This replaces the old `VisualGuideOverlay` SVG-DOM overlay. The overlay\n * approach lived outside the canvas element and had to re-derive the\n * canvas's viewport math from scratch (fixedMargin units, aspect-ratio\n * letterboxing, canvas-wrapper negative margins, etc.), which made it\n * drift away from the artboard at any zoom that wasn't 1×.\n *\n * This module draws the same visual layers (bleed ring, zones, die-cut\n * knockouts, boundary outline, safe-area wash, labels) directly into the\n * canvas render loop's 2D context — which is already in artboard-local\n * coordinates after the loop has applied DPR / zoom / padding translate.\n * That means piece-local shape coords work verbatim and no unit or\n * coordinate-system conversion is needed anywhere.\n *\n * ── Rendering contract (IMPORTANT) ────────────────────────────────────\n * Piece-guide products (phone cases, anything with a non-rectangular\n * outer shape) treat the canvas as a true knockout — the page chrome /\n * product mockup behind shows through wherever the artwork doesn't\n * cover. To make that work end-to-end, several paint passes that exist\n * for plain rectangular products are gated off in this mode:\n *\n * 1. The `<canvas>` element's CSS `background` is `transparent`\n * instead of `var(--color-canvas-bg)` (CanvasEditor.tsx).\n * 2. The artboard 2D bg fill is skipped (useCanvasRenderLoop.ts).\n * 3. The ArtboardRenderer's own background paint is skipped\n * (useCanvasRenderLoop.ts) — its default `'#ffffff'` would\n * otherwise blanket the corner wedges.\n * 4. The Pass-1 overflow-dim halo uses a *rounded* inverted clip,\n * so the 30% blurred preview of selected/hovered elements\n * extends naturally into the corner wedges during interaction\n * (CanvasRenderer.ts). At idle the halo is inactive, so corner\n * wedges read as fully transparent.\n * 5. Die-cut zones in `dieCutMode: 'knockout'` skip their\n * `destination-out` fill while the user is interacting (see\n * `interacting` param + `drawDieCutKnockout` below). Idle reads\n * as a clean punched hole; mid-edit the artwork and selection\n * chrome inside the cutout stay visible so manipulation\n * feedback isn't erased.\n *\n * Together, those gates mean the renderer here doesn't have to do any\n * per-boundary clipping or \"final crop\" pass — the surrounding paint\n * passes simply don't paint into anywhere that would need erasing.\n *\n * Rendering order (back → front) must match the old SVG overlay so the\n * visual language stays identical:\n *\n * 1. Bleed ring — `outerShape ∖ wantInner`, where `wantInner` is\n * `boundary` (default) or `safeArea` per `bleedInnerEdge`. Two\n * modes: `'gradient'` (Apple-style soft vignette via blurred\n * destination-out knockout) and `'solid'` (flat fill at\n * `bleedOpacity`).\n * 2. Non-die-cut zones — flat fill + stroke per zone kind.\n * 3. Die-cut zones — `'blur-tint'` (blurred content + dark tint,\n * reads as a punched window) or `'knockout'`\n * (`destination-out`-cleared pixels — true transparency through\n * the canvas, suppressed mid-interaction).\n * 4. Boundary outline — optional (default off), solid stroke.\n * 5. Safe-area outline — optional legacy dashed stroke (default off).\n * 6. Labels — halo-stroked text for zone/area annotations.\n */\n\nimport { applyPieceRotation, naturalDims } from '../utils/pieceRotation.js';\nimport type {\n PieceGuide,\n SvgPath,\n ZoneShape,\n GuideLabel,\n} from '../components/VisualGuideOverlay.js';\nimport { setZoomInvariantStroke } from '../core/GeometryUtils.js';\nimport { isInkDark, isInkLight, inkLuminance } from '../utils/inkLuminance.js';\n\nexport type { PieceGuide, SvgPath, ZoneShape, GuideLabel };\n\n/** Minimum piece info the renderer needs. Matches the ADR-0054 backend shape. */\nexport interface PieceGuideRendererPiece {\n /** Stable piece id (matches placements[].name and pieceGuides keys). */\n id: string;\n /** Piece's top-left position 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 (piece-local px) when no authored\n * `pieceGuide.safeArea` is present. Used only if the caller wants the\n * legacy centered-inset fallback; otherwise omit.\n */\n safeAreaInsetPx?: number;\n /** Clockwise rotation in degrees about the piece center (any multiple of 45). Default 0. */\n rotation?: number;\n /** Natural (unrotated) size; required when `rotation` isn't a right angle (utils/pieceRotation.ts). */\n naturalWidth?: number;\n naturalHeight?: number;\n}\n\nexport interface PieceGuideRendererStyle {\n /**\n * Bleed-ring rendering mode. Default `'gradient'` (the original\n * Apple-style vignette: opaque fill softly fading into the design\n * canvas via a blurred destination-out knockout).\n *\n * `'solid'` draws a flat uniform fill over `pieceRect ∖ boundary` at\n * `bleedOpacity`. Useful when the vignette's soft inner edge reads as\n * visual noise against the artwork — e.g. photographic designs where\n * a clean sharp bleed edge is preferred.\n */\n bleedMode?: 'gradient' | 'solid';\n /**\n * Which shape the bleed ring rings around. Default `'boundary'` (the\n * design-canvas edge — the current behaviour). `'safeArea'` pulls the\n * inner edge inwards to the safe-area shape, so the fade starts from\n * the text-safe zone outward and the region between safe area and\n * boundary is covered too. Falls back to `'boundary'` if the piece\n * has no safeArea authored.\n */\n bleedInnerEdge?: 'boundary' | 'safeArea';\n /** Bleed-ring fill colour. Defaults to #333. */\n bleedFill?: string;\n /**\n * Bleed-ring opacity (0–1). Default 1.0.\n *\n * Must be 1.0 whenever the main canvas does any per-boundary\n * clipping. Translucent overlays combined with sharp underlying\n * clips produce a visible \"halo then jump\" at the boundary instead\n * of a smooth vignette — see the rendering contract in this file's\n * header. If you lower this, the bleed band will read as a tinted\n * wash instead of a solid frame.\n */\n bleedOpacity?: number;\n /**\n * Die-cut rendering mode.\n *\n * - `'blur-tint'` (default) — the current behaviour. The zone's\n * artwork is blurred and a dark tint composites on top so the\n * punched hole reads as a \"window\" onto the product underneath.\n * - `'knockout'` — the zone's pixels are cleared from the canvas\n * with `destination-out` compositing. Whatever's drawn *beneath*\n * the canvas element (page background, a product mockup layered\n * underneath, etc.) shows through. Useful when the canvas sits\n * on top of a real product mockup and you want the hole to be\n * honestly transparent.\n */\n dieCutMode?: 'blur-tint' | 'knockout';\n /** Die-cut tint colour. Defaults to --color-bg-tertiary. Only used in `'blur-tint'` mode. */\n dieCutTint?: string;\n /** Alpha applied to the die-cut tint (0–1). Default 0.7 (matches old overlay). */\n dieCutTintAlpha?: number;\n /** Gaussian blur radius (CSS pixels) applied to bleed + die-cut content. Default 4. */\n dieCutBlurPx?: number;\n /**\n * Die-cut outline stroke colour. Empty string (default) disables the\n * stroke — matches the canvas renderer's historical behaviour. Set\n * to e.g. `'#ffffff'` to get a thin rim around the punched hole.\n */\n dieCutStroke?: string;\n /** Die-cut outline stroke width (CSS pixels). Default 1. */\n dieCutStrokeWidthPx?: number;\n /** Extra blur radius (CSS pixels) for the bleed ring only. Scales with artboard size. */\n bleedBlurScale?: number;\n /** Boundary outline stroke colour. Empty string disables (default). */\n boundaryStroke?: string;\n boundaryStrokeWidthPx?: number;\n /** Legacy dashed safe-area outline. Empty string disables (default). */\n safeAreaStroke?: string;\n safeAreaStrokeWidthPx?: number;\n safeAreaDashPx?: [number, number];\n /** Per-kind zone fill colours (rgba). */\n zoneFill?: Partial<Record<ZoneShape['kind'], string>>;\n /** Per-kind zone stroke colours. */\n zoneStroke?: Partial<Record<ZoneShape['kind'], string>>;\n /** Label text + halo style. */\n labelColor?: string;\n labelFontSizePx?: number;\n labelFontFamily?: string;\n labelHaloColor?: string;\n}\n\nexport interface PieceGuideVisibility {\n bleed?: boolean;\n zones?: boolean;\n boundary?: boolean;\n safeArea?: boolean;\n labels?: boolean;\n}\n\nexport interface PieceGuideRenderParams {\n ctx: CanvasRenderingContext2D;\n /**\n * The artboard-local origin (in world pixels). The render loop has\n * already applied DPR/zoom/padding transforms, so shape coordinates\n * can be used directly after `ctx.translate(artboard.x + piece.x, ...)`\n * — artboardOrigin is just where the artboard itself starts in world\n * space (typically the active artboard's (x, y)).\n */\n artboardOrigin: { x: number; y: number };\n artboardWidth: number;\n artboardHeight: number;\n pieces: PieceGuideRendererPiece[];\n pieceGuides?: Record<string, PieceGuide>;\n show?: PieceGuideVisibility;\n style?: PieceGuideRendererStyle;\n /** Canvas CSS pixels per world pixel (from useCanvasLayout). */\n zoom: number;\n /**\n * True while an element is selected, hovered, dragged, or otherwise\n * being manipulated. When set, die-cut zones in `dieCutMode:\n * 'knockout'` skip the `destination-out` fill that erases pixels\n * inside the cut shape — the stroke / outline still renders, but\n * underlying artwork (and any selection chrome the element drew\n * during its own render pass) stays visible. This mirrors the\n * rounded-outer-corner behaviour: idle reads as a hard knockout to\n * the page chrome below, but during interaction the user sees what\n * sits beneath the cutout so resize/drag feedback isn't suddenly\n * \"punched out\". Idle behaviour (false / undefined) is unchanged.\n */\n interacting?: boolean;\n /**\n * ADR-0060: id of the piece currently in focus. When set, ONLY that\n * piece's guide chrome (boundary, safe area, bleed ring, die-cut\n * zones, labels) is drawn — every other piece is skipped entirely.\n * The companion narrowing in `useCanvasRenderLoop` filters the\n * artboard's `composite-path` clip to the focused piece's\n * silhouette so element rendering only paints inside that piece\n * either; neighbour artwork and chrome are completely hidden.\n * When `null` / `undefined` every piece renders (spread mode).\n *\n * Earlier this dimmed neighbours to ≈0.3 alpha for \"structural\n * context\", but the current UX preference is to fully hide them\n * when focused — the focus picker is the affordance to switch back\n * to spread, neighbour pieces don't need to leak through.\n */\n focusedPieceId?: string | null;\n}\n\n/** Default style. Kept in sync with the old VisualGuideOverlay defaults. */\nconst DEFAULTS: Required<\n Omit<PieceGuideRendererStyle, 'zoneFill' | 'zoneStroke'>\n> & {\n zoneFill: Record<ZoneShape['kind'], string>;\n zoneStroke: Record<ZoneShape['kind'], string>;\n} = {\n bleedMode: 'gradient',\n bleedInnerEdge: 'boundary',\n // Pure black at full opacity. With the `multiply` blend mode applied\n // at composite time, this collapses both the editor surface and any\n // artwork in the bleed band to black — maximum visual signal that\n // \"this region is the crop zone.\" The themed default at render time\n // overrides this for dark mode (→ white + `screen`); this value is\n // the safe light-mode choice that callers fall back to.\n bleedFill: '#000000',\n bleedOpacity: 1,\n // Die-cut is a \"punched hole in the material\" — it should always\n // read as dark regardless of editor theme.\n dieCutMode: 'blur-tint',\n dieCutTint: '#333',\n dieCutTintAlpha: 0.7,\n dieCutBlurPx: 4,\n dieCutStroke: '',\n dieCutStrokeWidthPx: 1,\n // Width of the fade from transparent (deep inside boundary) to\n // opaque (at/past boundary edge), as a fraction of the piece's\n // short dimension. ~0.04 gives a visibly soft vignette on a phone\n // case without darkening the whole design area.\n bleedBlurScale: 0.04,\n boundaryStroke: '',\n boundaryStrokeWidthPx: 1,\n safeAreaStroke: '',\n safeAreaStrokeWidthPx: 1.5,\n safeAreaDashPx: [6, 4],\n zoneFill: {\n wrap: 'rgba(148, 163, 184, 0.25)',\n binding: 'rgba(234, 88, 12, 0.20)',\n dieCut: 'rgba(0, 0, 0, 0.7)', // not used directly — see renderDieCut\n warning: 'rgba(245, 158, 11, 0.25)',\n custom: 'rgba(100, 100, 100, 0.15)',\n },\n zoneStroke: {\n wrap: '#64748b',\n binding: '#ea580c',\n dieCut: '#ffffff',\n warning: '#f59e0b',\n custom: '#6b7280',\n },\n labelColor: '#111827',\n labelFontSizePx: 14,\n labelFontFamily:\n 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif',\n labelHaloColor: '#ffffff',\n};\n\n/**\n * Capture the current canvas pixels into a detached backing canvas. We\n * keep a single reusable element across calls so we're not allocating a\n * new canvas per render frame. Retina / DPI safety: the backing canvas\n * is sized to the source's physical pixel dims, not CSS dims.\n */\nlet cachedSnapshotEl: HTMLCanvasElement | null = null;\nfunction snapshotCanvas(source: HTMLCanvasElement): HTMLCanvasElement | null {\n const w = source.width;\n const h = source.height;\n if (w <= 0 || h <= 0) return null;\n if (!cachedSnapshotEl) cachedSnapshotEl = document.createElement('canvas');\n if (cachedSnapshotEl.width !== w) cachedSnapshotEl.width = w;\n if (cachedSnapshotEl.height !== h) cachedSnapshotEl.height = h;\n const sctx = cachedSnapshotEl.getContext('2d');\n if (!sctx) return null;\n sctx.setTransform(1, 0, 0, 1, 0, 0);\n sctx.clearRect(0, 0, w, h);\n sctx.drawImage(source, 0, 0);\n return cachedSnapshotEl;\n}\n\n/**\n * Resolve a possibly-CSS-var colour string into a concrete CSS colour\n * the canvas 2D context accepts. Canvas 2D does not resolve `var(...)`,\n * so we read the variable off the canvas element (or documentElement\n * fallback) and use the fallback literal inside the var() expression\n * if the property is unset.\n *\n * If the input is not a var() expression we return it unchanged.\n */\nfunction resolveCssColor(\n input: string,\n target: HTMLElement | null,\n): string {\n const trimmed = input.trim();\n if (!trimmed.startsWith('var(')) return trimmed;\n // Parse \"var(--name[, fallback])\"\n const inner = trimmed.slice(4, -1);\n const [name, ...rest] = inner.split(',');\n const varName = name.trim();\n const fallback = rest.length > 0 ? rest.join(',').trim() : '';\n const source = target ?? document.documentElement;\n const resolved = getComputedStyle(source).getPropertyValue(varName).trim();\n return resolved || fallback || '#333';\n}\n\n/**\n * True when the editor is in dark mode. Used to pick a\n * high-contrast bleed-wash colour.\n *\n * Trusts the app's explicit theme signal only:\n * 1. `.dark` class on `<html>` (Tailwind / ThemeContext convention)\n * 2. `data-theme=\"dark\"` attribute on `<html>` (alternative\n * convention some apps use)\n *\n * `prefers-color-scheme: dark` is intentionally NOT consulted: when\n * the system preference is dark but the app is explicitly in light\n * mode (no `.dark` class), falling through to the media query made\n * the bleed wash render with the dark-mode (white + screen) ink\n * over a white editor surface — the wash became invisible because\n * the renderer's notion of \"dark\" diverged from what the user\n * actually saw on screen. Apps that want dark behaviour must set\n * the explicit signal; this matches how the rest of the design\n * system resolves theme.\n */\nfunction isDocumentDark(_target: HTMLElement | null): boolean {\n if (typeof document === 'undefined') return false;\n const html = document.documentElement;\n if (html.classList.contains('dark')) return true;\n if (html.getAttribute('data-theme') === 'dark') return true;\n return false;\n}\n\n// Quick \"is this ink dark / light\" checks used to decide whether to\n// flip an author-supplied colour for the current theme. The implementation\n// moved to utils/inkLuminance.ts so element-creation defaults can share the\n// same luminance math; re-exported here for back-compat (@internal, tests).\nexport { isInkDark, isInkLight, inkLuminance };\n\n// ── SvgPath helpers ───────────────────────────────────────────────────────────\n\nfunction pathToCanvas(ctx: CanvasRenderingContext2D, path: SvgPath): void {\n if (path.kind === 'rect') {\n ctx.rect(path.x, path.y, path.width, path.height);\n return;\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 ctx.rect(path.x, path.y, path.width, path.height);\n return;\n }\n // `roundRect` is supported in every browser we target. rx/ry can\n // differ, so pass an array of [rx, ry, rx, ry] equivalents via the\n // { x, y } radii form.\n if (rx === ry) {\n ctx.roundRect(path.x, path.y, path.width, path.height, rx);\n } else {\n ctx.roundRect(path.x, path.y, path.width, path.height, [\n { x: rx, y: ry },\n ] as unknown as number);\n }\n return;\n }\n // Free-form SVG path. We rely on Path2D's SVG-path-data parser, then\n // add it to the current path. A bit indirect, but it covers arbitrary\n // author-supplied polygons without us writing a parser.\n const p2d = new Path2D(path.d);\n // Path2D path can be added but only via ctx.fill/ctx.stroke directly.\n // For our uses (fills, strokes), drawing the Path2D instance works\n // and respects the current transform.\n (ctx as unknown as { __pieceGuidePath2D?: Path2D }).__pieceGuidePath2D = p2d;\n}\n\nfunction fillSvgPath(\n ctx: CanvasRenderingContext2D,\n path: SvgPath,\n fillRule?: CanvasFillRule,\n): void {\n if (path.kind === 'path') {\n const p2d = new Path2D(path.d);\n if (fillRule) ctx.fill(p2d, fillRule);\n else ctx.fill(p2d);\n return;\n }\n ctx.beginPath();\n pathToCanvas(ctx, path);\n if (fillRule) ctx.fill(fillRule);\n else ctx.fill();\n}\n\nfunction strokeSvgPath(ctx: CanvasRenderingContext2D, path: SvgPath): void {\n if (path.kind === 'path') {\n const p2d = new Path2D(path.d);\n ctx.stroke(p2d);\n return;\n }\n ctx.beginPath();\n pathToCanvas(ctx, path);\n ctx.stroke();\n}\n\nfunction addSvgPathSubpath(ctx: CanvasRenderingContext2D, path: SvgPath): void {\n if (path.kind === 'path') {\n // Path-kind can't be added as a sub-path on the ctx (Path2D\n // commands don't have a \"merge into current ctx path\" API). Callers\n // that need a compound (outer ∖ inner) for path-kind use the\n // `addToPath2D` helper below, which takes a Path2D as the\n // accumulator and supports every SvgPath kind via Path2D.addPath\n // for path-kind. Pure-ctx callers either fall through to a flat\n // outer fill (drawBleedRing-step 1) or use `fillSvgPath` directly.\n return;\n }\n pathToCanvas(ctx, path);\n}\n\n/**\n * Add an SvgPath to a Path2D accumulator. Mirrors `addSvgPathSubpath`\n * but works for every SvgPath kind: rect/roundedRect via the Path2D\n * primitive methods, path-kind via `Path2D.addPath(new Path2D(d))`.\n *\n * Use the resulting Path2D with `ctx.fill(p2d, 'evenodd')` /\n * `ctx.clip(p2d, 'evenodd')` for compound (outer ∖ inner) operations\n * — including across mixed-kind shapes (rect outer, path-kind inner).\n */\nfunction addToPath2D(p2d: Path2D, path: SvgPath): void {\n if (path.kind === 'rect') {\n p2d.rect(path.x, path.y, path.width, path.height);\n return;\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 p2d.rect(path.x, path.y, path.width, path.height);\n return;\n }\n if (rx === ry) {\n p2d.roundRect(path.x, path.y, path.width, path.height, rx);\n } else {\n p2d.roundRect(path.x, path.y, path.width, path.height, [\n { x: rx, y: ry },\n ] as unknown as number);\n }\n return;\n }\n // path-kind: parse via Path2D, attach to accumulator.\n p2d.addPath(new Path2D(path.d));\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\n // a 90°/270° piece's fallback boundary lands sideways in the rotated\n // local frame.\n return (\n guide?.boundary ?? {\n kind: 'rect',\n x: 0,\n y: 0,\n width: naturalW,\n height: naturalH,\n }\n );\n}\n\nfunction resolveSafeArea(\n guide: PieceGuide | undefined,\n piece: PieceGuideRendererPiece,\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// ── Main renderer ─────────────────────────────────────────────────────────────\n\nexport function renderPieceGuides(params: PieceGuideRenderParams): void {\n const { ctx, pieces, pieceGuides, artboardOrigin, zoom } = params;\n if (!pieces.length) return;\n\n const show: Required<PieceGuideVisibility> = {\n bleed: params.show?.bleed ?? true,\n zones: params.show?.zones ?? true,\n boundary: params.show?.boundary ?? true,\n safeArea: params.show?.safeArea ?? true,\n labels: params.show?.labels ?? true,\n };\n\n const canvasEl = ctx.canvas as HTMLCanvasElement | OffscreenCanvas;\n const domTarget =\n canvasEl instanceof HTMLCanvasElement ? canvasEl : null;\n const isDark = isDocumentDark(domTarget);\n // Theme-aware default for the bleed wash. The wash is the band\n // that fades from transparent inside the piece to opaque past the\n // boundary. It needs to *contrast* with the editor surface\n // underneath:\n // - Light mode: surface is near-white → black ink pushed through `multiply`.\n // - Dark mode: surface is near-black → white ink pushed through `screen`.\n // Pure black / pure white (rather than `#333` / `#ccc`) so the band\n // pops against the editor surface AND visibly transforms artwork\n // that crosses into it — the blend math collapses everything in\n // the band to a single tone, telegraphing \"this region is the\n // crop zone, not part of your design.\"\n const themedDefaults = {\n ...DEFAULTS,\n bleedFill: isDark ? '#ffffff' : '#000000',\n };\n const s = { ...themedDefaults, ...params.style };\n // Merge per-kind colour maps shallowly.\n s.zoneFill = { ...DEFAULTS.zoneFill, ...(params.style?.zoneFill ?? {}) };\n s.zoneStroke = { ...DEFAULTS.zoneStroke, ...(params.style?.zoneStroke ?? {}) };\n\n // Resolve CSS vars ONCE per render against the canvas element. The\n // canvas 2D context doesn't understand var(...), but its owning\n // <canvas> can be read for computed styles because .sc-root (which\n // wraps the canvas) carries the theme variables we care about.\n let bleedFillCss = resolveCssColor(s.bleedFill, domTarget);\n const dieCutTintCss = resolveCssColor(s.dieCutTint, domTarget);\n let boundaryStrokeCss = resolveCssColor(s.boundaryStroke, domTarget);\n\n // Author-supplied `bleedFill` (e.g. `#000000` saved in a Loom\n // layout) was likely picked while looking at the white editor\n // surface. In dark mode that ink is invisible against the dark\n // surface — same value, no contrast. Flip dark fills to white in\n // dark mode (and vice versa) so the wash band stays visible\n // regardless of editor theme. Keep the original alpha — the\n // author's `bleedOpacity` is a deliberate intensity choice we\n // don't want to override.\n if (isDark && isInkDark(bleedFillCss)) {\n bleedFillCss = '#ffffff';\n } else if (!isDark && isInkLight(bleedFillCss)) {\n bleedFillCss = '#000000';\n }\n\n // Same flip for boundary outline: a multi-piece spread (AFOOES and\n // similar) authors a white boundary so it pops against a dark editor\n // surface, but on a light surface white is invisible. Mirror the\n // bleed-fill behaviour so the stroke stays high-contrast across themes.\n if (isDark && isInkDark(boundaryStrokeCss)) {\n boundaryStrokeCss = '#ffffff';\n } else if (!isDark && isInkLight(boundaryStrokeCss)) {\n boundaryStrokeCss = '#000000';\n }\n\n // ADR-0054: for die-cut blur we need a SNAPSHOT of the canvas taken\n // *before* we draw any guide pixels. Without this, the blur pass\n // copies the canvas onto itself and re-includes the bleed-ring +\n // boundary we're about to draw, producing a \"smaller vignette\n // inside the die-cut\" ghost artifact. Only take the snapshot when\n // there's at least one die-cut zone to draw.\n const hasDieCut = pieces.some((p) =>\n (pieceGuides?.[p.id]?.zones ?? []).some((z) => z.kind === 'dieCut'),\n );\n const backdrop =\n hasDieCut && domTarget ? snapshotCanvas(domTarget) : null;\n\n ctx.save();\n // Enter artboard coordinates. artboardOrigin is where the active\n // artboard starts in world space (usually (0, 0) for single-artboard\n // setups, but non-zero for multi-artboard layouts).\n ctx.translate(artboardOrigin.x, artboardOrigin.y);\n\n for (const piece of pieces) {\n // When focused on a specific piece, skip non-focused pieces\n // entirely — no boundary stroke, no zones, no labels, no chrome at\n // all. The downstream element-clip is already narrowed to the\n // focused silhouette in `useCanvasRenderLoop`, so painting\n // neighbour chrome here would just add stray strokes the user\n // can't actually design against. Earlier we dimmed neighbours to\n // `FOCUSED_NEIGHBOUR_ALPHA` (≈0.3) for \"structural context\"; the\n // current UX preference is to fully hide them when focused, with\n // the focus picker as the affordance to switch back to spread.\n if (\n params.focusedPieceId != null &&\n piece.id !== params.focusedPieceId\n ) {\n continue;\n }\n\n const guide = pieceGuides?.[piece.id];\n const zones = guide?.zones ?? [];\n const labels = guide?.labels ?? [];\n\n ctx.save();\n ctx.translate(piece.x, piece.y);\n // `piece.rotation` is the visualLayout rotation that swaps the\n // piece's displayed dim relative to its natural (bbox) dim. We need\n // to draw boundary / safeArea / bleed-ring in the piece's NATURAL\n // coord system (where they were authored) and have the result land\n // axis-aligned within the piece's DISPLAYED bounding box. The old\n // \"translate to center, rotate, translate back\" pattern uses the\n // displayed center as the pivot — when natural and displayed dims\n // differ (as they do whenever rotation is 90/270), the rotated rect\n // ends up offset from the displayed bounds by (Nw-Nh)/2 in each\n // axis, which made the bleed shading misalign with the actual\n // piece edges. The explicit per-rotation transforms below map\n // natural (0,0)→displayed corner exactly:\n // 90° CW : translate(D_w, 0) rotate(π/2)\n // 180° : translate(D_w, D_h) rotate(π)\n // 270° CW: translate(0, D_h) rotate(3π/2)\n // After the transform, drawing at natural piece-local coords lands\n // axis-aligned within the displayed bounds (0,0)-(piece.width,\n // piece.height).\n // 45° (bandana folded view) and every other multiple of 45: the same centre-pivot map —\n // translate(D/2) · rotate(θ) · translate(−N/2) — which reproduces the three right-angle forms exactly.\n applyPieceRotation(ctx, piece);\n // Natural (unrotated) dim — what boundary / safeArea / outerShape were authored against (naturalDims: explicit\n // on the piece, or the displayed box un-swapped at right angles).\n const { width: naturalW, height: naturalH } = naturalDims(piece);\n\n // Resolve boundary / safe area in the piece's NATURAL coord system.\n // `guide.boundary` and `guide.safeArea` are authored against natural\n // dims; the fallbacks (full-piece rect, inset rect) must match.\n const boundary = resolveBoundary(guide, naturalW, naturalH);\n const safeArea = resolveSafeArea(guide, piece, naturalW, naturalH);\n\n // We only reach this point for focused / spread pieces. In spread\n // mode `focusedPieceId == null`, so this is the focused piece by\n // definition; the variable is preserved for any future neighbour-\n // specific rendering inside this block.\n const isFocusedPiece = true;\n\n // Piece outer shape — the *physical* outer edge of the product\n // (e.g. a phone case's rounded corners). If the author set\n // `outerRadius` on the guide, use a rounded rect; otherwise fall\n // back to the sharp piece rect. All per-piece rendering below\n // clips to this shape so nothing bleeds into the sharp corners\n // of the piece's bounding rect.\n const outerRadius = Math.max(0, guide?.outerRadius ?? 0);\n // A PATH-kind boundary IS the physical outer edge (a trapezoid slide\n // strap, a die-cut shape): the product ends there, so the bleed ring\n // ends there too — `boundary ∖ safeArea` is exactly the supplier's\n // bleed band. Without this the ring painted out to the piece rect's\n // corners and read as \"the whole rectangle outside the safe line is\n // bleed\" (the pre-0101 slides bug); the element layer was already\n // clipped to the silhouette by the render loop, so only the chrome\n // overshot. Rounded-rect products keep `outerRadius` (or a rounded\n // boundary) as before; plain rects stay sharp.\n const pathBoundary: SvgPath | null =\n guide?.boundary && guide.boundary.kind === 'path' ? guide.boundary : null;\n const outerShape: SvgPath =\n pathBoundary ??\n (outerRadius > 0\n ? {\n kind: 'roundedRect',\n x: 0,\n y: 0,\n width: naturalW,\n height: naturalH,\n rx: outerRadius,\n ry: outerRadius,\n }\n : {\n kind: 'rect',\n x: 0,\n y: 0,\n width: naturalW,\n height: naturalH,\n });\n // Guides are drawn inside the outer shape only (rounded rect or path\n // boundary). Kept as an inner save() so the clip is released at the\n // end of the piece.\n const clipToOuterShape = outerRadius > 0 || pathBoundary !== null;\n if (clipToOuterShape) {\n ctx.save();\n if (pathBoundary) {\n ctx.clip(new Path2D(pathBoundary.d), 'nonzero');\n } else {\n ctx.beginPath();\n addSvgPathSubpath(ctx, outerShape);\n ctx.clip();\n }\n }\n\n // 1. Bleed ring — outerShape ∖ (boundary | safeArea).\n //\n // `bleedInnerEdge: 'safeArea'` lets authors pull the fade inwards\n // so the \"safe zone\" reads as the clean area instead of the\n // design-canvas boundary. Falls back to boundary when no safe\n // area is authored or when it's a free-form path we can't yet\n // knock out via the blur trick.\n // `bleedOpacity: 0` is the explicit \"disable bleed wash\" signal —\n // some products (e.g. notebooks where the wrap geometry is\n // captured by the artboard margin, not by piece bleed) don't want\n // a visible bleed band at all. Honour that here so the data is\n // the source of truth.\n if (show.bleed && s.bleedOpacity > 0) {\n const wantInner: SvgPath | null =\n s.bleedInnerEdge === 'safeArea' && safeArea ? safeArea : boundary;\n // All SvgPath kinds are supported now — `addToPath2D` handles\n // rect / roundedRect / path uniformly via Path2D composition.\n // Earlier versions gated path-kind out because the legacy\n // `addSvgPathSubpath` was a no-op for path-kind in compound\n // operations; that limitation is gone.\n if (wantInner) {\n // Theme-aware blend: in light mode `multiply` darkens artwork\n // that crosses into the bleed area (and produces a dark wash\n // over empty canvas). In dark mode `screen` lightens — both\n // make the cropped artwork *visibly affected* instead of being\n // covered by a flat tint.\n const bleedBlend: GlobalCompositeOperation = isDark\n ? 'screen'\n : 'multiply';\n // Honour the author's `bleedOpacity` — multiply/screen blend\n // amplifies the contrast (compared to the old `source-over`\n // paradigm), so legacy values like `0.15` now produce a\n // visible-but-subtle wash; values up to ~0.3 read as the\n // \"strong dark band\" UX. `0` is honoured upstream as\n // \"disable wash entirely\" so we don't bother short-circuiting\n // here. Authors set the strength per layout — phone case ≈ 0.3\n // for a strong frame, notebook ≈ 0.1–0.15 for subtle structure.\n const effectiveBleedOpacity = s.bleedOpacity;\n // Bleed ring fills `outerShape ∖ wantInner` — i.e. from the\n // piece edge inward, stopping at the safe-area (or boundary,\n // depending on `bleedInnerEdge`). The wash hugs the actual\n // clip edge so the user reads \"everything outside the safe\n // area is bleed.\"\n if (s.bleedMode === 'solid') {\n drawBleedRingSolid(\n ctx,\n outerShape,\n wantInner,\n bleedFillCss,\n effectiveBleedOpacity,\n bleedBlend,\n );\n } else {\n drawBleedRing(\n ctx,\n naturalW,\n naturalH,\n outerShape,\n wantInner,\n bleedFillCss,\n effectiveBleedOpacity,\n s.bleedBlurScale,\n zoom,\n bleedBlend,\n );\n }\n }\n }\n\n // 2. Non-die-cut zones.\n if (show.zones) {\n for (const zone of zones) {\n if (zone.kind === 'dieCut') continue;\n ctx.save();\n // `s.zoneFill`/`s.zoneStroke` come from a `Partial` style override\n // map, so a per-kind entry can be missing — fall back to the\n // (complete) DEFAULTS map rather than painting with `undefined`.\n ctx.fillStyle = s.zoneFill[zone.kind] ?? DEFAULTS.zoneFill[zone.kind];\n fillSvgPath(ctx, zone.path);\n ctx.strokeStyle = s.zoneStroke[zone.kind] ?? DEFAULTS.zoneStroke[zone.kind];\n setZoomInvariantStroke(\n ctx,\n 1.5,\n zone.kind === 'wrap' ? [4, 3] : [],\n );\n strokeSvgPath(ctx, zone.path);\n ctx.setLineDash([]);\n ctx.restore();\n }\n }\n\n // 3. Die-cut zones.\n if (show.zones) {\n for (const zone of zones) {\n if (zone.kind !== 'dieCut') continue;\n if (s.dieCutMode === 'knockout') {\n // ADR-0060: for non-focused pieces, draw only the outline\n // stroke and skip the destination-out fill. Punching real\n // transparent holes through the canvas for neighbour\n // pieces would dominate the focused-piece view — the\n // structural context is communicated by the dimmed stroke\n // alone. The focused piece keeps the full knockout\n // (including the partial-knockout-while-interacting\n // behaviour from earlier).\n drawDieCutKnockout(\n ctx,\n zone,\n s.dieCutStroke,\n s.dieCutStrokeWidthPx,\n params.interacting === true,\n isFocusedPiece,\n backdrop,\n s.dieCutBlurPx,\n );\n } else {\n drawDieCut(\n ctx,\n zone,\n backdrop,\n dieCutTintCss,\n s.dieCutTintAlpha,\n s.dieCutBlurPx,\n s.dieCutStroke,\n s.dieCutStrokeWidthPx,\n );\n }\n }\n }\n\n // 4. Boundary outline (opt-in). Uses the theme-flipped colour so\n // the stroke stays visible on both light and dark editor surfaces.\n if (show.boundary && boundaryStrokeCss && s.boundaryStrokeWidthPx > 0) {\n ctx.save();\n ctx.strokeStyle = boundaryStrokeCss;\n setZoomInvariantStroke(ctx, s.boundaryStrokeWidthPx);\n strokeSvgPath(ctx, boundary);\n ctx.restore();\n }\n\n // 5. Legacy dashed safe-area outline (opt-in).\n if (show.safeArea && safeArea && s.safeAreaStroke) {\n ctx.save();\n ctx.strokeStyle = s.safeAreaStroke;\n setZoomInvariantStroke(ctx, s.safeAreaStrokeWidthPx, s.safeAreaDashPx);\n strokeSvgPath(ctx, safeArea);\n ctx.setLineDash([]);\n ctx.restore();\n }\n\n // 6. Labels.\n if (show.labels && labels.length > 0) {\n drawLabels(ctx, labels, s, zoom);\n }\n\n // Release the outer-shape clip. We intentionally do NOT run a\n // `destination-out` \"final crop\" pass to clear the piece rect's\n // corner wedges anymore.\n //\n // Why we used to: the bg fill / artwork was painted as a sharp\n // rect, so the corners had to be erased after the fact to get a\n // rounded outer shape.\n //\n // Why we no longer need it: the artboard bg fill in\n // `useCanvasRenderLoop.ts` already rounds itself to\n // `effectiveBorderRadius` (the piece's `outerRadius`), and the\n // element-layer clip in `CanvasRenderer.applyArtboardClipPath`\n // does the same — neither paints into the corner wedges to begin\n // with. The crop pass was only catching the overflow-dim 30%\n // halo that selected/hovered elements draw outside the artboard.\n // Erasing that halo while a user was resizing or repositioning\n // an image stripped exactly the manipulation feedback users rely\n // on, so during interaction the corners would suddenly read as\n // hard rounded clips instead of letting the user see how far the\n // element actually extended. Letting the halo through restores\n // that feedback at idle (no halo, corners transparent) and\n // during interaction (soft 30% preview into the corners).\n if (clipToOuterShape) {\n ctx.restore();\n }\n\n ctx.restore();\n }\n\n ctx.restore();\n}\n\n// ── Layer helpers ─────────────────────────────────────────────────────────────\n\n/**\n * Bleed ring = pieceRect ∖ boundary, rendered as a soft vignette that\n * fades gradually on *both* edges — the outer edge at the piece rect\n * (into the editor bg) and the inner edge at the boundary (into the\n * design canvas). Crucially the fade lives in the alpha channel only,\n * so the translucent fill doesn't let artwork colours \"glow\" through.\n *\n * Technique:\n * 1. Render to an offscreen canvas PADDED by the blur radius on each\n * side. Without padding, canvas 2D's blur filter can't spread\n * beyond the canvas dimensions — it clamps at the edge, which\n * leaves a hard rectangular outer edge where the vignette meets\n * the editor bg. Padding gives the blur room to fade outward.\n * 2. Fill the piece rect (offset into the padded offscreen) with the\n * bleed colour, using a CSS blur filter. The blur softens the\n * outer edge: alpha ≈ opacity inside the piece, tapers to 0 about\n * blurRadius outside the piece rect.\n * 3. Punch out the boundary using `destination-out` with the same\n * blur — this fades the fill to zero along the inner boundary\n * edge, creating the symmetric inner fade.\n * 4. `drawImage` the padded offscreen back onto the main ctx, shifted\n * by `-pad` so the padded fade region sits outside the piece rect\n * as intended.\n *\n * Result: soft bi-directional gradient in the bleed band, no hard line\n * at either edge, no colour bleeding from artwork.\n */\nfunction drawBleedRing(\n ctx: CanvasRenderingContext2D,\n /**\n * Natural (unrotated) piece dims. Drawing here happens in the piece's\n * natural coord system AFTER the per-rotation transform set up by the\n * caller, so the offscreen bitmap and blur radius must be sized\n * against natural dims, not the displayed (post-rotation) dims.\n */\n naturalW: number,\n naturalH: number,\n outerShape: SvgPath,\n boundary: SvgPath,\n fill: string,\n opacity: number,\n blurScale: number,\n zoom: number,\n /**\n * Blend mode used to composite the band onto the main canvas. `multiply`\n * (light mode) darkens artwork that crosses into the bleed area; `screen`\n * (dark mode) lightens it. Falls back to `source-over` when not provided\n * so the wash sits as a flat tint over empty canvas.\n */\n blendMode: GlobalCompositeOperation = 'source-over',\n): void {\n const blurWorldPx = Math.max(\n 6,\n Math.round(Math.min(naturalW, naturalH) * blurScale),\n );\n const blurCssPx = zoom > 0 ? blurWorldPx * zoom : blurWorldPx;\n\n const dpr =\n typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n\n // Pad the offscreen canvas by the blur radius on each side so the\n // Gaussian can fade outward past the piece edge. `pad` is in world\n // (piece-local) px.\n const pad = blurWorldPx;\n const paddedW = naturalW + 2 * pad;\n const paddedH = naturalH + 2 * pad;\n const physW = Math.max(1, Math.ceil(paddedW * zoom * dpr));\n const physH = Math.max(1, Math.ceil(paddedH * zoom * dpr));\n\n const off = acquireOffscreen(physW, physH);\n if (!off) return;\n const octx = off.getContext('2d');\n if (!octx) return;\n\n octx.setTransform(1, 0, 0, 1, 0, 0);\n octx.clearRect(0, 0, physW, physH);\n // Piece-local → physical pixel transform. After this, (0, 0) in the\n // offscreen is the padded-offscreen's top-left; we translate by\n // `pad` so piece (0, 0) lands at offscreen (pad, pad).\n octx.scale(zoom * dpr, zoom * dpr);\n octx.translate(pad, pad);\n\n // 1. Flood-fill the piece outer shape with the bleed colour, NO\n // blur. Previously this pass was also blurred so the outer edge\n // of the ring faded into the surrounding editor surface — but\n // that tapered alpha to ~0 at the piece corners, revealing\n // whatever's behind the canvas element (white editor bg =>\n // \"white corners\"). We now fill the outer shape cleanly at the\n // author's `bleedOpacity` and rely on step 2's blurred knockout\n // at the boundary for the inner soft fade. The outer edge sits\n // exactly at the piece's outer shape, no fade required.\n octx.fillStyle = fill;\n octx.globalAlpha = opacity;\n const outerPath = new Path2D();\n addToPath2D(outerPath, outerShape);\n octx.fill(outerPath);\n\n // 2. Punch out the boundary with a blurred alpha mask. Same blur\n // radius so the inner fade visually matches the outer fade.\n // Uses Path2D so path-kind boundaries (multi-piece spreads) work\n // the same as rect/roundedRect.\n octx.globalAlpha = 1;\n octx.filter = `blur(${blurCssPx * dpr}px)`;\n octx.globalCompositeOperation = 'destination-out';\n octx.fillStyle = '#000';\n const boundaryPath = new Path2D();\n addToPath2D(boundaryPath, boundary);\n octx.fill(boundaryPath);\n octx.filter = 'none';\n octx.globalCompositeOperation = 'source-over';\n\n // 3. Composite the padded offscreen onto the main ctx. The dest rect\n // starts at (-pad, -pad) in piece-local coords so the offscreen's\n // padded zone sits just outside the piece rect (where the outer\n // fade belongs). Apply the theme-aware blend so artwork that\n // crosses into the bleed band visibly darkens (light mode) or\n // lightens (dark mode), instead of the band sitting as a flat\n // tint that hides the underlying pixels.\n const prevOp = ctx.globalCompositeOperation;\n ctx.globalCompositeOperation = blendMode;\n ctx.drawImage(off, -pad, -pad, paddedW, paddedH);\n ctx.globalCompositeOperation = prevOp;\n}\n\n/**\n * Flat-fill variant of the bleed ring — `pieceRect ∖ boundary`, no blur.\n * Uses the even-odd fill rule with two sub-paths so the inside of the\n * boundary is cut out cleanly regardless of the boundary shape.\n * `opacity` is honoured verbatim here (unlike the gradient variant\n * which relies on `opacity === 1` to avoid a halo-then-jump artifact).\n */\nfunction drawBleedRingSolid(\n ctx: CanvasRenderingContext2D,\n outerShape: SvgPath,\n boundary: SvgPath,\n fill: string,\n opacity: number,\n /** See `drawBleedRing` — applied via `globalCompositeOperation`. */\n blendMode: GlobalCompositeOperation = 'source-over',\n): void {\n ctx.save();\n // Multiply against any existing globalAlpha (set by the per-piece\n // dim wrapper for non-focused pieces in ADR-0060) instead of\n // overwriting, so the bleed wash scales with the piece's dim level.\n ctx.globalAlpha = ctx.globalAlpha * opacity;\n ctx.globalCompositeOperation = blendMode;\n ctx.fillStyle = fill;\n // Build a compound Path2D so we can mix path-kind boundaries with\n // rect / roundedRect outers (or any other combination). Even-odd\n // fill rule turns \"outer + inner\" into \"outer minus inner\".\n const compound = new Path2D();\n addToPath2D(compound, outerShape);\n addToPath2D(compound, boundary);\n ctx.fill(compound, 'evenodd');\n ctx.restore();\n}\n\n/**\n * A single reusable offscreen canvas for the bleed-ring compositing\n * pass, resized lazily. Avoids allocating a new canvas per frame.\n */\nlet cachedBleedOffscreen: HTMLCanvasElement | null = null;\nfunction acquireOffscreen(\n physW: number,\n physH: number,\n): HTMLCanvasElement | null {\n if (typeof document === 'undefined') return null;\n if (!cachedBleedOffscreen) {\n cachedBleedOffscreen = document.createElement('canvas');\n }\n if (cachedBleedOffscreen.width !== physW)\n cachedBleedOffscreen.width = physW;\n if (cachedBleedOffscreen.height !== physH)\n cachedBleedOffscreen.height = physH;\n return cachedBleedOffscreen;\n}\n\n/**\n * Die-cut zones look like a \"punched hole\" in the design canvas. The\n * old overlay did this via `backdrop-filter: blur(4px)` + semi-transparent\n * dark fill in a <foreignObject>. We reproduce it by:\n *\n * 1. Clipping to the zone's shape.\n * 2. Drawing a pre-guide snapshot of the canvas (taken before any\n * piece-guide pixels landed) with a CSS blur filter applied, in\n * identity transform space so the snapshot's physical pixels line\n * up 1:1 with the live canvas. The snapshot predates the bleed\n * ring, so blurring it doesn't re-include the ring and produce a\n * ghosted \"smaller vignette\" inside the cutout.\n * 3. Overlaying the dark tint to read as a punched hole.\n *\n * No stroke — the dark tint on its own reads as a hole; the 1px white\n * outline we used to draw just added chrome that fought with the\n * artwork. Bring it back via `style.zoneStroke.dieCut` if needed.\n */\nfunction drawDieCut(\n ctx: CanvasRenderingContext2D,\n zone: ZoneShape,\n backdrop: HTMLCanvasElement | null,\n tint: string,\n tintAlpha: number,\n blurCssPx: number,\n stroke: string,\n strokeWidthPx: number,\n): void {\n const p = zone.path;\n\n ctx.save();\n // Clip to the zone in the CURRENT (world) transform space. The clip\n // path is rasterised to physical pixels at clip() time, so it\n // survives any transform changes we make below.\n ctx.beginPath();\n pathToCanvas(ctx, p);\n ctx.clip();\n\n if (backdrop) {\n // Match the overflow-dim treatment exactly (CanvasRenderer's\n // `renderElementWithOverflowDimming`, Pass 1): erase the canvas\n // pixels in the zone, then redraw the blurred backdrop at\n // `INTERACTION_PARTIAL_KNOCKOUT_RETAIN` (0.3) alpha. The result\n // is artwork at 30% over whatever the editor surface shows\n // through — same vocabulary as the overflow halo, so the\n // die-cut and overflow regions read as the same \"dimmed\n // preview\" treatment instead of the old constant-tint look.\n //\n // 1. Knock out the canvas pixels in the zone so the redraw\n // composites against transparency (and thus the editor\n // surface), not on top of the original opaque artwork.\n ctx.save();\n ctx.globalCompositeOperation = 'destination-out';\n ctx.fillStyle = '#000';\n if (p.kind === 'path') {\n fillSvgPath(ctx, p);\n } else {\n ctx.fillRect(p.x, p.y, p.width, p.height);\n }\n ctx.restore();\n\n // 2. Redraw the blurred snapshot at the dim alpha. We reset to\n // identity transform so the snapshot's physical pixels map\n // 1:1 to the canvas's pixel grid. Blur radius is unscaled by\n // DPR to match CanvasRenderer's overflow-dim pass\n // (`blur(4px)` at CanvasRenderer.ts line ~269) — both passes\n // should read as the same blur strength on the same display.\n if (blurCssPx > 0) {\n const baseAlpha = ctx.globalAlpha;\n const t = ctx.getTransform();\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.filter = `blur(${blurCssPx}px)`;\n // Multiply against any per-piece dim already applied via\n // `ctx.globalAlpha` (ADR-0060) so non-focused pieces' die-cut\n // areas dim with the rest of the piece chrome.\n // 0.4 instead of `INTERACTION_PARTIAL_KNOCKOUT_RETAIN` (0.3): the\n // overflow halo composites artwork at 0.3 over a partially\n // populated canvas (other elements bleed through), so the eye\n // reads the *visible* alpha as a bit higher than the literal\n // 0.3. The die-cut snapshot here erases first and redraws onto\n // a clean transparent slate, so 0.3 looks subjectively darker.\n // 0.4 brings the perceived dim into alignment with the overflow\n // region.\n ctx.globalAlpha = baseAlpha * 0.4;\n ctx.drawImage(backdrop, 0, 0);\n ctx.filter = 'none';\n ctx.globalAlpha = baseAlpha;\n ctx.setTransform(t);\n ctx.restore();\n }\n }\n\n // Optional flat tint on top of the dimmed-blurred backdrop. Authors\n // can set `dieCutTintAlpha: 0` to disable entirely (matches the\n // overflow treatment). Default behaviour keeps a subtle wash for\n // legacy layouts that still expect it.\n if (tintAlpha > 0) {\n const baseAlpha = ctx.globalAlpha;\n ctx.fillStyle = tint;\n ctx.globalAlpha = baseAlpha * tintAlpha;\n if (p.kind === 'path') {\n fillSvgPath(ctx, p);\n } else {\n ctx.fillRect(p.x, p.y, p.width, p.height);\n }\n ctx.globalAlpha = baseAlpha;\n }\n\n ctx.restore();\n\n // Outline stroke — drawn OUTSIDE the clip so the stroke sits on top\n // of everything (blurred backdrop + tint + artwork outside). Only if\n // the author opted in via a non-empty colour. Because the stroke is\n // centred on the path, clipping it to the zone would hide half of it.\n if (stroke) {\n ctx.save();\n ctx.strokeStyle = stroke;\n setZoomInvariantStroke(ctx, strokeWidthPx);\n strokeSvgPath(ctx, p);\n ctx.restore();\n }\n}\n\n/**\n * Knockout variant — clears the zone's pixels from the canvas using\n * `destination-out` compositing. Whatever is beneath the canvas\n * element (page background, an underlying mockup, etc.) shows\n * through. The outline is still drawn on top.\n *\n * When `interacting` is true (the editor is mid-interaction — an\n * element is selected, hovered, or being dragged) we do a *partial*\n * knockout instead of a full one: `destination-out` with\n * `globalAlpha = 1 - INTERACTION_PARTIAL_KNOCKOUT_RETAIN`, which\n * leaves 30% of the underlying alpha intact. That matches the\n * overflow-dim halo's 30% multiplier on the outside corner wedges\n * (see `renderElementWithOverflowDimming` Pass 1) — visually, the\n * cutout reads as a window onto the artwork at the same opacity as\n * the overflow halo, so resize/drag feedback is consistent on both\n * sides of the artboard's outer shape. At idle (no interaction)\n * we fall through to the original full knockout for a clean punched\n * hole through to the page chrome below.\n */\nconst INTERACTION_PARTIAL_KNOCKOUT_RETAIN = 0.3;\n\nfunction drawDieCutKnockout(\n ctx: CanvasRenderingContext2D,\n zone: ZoneShape,\n stroke: string,\n strokeWidthPx: number,\n interacting: boolean,\n /**\n * ADR-0060: when false (this is a non-focused piece's die-cut in a\n * multi-piece spread), skip the destination-out fill entirely —\n * only the outline stroke draws. Real transparent holes in\n * neighbour pieces would dominate the focused-piece view; the\n * dimmed stroke is enough to communicate where the cutout sits.\n */\n isFocusedPiece: boolean = true,\n /**\n * Mid-interaction backdrop snapshot. When set AND `interacting` is\n * true, the cutout shows a blurred + dimmed preview of the artwork\n * underneath (matching the overflow halo treatment) so the user\n * can see what's getting clipped while they drag/resize. At idle,\n * this param is unused — full knockout fires regardless.\n */\n backdrop?: HTMLCanvasElement | null,\n blurCssPx: number = 0,\n): void {\n const p = zone.path;\n if (isFocusedPiece) {\n if (interacting && backdrop && blurCssPx > 0) {\n // Interacting + backdrop available: do the blur+dim preview\n // instead of a partial knockout. Step 1 erases the canvas\n // pixels in the zone (full destination-out), step 2 redraws\n // the blurred snapshot at the overflow-halo dim alpha so the\n // artwork crossing the cutout reads as \"this is what gets\n // clipped.\" Mirrors `drawDieCut`'s blur-tint pattern but\n // gated on interaction so idle stays a clean punched hole.\n ctx.save();\n ctx.beginPath();\n pathToCanvas(ctx, p);\n ctx.clip();\n\n // 1. Erase canvas pixels in the zone.\n ctx.save();\n ctx.globalCompositeOperation = 'destination-out';\n ctx.fillStyle = '#000';\n if (p.kind === 'path' || p.kind === 'roundedRect') {\n fillSvgPath(ctx, p);\n } else {\n ctx.fillRect(p.x, p.y, p.width, p.height);\n }\n ctx.restore();\n\n // 2. Redraw blurred snapshot at the overflow-halo alpha.\n const baseAlpha = ctx.globalAlpha;\n const t = ctx.getTransform();\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.filter = `blur(${blurCssPx}px)`;\n ctx.globalAlpha = baseAlpha * 0.4;\n ctx.drawImage(backdrop, 0, 0);\n ctx.filter = 'none';\n ctx.globalAlpha = baseAlpha;\n ctx.setTransform(t);\n ctx.restore();\n\n ctx.restore();\n } else {\n // Idle (or no backdrop available): full knockout — punched\n // hole through to the page chrome below.\n ctx.save();\n ctx.globalCompositeOperation = 'destination-out';\n ctx.globalAlpha = interacting\n ? 1 - INTERACTION_PARTIAL_KNOCKOUT_RETAIN\n : 1;\n ctx.fillStyle = '#000';\n if (p.kind === 'path' || p.kind === 'roundedRect') {\n fillSvgPath(ctx, p);\n } else {\n ctx.fillRect(p.x, p.y, p.width, p.height);\n }\n ctx.restore();\n }\n }\n if (stroke) {\n ctx.save();\n ctx.strokeStyle = stroke;\n setZoomInvariantStroke(ctx, strokeWidthPx);\n strokeSvgPath(ctx, p);\n ctx.restore();\n }\n}\n\nfunction drawLabels(\n ctx: CanvasRenderingContext2D,\n labels: GuideLabel[],\n s: Required<Omit<PieceGuideRendererStyle, 'zoneFill' | 'zoneStroke'>>,\n zoom: number,\n): void {\n // Fonts and strokes naturally scale with ctx.scale(zoom). We want a\n // fixed CSS-pixel appearance, so divide by zoom to counteract.\n const fontWorldPx = zoom > 0 ? s.labelFontSizePx / zoom : s.labelFontSizePx;\n const haloWidth = Math.max(2, s.labelFontSizePx * 0.25);\n const haloWorldPx = zoom > 0 ? haloWidth / zoom : haloWidth;\n\n ctx.save();\n ctx.font = `${fontWorldPx}px ${s.labelFontFamily}`;\n\n for (const label of labels) {\n const anchor = label.anchor ?? 'c';\n ctx.textAlign = anchorToAlign(anchor);\n ctx.textBaseline = anchorToBaseline(anchor);\n\n // Halo: stroke first (white, thick) then fill on top for readable\n // labels over any background. Matches the old overlay's\n // paintOrder: 'stroke' behaviour.\n ctx.lineJoin = 'round';\n ctx.strokeStyle = s.labelHaloColor;\n ctx.lineWidth = haloWorldPx;\n ctx.strokeText(label.text, label.x, label.y);\n ctx.fillStyle = s.labelColor;\n ctx.fillText(label.text, label.x, label.y);\n }\n ctx.restore();\n}\n\nfunction anchorToAlign(a: NonNullable<GuideLabel['anchor']>): CanvasTextAlign {\n if (a.endsWith('l')) return 'start';\n if (a.endsWith('r')) return 'end';\n return 'center';\n}\n\nfunction anchorToBaseline(\n a: NonNullable<GuideLabel['anchor']>,\n): CanvasTextBaseline {\n if (a.startsWith('t')) return 'hanging';\n if (a.startsWith('b')) return 'alphabetic';\n return 'middle';\n}\n","/**\n * Multi-Selection Renderer\n * Handles rendering of multi-selection indicators, group bounds, handles, and dimension labels\n */\n\nimport { TextElement } from '../../../core/TextElement.js';\nimport { ImageElement } from '../../../core/ImageElement.js';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { PathElement } from '../../../core/PathElement.js';\nimport type { InteractionStateMachine } from '../../../core/InteractionStateMachine.js';\n\n/** Elements that can be on the canvas */\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\n/** Multi-selection drag context stored on the state machine */\ninterface MultiSelectionDragContext {\n startX: number;\n startY: number;\n elementsStartData: Array<{ element: CanvasElement; startData: Record<string, unknown> }>;\n currentElements?: Map<string, CanvasElement>;\n currentDelta?: { dx: number; dy: number };\n startOBB?: MultiSelectionOBB;\n}\n\n/** Multi-selection resize context stored on the state machine */\ninterface MultiSelectionResizeContext {\n anchor: string;\n startBounds: { minX: number; maxX: number; minY: number; maxY: number };\n elementsStartData: Array<{ element: CanvasElement; startData: Record<string, unknown> }>;\n}\n\n/** Augmented state machine with multi-selection ad-hoc properties */\ntype AugmentedStateMachine = InteractionStateMachine & {\n _multiSelectionDragContext?: MultiSelectionDragContext;\n _multiSelectionResizeContext?: MultiSelectionResizeContext;\n};\n\nimport { getThemeAccentColor, ROTATION_HANDLE_DISTANCE } from '../../../constants.js';\nimport {\n getLineWidth,\n getHandleRadius,\n getDashPattern,\n renderRotationHandle,\n} from './renderingConstants.js';\n\nexport interface MultiSelectionOBB {\n centerX: number;\n centerY: number;\n width: number;\n height: number;\n rotation: number;\n}\n\nexport interface MultiSelectionGroupBounds {\n minX: number;\n minY: number;\n maxX: number;\n maxY: number;\n centerX: number;\n centerY: number;\n rotationHandleX: number;\n rotationHandleY: number;\n rotation: number;\n}\n\nexport interface RenderMultiSelectionParams {\n ctx: CanvasRenderingContext2D;\n displaySelection: string[];\n elements: CanvasElement[];\n activeArtboardElementIds: Set<string>;\n stateMachine: InteractionStateMachine;\n multiSelectionOBB: MultiSelectionOBB | null;\n isRotating: boolean;\n currentRotation: number;\n isMarqueeSelecting: boolean;\n zoom?: number;\n}\n\nexport interface RenderMultiSelectionResult {\n updatedOBB: MultiSelectionOBB | null;\n groupBounds: MultiSelectionGroupBounds | null;\n}\n\n/**\n * Renders multi-selection indicators including:\n * - Individual element borders\n * - Group bounding box with dotted border\n * - Corner resize handles\n * - Rotation handle\n * - Dimension label during resize\n */\nexport function renderMultiSelectionIndicators(params: RenderMultiSelectionParams): RenderMultiSelectionResult {\n const {\n ctx,\n displaySelection,\n elements,\n activeArtboardElementIds,\n stateMachine,\n multiSelectionOBB,\n isRotating,\n currentRotation,\n isMarqueeSelecting,\n zoom = 1.0,\n } = params;\n\n // Filter to only show elements in the active artboard\n const filteredDisplaySelection = displaySelection.filter((id) => activeArtboardElementIds.has(id));\n\n if (filteredDisplaySelection.length === 0) {\n return { updatedOBB: null, groupBounds: null };\n }\n\n // Check if we're currently dragging multi-selection to use in-progress positions\n const multiDragContext = (stateMachine as AugmentedStateMachine)._multiSelectionDragContext;\n const isCurrentlyDraggingMulti = stateMachine.getMode() === 'drag' && multiDragContext;\n\n // Render individual element borders\n filteredDisplaySelection.forEach((id) => {\n let element = elements.find((e) => e.id === id);\n\n // If actively dragging multi-selection, use the cached updated element from drag context\n if (isCurrentlyDraggingMulti && multiDragContext.currentElements) {\n const cachedElement = multiDragContext.currentElements.get(id);\n if (cachedElement) {\n element = cachedElement;\n }\n }\n\n if (element) {\n const bbox = element.getVisualBoundingBox();\n const rotationAnchor = element.getRotationAnchor();\n\n ctx.save();\n\n // Apply rotation around the element's rotation anchor\n if (element.rotation !== 0) {\n ctx.translate(rotationAnchor.x, rotationAnchor.y);\n ctx.rotate((-element.rotation * Math.PI) / 180);\n ctx.translate(-rotationAnchor.x, -rotationAnchor.y);\n }\n\n ctx.strokeStyle = getThemeAccentColor();\n ctx.lineWidth = getLineWidth(zoom);\n ctx.strokeRect(bbox.x, bbox.y, bbox.width, bbox.height);\n ctx.restore();\n }\n });\n\n // Calculate and render group bounds (combined bounding box)\n let selectedElements = filteredDisplaySelection\n .map((id) => elements.find((e) => e.id === id))\n .filter((e) => e !== undefined);\n\n // If actively dragging, use cached current elements for group bounds calculation\n if (isCurrentlyDraggingMulti && multiDragContext.currentElements) {\n selectedElements = selectedElements.map((element) => {\n const cachedElement = multiDragContext.currentElements!.get(element.id);\n return cachedElement || element;\n });\n }\n\n if (selectedElements.length === 0) {\n return { updatedOBB: null, groupBounds: null };\n }\n\n // Calculate axis-aligned bounding box containing all selected elements\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n selectedElements.forEach((element) => {\n const bbox = element.getVisualBoundingBox();\n // Get corners of potentially rotated element\n const corners = [\n { x: bbox.x, y: bbox.y },\n { x: bbox.x + bbox.width, y: bbox.y },\n { x: bbox.x, y: bbox.y + bbox.height },\n { x: bbox.x + bbox.width, y: bbox.y + bbox.height },\n ];\n\n // If element is rotated, we need to rotate corners\n if (element.rotation !== 0) {\n const rotationAnchor = element.getRotationAnchor();\n const rotationRad = (-element.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n corners.forEach((corner) => {\n const dx = corner.x - rotationAnchor.x;\n const dy = corner.y - rotationAnchor.y;\n corner.x = rotationAnchor.x + (dx * cos - dy * sin);\n corner.y = rotationAnchor.y + (dx * sin + dy * cos);\n });\n }\n\n // Update bounds\n corners.forEach((corner) => {\n minX = Math.min(minX, corner.x);\n minY = Math.min(minY, corner.y);\n maxX = Math.max(maxX, corner.x);\n maxY = Math.max(maxY, corner.y);\n });\n });\n\n const groupWidth = maxX - minX;\n const groupHeight = maxY - minY;\n const centerX = (minX + maxX) / 2;\n const centerY = (minY + maxY) / 2;\n\n // Initialize or update OBB for multi-selection\n let obb = multiSelectionOBB;\n\n // If OBB doesn't exist or selection just changed, initialize it\n if (!obb || obb.width === 0 || obb.height === 0) {\n obb = {\n centerX,\n centerY,\n width: groupWidth,\n height: groupHeight,\n rotation: 0,\n };\n }\n\n // During drag, update the OBB center to follow the elements\n const isCurrentlyDragging = stateMachine.getMode() === 'drag' && (stateMachine as AugmentedStateMachine)._multiSelectionDragContext;\n if (isCurrentlyDragging) {\n const dragContext = (stateMachine as AugmentedStateMachine)._multiSelectionDragContext;\n // Use the stored start OBB and apply current delta for smooth rendering\n if (dragContext?.startOBB && dragContext?.currentDelta) {\n const { dx, dy } = dragContext.currentDelta;\n obb = {\n ...obb,\n centerX: dragContext.startOBB.centerX + dx,\n centerY: dragContext.startOBB.centerY + dy,\n };\n } else {\n // Fallback: use recalculated center from actual element positions\n obb = { ...obb, centerX, centerY };\n }\n }\n\n // During rotation, update the OBB rotation\n const isCurrentlyRotating = isRotating && stateMachine.getMode() === 'rotate';\n const groupRotation = isCurrentlyRotating ? obb.rotation + currentRotation : obb.rotation;\n\n // Use OBB dimensions for rendering (not recalculated axis-aligned bounds)\n const renderWidth = obb.width;\n const renderHeight = obb.height;\n const renderCenterX = obb.centerX;\n const renderCenterY = obb.centerY;\n const renderMinX = renderCenterX - renderWidth / 2;\n const renderMinY = renderCenterY - renderHeight / 2;\n const renderMaxX = renderCenterX + renderWidth / 2;\n const renderMaxY = renderCenterY + renderHeight / 2;\n\n // Calculate rotation handle position (below bottom center, rotated with the group)\n let rotationHandleX = renderCenterX;\n let rotationHandleY = renderMaxY + ROTATION_HANDLE_DISTANCE;\n\n if (groupRotation !== 0) {\n const rotationRad = (-groupRotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const dx = rotationHandleX - renderCenterX;\n const dy = rotationHandleY - renderCenterY;\n rotationHandleX = renderCenterX + (dx * cos - dy * sin);\n rotationHandleY = renderCenterY + (dx * sin + dy * cos);\n }\n\n // Prepare group bounds for hit testing\n const groupBounds: MultiSelectionGroupBounds = {\n minX: renderMinX,\n minY: renderMinY,\n maxX: renderMaxX,\n maxY: renderMaxY,\n centerX: renderCenterX,\n centerY: renderCenterY,\n rotationHandleX,\n rotationHandleY,\n rotation: groupRotation,\n };\n\n // Render dotted border around group bounds (with rotation)\n ctx.save();\n ctx.translate(renderCenterX, renderCenterY);\n ctx.rotate((-groupRotation * Math.PI) / 180);\n ctx.translate(-renderCenterX, -renderCenterY);\n\n ctx.strokeStyle = getThemeAccentColor();\n ctx.lineWidth = getLineWidth(zoom);\n ctx.setLineDash(getDashPattern(zoom));\n ctx.strokeRect(renderMinX, renderMinY, renderWidth, renderHeight);\n ctx.restore();\n\n // Don't render handles during marquee selection (only show after mouse release)\n if (!isMarqueeSelecting) {\n renderMultiSelectionHandles(ctx, {\n renderMinX,\n renderMinY,\n renderMaxX,\n renderMaxY,\n renderCenterX,\n renderCenterY,\n renderWidth,\n renderHeight,\n groupRotation,\n rotationHandleX,\n rotationHandleY,\n stateMachine,\n zoom,\n });\n }\n\n return { updatedOBB: obb, groupBounds };\n}\n\ninterface RenderHandlesParams {\n renderMinX: number;\n renderMinY: number;\n renderMaxX: number;\n renderMaxY: number;\n renderCenterX: number;\n renderCenterY: number;\n renderWidth: number;\n renderHeight: number;\n groupRotation: number;\n rotationHandleX: number;\n rotationHandleY: number;\n stateMachine: InteractionStateMachine;\n zoom: number;\n}\n\n/**\n * Renders corner resize handles and rotation handle for multi-selection\n */\nfunction renderMultiSelectionHandles(ctx: CanvasRenderingContext2D, params: RenderHandlesParams): void {\n const {\n renderMinX,\n renderMinY,\n renderMaxX,\n renderMaxY,\n renderCenterX,\n renderCenterY,\n renderWidth,\n renderHeight,\n groupRotation,\n rotationHandleX,\n rotationHandleY,\n stateMachine,\n zoom,\n } = params;\n\n // Render 4 white circle corner handles (with rotation)\n const handleRadius = getHandleRadius(zoom);\n const corners = [\n { x: renderMinX, y: renderMinY, anchor: 'top-left' },\n { x: renderMaxX, y: renderMinY, anchor: 'top-right' },\n { x: renderMinX, y: renderMaxY, anchor: 'bottom-left' },\n { x: renderMaxX, y: renderMaxY, anchor: 'bottom-right' },\n ];\n\n const isResizingMultiSelection = stateMachine.getMode() === 'resize';\n const activeHandle = isResizingMultiSelection ? (stateMachine as AugmentedStateMachine)._multiSelectionResizeContext?.anchor : null;\n\n corners.forEach((corner) => {\n // During resize, only show the active handle\n if (isResizingMultiSelection && activeHandle && corner.anchor !== activeHandle) {\n return;\n }\n\n // Rotate corner position around center\n let cornerX = corner.x;\n let cornerY = corner.y;\n\n if (groupRotation !== 0) {\n const rotationRad = (-groupRotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const dx = corner.x - renderCenterX;\n const dy = corner.y - renderCenterY;\n cornerX = renderCenterX + (dx * cos - dy * sin);\n cornerY = renderCenterY + (dx * sin + dy * cos);\n }\n\n ctx.save();\n\n ctx.beginPath();\n ctx.arc(cornerX, cornerY, handleRadius, 0, Math.PI * 2);\n ctx.fillStyle = '#fff';\n ctx.fill();\n ctx.strokeStyle = getThemeAccentColor();\n ctx.lineWidth = isResizingMultiSelection && activeHandle && corner.anchor === activeHandle\n ? getLineWidth(zoom) * 1.25\n : getLineWidth(zoom);\n ctx.stroke();\n ctx.restore();\n });\n\n // Render rotation handle (same style as regular elements)\n // Hide during resize\n if (!isResizingMultiSelection) {\n renderRotationHandle(ctx, rotationHandleX, rotationHandleY, zoom, getThemeAccentColor());\n }\n\n // Render dimension label during resize\n if (stateMachine.getMode() === 'resize') {\n renderDimensionLabel(ctx, renderCenterX, renderMaxY, renderWidth, renderHeight);\n }\n}\n\n/**\n * Renders dimension label showing width × height\n */\nfunction renderDimensionLabel(\n ctx: CanvasRenderingContext2D,\n centerX: number,\n maxY: number,\n width: number,\n height: number\n): void {\n const infoText = `${Math.round(width)} × ${Math.round(height)}`;\n\n // Position below the bottom edge\n const tooltipX = centerX;\n const tooltipY = maxY + 30;\n\n ctx.save();\n\n // Set smaller font\n ctx.font = '10px system-ui, -apple-system, sans-serif';\n const textMetrics = ctx.measureText(infoText);\n const tooltipWidth = textMetrics.width + 12;\n const tooltipHeight = 18;\n\n // Shadow for depth\n ctx.shadowColor = 'rgba(0,0,0,0.2)';\n ctx.shadowBlur = 4;\n ctx.shadowOffsetY = 1;\n\n // Background\n ctx.fillStyle = '#333';\n ctx.beginPath();\n ctx.roundRect(tooltipX - tooltipWidth / 2, tooltipY - tooltipHeight / 2, tooltipWidth, tooltipHeight, 3);\n ctx.fill();\n\n // Reset shadow\n ctx.shadowColor = 'transparent';\n ctx.shadowBlur = 0;\n ctx.shadowOffsetY = 0;\n\n // Text\n ctx.fillStyle = '#fff';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(infoText, tooltipX, tooltipY + 0.5);\n\n ctx.restore();\n}\n","/**\n * Marquee Selection Renderer\n * Handles rendering of the marquee selection box during drag\n */\n\nimport { getThemeAccentColor, getThemeAccentColorWithAlpha } from '../../../constants.js';\nimport { getLineWidth } from './renderingConstants.js';\n\nexport interface RenderMarqueeParams {\n ctx: CanvasRenderingContext2D;\n marqueeStart: { x: number; y: number };\n marqueeEnd: { x: number; y: number };\n zoom?: number;\n}\n\n/**\n * Renders a marquee selection box with stroke and semi-transparent fill\n * @param params.zoom - Zoom factor (default 1.0) - line width is adjusted to maintain visual consistency\n */\nexport function renderMarqueeSelection(params: RenderMarqueeParams): void {\n const { ctx, marqueeStart, marqueeEnd, zoom = 1.0 } = params;\n\n const minX = Math.min(marqueeStart.x, marqueeEnd.x);\n const maxX = Math.max(marqueeStart.x, marqueeEnd.x);\n const minY = Math.min(marqueeStart.y, marqueeEnd.y);\n const maxY = Math.max(marqueeStart.y, marqueeEnd.y);\n\n ctx.save();\n ctx.strokeStyle = getThemeAccentColor();\n ctx.lineWidth = getLineWidth(zoom);\n ctx.strokeRect(minX, minY, maxX - minX, maxY - minY);\n\n // Semi-transparent fill\n ctx.fillStyle = getThemeAccentColorWithAlpha(0.1);\n ctx.fillRect(minX, minY, maxX - minX, maxY - minY);\n ctx.restore();\n}\n","/**\n * Hover Effect Renderer\n * Handles rendering of hover effects for group siblings\n */\n\nimport { TextElement } from '../../../core/TextElement.js';\nimport { ImageElement } from '../../../core/ImageElement.js';\nimport { getThemeHoverBorderColor } from '../../../constants.js';\nimport { getLineWidth } from './renderingConstants.js';\n\nexport interface RenderGroupSiblingHoverParams {\n ctx: CanvasRenderingContext2D;\n hoveredChild: TextElement | ImageElement;\n zoom?: number;\n}\n\n/**\n * Renders hover effect outline for a group sibling element\n * @param params.zoom - Zoom factor (default 1.0) - line width is adjusted to maintain visual consistency\n */\nexport function renderGroupSiblingHover(params: RenderGroupSiblingHoverParams): void {\n const { ctx, hoveredChild, zoom = 1.0 } = params;\n const bbox = hoveredChild.getVisualBoundingBox();\n\n ctx.save();\n ctx.translate(bbox.x + bbox.width / 2, bbox.y + bbox.height / 2);\n ctx.rotate((-hoveredChild.rotation * Math.PI) / 180);\n ctx.translate(-(bbox.x + bbox.width / 2), -(bbox.y + bbox.height / 2));\n\n // Draw solid outline with hover border color\n ctx.strokeStyle = getThemeHoverBorderColor();\n ctx.lineWidth = getLineWidth(zoom);\n ctx.setLineDash([]); // Solid line\n ctx.strokeRect(bbox.x, bbox.y, bbox.width, bbox.height);\n\n ctx.restore();\n}\n","/**\n * PerformanceMonitor - Dev-mode performance metrics for the canvas render loop.\n *\n * Singleton class that tracks frame times, element counts, cache hit rates,\n * and export durations. Exposed on `window.__CANVAS_PERF__` in dev mode\n * so developers can inspect real-time metrics from the browser console.\n *\n * @example\n * ```ts\n * // Record a frame render time\n * const start = performance.now();\n * renderFrame();\n * PerformanceMonitor.getInstance().recordFrameTime(performance.now() - start);\n *\n * // Read metrics from console in dev mode\n * window.__CANVAS_PERF__.getMetrics();\n * ```\n */\n\nexport interface CanvasPerformanceMetrics {\n /** Average frame render time in ms (rolling window of last 60 frames) */\n frameTime: number;\n /** Total frames rendered since last reset */\n frameCount: number;\n /** Current element count on the active artboard */\n elementCount: number;\n /** Bitmap cache hit rate as a ratio 0-1 */\n cacheHitRate: number;\n /** Duration of the last export operation in ms */\n lastExportDuration: number;\n}\n\n/** Size of the rolling window for frame time averaging */\nconst ROLLING_WINDOW_SIZE = 60;\n\nexport class PerformanceMonitor {\n private static instance: PerformanceMonitor;\n\n private metrics: CanvasPerformanceMetrics;\n /** Rolling window of the last N frame times (ms) */\n private frameTimes: number[];\n\n private constructor() {\n this.frameTimes = [];\n this.metrics = {\n frameTime: 0,\n frameCount: 0,\n elementCount: 0,\n cacheHitRate: 0,\n lastExportDuration: 0,\n };\n }\n\n static getInstance(): PerformanceMonitor {\n if (!PerformanceMonitor.instance) {\n PerformanceMonitor.instance = new PerformanceMonitor();\n }\n return PerformanceMonitor.instance;\n }\n\n /**\n * Record a single frame's render duration. Maintains a rolling window\n * of the last 60 values and recomputes the average.\n */\n recordFrameTime(ms: number): void {\n this.frameTimes.push(ms);\n if (this.frameTimes.length > ROLLING_WINDOW_SIZE) {\n this.frameTimes.shift();\n }\n this.metrics.frameCount++;\n this.metrics.frameTime = this._average(this.frameTimes);\n }\n\n /** Record the duration of an export operation. */\n recordExportDuration(ms: number): void {\n this.metrics.lastExportDuration = ms;\n }\n\n /** Update the current element count (call once per frame or on change). */\n setElementCount(count: number): void {\n this.metrics.elementCount = count;\n }\n\n /** Update the bitmap cache hit rate (0-1). */\n setCacheHitRate(rate: number): void {\n this.metrics.cacheHitRate = rate;\n }\n\n /** Return a readonly snapshot of the current metrics. */\n getMetrics(): Readonly<CanvasPerformanceMetrics> {\n return { ...this.metrics };\n }\n\n /** Reset all metrics and the rolling window. */\n reset(): void {\n this.frameTimes = [];\n this.metrics = {\n frameTime: 0,\n frameCount: 0,\n elementCount: 0,\n cacheHitRate: 0,\n lastExportDuration: 0,\n };\n }\n\n // ── internal helpers ──────────────────────────────────────────────────\n\n private _average(values: number[]): number {\n if (values.length === 0) return 0;\n let sum = 0;\n for (let i = 0; i < values.length; i++) {\n sum += values[i];\n }\n return sum / values.length;\n }\n}\n\n// Expose globally in dev mode so developers can inspect from the console:\n// window.__CANVAS_PERF__.getMetrics()\nif (typeof window !== 'undefined' && import.meta.env?.DEV) {\n (window as any).__CANVAS_PERF__ = PerformanceMonitor.getInstance();\n}\n","import type { ClipShape } from '../../../types/index.js';\nimport { naturalDims } from '../../../utils/pieceRotation.js';\nimport type {\n OverlayPiece,\n PieceGuide,\n} from '../../VisualGuideOverlay.js';\n\n/**\n * Build the artboard's `composite-path` clip shape from a piece-guide\n * bundle. Returns `undefined` when the bundle isn't fully path-kind —\n * the renderer falls back to its rectangular / rounded-rect path then.\n *\n * - When `focusedPieceId` is set and matches one of the pieces, the clip\n * narrows to just that piece's silhouette. Other pieces are excluded\n * so element rendering, the Pass-1 overflow-dim cutout, and Pass-2\n * per-element clipping all align on the same shape — only the focused\n * piece gets artwork painted into it. (Companion behaviour in\n * `PieceGuideRenderer` skips non-focused pieces' chrome too.)\n * - When `focusedPieceId` is null, the clip is the union of every\n * piece (spread mode).\n *\n * Pure function — extracted from `useCanvasRenderLoop.ts` to make the\n * filtering + rotation/swap math directly unit-testable.\n */\nexport function buildSpreadClipShape(\n pieces: OverlayPiece[],\n guides: Record<string, PieceGuide> | undefined,\n focusedPieceId: string | null | undefined,\n): ClipShape | undefined {\n if (pieces.length === 0) return undefined;\n const allPathKind = pieces.every((p) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const b = (guides?.[p.id] as any)?.boundary;\n return (\n b &&\n typeof b === 'object' &&\n b.kind === 'path' &&\n typeof b.d === 'string'\n );\n });\n if (!allPathKind) return undefined;\n\n const focused = focusedPieceId\n ? pieces.filter((p) => p.id === focusedPieceId)\n : pieces;\n // Defensive: an unknown focusedPieceId would otherwise produce a\n // zero-piece clip and hide the entire canvas. Fall back to the full\n // spread so the user still sees something.\n const composite = focused.length > 0 ? focused : pieces;\n\n return {\n type: 'composite-path',\n pieces: composite.map((p) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const boundary = (guides?.[p.id] as any)?.boundary;\n const rot = (((p.rotation ?? 0) % 360) + 360) % 360;\n // p.width / p.height are POST-rotation visual extents. The path\n // itself is in the UN-rotated frame: its native bbox is the piece's\n // natural size (used by the renderer to rotate around the piece center).\n const natural = naturalDims(p);\n return {\n d: boundary.d as string,\n x: p.x,\n y: p.y,\n rotation: rot,\n baseWidth: natural.width,\n baseHeight: natural.height,\n };\n }),\n };\n}\n","/**\n * useCanvasRenderLoop - Manages the main canvas render loop\n *\n * Extracted from CanvasEditor.tsx to isolate all rendering logic including:\n * - Canvas clearing and DPR handling\n * - Zoom and padding transforms\n * - Artboard background rendering\n * - Element rendering via CanvasRenderer\n * - Multi-selection indicators\n * - Marquee selection rendering\n * - Hover effects\n * - Pen tool preview\n * - Cursor animation during text editing\n */\n\nimport React, { useEffect, useRef } from 'react';\nimport { CanvasRenderer } from '../../../core/CanvasRenderer.js';\nimport { renderPieceGuides } from '../../../rendering/PieceGuideRenderer.js';\nimport type { PieceGuidesConfig } from '../../embed/SnowconeCanvas.js';\nimport type { ArtboardRenderer } from '../../../core/ArtboardRenderer.js';\nimport type { ArtboardElement } from '../../../core/ArtboardElement.js';\nimport type { ArtboardManager } from '../../../core/ArtboardManager.js';\nimport type { TransformHandles } from '../../../core/TransformHandles.js';\nimport type { InteractionStateMachine } from '../../../core/InteractionStateMachine.js';\nimport type { BaseElement } from '../../../core/BaseElement.js';\nimport { TextElement } from '../../../core/TextElement.js';\nimport type { ImageElement } from '../../../core/ImageElement.js';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport { PathElement } from '../../../core/PathElement.js';\nimport type { SnapGuide, SpacingIndicator, RichText, ClipShape } from '../../../types/index.js';\nimport type { LocalHoverState } from '../types/index.js';\nimport {\n renderMultiSelectionIndicators,\n renderMarqueeSelection,\n renderGroupSiblingHover,\n} from '../renderers/index.js';\nimport type { MultiSelectionOBB, MultiSelectionGroupBounds } from '../renderers/index.js';\nimport { PerformanceMonitor } from '../../../utils/PerformanceMonitor.js';\nimport { buildSpreadClipShape } from './buildSpreadClipShape.js';\n\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\ntype ChildElement = TextElement | ImageElement | ShapeElement | PathElement | GroupElement;\n\n/** Cached clone produced during text editing to avoid cloning every frame */\ninterface EditCache {\n elementId: string;\n childId: string | null;\n editRichText: RichText | null;\n clonedElement: CanvasElement;\n}\n\n/** PenTool interface - only the methods used by the render loop */\ninterface PenToolLike {\n isActive(): boolean;\n renderPreview(ctx: CanvasRenderingContext2D, element: PathElement): void;\n}\n\nexport interface UseCanvasRenderLoopParams {\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n elements: CanvasElement[];\n elementsByArtboard: Map<string | null, CanvasElement[]>;\n artboards: ArtboardElement[];\n artboardManager: ArtboardManager;\n artboardRenderer: ArtboardRenderer;\n selectedId: string | null;\n selectedElement: CanvasElement | undefined;\n activeChildElement: ChildElement | null;\n editingChildIdRef: React.MutableRefObject<string | null>;\n transformHandles: TransformHandles;\n stateMachine: InteractionStateMachine;\n\n // Canvas dimensions and view\n canvasSize: { width: number; height: number };\n paddedCanvasWidth: number;\n paddedCanvasHeight: number;\n paddingOffsetX: number;\n paddingOffsetY: number;\n zoom: number;\n zoomInvariantBorderRadius: number;\n effectiveViewPadding: number;\n showRotationHandle: boolean;\n\n // Interaction state\n isRotating: boolean;\n currentRotation: number;\n hoverState: LocalHoverState;\n isEditing: boolean;\n editText: string;\n editRichText: RichText | null;\n cursorPosition: number;\n selectionStart: number;\n selectionEnd: number;\n cursorOpacityRef: React.MutableRefObject<number>;\n cursorStartTimeRef: React.MutableRefObject<number>;\n isTouchDevice: boolean;\n selectionHandlePositionsRef: React.MutableRefObject<{\n start: { center: { x: number; y: number }; textEdge: { x: number; y: number } } | null;\n end: { center: { x: number; y: number }; textEdge: { x: number; y: number } } | null;\n }>;\n\n // Multi-click debounce refs\n multiClickSelectionRef: React.MutableRefObject<{ start: number; end: number; cursor: number } | null>;\n lastMultiClickTimeRef: React.MutableRefObject<number>;\n MULTI_CLICK_DEBOUNCE: number;\n\n // Snap and spacing\n snapGuides: SnapGuide[];\n spacingIndicators: SpacingIndicator[];\n isAltKeyPressed: boolean;\n\n // Selection state\n multiSelection: string[];\n multiSelectionOBBRef: React.MutableRefObject<MultiSelectionOBB | null>;\n multiSelectionGroupBoundsRef: React.MutableRefObject<MultiSelectionGroupBounds | null>;\n isMarqueeSelecting: boolean;\n marqueeStart: { x: number; y: number };\n marqueeEnd: { x: number; y: number };\n marqueePreviewSelection: string[];\n\n // Pen tool\n penTool: PenToolLike;\n\n // Handles version (for triggering re-renders)\n handlesVersion: number;\n\n // Hide handles flag\n hideHandles: boolean;\n\n // Theme\n resolvedTheme: string;\n\n // Cursor animation frame (triggers re-render)\n cursorAnimationFrame: number;\n\n // Render version (for forceRender)\n renderVersion: number;\n\n /** ADR-0054: piece-guide overlays drawn after elements, before\n * selection UI. Coordinates are in artboard-local px. */\n pieceGuides?: PieceGuidesConfig;\n\n /** ADR-0060: id of the currently focused piece (or null for spread\n * / single-piece). Forwarded to `renderPieceGuides` so non-focused\n * pieces' guide chrome dims to keep visual focus on the active\n * piece without losing the structural context of neighbours. */\n focusedPieceId?: string | null;\n\n /** ADR-0060: animated rect of the focused piece (in artboard\n * coords). Drives the post-pass that dims artwork outside the\n * focused piece. Decoupled from `focusedPieceId` so the parent\n * can interpolate the rect during piece switches without\n * changing which piece is \"logically\" focused. Falls back to\n * the static rect of `focusedPieceId` when not provided. */\n focusedPieceRect?: { x: number; y: number; width: number; height: number } | null;\n}\n\n/**\n * Hook that manages the main canvas render loop.\n *\n * Uses a dirty flag + requestAnimationFrame pattern to prevent double-renders\n * when multiple state updates happen in the same frame. Dependencies mark the\n * render as dirty, and a single RAF loop performs the actual render only when needed.\n */\nexport function useCanvasRenderLoop(params: UseCanvasRenderLoopParams): void {\n // Keep a ref to latest params so the RAF loop always reads current values\n const paramsRef = useRef(params);\n paramsRef.current = params;\n\n // Dirty flag: when true, the next RAF iteration will render\n const dirtyRef = useRef(true);\n\n // Cache for cloned elements during text editing — rebuilt only when editRichText changes\n const editCacheRef = useRef<EditCache | null>(null);\n\n // Cache the canvas background color so getComputedStyle is not called every RAF frame.\n // Only re-read when the theme changes (resolvedTheme dependency).\n const canvasBgColorRef = useRef('#0a0a0a');\n useEffect(() => {\n const targetElement = paramsRef.current.canvasRef.current ?? document.documentElement;\n const color = getComputedStyle(targetElement).getPropertyValue('--color-canvas-bg').trim();\n canvasBgColorRef.current = color || '#0a0a0a';\n }, [params.resolvedTheme]);\n\n // Mark dirty when core visual content changes (elements, artboard, canvas view).\n // This effect is NOT triggered by interaction-only state changes (hover, drag handles, etc.)\n // which means React skips re-running this closure during pointer-move events.\n useEffect(() => {\n dirtyRef.current = true;\n }, [\n params.elementsByArtboard,\n params.artboards,\n params.selectedId,\n params.selectedElement,\n params.activeChildElement,\n params.canvasSize,\n params.zoom,\n params.renderVersion,\n params.resolvedTheme,\n params.pieceGuides,\n ]);\n\n // Mark dirty when interaction-visual state changes (hover, selection, handles, text editing).\n // Separated from core deps so that canvas-content changes don't unnecessarily\n // re-run interaction closures, and vice-versa.\n useEffect(() => {\n dirtyRef.current = true;\n }, [\n params.transformHandles,\n params.currentRotation,\n params.isRotating,\n params.hoverState,\n params.isEditing,\n params.editText,\n params.editRichText,\n params.cursorPosition,\n params.selectionStart,\n params.selectionEnd,\n params.stateMachine,\n params.snapGuides,\n params.spacingIndicators,\n params.handlesVersion,\n params.multiSelection,\n params.isMarqueeSelecting,\n params.marqueeStart,\n params.marqueeEnd,\n params.marqueePreviewSelection,\n params.isAltKeyPressed,\n params.penTool,\n params.cursorAnimationFrame,\n ]);\n\n // Single RAF loop that only renders when dirty\n useEffect(() => {\n let frameId: number;\n\n const HOVER_FADE_DURATION = 150; // Must match HoverRenderer.ts FADE_DURATION\n\n const renderLoop = () => {\n // Keep dirty while hover fade animation is running so the RAF loop\n // drives the animation instead of a setInterval forcing React re-renders.\n const { hoverState } = paramsRef.current;\n if (hoverState.startTime !== null && hoverState.type !== null) {\n const elapsed = performance.now() - hoverState.startTime;\n if (elapsed < HOVER_FADE_DURATION) {\n dirtyRef.current = true;\n }\n }\n\n if (dirtyRef.current) {\n dirtyRef.current = false;\n const t0 = performance.now();\n renderFrame(paramsRef.current, canvasBgColorRef, editCacheRef);\n PerformanceMonitor.getInstance().recordFrameTime(performance.now() - t0);\n }\n frameId = requestAnimationFrame(renderLoop);\n };\n\n frameId = requestAnimationFrame(renderLoop);\n return () => cancelAnimationFrame(frameId);\n }, []);\n}\n\n/**\n * Performs a single render frame. Extracted from the original useEffect body\n * so it can be called from the RAF loop.\n */\nfunction renderFrame(\n params: UseCanvasRenderLoopParams,\n canvasBgColorRef: React.MutableRefObject<string>,\n editCacheRef: React.MutableRefObject<EditCache | null>\n): void {\n const {\n canvasRef,\n elements,\n elementsByArtboard,\n artboards,\n artboardManager,\n artboardRenderer,\n selectedId,\n selectedElement,\n activeChildElement,\n editingChildIdRef,\n transformHandles,\n stateMachine,\n paddedCanvasWidth,\n paddedCanvasHeight,\n paddingOffsetX,\n paddingOffsetY,\n zoom,\n zoomInvariantBorderRadius,\n effectiveViewPadding,\n showRotationHandle,\n isRotating,\n currentRotation,\n hoverState,\n isEditing,\n editText,\n editRichText,\n cursorPosition,\n selectionStart,\n selectionEnd,\n cursorOpacityRef,\n cursorStartTimeRef,\n isTouchDevice,\n selectionHandlePositionsRef,\n multiClickSelectionRef,\n lastMultiClickTimeRef,\n MULTI_CLICK_DEBOUNCE,\n snapGuides,\n spacingIndicators,\n isAltKeyPressed,\n multiSelection,\n multiSelectionOBBRef,\n multiSelectionGroupBoundsRef,\n isMarqueeSelecting,\n marqueeStart,\n marqueeEnd,\n marqueePreviewSelection,\n penTool,\n hideHandles,\n pieceGuides,\n } = params;\n\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n // Calculate cursor opacity for text editing cursor animation\n // This runs in the main render loop to avoid separate RAF + setInterval\n if (isEditing) {\n const cycleDuration = 1060;\n const easeInOutCubic = (t: number): number => {\n return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;\n };\n\n const elapsed = performance.now() - cursorStartTimeRef.current;\n const progress = (elapsed % cycleDuration) / cycleDuration;\n\n const opacity =\n progress < 0.5\n ? 1 - easeInOutCubic(progress * 2)\n : easeInOutCubic((progress - 0.5) * 2);\n\n cursorOpacityRef.current = opacity;\n } else {\n cursorOpacityRef.current = 1;\n }\n\n // Support high DPI displays\n const dpr = window.devicePixelRatio || 1;\n\n // Set actual size in memory (display size x DPR, accounting for zoom and padding)\n const displayWidth = paddedCanvasWidth * zoom;\n const displayHeight = paddedCanvasHeight * zoom;\n\n // Skip rendering when dimensions are zero (e.g. before zoom calculation completes).\n // Without this guard, knockout/compositing renderers create 0-size OffscreenCanvas\n // which throws and kills the RAF loop permanently.\n if (displayWidth <= 0 || displayHeight <= 0) return;\n\n // Cap the canvas backing buffer to at most `MAX_BUFFER_DIM` on its\n // longest side. Without this, user-zoom (up to 8× in editor mode)\n // multiplies the buffer's pixel count by 64×, blowing past 90+\n // megapixels for typical artboards — the canvas re-paints from\n // scratch each dirty frame, and that much pixel work tanks\n // interactivity (especially on mobile Safari, which also has\n // tight memory limits). The cap means past the threshold the CSS\n // box keeps growing (so layout/pan still feels right) while the\n // backing buffer holds at the cap and the browser bilinear-\n // stretches the last buffer-resolution to fill the box. Slightly\n // softer at extreme zooms; smooth interaction always.\n //\n // 4096 is the safe everywhere choice: every browser canvas\n // implementation in the wild supports it, including older iPads.\n const MAX_BUFFER_DIM = 4096;\n const rawBufferWidth = displayWidth * dpr;\n const rawBufferHeight = displayHeight * dpr;\n const longest = Math.max(rawBufferWidth, rawBufferHeight);\n const scaleFactor = longest > MAX_BUFFER_DIM ? MAX_BUFFER_DIM / longest : 1;\n const effectiveDpr = dpr * scaleFactor;\n\n canvas.width = displayWidth * effectiveDpr;\n canvas.height = displayHeight * effectiveDpr;\n\n // IMPORTANT: Reset transform to identity before applying dpr scaling\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n\n // Clear canvas FIRST with no transform (use full buffer size)\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n // Use the cached background color (updated on theme change, never called in-frame)\n const canvasBgColor = canvasBgColorRef.current;\n\n // NOW apply transforms for content rendering. Using `effectiveDpr`\n // (not `dpr`) keeps the world-coord-to-buffer-pixel mapping\n // consistent with the capped canvas dimensions above — the canvas\n // CSS still occupies the full `displayWidth × displayHeight` box\n // and the browser scales our smaller buffer up to fit.\n ctx.scale(effectiveDpr, effectiveDpr);\n\n // Apply zoom transform\n if (zoom !== 1.0) {\n ctx.scale(zoom, zoom);\n }\n\n // Apply padding offset to center artboard within padded canvas\n if (paddingOffsetX !== 0 || paddingOffsetY !== 0) {\n ctx.translate(paddingOffsetX, paddingOffsetY);\n }\n\n // Find the active artboard once and reuse for both background and rendering\n const activeArtboard = artboards.find((a) => a.id === artboardManager.getActiveArtboardId());\n const activeArtboardId = activeArtboard?.id ?? null;\n\n // ADR-0054 rendering contract — IMPORTANT:\n // Do NOT derive a clip path from `pieceGuides.boundary` here when the\n // boundary is a rounded-rect — the gradient bleed-ring vignette\n // assumes a continuous underlying canvas to feather into; clipping at\n // the boundary would defeat the soft fade. See the \"Rendering\n // contract\" block at the top of PieceGuideRenderer.ts.\n //\n // BUT for **path-kind** boundaries (multi-piece spreads — AFOOES\n // hoodie etc.), the bleed-ring pathway is already skipped server-side\n // (the API only injects the vignette default when boundary is rect-\n // shaped). With no vignette to preserve, we *do* want a hard clip:\n // without it, artwork bleeds into corner wedges of each piece's bbox\n // and into the gaps between pieces (the \"looks like a rectangle\" bug).\n // Build a composite clipShape from the union of every piece's\n // path-kind boundary, translated/rotated into the artboard frame.\n const effectiveClipShape: ClipShape | undefined =\n pieceGuides && activeArtboard\n ? buildSpreadClipShape(\n pieceGuides.pieces,\n pieceGuides.guides,\n params.focusedPieceId ?? null,\n )\n : undefined;\n // Prefer the piece-guide's own outer corner radius if authored\n // (either explicit `outerRadius` on the guide, or inferred from a\n // rounded-rect `boundary`). The radius drives Pass-2's rounded\n // element clip in CanvasRenderer (so the artwork's natural\n // rectangle is shaped like the product's outer edge — phone case,\n // rounded card, etc.). Pass-1 overflow-dim and beyond-rect\n // selection chrome use the same radius to determine which corner\n // wedges to paint into during interaction.\n // For piece-guide products the artboard's rounded radius should\n // match the *product*, not the consumer's stylistic\n // `artboardBorderRadius` prop. A rectangular notebook spread should\n // render with sharp corners (matching its spec sheet), and a phone\n // case should use its piece's `outerRadius`. Default to 0 here for\n // piece-guide products; the per-piece block below bumps it back up\n // when the piece itself encodes a rounded outer shape. Non-piece-\n // guide products still inherit the legacy `zoomInvariantBorderRadius`\n // for the soft-corner editor look.\n let effectiveBorderRadius = pieceGuides ? 0 : zoomInvariantBorderRadius;\n if (\n pieceGuides &&\n pieceGuides.pieces.length === 1 &&\n activeArtboard\n ) {\n const firstPieceId = pieceGuides.pieces[0].id;\n const guide = pieceGuides.guides?.[firstPieceId];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const guideAny = guide as any;\n const explicit = guideAny?.outerRadius;\n const boundary = guideAny?.boundary;\n // Fall back to the rounded-rect boundary's rx when no explicit\n // outerRadius is set — matches typical phone-case authoring\n // where the boundary already encodes the product's outer curve.\n const inferred =\n boundary &&\n typeof boundary === 'object' &&\n boundary.kind === 'roundedRect'\n ? (boundary.rx ?? boundary.ry)\n : undefined;\n const candidate =\n typeof explicit === 'number' && explicit > 0\n ? explicit\n : typeof inferred === 'number' && inferred > 0\n ? inferred\n : 0;\n if (candidate > 0) {\n effectiveBorderRadius = candidate;\n }\n }\n\n // Artboard bg fill — full artboard rect (rounded at the legacy\n // CSS-px border radius). Vignette is added on top by the overlay.\n //\n // Skipped entirely when piece guides are present: those products\n // (phone cases, anything with a non-rectangular outer shape) need\n // the canvas to be a true knockout so the page chrome / product\n // mockup behind shows through everywhere the artwork doesn't cover.\n // Painting `--color-canvas-bg` here was leaking a white frame\n // between the artwork and the bleed ring whenever the artwork\n // didn't fill the artboard (small images, mid-resize, mid-drag),\n // and it was the same opaque fill that the rounded-corner knockout\n // had to erase via a `destination-out` final crop. Removing it\n // makes both situations Just Work — alpha goes straight to the\n // page below.\n if (activeArtboard && !pieceGuides) {\n ctx.fillStyle = canvasBgColor;\n if (\n effectiveClipShape &&\n typeof effectiveClipShape === 'object' &&\n effectiveClipShape.type === 'path'\n ) {\n try {\n const p2d = new Path2D(effectiveClipShape.d);\n ctx.save();\n ctx.translate(activeArtboard.x, activeArtboard.y);\n ctx.fill(p2d);\n ctx.restore();\n } catch {\n ctx.fillRect(\n activeArtboard.x,\n activeArtboard.y,\n activeArtboard.width,\n activeArtboard.height,\n );\n }\n } else if (effectiveBorderRadius > 0) {\n ctx.beginPath();\n ctx.roundRect(\n activeArtboard.x,\n activeArtboard.y,\n activeArtboard.width,\n activeArtboard.height,\n effectiveBorderRadius\n );\n ctx.fill();\n } else {\n ctx.fillRect(\n activeArtboard.x,\n activeArtboard.y,\n activeArtboard.width,\n activeArtboard.height\n );\n }\n }\n\n // Render only the active artboard.\n //\n // Skipped entirely for piece-guide products. ArtboardRenderer paints\n // `artboard.backgroundColor` (which defaults to '#ffffff' in\n // ArtboardElement) across the *sharp* artboard rect with no\n // outerRadius awareness — that's the white frame that was bleeding\n // through the rounded outer corners and the bleed-ring band on\n // phone cases. The piece-guide overlay renders its own boundary,\n // safe-area, and label chrome from the authored layout JSON, so\n // dropping the ArtboardRenderer chrome here also avoids border/\n // label duplication. Plain rectangular products keep the legacy\n // ArtboardRenderer pass for the editor's design surface.\n if (activeArtboard && !pieceGuides) {\n artboardRenderer.render(ctx, activeArtboard, true);\n }\n\n // Render all layers using CanvasRenderer\n let elementToRender = activeChildElement || selectedElement;\n\n // Get cached elements for active artboard\n let elementsForRender = elementsByArtboard.get(activeArtboardId) || [];\n\n // During drag, use cached element if available for immediate visual feedback\n if (stateMachine.getMode() === 'drag' && selectedElement) {\n const dragContext = stateMachine.getContext() as ReturnType<typeof stateMachine.getContext> & {\n _cachedElement?: Record<string, CanvasElement>;\n };\n const cachedElements = dragContext._cachedElement;\n if (cachedElements && cachedElements[selectedElement.id]) {\n elementsForRender = elementsForRender.map((e) =>\n e.id === selectedElement.id ? cachedElements[selectedElement.id] : e\n );\n if (!activeChildElement) {\n elementToRender = cachedElements[selectedElement.id];\n }\n }\n }\n\n // During edit mode, update the element being edited with current edit text.\n // Cache the cloned element and only re-clone when editRichText reference changes —\n // the cursor-blink RAF ticks (60fps) no longer trigger an expensive clone each frame.\n if (isEditing && selectedElement) {\n const childId = editingChildIdRef.current ?? null;\n const cache = editCacheRef.current;\n const cacheHit =\n cache !== null &&\n cache.elementId === selectedElement.id &&\n cache.childId === childId &&\n cache.editRichText === editRichText;\n\n if (!cacheHit) {\n // (Re-)build the clone only when something meaningful changed\n let clonedElement: CanvasElement | null = null;\n\n if (childId && selectedElement instanceof GroupElement) {\n const sourceGroup = elementsForRender.find(\n (e) => e.id === selectedElement.id && e instanceof GroupElement\n ) as GroupElement | undefined;\n if (sourceGroup) {\n const clonedGroup = sourceGroup.clone();\n clonedGroup.children = clonedGroup.children.map((child) => {\n if (child.id === childId && editRichText && child instanceof TextElement) {\n const clonedChild = child.clone();\n clonedChild.setRichText(editRichText);\n return clonedChild;\n }\n return child;\n });\n clonedElement = clonedGroup;\n }\n } else if (editRichText && selectedElement instanceof TextElement) {\n const cloned = selectedElement.clone();\n cloned.setRichText(editRichText);\n clonedElement = cloned;\n }\n\n if (clonedElement) {\n editCacheRef.current = { elementId: selectedElement.id, childId, editRichText, clonedElement };\n } else {\n editCacheRef.current = null;\n }\n }\n\n if (editCacheRef.current) {\n const { clonedElement } = editCacheRef.current;\n elementsForRender = elementsForRender.map((e) =>\n e.id === selectedElement.id ? clonedElement : e\n );\n }\n } else {\n // Not editing — clear the cache so a fresh clone is built next time editing starts\n editCacheRef.current = null;\n }\n\n // Hide handles when pen tool is active\n const shouldHideHandles = hideHandles || (penTool.isActive() && selectedElement instanceof PathElement) || isEditing;\n\n CanvasRenderer.render({\n ctx,\n elements: elementsForRender,\n selectedId,\n selectedElement: elementToRender || null,\n transformHandles,\n currentRotation,\n isRotating,\n hoverState: hoverState as { type: 'element' | 'resize-handle' | 'rotation-handle' | null; data?: BaseElement | null },\n isEditing,\n stateMachine,\n snapGuides: snapGuides,\n spacingIndicators: spacingIndicators,\n hideHandles: shouldHideHandles,\n isAltKeyPressed,\n artboardBackgroundColor: activeArtboard?.backgroundColor,\n distressTexture: activeArtboard?.distressTexture,\n imageMask: activeArtboard?.imageMask,\n artboard: activeArtboard\n ? {\n x: activeArtboard.x,\n y: activeArtboard.y,\n width: activeArtboard.width,\n height: activeArtboard.height,\n borderRadius: effectiveBorderRadius,\n clipShape: effectiveClipShape ?? activeArtboard.clipShape,\n }\n : undefined,\n zoom,\n paddingOffset: { x: paddingOffsetX, y: paddingOffsetY },\n showOverflowDimmed: effectiveViewPadding < 1.0,\n // ADR-0054: overflow-dim renders a 30%+blur version of selected\n // elements outside the artboard's rounded shape. Users rely on\n // this as feedback while a layer is selected — they need to see\n // how far the element extends past the visible piece edge.\n // Hover doesn't trigger this anymore (CanvasRenderer.ts ~line\n // 489 gates on `shouldShowAsSelected` only), so the halo only\n // fires on explicit selection — no on-mouseover noise.\n suppressOverflowDim: false,\n showRotationHandle,\n editModeData: isEditing\n ? (() => {\n const savedSelection = multiClickSelectionRef.current;\n const withinDebounce = Date.now() - lastMultiClickTimeRef.current < MULTI_CLICK_DEBOUNCE;\n const useRefSelection = savedSelection && withinDebounce;\n\n return {\n cursorPosition: useRefSelection ? savedSelection.cursor : cursorPosition,\n selectionStart: useRefSelection ? savedSelection.start : selectionStart,\n selectionEnd: useRefSelection ? savedSelection.end : selectionEnd,\n editText,\n editRichText,\n cursorOpacity: cursorOpacityRef.current,\n isTouchDevice,\n selectionHandlePositionsRef,\n };\n })()\n : undefined,\n });\n\n // ADR-0054: piece-guide overlays. Drawn AFTER the artwork / elements\n // so boundary + zone + safe-area visuals sit on top of the user's\n // design (which is the whole point — they dim the bleed and flag\n // die-cuts), but BEFORE selection/marquee/handle UI so interaction\n // chrome stays legible on top. Coordinates are in artboard-local px;\n // the renderer translates to (activeArtboard.x, activeArtboard.y)\n // internally so multi-artboard layouts compose correctly.\n if (pieceGuides && pieceGuides.pieces.length > 0 && activeArtboard) {\n // Detect whether the user is mid-interaction with an element. We\n // pass this to the piece-guide renderer so die-cut zones in\n // `dieCutMode: 'knockout'` swap their full destination-out for a\n // partial one (alpha 0.7 → 30% of underlying pixels survive)\n // while a user is selecting, hovering, or dragging. That matches\n // the outside overflow halo's 30% multiplier, so the cutout reads\n // as a soft window onto the artwork at the same opacity as the\n // rounded outer corners, instead of a hard knockout that punches\n // through the user's manipulation feedback.\n // Only switch the die-cut to partial knockout when the focused\n // element actually overlaps a die-cut zone. Without this gate,\n // selecting any element (e.g. text far away) would soften every\n // die-cut on the artboard, letting the topmost layers under the\n // cutout bleed through 30% — visually wrong because the user\n // isn't manipulating anything inside that cutout.\n let selectedOverlapsDieCut = false;\n // Only the *selected* element triggers the die-cut blur preview —\n // hover used to fall through here, but that meant the cutout\n // flickered between knockout and blurred-preview every time the\n // cursor moved over a layer near the cutout. The signal is\n // reserved for explicit selection now (matches the overflow-halo\n // gating in CanvasRenderer).\n const interactingElement = (elementToRender as BaseElement | null) ?? null;\n if (interactingElement && pieceGuides) {\n const bbox = interactingElement.getVisualBoundingBox();\n outer: for (const piece of pieceGuides.pieces) {\n const zones = pieceGuides.guides?.[piece.id]?.zones ?? [];\n for (const zone of zones) {\n if (zone.kind !== 'dieCut') continue;\n // Only rect / roundedRect paths expose easy bbox info.\n // `path`-kind die-cuts (raw SVG) skip overlap detection\n // and fall through to the historical \"any selection\n // softens die-cut\" behaviour for them — the geometry to\n // do AABB on a path string is non-trivial and rare.\n const path = zone.path as { kind: string; x?: number; y?: number; width?: number; height?: number };\n if (path.kind !== 'rect' && path.kind !== 'roundedRect') {\n selectedOverlapsDieCut = true;\n break outer;\n }\n const zx = piece.x + (path.x ?? 0);\n const zy = piece.y + (path.y ?? 0);\n const zw = path.width ?? 0;\n const zh = path.height ?? 0;\n if (\n bbox.x < zx + zw &&\n bbox.x + bbox.width > zx &&\n bbox.y < zy + zh &&\n bbox.y + bbox.height > zy\n ) {\n selectedOverlapsDieCut = true;\n break outer;\n }\n }\n }\n }\n const interacting = selectedOverlapsDieCut;\n // Clip the piece-guide pass to the same rounded artboard shape\n // the artwork was clipped to (Pass 2). Without this, the bleed\n // wash + per-piece chrome paint with sharp piece-rect corners\n // and overshoot the artwork's rounded corners — leaving an\n // L-shaped wedge of bleed wash visible outside the rounded\n // artboard edge.\n const guidesNeedRoundedClip =\n effectiveBorderRadius > 0 &&\n activeArtboard !== undefined;\n if (guidesNeedRoundedClip) {\n ctx.save();\n ctx.beginPath();\n ctx.roundRect(\n activeArtboard.x,\n activeArtboard.y,\n activeArtboard.width,\n activeArtboard.height,\n effectiveBorderRadius,\n );\n ctx.clip();\n }\n renderPieceGuides({\n ctx,\n artboardOrigin: { x: activeArtboard.x, y: activeArtboard.y },\n artboardWidth: activeArtboard.width,\n artboardHeight: activeArtboard.height,\n pieces: pieceGuides.pieces,\n pieceGuides: pieceGuides.guides,\n show: pieceGuides.show,\n style: pieceGuides.style,\n zoom,\n interacting,\n focusedPieceId: params.focusedPieceId ?? null,\n });\n if (guidesNeedRoundedClip) {\n ctx.restore();\n }\n\n // ADR-0060 post-pass: when in focused mode, dim everything that\n // sits OUTSIDE the focused piece's rect to ~30% alpha. This gives\n // neighbour artwork + their chrome the same \"overflow\" treatment\n // the user already sees outside the artboard's rounded shape via\n // `renderElementWithOverflowDimming` (Pass 1, 30% halo). The\n // per-piece chrome dim earlier was correct but only covered guide\n // chrome — the actual painted artwork on neighbour pieces stayed\n // at full alpha and read as competing visual subjects. This pass\n // unifies the dim across artwork + chrome.\n //\n // Drawn AFTER renderPieceGuides and BEFORE the interaction chrome\n // layer below, so selection bbox / handles stay at full 100%\n // alpha even when they cross the focused-piece boundary. Idle and\n // spread mode are no-ops (nothing in `pieces.length <= 1` is\n // multi-piece, and no `focusedPieceId` means spread).\n if (params.focusedPieceId && pieceGuides.pieces.length > 1) {\n // Prefer the animated rect when provided; fall back to the\n // static rect of `focusedPieceId` otherwise.\n const preservedRect =\n params.focusedPieceRect ??\n pieceGuides.pieces.find((p) => p.id === params.focusedPieceId) ??\n null;\n if (preservedRect) {\n ctx.save();\n ctx.translate(activeArtboard.x, activeArtboard.y);\n ctx.globalCompositeOperation = 'destination-out';\n // When an element is selected, dim the neighbouring pieces to\n // 30% so the user can see how their selection extends past\n // the focused piece's edge — same opacity multiplier as the\n // overflow halo. When *nothing* is selected, fully erase the\n // neighbours: at idle the \"peek\" of adjacent artwork reads as\n // visual chatter (\"why is half another page in my view?\"),\n // and the user always has the `All (spread)` toggle to see\n // the full layout when they want.\n const hasSelection = !!selectedElement;\n ctx.globalAlpha = hasSelection ? 1 - 0.3 : 1;\n ctx.fillStyle = '#000';\n ctx.beginPath();\n // Outer rect covering the entire visible canvas in artboard\n // coords. Use a generous bound rather than computing exactly\n // — `evenodd` cares only that it includes everything that\n // could be on-screen; oversized doesn't hurt.\n const HUGE = 1e7;\n ctx.rect(-HUGE, -HUGE, 2 * HUGE, 2 * HUGE);\n // Inner rect: the focused piece — carved out so its content\n // stays at full alpha.\n ctx.rect(\n preservedRect.x,\n preservedRect.y,\n preservedRect.width,\n preservedRect.height,\n );\n ctx.fill('evenodd');\n ctx.restore();\n }\n }\n }\n\n // Single-selection interaction chrome (bbox + handles + position/size\n // info + hover outline + crop-mode chrome). Drawn here, AFTER piece\n // guides, so the die-cut partial knockout (which intentionally dims\n // artwork inside the cutout to 30% to match the outside overflow\n // halo) doesn't also dim selection chrome — chrome stays at full\n // 100% opacity, sharp blue bbox edge and fully opaque resize-handle\n // dots, regardless of where the cutout sits relative to the element.\n // Mirrors how multi-selection chrome already renders below.\n if (activeArtboard) {\n CanvasRenderer.renderInteractionChromeLayer({\n ctx,\n selectedElement: elementToRender || null,\n transformHandles,\n stateMachine,\n currentRotation,\n isRotating,\n hoverState: hoverState as { type: 'element' | 'resize-handle' | 'rotation-handle' | null; data?: BaseElement | null },\n selectedId,\n hideHandles: shouldHideHandles,\n isEditing,\n isAltKeyPressed,\n artboardBackgroundColor: activeArtboard.backgroundColor,\n // Hover ring is clipped to the visible artboard / piece bounds\n // so it doesn't paint into the overflow region. Uses the piece-\n // shape-aware `effectiveBorderRadius` computed above, which\n // matches how the artwork itself is clipped, so the ring hugs\n // the same edge the user sees as the design canvas.\n hoverClipArtboard: {\n x: activeArtboard.x,\n y: activeArtboard.y,\n width: activeArtboard.width,\n height: activeArtboard.height,\n borderRadius: effectiveBorderRadius,\n clipShape: activeArtboard.clipShape,\n },\n showRotationHandle,\n zoom,\n paddingOffset: { x: paddingOffsetX, y: paddingOffsetY },\n });\n }\n\n // Render multi-selection indicators\n const displaySelection = isMarqueeSelecting ? marqueePreviewSelection : multiSelection;\n const activeArtboardElementIds = new Set(elementsForRender.map((e) => e.id));\n\n const { updatedOBB, groupBounds } = renderMultiSelectionIndicators({\n ctx,\n displaySelection,\n elements,\n activeArtboardElementIds,\n stateMachine,\n multiSelectionOBB: multiSelectionOBBRef.current,\n isRotating,\n currentRotation,\n isMarqueeSelecting,\n zoom,\n });\n\n // Write computed OBB directly to ref — no React setState, no re-render triggered from RAF.\n if (updatedOBB && multiSelectionOBBRef.current !== updatedOBB) {\n multiSelectionOBBRef.current = updatedOBB;\n }\n multiSelectionGroupBoundsRef.current = groupBounds;\n\n // Render marquee selection box\n if (isMarqueeSelecting) {\n renderMarqueeSelection({ ctx, marqueeStart, marqueeEnd, zoom });\n }\n\n // Render hover effect for group siblings\n if (hoverState.type === 'group-sibling' && hoverState.data) {\n renderGroupSiblingHover({ ctx, hoveredChild: hoverState.data as TextElement | ImageElement, zoom });\n }\n\n // Render pen tool preview\n if (penTool.isActive() && selectedElement instanceof PathElement) {\n penTool.renderPreview(ctx, selectedElement);\n }\n}\n","/**\n * PinchHandler — applies a two-finger pinch gesture (uniform scale +\n * rotation around the gesture centroid) to a canvas element.\n *\n * Stateless mirror of `ResizeHandler.calculateResize`. Inputs are:\n * - the element's start snapshot (`startData` from\n * `BaseElement.getTransformStartData`),\n * - the live gesture state (scale ratio, rotation delta, and\n * start / current centroid in world coords) produced by\n * `InteractionStateMachine.updatePinch`.\n *\n * Output is a fresh cloned element with the new x / y / size /\n * rotation. Aspect ratio is preserved (uniform scale). Stroke width\n * scales with size to keep visual proportions intact.\n *\n * The geometry: keep the element-local point that was under the\n * gesture centroid at start fixed under the centroid for every\n * subsequent frame. Equivalent to the affine transform\n *\n * T(P) = currentCentroid + R(rotationDelta, scale * (P - startCentroid))\n *\n * applied to the element's origin, where `R(θ, p)` rotates `p` by `θ`\n * radians. The element's local axes also rotate by `rotationDelta`,\n * so `rotation` becomes `startRotation + rotationDelta` (in degrees).\n *\n * The point of this approach: it's the natural \"the photograph stays\n * under my fingers\" feel users expect from native pinch gestures —\n * no element-center bias, no opposite-corner pinning. The two\n * fingers ARE the frame of reference.\n */\n\nimport type { BaseElement } from './BaseElement.js';\nimport type { Point, TransformStartData } from '../types/index.js';\n\nexport interface PinchInputs {\n /** Cloned element to mutate — typically `stateMachine.context.element`. */\n element: BaseElement;\n /** Start snapshot of the element's transform (from\n * `BaseElement.getTransformStartData`). Carries the start position,\n * rotation, dimensions, and stroke width that the math reads. */\n startData: TransformStartData;\n /** Distance ratio (current / start). 1 = no scale change. */\n scale: number;\n /** Angle delta in radians (current - start). 0 = no rotate change. */\n rotationDelta: number;\n /** Gesture centroid at gesture start, in world coords. */\n startCentroid: Point;\n /** Gesture centroid right now, in world coords. */\n currentCentroid: Point;\n /**\n * Optional snap hook for cardinal-angle rotation snapping. Same\n * shape as `InteractionStateMachine`'s rotation snap callback —\n * returns possibly-adjusted rotation in degrees.\n */\n snapRotation?: (rotation: number, element: BaseElement) => number;\n}\n\nexport class PinchHandler {\n /**\n * Apply the pinch gesture to `element` in place and return it. The\n * element is expected to already be a clone (the state machine\n * clones on `startPinch`) — this method does not clone again.\n */\n static calculatePinch({\n element,\n startData,\n scale,\n rotationDelta,\n startCentroid,\n currentCentroid,\n snapRotation,\n }: PinchInputs): BaseElement {\n const startWidth = startData.width ?? 0;\n const startHeight = startData.height ?? 0;\n const newWidth = Math.max(1, startWidth * scale);\n const newHeight = Math.max(1, startHeight * scale);\n\n // Apply size via the element's own resize() so type-specific\n // scaling (fontSize on text, lineCount, etc.) lands. Anchor is\n // arbitrary here — pinch overrides position below — but\n // 'bottom-right' is the natural identity anchor: scaling from\n // the top-left without rotation. Element-specific resize\n // implementations may also scale stroke width based on\n // `startData.strokeWidth` so we don't need to do that ourselves.\n element.resize('bottom-right', newWidth, newHeight, startData);\n\n // Position the element so the gesture's start-centroid local\n // point (the canvas pixel that was under the user's fingertip\n // midpoint at gesture start) lands exactly at currentCentroid.\n //\n // T(P) = currentCentroid + R(rotationDelta, scale * (P - startCentroid))\n //\n // applied to the element's origin (startData.x, startData.y).\n const dx = startData.x - startCentroid.x;\n const dy = startData.y - startCentroid.y;\n const cos = Math.cos(rotationDelta);\n const sin = Math.sin(rotationDelta);\n const sdx = scale * dx;\n const sdy = scale * dy;\n element.x = currentCentroid.x + sdx * cos - sdy * sin;\n element.y = currentCentroid.y + sdx * sin + sdy * cos;\n\n // Rotation value: in this codebase, the element's stored\n // `rotation` field uses an inverted sign relative to the screen\n // coordinate system (`RotationUtils.toRadians` negates when\n // converting for rendering, and the rotate-handle handler does\n // `newRotation = current - delta` for the same reason). We\n // mirror that here: subtract the gesture's atan2 delta so a\n // CW-visual finger twist produces a CW-visual element rotation.\n // Position math above intentionally stays un-negated — the\n // origin's location around the gesture centroid is computed in\n // screen coords, where the same `rotationDelta` produces the\n // matching visual motion. Only the stored rotation field flips.\n let newRotation = startData.rotation - (rotationDelta * 180) / Math.PI;\n // Normalize to (-180, 180] so snap callbacks receive a stable\n // domain regardless of how many full turns the user has done.\n newRotation = ((newRotation % 360) + 360) % 360;\n if (newRotation > 180) newRotation -= 360;\n if (snapRotation) {\n newRotation = snapRotation(newRotation, element);\n }\n element.setRotation(newRotation);\n\n return element;\n }\n}\n","/**\n * Multi-Selection Interaction Handlers\n * Handles hit testing and interaction for multi-selection bounds, handles, and rotation\n */\n\nimport { TextElement } from '../../../core/TextElement.js';\nimport { ImageElement } from '../../../core/ImageElement.js';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { PathElement } from '../../../core/PathElement.js';\nimport type { InteractionStateMachine } from '../../../core/InteractionStateMachine.js';\nimport type { MultiSelectionGroupBounds } from '../renderers/index.js';\nimport { calculateAngle } from '../../../core/GeometryUtils.js';\nimport { CORNER_HANDLE_HIT_RADIUS } from '../../../constants.js';\n\nimport type { LocalHoverState } from '../types/index.js';\n\n/** Elements that can be on the canvas */\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\n/** Multi-selection resize context stored on the state machine */\ninterface MultiSelectionResizeContext {\n anchor: string;\n startBounds: { minX: number; maxX: number; minY: number; maxY: number };\n elementsStartData: Array<{ element: CanvasElement; startData: Record<string, unknown> }>;\n}\n\n/** Multi-selection rotation context stored on the state machine */\ninterface MultiSelectionRotationContext {\n startBounds: { centerX: number; centerY: number };\n elementsStartData: Array<{ element: CanvasElement; startData: Record<string, unknown> }>;\n}\n\n/** Multi-selection drag context stored on the state machine */\ninterface MultiSelectionDragContext {\n startX: number;\n startY: number;\n elementsStartData: Array<{ element: CanvasElement; startData: Record<string, unknown> }>;\n currentElements: Map<string, CanvasElement>;\n}\n\n/** Augmented state machine with multi-selection ad-hoc properties */\ntype AugmentedStateMachine = InteractionStateMachine & {\n _multiSelectionDragContext?: MultiSelectionDragContext;\n _multiSelectionResizeContext?: MultiSelectionResizeContext;\n _multiSelectionRotationContext?: MultiSelectionRotationContext;\n};\n\nexport type MultiSelectionHitResult =\n | { type: 'corner-handle'; anchor: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; corner: { x: number; y: number } }\n | { type: 'rotation-handle' }\n | { type: 'bounds-drag' }\n | { type: 'none' };\n\nexport interface MultiSelectionHitTestParams {\n x: number;\n y: number;\n bounds: MultiSelectionGroupBounds;\n multiSelection: string[];\n elements: CanvasElement[];\n zoom?: number;\n}\n\n/**\n * Hit test for multi-selection interactions\n * Returns what was clicked: corner handle, rotation handle, bounds drag area, or nothing\n */\nexport function hitTestMultiSelection(params: MultiSelectionHitTestParams): MultiSelectionHitResult {\n const { x, y, bounds, zoom = 1.0 } = params;\n\n // Use the same hit radius as single-selection handles, scaled by zoom\n // Coordinates are in world space, so hit radius needs to be in world space too\n const hitRadius = CORNER_HANDLE_HIT_RADIUS / zoom;\n const groupRotation = bounds.rotation || 0;\n\n // Define corners\n const corners = [\n { x: bounds.minX, y: bounds.minY, anchor: 'top-left' as const },\n { x: bounds.maxX, y: bounds.minY, anchor: 'top-right' as const },\n { x: bounds.minX, y: bounds.maxY, anchor: 'bottom-left' as const },\n { x: bounds.maxX, y: bounds.maxY, anchor: 'bottom-right' as const },\n ];\n\n // Apply rotation to corner positions if group is rotated\n const rotatedCorners = corners.map((corner) => {\n if (groupRotation !== 0) {\n const rotationRad = (-groupRotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const dx = corner.x - bounds.centerX;\n const dy = corner.y - bounds.centerY;\n return {\n x: bounds.centerX + (dx * cos - dy * sin),\n y: bounds.centerY + (dx * sin + dy * cos),\n anchor: corner.anchor,\n };\n }\n return corner;\n });\n\n // Check corner handles\n for (const corner of rotatedCorners) {\n const dx = x - corner.x;\n const dy = y - corner.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n if (distance <= hitRadius) {\n return { type: 'corner-handle', anchor: corner.anchor, corner };\n }\n }\n\n // Check rotation handle - use larger hit radius scaled by zoom\n const rotationHitRadius = (16 + 2) / zoom; // 16px visual + 2px padding, in world space\n const rotationHandleDistance = Math.sqrt(\n Math.pow(x - bounds.rotationHandleX, 2) + Math.pow(y - bounds.rotationHandleY, 2)\n );\n\n if (rotationHandleDistance <= rotationHitRadius) {\n return { type: 'rotation-handle' };\n }\n\n // Check if clicking within bounds (for dragging)\n // Transform click point to the rotated coordinate system\n let localX = x;\n let localY = y;\n\n if (groupRotation !== 0) {\n const rotationRad = (groupRotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const dx = x - bounds.centerX;\n const dy = y - bounds.centerY;\n localX = bounds.centerX + (dx * cos - dy * sin);\n localY = bounds.centerY + (dx * sin + dy * cos);\n }\n\n // Check if point is within the bounding box\n if (localX >= bounds.minX && localX <= bounds.maxX && localY >= bounds.minY && localY <= bounds.maxY) {\n return { type: 'bounds-drag' };\n }\n\n return { type: 'none' };\n}\n\nexport interface StartMultiSelectionResizeParams {\n multiSelection: string[];\n elements: CanvasElement[];\n bounds: MultiSelectionGroupBounds;\n anchor: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n corner: { x: number; y: number };\n x: number;\n y: number;\n stateMachine: InteractionStateMachine;\n}\n\n/**\n * Starts a resize operation on multi-selection\n * Sets up state machine context for multi-selection resize\n */\nexport function startMultiSelectionResize(params: StartMultiSelectionResizeParams): boolean {\n const { multiSelection, elements, bounds, anchor, corner, x, y, stateMachine } = params;\n\n // Get all selected elements (excluding locked ones)\n const selectedElements = multiSelection\n .map((id) => elements.find((e) => e.id === id))\n .filter((e): e is CanvasElement => e !== undefined && !e.locked);\n\n // If all elements are locked, don't allow resize\n if (selectedElements.length === 0) {\n return false;\n }\n\n (stateMachine as AugmentedStateMachine)._multiSelectionResizeContext = {\n anchor,\n startBounds: { ...bounds },\n elementsStartData: selectedElements.map((element) => ({\n element,\n startData: element.getTransformStartData(),\n })),\n };\n\n stateMachine.startResize(\n selectedElements[0], // Use first element as reference\n {\n anchor,\n x: corner.x,\n y: corner.y,\n cursor: 'nwse-resize',\n type: 'corner',\n },\n x,\n y,\n {\n x: bounds.minX,\n y: bounds.minY,\n width: bounds.maxX - bounds.minX,\n height: bounds.maxY - bounds.minY,\n },\n {\n x: bounds.minX,\n y: bounds.minY,\n width: bounds.maxX - bounds.minX,\n height: bounds.maxY - bounds.minY,\n }\n );\n\n return true;\n}\n\nexport interface StartMultiSelectionRotateParams {\n multiSelection: string[];\n elements: CanvasElement[];\n bounds: MultiSelectionGroupBounds;\n x: number;\n y: number;\n stateMachine: InteractionStateMachine;\n setIsRotating: (value: boolean) => void;\n setRotationStartAngle: (angle: number) => void;\n setCurrentRotation: (rotation: number) => void;\n}\n\n/**\n * Starts a rotation operation on multi-selection\n * Sets up state machine context for multi-selection rotation\n */\nexport function startMultiSelectionRotate(params: StartMultiSelectionRotateParams): boolean {\n const {\n multiSelection,\n elements,\n bounds,\n x,\n y,\n stateMachine,\n setIsRotating,\n setRotationStartAngle,\n setCurrentRotation,\n } = params;\n\n // Get all selected elements (excluding locked ones)\n const selectedElements = multiSelection\n .map((id) => elements.find((e) => e.id === id))\n .filter((e): e is CanvasElement => e !== undefined && !e.locked);\n\n // If all elements are locked, don't allow rotation\n if (selectedElements.length === 0) {\n return false;\n }\n\n const startAngle = calculateAngle(bounds.centerX, bounds.centerY, x, y);\n\n (stateMachine as AugmentedStateMachine)._multiSelectionRotationContext = {\n startBounds: { ...bounds },\n elementsStartData: selectedElements.map((element) => ({\n element,\n startData: element.getTransformStartData(),\n })),\n };\n\n setIsRotating(true);\n setRotationStartAngle(startAngle);\n setCurrentRotation(0);\n stateMachine.startRotate(\n selectedElements[0], // Use first element as reference\n x,\n y,\n startAngle,\n 0 // Initial rotation for the group\n );\n\n return true;\n}\n\nexport interface StartMultiSelectionDragParams {\n multiSelection: string[];\n elements: CanvasElement[];\n x: number;\n y: number;\n stateMachine: InteractionStateMachine;\n dragOriginalElementsRef: React.MutableRefObject<Map<string, CanvasElement> | null>;\n setHoverState: (state: Omit<LocalHoverState, 'startTime'>) => void;\n}\n\n/**\n * Starts a drag operation on multi-selection\n * Sets up state machine context for multi-selection group drag\n */\nexport function startMultiSelectionDrag(params: StartMultiSelectionDragParams): boolean {\n const { multiSelection, elements, x, y, stateMachine, dragOriginalElementsRef, setHoverState } = params;\n\n // Get all selected elements (excluding locked ones)\n const selectedElements = multiSelection\n .map((id) => elements.find((e) => e.id === id))\n .filter((e): e is CanvasElement => e !== undefined && !e.locked);\n\n if (selectedElements.length === 0) {\n return false;\n }\n\n // Store start data for all selected elements to enable group dragging\n (stateMachine as AugmentedStateMachine)._multiSelectionDragContext = {\n startX: x,\n startY: y,\n elementsStartData: selectedElements.map((element) => ({\n element,\n startData: element.getTransformStartData(),\n })),\n currentElements: new Map(), // Track current elements during drag\n };\n\n // Store original elements before drag for single undo entry\n dragOriginalElementsRef.current = new Map();\n selectedElements.forEach((element) => {\n dragOriginalElementsRef.current!.set(element.id, element.clone());\n });\n\n // Start drag with the first element as reference\n stateMachine.startDrag(selectedElements[0], x, y);\n setHoverState({ type: null, data: null }); // Clear hover state\n\n return true;\n}\n","/**\n * Crop Mode Interaction Handlers\n * Handles crop mode interactions for images (both standalone and within groups)\n */\n\nimport { TextElement } from '../../../core/TextElement.js';\nimport { ImageElement } from '../../../core/ImageElement.js';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { PathElement } from '../../../core/PathElement.js';\nimport type { InteractionStateMachine } from '../../../core/InteractionStateMachine.js';\nimport type { TransformHandles } from '../../../core/TransformHandles.js';\nimport type { HandleInfo, ResizeAnchor } from '../../../types/index.js';\n\n/** Return type from ImageElement.hitTestCropHandle() */\ninterface CropHandleHitResult {\n type: 'corner' | 'edge';\n anchor: string;\n worldX: number;\n worldY: number;\n}\n\n/** Elements that can be on the canvas */\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\n/** Elements that can be children of a group (including nested groups) */\ntype ChildElement = TextElement | ImageElement | ShapeElement | PathElement | GroupElement;\n\nexport interface CropModeContext {\n croppingImage: ImageElement;\n croppingChild: ImageElement | null;\n croppingChildIndex: number;\n isChildInGroup: boolean;\n}\n\nexport interface FindCroppingImageParams {\n selectedElement: CanvasElement | null;\n activeChildElement: ChildElement | null;\n}\n\n/**\n * Finds the image currently in crop mode (either standalone or child in group)\n */\nexport function findCroppingImage(params: FindCroppingImageParams): CropModeContext | null {\n const { selectedElement, activeChildElement } = params;\n\n let croppingChild: ImageElement | null = null;\n let croppingChildIndex = -1;\n\n // First check if activeChildElement is in crop mode\n if (activeChildElement instanceof ImageElement && activeChildElement.isCropping) {\n croppingChild = activeChildElement;\n // Find the index in the group\n if (selectedElement instanceof GroupElement) {\n croppingChildIndex = selectedElement.children.findIndex((c) => c.id === activeChildElement.id);\n }\n } else if (selectedElement instanceof GroupElement) {\n // Otherwise look for any child in crop mode\n for (let i = 0; i < selectedElement.children.length; i++) {\n const child = selectedElement.children[i];\n // Skip invisible children\n if (child.visible === false) continue;\n\n if (child instanceof ImageElement && child.isCropping) {\n croppingChild = child;\n croppingChildIndex = i;\n break;\n }\n }\n }\n\n // Determine the actual cropping image (child or standalone)\n const croppingImage =\n croppingChild || (selectedElement instanceof ImageElement && selectedElement.isCropping ? selectedElement : null);\n\n if (!croppingImage) {\n return null;\n }\n\n return {\n croppingImage,\n croppingChild,\n croppingChildIndex,\n isChildInGroup: croppingChild !== null,\n };\n}\n\nexport type CropModeHitResult =\n | { type: 'crop-handle'; handle: CropHandleHitResult }\n | { type: 'image-handle'; handle: HandleInfo }\n | { type: 'image-drag' }\n | { type: 'exit-crop' }\n | { type: 'none' };\n\nexport interface CropModeHitTestParams {\n x: number;\n y: number;\n cropContext: CropModeContext;\n transformHandles: TransformHandles;\n zoom?: number;\n}\n\n/**\n * Hit test for crop mode interactions\n * Priority: crop handles > image handles > image drag > exit crop mode\n */\nexport function hitTestCropMode(params: CropModeHitTestParams): CropModeHitResult {\n const { x, y, cropContext, transformHandles, zoom = 1.0 } = params;\n const { croppingImage } = cropContext;\n\n // Priority 1: Check if clicking on crop box handles (for resizing crop area)\n const cropHandle = croppingImage.hitTestCropHandle(x, y, zoom);\n if (cropHandle) {\n return { type: 'crop-handle', handle: cropHandle };\n }\n\n // Priority 2: Check if clicking on image corner handles (for scaling the image)\n const imageHandle = transformHandles.hitTestResize(x, y, zoom);\n if (imageHandle) {\n return { type: 'image-handle', handle: imageHandle };\n }\n\n // Priority 3: Check if clicking inside the full image (for dragging image within crop)\n if (croppingImage.hitTest(x, y)) {\n return { type: 'image-drag' };\n }\n\n // Priority 4: Clicking outside exits crop mode\n return { type: 'exit-crop' };\n}\n\nexport interface HandleCropHandleClickParams {\n cropContext: CropModeContext;\n cropHandle: CropHandleHitResult;\n x: number;\n y: number;\n selectedElement: CanvasElement | null;\n activeChildElement: ChildElement | null;\n stateMachine: InteractionStateMachine;\n}\n\n/**\n * Handles clicking on a crop box handle (resize crop area)\n */\nexport function handleCropHandleClick(params: HandleCropHandleClickParams): void {\n const { cropContext, cropHandle, x, y, selectedElement, activeChildElement, stateMachine } = params;\n const { croppingImage, croppingChild, croppingChildIndex } = cropContext;\n\n const cropBox = croppingImage.getCropBoxBounds();\n if (!cropBox) return;\n\n stateMachine.startResize(\n croppingImage,\n {\n // Crop anchors use a 'crop-' prefix to distinguish from regular resize anchors.\n // The ResizeHandler checks for this prefix via anchor.startsWith('crop-').\n // This cast is necessary because ResizeAnchor doesn't include crop-prefixed variants.\n anchor: ('crop-' + cropHandle.anchor) as ResizeAnchor,\n x: 0,\n y: 0,\n cursor: 'grab',\n type: cropHandle.type,\n },\n x,\n y,\n cropBox,\n cropBox\n );\n\n // Store group context if editing child\n if (croppingChild) {\n // Use activeChildContext if we came from child editing mode\n if (activeChildElement && activeChildElement.id === croppingChild.id) {\n stateMachine._activeChildContext = {\n group: selectedElement as GroupElement,\n childId: croppingChild.id,\n };\n } else {\n stateMachine._cropGroupContext = {\n group: selectedElement!,\n childIndex: croppingChildIndex,\n };\n }\n }\n}\n\nexport interface HandleImageHandleClickParams {\n cropContext: CropModeContext;\n imageHandle: HandleInfo;\n x: number;\n y: number;\n selectedElement: CanvasElement | null;\n activeChildElement: ChildElement | null;\n stateMachine: InteractionStateMachine;\n}\n\n/**\n * Handles clicking on an image corner handle (scale image)\n */\nexport function handleImageHandleClick(params: HandleImageHandleClickParams): void {\n const { cropContext, imageHandle, x, y, selectedElement, activeChildElement, stateMachine } = params;\n const { croppingImage, croppingChild, croppingChildIndex } = cropContext;\n\n const bbox = croppingImage.getBoundingBox();\n const visualBbox = croppingImage.getVisualBoundingBox();\n stateMachine.startResize(croppingImage, imageHandle, x, y, bbox, visualBbox);\n\n // Store group context if editing child\n if (croppingChild) {\n // Use activeChildContext if we came from child editing mode\n if (activeChildElement && activeChildElement.id === croppingChild.id) {\n stateMachine._activeChildContext = {\n group: selectedElement as GroupElement,\n childId: croppingChild.id,\n };\n } else {\n stateMachine._cropGroupContext = {\n group: selectedElement!,\n childIndex: croppingChildIndex,\n };\n }\n }\n}\n\nexport interface HandleImageDragClickParams {\n cropContext: CropModeContext;\n x: number;\n y: number;\n selectedElement: CanvasElement | null;\n activeChildElement: ChildElement | null;\n stateMachine: InteractionStateMachine;\n dragOriginalElementsRef: React.MutableRefObject<Map<string, CanvasElement> | null>;\n setHoverState: (state: { type: null; data: null }) => void;\n}\n\n/**\n * Handles clicking inside the image (drag image within crop)\n */\nexport function handleImageDragClick(params: HandleImageDragClickParams): void {\n const { cropContext, x, y, selectedElement, activeChildElement, stateMachine, dragOriginalElementsRef, setHoverState } = params;\n const { croppingImage, croppingChild, croppingChildIndex } = cropContext;\n\n // Store original element before drag for single undo entry\n dragOriginalElementsRef.current = new Map();\n if (croppingChild && selectedElement) {\n // If cropping a child, store the parent group\n dragOriginalElementsRef.current.set(selectedElement.id, selectedElement.clone());\n } else {\n dragOriginalElementsRef.current.set(croppingImage.id, croppingImage.clone());\n }\n\n stateMachine.startDrag(croppingImage, x, y, 'crop-image-drag');\n setHoverState({ type: null, data: null }); // Clear hover state\n\n // Store group context if editing child\n if (croppingChild) {\n // Use activeChildContext if we came from child editing mode\n if (activeChildElement && activeChildElement.id === croppingChild.id) {\n stateMachine._activeChildContext = {\n group: selectedElement as GroupElement,\n childId: croppingChild.id,\n };\n } else {\n stateMachine._cropGroupContext = {\n group: selectedElement!,\n childIndex: croppingChildIndex,\n };\n }\n }\n}\n\nexport interface ExitCropModeParams {\n cropContext: CropModeContext;\n selectedElement: CanvasElement | null;\n onElementUpdate: (element: CanvasElement) => void;\n transformHandles: TransformHandles;\n updateActiveChild: (child: ChildElement | null) => void;\n zoom: number;\n}\n\n/**\n * Exits crop mode when clicking outside the image\n */\nexport function exitCropMode(params: ExitCropModeParams): void {\n const { cropContext, selectedElement, onElementUpdate, transformHandles, updateActiveChild, zoom } = params;\n const { croppingImage, croppingChild, croppingChildIndex } = cropContext;\n\n if (croppingChild && selectedElement instanceof GroupElement) {\n // Exit crop mode for child in group\n const updatedChild = croppingChild.clone();\n updatedChild.exitCropMode();\n const updatedGroup = selectedElement.clone();\n updatedGroup.children[croppingChildIndex] = updatedChild;\n onElementUpdate(updatedGroup);\n transformHandles.update(updatedGroup, zoom);\n updateActiveChild(null);\n } else {\n // Exit crop mode for direct image\n const updatedElement = croppingImage.clone();\n updatedElement.exitCropMode();\n onElementUpdate(updatedElement);\n transformHandles.update(updatedElement, zoom);\n }\n}\n","/**\n * Group Child Interaction Handlers\n * Handles selection and interaction with children within groups\n */\n\nimport { TextElement } from '../../../core/TextElement.js';\nimport { ImageElement } from '../../../core/ImageElement.js';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport type { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { PathElement } from '../../../core/PathElement.js';\nimport type { InteractionStateMachine } from '../../../core/InteractionStateMachine.js';\nimport type { TransformHandles } from '../../../core/TransformHandles.js';\nimport type { LocalHoverState } from '../types/index.js';\n\n/** Elements that can be on the canvas */\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\n/** Elements that can be children of a group (including nested groups) */\ntype ChildElement = TextElement | ImageElement | ShapeElement | PathElement | GroupElement;\n\nexport interface HandleSiblingClickParams {\n x: number;\n y: number;\n activeChildElement: ChildElement;\n selectedElement: GroupElement;\n updateActiveChild: (child: ChildElement | null) => void;\n transformHandles: TransformHandles;\n setHandlesVersion: React.Dispatch<React.SetStateAction<number>>;\n stateMachine: InteractionStateMachine;\n dragOriginalElementsRef: React.MutableRefObject<Map<string, CanvasElement> | null>;\n setHoverState: (state: Omit<LocalHoverState, 'startTime'>) => void;\n forceUpdate: (value: Record<string, unknown>) => void;\n zoom: number;\n}\n\n/**\n * Handles clicking on a sibling element when in active child mode\n * Selects the sibling and starts drag if clicked\n * Returns true if a sibling was clicked, false otherwise\n */\nexport function handleSiblingClick(params: HandleSiblingClickParams): boolean {\n const {\n x,\n y,\n activeChildElement,\n selectedElement,\n updateActiveChild,\n transformHandles,\n setHandlesVersion,\n stateMachine,\n dragOriginalElementsRef,\n setHoverState,\n forceUpdate,\n zoom,\n } = params;\n\n // Check if clicking on a sibling\n for (let i = selectedElement.children.length - 1; i >= 0; i--) {\n const child = selectedElement.children[i];\n\n // Skip invisible children and the currently active child\n if (child.visible === false || child.id === activeChildElement.id) continue;\n\n if (child.hitTest(x, y)) {\n\n // Select this sibling\n updateActiveChild(child as ImageElement | TextElement);\n transformHandles.update(child, zoom);\n setHandlesVersion((v) => v + 1);\n\n // Start drag for the newly selected sibling\n stateMachine.startDrag(child, x, y);\n\n // Store original element before drag for single undo entry\n dragOriginalElementsRef.current = new Map();\n dragOriginalElementsRef.current.set(child.id, child.clone());\n\n // Clear hover state when starting drag\n setHoverState({ type: null, data: null });\n\n // Store group context\n (stateMachine as unknown as Record<string, unknown>)._activeChildContext = {\n group: selectedElement,\n childId: child.id,\n };\n\n forceUpdate({});\n return true;\n }\n }\n\n return false;\n}\n\nexport interface ExitActiveChildModeParams {\n updateActiveChild: (child: ChildElement | null) => void;\n selectedElement: CanvasElement | null;\n transformHandles: TransformHandles;\n setHandlesVersion: React.Dispatch<React.SetStateAction<number>>;\n zoom: number;\n}\n\n/**\n * Exits active child editing mode\n * Called when clicking outside all children in a group\n */\nexport function exitActiveChildMode(params: ExitActiveChildModeParams): void {\n const { updateActiveChild, selectedElement, transformHandles, setHandlesVersion, zoom } = params;\n\n updateActiveChild(null);\n if (selectedElement) {\n transformHandles.update(selectedElement, zoom);\n setHandlesVersion((v) => v + 1);\n }\n}\n\nexport interface HandleElementSelectionParams {\n x: number;\n y: number;\n shiftKey: boolean;\n activeArtboardElements: CanvasElement[];\n selectedElement: CanvasElement | null;\n multiSelection: string[];\n onSelectionChange: (id: string | null) => void;\n onMultiSelectionChange: (ids: string[]) => void;\n stateMachine: InteractionStateMachine;\n dragOriginalElementsRef: React.MutableRefObject<Map<string, CanvasElement> | null>;\n setHoverState: (state: Omit<LocalHoverState, 'startTime'>) => void;\n forceUpdate: (value: Record<string, unknown>) => void;\n}\n\n/**\n * Handles clicking on elements in the canvas for selection\n * Supports shift-click multi-selection and normal selection\n * Returns true if an element was clicked, false otherwise\n */\nexport function handleElementSelection(params: HandleElementSelectionParams): boolean {\n const {\n x,\n y,\n shiftKey,\n activeArtboardElements,\n selectedElement,\n multiSelection,\n onSelectionChange,\n onMultiSelectionChange,\n stateMachine,\n dragOriginalElementsRef,\n setHoverState,\n forceUpdate,\n } = params;\n\n for (let i = activeArtboardElements.length - 1; i >= 0; i--) {\n // Skip invisible elements. Locked elements remain selectable —\n // they just can't be manipulated (drag/resize/rotate are gated\n // by `if (locked) return` in the gesture handlers). Selecting a\n // locked element lets the user open the lock toggle in the\n // selection chip without first having to unlock via a panel.\n if (activeArtboardElements[i].visible === false) continue;\n\n if (activeArtboardElements[i].hitTest(x, y)) {\n\n const clickedId = activeArtboardElements[i].id;\n\n // Shift-click: add/remove from multi-selection\n if (shiftKey) {\n\n // If there's a single selected element, convert it to multi-selection\n if (selectedElement && multiSelection.length === 0) {\n onMultiSelectionChange([selectedElement.id, clickedId]);\n onSelectionChange(null);\n } else {\n // Toggle element in multi-selection\n if (multiSelection.includes(clickedId)) {\n // Remove from selection\n const newSelection = multiSelection.filter((id) => id !== clickedId);\n onMultiSelectionChange(newSelection);\n } else {\n // Add to selection\n onMultiSelectionChange([...multiSelection, clickedId]);\n }\n }\n return true;\n }\n\n // Check if clicking on an element that's already in the multi-selection\n if (multiSelection.length > 0 && multiSelection.includes(clickedId)) {\n\n // Get all selected elements (excluding locked ones)\n const selectedElements = multiSelection\n .map((id) => activeArtboardElements.find((e) => e.id === id))\n .filter((e): e is CanvasElement => e !== undefined && !e.locked);\n\n if (selectedElements.length > 0) {\n // Store start data for all selected elements to enable group dragging\n (stateMachine as unknown as Record<string, unknown>)._multiSelectionDragContext = {\n startX: x,\n startY: y,\n elementsStartData: selectedElements.map((element) => ({\n element,\n startData: element.getTransformStartData(),\n })),\n currentElements: new Map(), // Track current elements during drag\n };\n\n // Store original elements before drag for single undo entry\n dragOriginalElementsRef.current = new Map();\n selectedElements.forEach((element) => {\n dragOriginalElementsRef.current!.set(element.id, element.clone());\n });\n\n // Start drag with the first element as reference\n stateMachine.startDrag(selectedElements[0], x, y);\n setHoverState({ type: null, data: null }); // Clear hover state\n forceUpdate({});\n return true;\n }\n }\n\n // Normal click: select only this element\n onSelectionChange(clickedId);\n onMultiSelectionChange([]); // Clear multi-selection\n\n // Store original element before drag for single undo entry\n dragOriginalElementsRef.current = new Map();\n dragOriginalElementsRef.current.set(clickedId, activeArtboardElements[i].clone());\n\n stateMachine.startDrag(activeArtboardElements[i], x, y);\n setHoverState({ type: null, data: null }); // Clear hover state\n\n // For groups, store the original element reference to clone from during drag\n if (activeArtboardElements[i] instanceof GroupElement) {\n (stateMachine as unknown as Record<string, unknown>)._groupDragOriginalElement = activeArtboardElements[i].clone();\n }\n\n forceUpdate({});\n return true;\n }\n }\n\n return false;\n}\n","/**\n * useCanvasInteraction - Canvas pointer/mouse event handlers\n *\n * Extracted from CanvasEditor.tsx to isolate all interaction logic including:\n * - handlePointerDown: Selection, drag start, resize start, rotation start, crop, pen tool\n * - handlePointerMove: Drag, resize, rotation updates, hover cursor, spacing detection\n * - handlePointerUp: Commit changes, clear contexts, text edit mode entry\n * - handleDoubleClick: Enter text edit or crop mode\n * - Document-level event forwarding for out-of-canvas interactions\n */\n\nimport React, { useCallback, useEffect, useRef } from 'react';\nimport { TextElement } from '../../../core/TextElement.js';\nimport { ImageElement } from '../../../core/ImageElement.js';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport { ShapeElement } from '../../../core/ShapeElement.js';\nimport { PathElement } from '../../../core/PathElement.js';\nimport type { BaseElement } from '../../../core/BaseElement.js';\nimport { CustomTransform } from '../../../transforms/CustomTransform.js';\nimport { useViewportContext } from '../../../contexts/ViewportContext.js';\n\nimport type { TransformHandles } from '../../../core/TransformHandles.js';\nimport type { InteractionStateMachine, ActiveChildContext } from '../../../core/InteractionStateMachine.js';\nimport type { AlignmentSnapSystem } from '../../../core/AlignmentSnapSystem.js';\nimport type { SpacingSystem } from '../../../core/SpacingSystem.js';\nimport { globalResizePipeline } from '../../../core/ResizePipeline.js';\nimport { constrainDragDelta } from '../../../core/GestureConstraints.js';\nimport { PinchHandler } from '../../../core/PinchHandler.js';\nimport { calculateAngle, getBoundingBoxCenter, getCursorForWorldPosition } from '../../../core/GeometryUtils.js';\nimport { Transform } from '../../../core/Transform.js';\nimport { RotationUtils } from '../../../core/RotationUtils.js';\nimport { clickProtection } from '../../../utils/clickProtection.js';\nimport { shouldPreserveSelection } from '../../../utils/selectionPreservation.js';\nimport { getCursorIndexFromPoint } from '../../../utils/textCursorUtils.js';\nimport { getWordBoundaryAt } from '../../../utils/textCursorUtils.js';\nimport type { SnapGuide, SpacingIndicator, RichText, CharacterStyle, ImageTransformData, ResizeAnchor, TransformStartData } from '../../../types/index.js';\nimport type { LocalHoverState } from '../types/index.js';\nimport type { MultiSelectionOBB, MultiSelectionGroupBounds } from '../renderers/index.js';\n\nimport {\n hitTestMultiSelection,\n startMultiSelectionResize,\n startMultiSelectionRotate,\n startMultiSelectionDrag,\n findCroppingImage,\n hitTestCropMode,\n handleCropHandleClick,\n handleImageHandleClick,\n handleImageDragClick,\n exitCropMode,\n handleSiblingClick,\n exitActiveChildMode,\n handleElementSelection,\n} from '../handlers/index.js';\n\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\ntype ChildElement = TextElement | ImageElement | ShapeElement | PathElement | GroupElement;\n\n// ---------------------------------------------------------------------------\n// Ad-hoc context types used on InteractionStateMachine\n//\n// The InteractionStateMachine is extended at runtime with temporary properties\n// that live only for the duration of a single interaction (drag/resize/rotate).\n// These interfaces describe those properties so we can avoid `as any`.\n// ---------------------------------------------------------------------------\n\n/** Context attached when the user clicks a group child (pending selection on pointerUp) */\ntype PendingChildSelection = ChildElement;\n\n/** Context for pending text edit mode entry (set on pointerDown, consumed on pointerUp) */\ninterface PendingTextEditMode {\n elementId: string;\n originalX: number;\n originalY: number;\n clickX: number;\n clickY: number;\n}\n\n/** Per-element start data stored in multi-selection drag/resize/rotation contexts */\ninterface MultiElementStartData {\n element: CanvasElement;\n startData: {\n x: number;\n y: number;\n width: number;\n height: number;\n rotation: number;\n transformData: ImageTransformData | Record<string, unknown>;\n [key: string]: unknown;\n };\n}\n\n/**\n * Minimum absolute area (in world / artboard pixels) an element\n * must have inside the clipped zone for the focus filter to\n * restrict its hit-area to inside that zone. Roughly a 30×30 world\n * pixel region — large enough to reject sliver overlaps that the\n * user couldn't intentionally tap, small enough that any element\n * with a visible presence in the clipped zone qualifies.\n */\nconst HIT_FILTER_MIN_VISIBLE_AREA = 900;\n\n/**\n * Gate element hit-tests by clipped-zone overlap.\n *\n * If an element has at least `HIT_FILTER_MIN_VISIBLE_AREA` square\n * world-pixels INSIDE the clipped rect, we treat it as \"primarily\n * visible inside\" — taps outside the rect won't select it, so a\n * user can't grab an element from the dim overflow next to it.\n * Below that threshold the overlap is too small to count as\n * \"visibly present in the clipped area\" and we let tap-anywhere\n * behaviour stand (covers neighbour-piece elements that just\n * graze the clipped area).\n *\n * Absolute area instead of a percentage handles the case where the\n * element is huge but the clipped area is small — the percentage\n * rule was missing those because the element's total area dwarfed\n * the overlap.\n *\n * No-op when `focusedPieceRect` is null (no clipped area defined).\n *\n * @internal — exposed for tests; not part of the public canvas API.\n */\nexport function isHitAllowedInFocus(\n element: BaseElement,\n pointX: number,\n pointY: number,\n focusedPieceRect:\n | { x: number; y: number; width: number; height: number }\n | null\n | undefined,\n): boolean {\n if (!focusedPieceRect) return true;\n const bbox = element.getVisualBoundingBox();\n const ix = Math.max(bbox.x, focusedPieceRect.x);\n const iy = Math.max(bbox.y, focusedPieceRect.y);\n const ix2 = Math.min(\n bbox.x + bbox.width,\n focusedPieceRect.x + focusedPieceRect.width,\n );\n const iy2 = Math.min(\n bbox.y + bbox.height,\n focusedPieceRect.y + focusedPieceRect.height,\n );\n const overlapArea = Math.max(0, ix2 - ix) * Math.max(0, iy2 - iy);\n if (overlapArea < HIT_FILTER_MIN_VISIBLE_AREA) return true;\n // Enough of the element sits inside the clipped zone — only honour\n // clicks that also land inside it.\n const insideFocus =\n pointX >= focusedPieceRect.x &&\n pointX <= focusedPieceRect.x + focusedPieceRect.width &&\n pointY >= focusedPieceRect.y &&\n pointY <= focusedPieceRect.y + focusedPieceRect.height;\n return insideFocus;\n}\n\n/** Multi-selection drag context */\ninterface MultiSelectionDragContext {\n startX: number;\n startY: number;\n elementsStartData: MultiElementStartData[];\n currentElements?: Map<string, CanvasElement>;\n currentDelta?: { dx: number; dy: number };\n startOBB?: MultiSelectionOBB;\n}\n\n/** Multi-selection resize context */\ninterface MultiSelectionResizeContext {\n startBounds: { minX: number; maxX: number; minY: number; maxY: number };\n anchor: string;\n elementsStartData: MultiElementStartData[];\n}\n\n/** Multi-selection rotation context */\ninterface MultiSelectionRotationContext {\n startBounds: { centerX: number; centerY: number };\n elementsStartData: MultiElementStartData[];\n currentRotation: number;\n}\n\n/** Extended context data that may contain a cached element map */\ninterface ExtendedInteractionContext {\n mode: string | null;\n dragMode?: string | null;\n startX: number;\n startY: number;\n startData: Record<string, unknown>;\n activeHandle: unknown;\n element: CanvasElement | null;\n _cachedElement?: Record<string, CanvasElement>;\n}\n\n/**\n * Augmented InteractionStateMachine type that includes the temporary ad-hoc\n * properties used during interactions. This avoids `as any` throughout the file.\n */\ntype AugmentedStateMachine = InteractionStateMachine & {\n _pendingChildSelection?: PendingChildSelection;\n _pendingTextEditMode?: PendingTextEditMode;\n _groupDragOriginalElement?: CanvasElement;\n _multiSelectionDragContext?: MultiSelectionDragContext;\n _multiSelectionResizeContext?: MultiSelectionResizeContext;\n _multiSelectionRotationContext?: MultiSelectionRotationContext;\n};\n\n/** PenTool interface - only the methods needed by interaction handlers */\ninterface PenToolLike {\n isActive(): boolean;\n canClosePath(x: number, y: number, threshold: number): boolean;\n startClosing(x: number, y: number): void;\n startDraggingHandle(): void;\n addPoint(x: number, y: number): void;\n isDraggingHandle(): boolean;\n updateHandle(x: number, y: number): void;\n setPreviewPoint(x: number, y: number): void;\n finishDraggingHandle(): void;\n isClosingPath(): boolean;\n editPath(element: PathElement): void;\n finishPath(): void;\n deleteLastPoint(): void;\n}\n\n/** CropController interface - mirrors CropModeController methods */\ninterface CropControllerLike {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- context types from InteractionStateMachine are complex; CropModeController handles narrowing\n handleCropImageDrag(image: BaseElement, context: any, x: number, y: number): {\n x: number; y: number; cropX: number; cropY: number; cropWidth: number; cropHeight: number;\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- context types from InteractionStateMachine are complex; CropModeController handles narrowing\n handleCropBoxResize(image: BaseElement, anchor: string, resizeContext: any, context: any, x: number, y: number): {\n x: number; y: number; rotation: number; width: number; height: number;\n cropX: number; cropY: number; cropWidth: number; cropHeight: number; cursor: string;\n };\n}\n\nexport interface UseCanvasInteractionParams {\n // Refs\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n inputRef: React.RefObject<HTMLTextAreaElement | null>;\n repositioningCursorRef: React.MutableRefObject<boolean>;\n lastClickTimeRef: React.MutableRefObject<number>;\n clickCountRef: React.MutableRefObject<number>;\n lastProcessedClickTimeRef: React.MutableRefObject<number>;\n lastMultiClickTimeRef: React.MutableRefObject<number>;\n multiClickSelectionRef: React.MutableRefObject<{ start: number; end: number; cursor: number } | null>;\n skipDragSelectionRef: React.MutableRefObject<boolean>;\n draggingSelectionHandleRef: React.MutableRefObject<'start' | 'end' | null>;\n selectionHandlePositionsRef: React.MutableRefObject<{\n start: { center: { x: number; y: number }; textEdge: { x: number; y: number } } | null;\n end: { center: { x: number; y: number }; textEdge: { x: number; y: number } } | null;\n }>;\n lastMousePosRef: React.MutableRefObject<{ x: number; y: number } | null>;\n dragOriginalElementsRef: React.MutableRefObject<Map<string, CanvasElement> | null>;\n rotatingElementRef: React.MutableRefObject<CanvasElement | null>;\n editingChildIdRef: React.MutableRefObject<string | null>;\n editRichTextRef: React.MutableRefObject<RichText | null>;\n editModeEnteredAtRef: React.MutableRefObject<number>;\n multiSelectionGroupBoundsRef: React.MutableRefObject<MultiSelectionGroupBounds | null>;\n elementsRef: React.MutableRefObject<CanvasElement[]>;\n\n // Constants\n MULTI_CLICK_THRESHOLD: number;\n MULTI_CLICK_DEBOUNCE: number;\n CLICK_DEDUP_THRESHOLD: number;\n\n // State values\n isEditing: boolean;\n editText: string;\n isTouchDevice: boolean;\n selectionStart: number;\n selectionEnd: number;\n isRotating: boolean;\n isMarqueeSelecting: boolean;\n isAltKeyPressed: boolean;\n selectedId: string | null;\n selectedElement: CanvasElement | undefined;\n activeChildElement: ChildElement | null;\n elements: CanvasElement[];\n multiSelection: string[];\n multiSelectionOBBRef: React.MutableRefObject<MultiSelectionOBB | null>;\n zoom: number;\n paddingOffsetX: number;\n paddingOffsetY: number;\n spacingIndicators: SpacingIndicator[];\n /**\n * ADR-0060 focused-piece rect (in artboard coords). When set,\n * elements whose visible-bbox sits primarily inside this rect can\n * only be hit-tested by pointer positions also inside the rect —\n * a tap on the dim overflow next to a \"focused\" element no longer\n * selects it. Lets users tap dim regions without accidentally\n * grabbing elements that happen to extend into them.\n */\n focusedPieceRect?: { x: number; y: number; width: number; height: number } | null;\n\n // State setters\n setCursorPosition: React.Dispatch<React.SetStateAction<number>>;\n setSelectionStart: React.Dispatch<React.SetStateAction<number>>;\n setSelectionEnd: React.Dispatch<React.SetStateAction<number>>;\n setIsRotating: (v: boolean) => void;\n setRotationStartAngle: (v: number) => void;\n setCurrentRotation: (v: number) => void;\n setRotatingElementVersion: React.Dispatch<React.SetStateAction<number>>;\n setIsAltKeyPressed: (v: boolean) => void;\n setSnapGuides: (v: SnapGuide[]) => void;\n setSpacingIndicators: (v: SpacingIndicator[]) => void;\n setHandlesVersion: React.Dispatch<React.SetStateAction<number>>;\n setHoverState: (v: Omit<LocalHoverState, 'startTime'>) => void;\n setElements: React.Dispatch<React.SetStateAction<CanvasElement[]>>;\n forceUpdate: (v: object) => void;\n\n // Systems\n stateMachine: AugmentedStateMachine;\n transformHandles: TransformHandles;\n snapSystem: AlignmentSnapSystem;\n spacingSystem: SpacingSystem;\n penTool: PenToolLike;\n cropController: CropControllerLike;\n\n // Callbacks\n onElementUpdate: (element: CanvasElement) => void;\n executeElementUpdate: (oldElement: CanvasElement | undefined, newElement: CanvasElement) => void;\n onSelectionChange: (id: string | null) => void;\n onMultiSelectionChange: (ids: string[]) => void;\n onRotationStateChange?: (isRotating: boolean) => void;\n onCropModeEnter?: () => void;\n /**\n * Fired by {@link CanvasInteractionResult.handleContextMenu} after the\n * right-click target has been hit-tested and selected. The host renders\n * the actual menu UI (see `CanvasContextMenu`). When omitted, right-click\n * still never enters the select/drag pipeline (the button-2 interception\n * below), and the browser's native menu is left alone.\n */\n onContextMenuRequest?: (request: CanvasContextMenuRequest) => void;\n updateActiveChild: (child: ChildElement | null) => void;\n getActiveArtboardElements: () => CanvasElement[];\n startMarqueeSelection: (x: number, y: number) => void;\n updateMarqueeSelection: (x: number, y: number, elements: CanvasElement[]) => void;\n finishMarqueeSelection: (onMultiSelectionChange: (ids: string[]) => void) => void;\n enterTextEditMode: (element: TextElement, worldX: number, worldY: number) => void;\n\n // Text editing state/setters for double-click entering edit mode\n setEditPosition: (pos: { x: number; y: number; width: number; height: number }) => void;\n setEditText: (v: string) => void;\n setEditRichText: (v: RichText | null) => void;\n setIsEditing: (v: boolean) => void;\n setCurrentFormattingStyle: React.Dispatch<React.SetStateAction<CharacterStyle | null>>;\n getWrappedTextForEditing: (element: TextElement) => string;\n}\n\n/**\n * Payload handed to {@link UseCanvasInteractionParams.onContextMenuRequest}\n * when the user right-clicks (or long-presses, where the platform\n * synthesizes a `contextmenu` event) on the canvas. By the time this fires,\n * selection has already been updated Figma-style: the element under the\n * cursor is selected (an existing multi-selection is preserved when the\n * target is part of it), or the selection is left untouched on empty canvas.\n */\nexport interface CanvasContextMenuRequest {\n /** Viewport coords of the gesture — position the menu here. */\n clientX: number;\n clientY: number;\n /** World/artboard coords of the gesture. */\n worldX: number;\n worldY: number;\n /** Topmost hit element id, or null when the gesture landed on empty canvas. */\n elementId: string | null;\n}\n\nexport interface CanvasInteractionResult {\n handlePointerDown: (e: React.PointerEvent<HTMLCanvasElement>) => void;\n handlePointerMove: (e: React.PointerEvent<HTMLCanvasElement>) => void;\n handlePointerUp: (e: React.PointerEvent<HTMLCanvasElement>) => void;\n handleDoubleClick: (e: React.MouseEvent<HTMLCanvasElement>) => void;\n handleContextMenu: (e: React.MouseEvent<HTMLCanvasElement>) => void;\n}\n\nexport function useCanvasInteraction(params: UseCanvasInteractionParams): CanvasInteractionResult {\n const {\n canvasRef,\n inputRef,\n repositioningCursorRef,\n lastClickTimeRef,\n clickCountRef,\n lastProcessedClickTimeRef,\n lastMultiClickTimeRef,\n multiClickSelectionRef,\n skipDragSelectionRef,\n draggingSelectionHandleRef,\n selectionHandlePositionsRef,\n lastMousePosRef,\n dragOriginalElementsRef,\n rotatingElementRef,\n editingChildIdRef,\n editRichTextRef,\n editModeEnteredAtRef,\n multiSelectionGroupBoundsRef,\n MULTI_CLICK_THRESHOLD,\n MULTI_CLICK_DEBOUNCE,\n CLICK_DEDUP_THRESHOLD,\n isEditing,\n editText,\n isTouchDevice,\n selectionStart,\n selectionEnd,\n isRotating,\n isMarqueeSelecting,\n isAltKeyPressed,\n selectedElement,\n activeChildElement,\n elements,\n multiSelection,\n multiSelectionOBBRef,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n spacingIndicators,\n focusedPieceRect,\n setCursorPosition,\n setSelectionStart,\n setSelectionEnd,\n setIsRotating,\n setRotationStartAngle,\n setCurrentRotation,\n setRotatingElementVersion,\n setIsAltKeyPressed,\n setSnapGuides,\n setSpacingIndicators,\n setHandlesVersion,\n setHoverState,\n setElements,\n forceUpdate,\n stateMachine,\n transformHandles,\n snapSystem,\n spacingSystem,\n penTool,\n cropController,\n onElementUpdate,\n executeElementUpdate,\n onSelectionChange,\n onMultiSelectionChange,\n onRotationStateChange,\n onCropModeEnter,\n onContextMenuRequest,\n updateActiveChild,\n getActiveArtboardElements,\n startMarqueeSelection,\n updateMarqueeSelection,\n finishMarqueeSelection,\n enterTextEditMode,\n setEditPosition,\n setEditText,\n setEditRichText,\n setIsEditing,\n setCurrentFormattingStyle,\n getWrappedTextForEditing,\n } = params;\n\n // Cache canvas bounding rect to avoid forced layout on every pointermove event.\n // The document-level pointermove handler fires hundreds of times per second during\n // drag, so calling getBoundingClientRect() inline causes continuous forced reflow.\n // Cache is updated on scroll, resize, and zoom change instead.\n const canvasBoundsRef = useRef<DOMRect | null>(null);\n\n // Multi-touch pinch tracking. The Pointer Events API delivers each\n // finger as its own pointerId — we collect the live world position\n // of every active pointer here. When a second pointer lands while\n // an element is selected, the pointer-down handler enters the\n // state machine's PINCHING mode and snapshots which two pointer\n // ids drive the gesture (handle further fingers gracefully — they\n // get tracked but ignored). On pointer-up we drop the entry; the\n // last-of-the-two leaving ends pinch.\n // Tracks every active pointer's positions. World coords (x/y) are\n // used by the element-resize pinch (matches the rest of the canvas\n // hit-test math); screen coords (clientX/clientY) are used by the\n // canvas-zoom pinch (works directly in CSS pixels relative to the\n // viewport, no zoom/pan round-trip needed).\n const activePointersRef = useRef<\n Map<number, { x: number; y: number; clientX: number; clientY: number }>\n >(new Map());\n const pinchPointerIdsRef = useRef<{ a: number; b: number } | null>(null);\n // Capture the original element snapshot at gesture start so the\n // commit step has a `before` to pass into `executeElementUpdate`\n // for clean undo/redo.\n const pinchOriginalElementRef = useRef<BaseElement | null>(null);\n\n // What selection existed BEFORE the user started touching the canvas.\n // Captured on the very first pointerdown of a gesture (when\n // `activePointersRef.size` is about to go from 0 → 1) and used by\n // the pinch entry path to decide: pinch resizes only when the user\n // had ALREADY selected an element prior to the gesture. If the\n // first pointer auto-selected something and a second pointer\n // arrives right after, that's a zoom-pinch on an unselected\n // element — revert the auto-select instead of resizing.\n const selectedIdAtGestureStartRef = useRef<string | null>(null);\n\n // When the user pointer-downs on the currently-selected element,\n // we tag this id as a \"deselect candidate\". Cleared if the gesture\n // turns into a drag (movement past threshold) or a multi-touch\n // pinch. On pointerup with no movement, the candidate fires and\n // we deselect — closes the \"tap to deselect\" loop. Skipped for\n // text elements (their tap-on-selected already enters edit mode).\n const pendingDeselectIdRef = useRef<string | null>(null);\n const pendingDeselectStartPosRef = useRef<{ x: number; y: number } | null>(null);\n\n // Canvas-zoom pinch state. When the user pinches on empty canvas\n // (or an unselected element — see Fix #1) we DON'T resize an\n // element; we zoom + pan the viewport. We capture the start state\n // here and apply scale + centroid drift on each pointermove.\n const canvasZoomPinchRef = useRef<{\n pointerA: number;\n pointerB: number;\n startUserZoom: number;\n startPanOffset: { x: number; y: number };\n startCentroidClient: { x: number; y: number };\n startDistance: number;\n /** Container's screen-left at gesture start, used to convert\n * cursor clientX into container-relative pixels for the pan\n * formula. Reading the container rect once at start avoids\n * per-frame layout churn. */\n containerRect: { left: number; top: number };\n } | null>(null);\n\n // Single-finger pan state. Active when the user is zoomed in\n // (userZoom > 1) and starts a pointer drag on empty canvas. At\n // fit zoom there's nothing to pan to, so we fall back to the\n // existing marquee-select behaviour and don't enter this mode.\n const singleFingerPanRef = useRef<{\n pointerId: number;\n startClientX: number;\n startClientY: number;\n startPanOffset: { x: number; y: number };\n /** Movement threshold (screen px). Until exceeded, the gesture\n * could still resolve as a tap rather than a drag, so we hold\n * off committing pan updates AND keep `pendingTap` live. */\n movedPastThreshold: boolean;\n /** What this gesture *would* fire on pointerup if it stays a\n * tap (no movement past threshold). When the gesture lands on\n * an unselected element at zoom > 1, we record the selection\n * intent here so a tap-to-select still works while a real\n * drag becomes a pan. `null` means \"this was a pan over empty\n * space, no tap action\". */\n pendingTap:\n | { kind: 'select-element'; elementId: string; x: number; y: number; shiftKey: boolean }\n | { kind: 'deselect' }\n | null;\n } | null>(null);\n\n const { userZoom, setUserZoom, panOffset, setPanOffset } = useViewportContext();\n const userZoomRef = useRef(userZoom);\n userZoomRef.current = userZoom;\n const panOffsetRef = useRef(panOffset);\n panOffsetRef.current = panOffset;\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n const updateBounds = () => {\n canvasBoundsRef.current = canvas.getBoundingClientRect();\n };\n updateBounds();\n window.addEventListener('scroll', updateBounds, { passive: true, capture: true });\n window.addEventListener('resize', updateBounds, { passive: true });\n return () => {\n window.removeEventListener('scroll', updateBounds, { capture: true });\n window.removeEventListener('resize', updateBounds);\n };\n }, [canvasRef]);\n\n // Re-cache when zoom changes — that resizes the canvas and shifts\n // its position. We deliberately do NOT depend on `panOffset` here:\n // a single-finger pan can fire panOffset updates 60×/sec, and a\n // synchronous `getBoundingClientRect` per update causes forced\n // reflows under sustained drag — observable as iOS Safari memory\n // pressure / spontaneous page refresh. `handlePointerDown`\n // explicitly refreshes the cache on every pointerdown, which\n // covers the \"drag-after-pan-without-releasing\" race that\n // originally motivated panOffset's inclusion.\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n canvasBoundsRef.current = canvas.getBoundingClientRect();\n }, [zoom, canvasRef]);\n\n // ============================================\n // POINTER DOWN\n // ============================================\n const handlePointerDown = useCallback(\n (e: React.PointerEvent<HTMLCanvasElement>) => {\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n // RIGHT-CLICK — context-menu interception (gotcha #18: intercept\n // at the top + early return). The right button must NEVER enter\n // the select/drag pipeline below — selection + menu-open happen in\n // `handleContextMenu` (the `contextmenu` event the browser fires\n // for the same gesture). Returning before `activePointersRef` is\n // touched guarantees no drag / pinch / pan / pending-tap state is\n // created for the right button, so a right-click can't move an\n // element or start a marquee. Left-click behaviour is untouched.\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n if (e.button === 2) return;\n\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const rect = canvas.getBoundingClientRect();\n // Sync the move-handler's bounds cache to the same fresh rect\n // pointer-down is using, so the first pointer-move can't end up\n // computing world coords against a stale cached rect (which\n // surfaced as an element-jump-on-drag-start after pan/pinch).\n canvasBoundsRef.current = rect;\n const x = (e.clientX - rect.left) / zoom - paddingOffsetX;\n const y = (e.clientY - rect.top) / zoom - paddingOffsetY;\n\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n // PINCH GESTURE — second-pointer interception\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n // Track every active pointer's world position. When the second\n // pointer lands and an element is selected (and we're not in a\n // modal mode like text-edit or crop), abandon the existing\n // single-pointer flow and start a pinch gesture. This means\n // two-finger gestures take over from corner-handle dragging\n // for resize on touch devices — natural UX, matches iOS /\n // native apps.\n //\n // BEFORE inserting into the active set, capture what was\n // selected when the gesture began. The pinch entry below uses\n // this to distinguish \"pinch on already-selected element\"\n // (resize) from \"pinch landed on an element while unselected\"\n // (zoom-pinch — should NOT auto-select + resize).\n if (activePointersRef.current.size === 0) {\n selectedIdAtGestureStartRef.current = selectedElement?.id ?? null;\n }\n activePointersRef.current.set(e.pointerId, {\n x,\n y,\n clientX: e.clientX,\n clientY: e.clientY,\n });\n\n const isModalMode =\n isEditing ||\n (selectedElement instanceof ImageElement && selectedElement.isCropping) ||\n penTool.isActive();\n\n // ━━━ Two-finger gesture routing ━━━\n // Element-resize pinch only fires when:\n // 1) An element was selected BEFORE the gesture (not auto-\n // selected by the first pointerdown — Fix #1), AND\n // 2) At least one finger landed ON the selected element's\n // hit area.\n // Otherwise the gesture is a canvas-zoom pinch (pan + zoom\n // the viewport). This lets users always pan with two fingers\n // by pinching in empty space, even when something is selected.\n if (activePointersRef.current.size === 2 && !isModalMode) {\n const ids = Array.from(activePointersRef.current.keys());\n const a = activePointersRef.current.get(ids[0])!;\n const b = activePointersRef.current.get(ids[1])!;\n const wasSelectedBefore = selectedIdAtGestureStartRef.current !== null;\n const hitSelectedElement =\n wasSelectedBefore &&\n selectedElement &&\n !selectedElement.locked &&\n (selectedElement.hitTest(a.x, a.y) || selectedElement.hitTest(b.x, b.y));\n\n if (hitSelectedElement) {\n // Element-resize pinch. Existing path; falls through to the\n // block below for the actual `startPinch` call.\n } else {\n // Canvas-zoom pinch. If the first pointerdown auto-selected\n // and started dragging an element, undo that side-effect:\n // restore the element to its pre-drag position, clear the\n // selection, kill the drag, drop pending tap-side-effects.\n if (dragOriginalElementsRef.current && dragOriginalElementsRef.current.size > 0) {\n const originals = dragOriginalElementsRef.current;\n setElements((prev) =>\n prev.map((el) => {\n const original = originals.get(el.id);\n return original ? (original.clone() as typeof el) : el;\n }),\n );\n dragOriginalElementsRef.current = null;\n }\n if (!wasSelectedBefore && selectedElement) {\n onSelectionChange(null);\n }\n if (stateMachine.isInteracting()) {\n stateMachine.end();\n }\n // Critical: kill any in-flight single-finger pan from the\n // first pointer. Without this, finger 1's pointermoves\n // continue to drive `panOffset` *in addition to* the pinch\n // handler updating it, producing jitter and runaway pan\n // (the symptom: pinch jumps and content disappears).\n singleFingerPanRef.current = null;\n pendingDeselectIdRef.current = null;\n pendingDeselectStartPosRef.current = null;\n delete stateMachine._pendingTextEditMode;\n delete stateMachine._pendingChildSelection;\n\n const dx = b.clientX - a.clientX;\n const dy = b.clientY - a.clientY;\n const distance = Math.hypot(dx, dy);\n const container =\n (canvas.parentElement?.parentElement as HTMLElement | null) ?? canvas;\n const containerRect = container.getBoundingClientRect();\n canvasZoomPinchRef.current = {\n pointerA: ids[0],\n pointerB: ids[1],\n startUserZoom: userZoomRef.current,\n startPanOffset: { ...panOffsetRef.current },\n startCentroidClient: {\n x: (a.clientX + b.clientX) / 2,\n y: (a.clientY + b.clientY) / 2,\n },\n startDistance: Math.max(distance, 1),\n containerRect: { left: containerRect.left, top: containerRect.top },\n };\n try {\n canvas.setPointerCapture(e.pointerId);\n } catch {\n /* non-fatal */\n }\n e.preventDefault();\n return;\n }\n }\n\n if (\n activePointersRef.current.size === 2 &&\n selectedElement &&\n !selectedElement.locked &&\n !isModalMode\n ) {\n // If a single-pointer interaction is in progress, commit it\n // as its own undo step before pinch starts. The user's\n // mental model is \"I dragged here, then pinched\" — splitting\n // those into two undo steps is fine and avoids splicing two\n // unrelated geometry deltas into one history entry.\n if (stateMachine.isInteracting()) {\n const ctx = stateMachine.getContext();\n if (ctx.element) {\n // The state machine holds the cloned element at its\n // current (live-updated) position. The matching element\n // in the elements array is the `before` snapshot.\n const original = getActiveArtboardElements().find(\n (el) => el.id === ctx.element!.id,\n );\n executeElementUpdate(original, ctx.element as CanvasElement);\n }\n stateMachine.end();\n }\n\n const ids = Array.from(activePointersRef.current.keys());\n pinchPointerIdsRef.current = { a: ids[0], b: ids[1] };\n const p1 = activePointersRef.current.get(ids[0])!;\n const p2 = activePointersRef.current.get(ids[1])!;\n\n // Pinch supersedes a tap — no deselect should fire when the\n // gesture finishes. Also kill any in-flight single-finger\n // pan from the first pointer so it doesn't drive panOffset\n // in parallel with the pinch resize.\n pendingDeselectIdRef.current = null;\n pendingDeselectStartPosRef.current = null;\n singleFingerPanRef.current = null;\n\n pinchOriginalElementRef.current = selectedElement.clone();\n stateMachine.startPinch(selectedElement, p1, p2);\n\n try {\n canvas.setPointerCapture(e.pointerId);\n } catch {\n // setPointerCapture can throw if the pointer is already\n // captured by another element — non-fatal, the document-\n // level pointermove listeners will still receive events.\n }\n e.preventDefault();\n return;\n }\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n // Handle clicks while editing text\n if (isEditing && selectedElement instanceof TextElement) {\n // Check if tapping on a selection handle first (iOS-style selection adjustment)\n if (isTouchDevice && selectionStart !== selectionEnd) {\n const handlePositions = selectionHandlePositionsRef.current;\n if (handlePositions.start || handlePositions.end) {\n const rotationRad = RotationUtils.toRadiansInverse(selectedElement.rotation);\n const center = selectedElement.getRotationAnchor();\n const dx = x - center.x;\n const dy = y - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = dx * cos - dy * sin;\n const localRotatedY = dx * sin + dy * cos;\n const localX = localRotatedX;\n const localY = localRotatedY;\n\n const hitRadius = 44 / zoom;\n\n if (handlePositions.start) {\n const distStart = Math.sqrt(\n Math.pow(localX - handlePositions.start.center.x, 2) +\n Math.pow(localY - handlePositions.start.center.y, 2)\n );\n if (distStart <= hitRadius) {\n e.preventDefault();\n draggingSelectionHandleRef.current = 'start';\n inputRef.current?.focus();\n return;\n }\n }\n\n if (handlePositions.end) {\n const distEnd = Math.sqrt(\n Math.pow(localX - handlePositions.end.center.x, 2) +\n Math.pow(localY - handlePositions.end.center.y, 2)\n );\n if (distEnd <= hitRadius) {\n e.preventDefault();\n draggingSelectionHandleRef.current = 'end';\n inputRef.current?.focus();\n return;\n }\n }\n }\n }\n\n // Check if clicking on the same text element - if so, reposition cursor\n if (selectedElement.hitTest(x, y)) {\n e.preventDefault();\n repositioningCursorRef.current = true;\n\n const visualBbox = selectedElement.getVisualBoundingBox();\n const rotationRad = RotationUtils.toRadiansInverse(selectedElement.rotation);\n const center = selectedElement.getRotationAnchor();\n const dx = x - center.x;\n const dy = y - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = dx * cos - dy * sin;\n const localRotatedY = dx * sin + dy * cos;\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = localRotatedY + visualBbox.height / 2;\n\n const clickIndex = getCursorIndexFromPoint(selectedElement, { x: relativeX, y: relativeY });\n\n const now = Date.now();\n\n if (now - lastProcessedClickTimeRef.current < CLICK_DEDUP_THRESHOLD) {\n inputRef.current?.focus();\n return;\n }\n lastProcessedClickTimeRef.current = now;\n\n if (now - lastMultiClickTimeRef.current >= MULTI_CLICK_DEBOUNCE) {\n multiClickSelectionRef.current = null;\n }\n\n const timeSinceLastClick = now - lastClickTimeRef.current;\n\n if (timeSinceLastClick < MULTI_CLICK_THRESHOLD) {\n clickCountRef.current += 1;\n } else {\n clickCountRef.current = 1;\n }\n lastClickTimeRef.current = now;\n\n const currentText = editText;\n\n if (clickCountRef.current >= 3) {\n e.preventDefault();\n lastMultiClickTimeRef.current = now;\n multiClickSelectionRef.current = { start: 0, end: currentText.length, cursor: currentText.length };\n skipDragSelectionRef.current = true;\n clickCountRef.current = 0;\n setCursorPosition(currentText.length);\n setSelectionStart(0);\n setSelectionEnd(currentText.length);\n if (inputRef.current) {\n inputRef.current.focus();\n inputRef.current.setSelectionRange(0, currentText.length);\n }\n } else if (clickCountRef.current === 2) {\n e.preventDefault();\n lastMultiClickTimeRef.current = now;\n skipDragSelectionRef.current = true;\n const wordBounds = getWordBoundaryAt(currentText, clickIndex);\n multiClickSelectionRef.current = { start: wordBounds.start, end: wordBounds.end, cursor: wordBounds.end };\n setCursorPosition(wordBounds.end);\n setSelectionStart(wordBounds.start);\n setSelectionEnd(wordBounds.end);\n if (inputRef.current) {\n inputRef.current.focus();\n inputRef.current.setSelectionRange(wordBounds.start, wordBounds.end);\n }\n } else {\n multiClickSelectionRef.current = null;\n setCursorPosition(clickIndex);\n setSelectionStart(clickIndex);\n setSelectionEnd(clickIndex);\n if (inputRef.current) {\n inputRef.current.focus();\n inputRef.current.setSelectionRange(clickIndex, clickIndex);\n }\n skipDragSelectionRef.current = false;\n }\n return;\n }\n multiClickSelectionRef.current = null;\n inputRef.current?.blur();\n return;\n }\n\n if (isEditing) return;\n\n (e.target as HTMLCanvasElement).setPointerCapture(e.pointerId);\n\n // Handle pen tool mode\n if (penTool.isActive() && selectedElement instanceof PathElement) {\n const relX = x - selectedElement.x;\n const relY = y - selectedElement.y;\n\n if (penTool.canClosePath(relX, relY, 15)) {\n penTool.startClosing(relX, relY);\n penTool.startDraggingHandle();\n setHandlesVersion((v) => v + 1);\n return;\n }\n\n penTool.addPoint(relX, relY);\n penTool.startDraggingHandle();\n setHandlesVersion((v) => v + 1);\n return;\n }\n\n // Check multi-selection group bounds handles\n if (multiSelection.length > 0 && multiSelectionGroupBoundsRef.current) {\n const bounds = multiSelectionGroupBoundsRef.current;\n\n const hitResult = hitTestMultiSelection({ x, y, bounds, multiSelection, elements, zoom });\n\n if (hitResult.type === 'corner-handle') {\n const success = startMultiSelectionResize({\n multiSelection,\n elements,\n bounds,\n anchor: hitResult.anchor,\n corner: hitResult.corner,\n x,\n y,\n stateMachine,\n });\n if (success) {\n forceUpdate({});\n return;\n }\n } else if (hitResult.type === 'rotation-handle') {\n const success = startMultiSelectionRotate({\n multiSelection,\n elements,\n bounds,\n x,\n y,\n stateMachine,\n setIsRotating,\n setRotationStartAngle,\n setCurrentRotation,\n });\n if (success) {\n forceUpdate({});\n return;\n }\n } else if (hitResult.type === 'bounds-drag') {\n const success = startMultiSelectionDrag({\n multiSelection,\n elements,\n x,\n y,\n stateMachine,\n dragOriginalElementsRef,\n setHoverState,\n });\n if (success) {\n forceUpdate({});\n return;\n }\n }\n }\n\n const workingElement = activeChildElement || selectedElement;\n\n // Locked workingElement → skip the entire rotation / resize /\n // drag-on-selected branch and fall through to the single-finger\n // pan + tap-to-cycle path. This makes a locked layer behave\n // \"like nothing is selected\" for gesture routing (pan / zoom),\n // while still letting the user see the selection chip and lock\n // toggle. Tapping pans, pinching zooms, repeated taps cycle\n // through stacked elements (handled by my pendingTap branch).\n if (workingElement && workingElement.visible !== false && !workingElement.locked && (multiSelection.length === 0 || activeChildElement)) {\n // Handle crop mode interactions\n const cropContext = findCroppingImage({ selectedElement: selectedElement || null, activeChildElement });\n if (cropContext) {\n const cropHitResult = hitTestCropMode({ x, y, cropContext, transformHandles, zoom });\n\n if (cropHitResult.type === 'crop-handle') {\n handleCropHandleClick({\n cropContext,\n cropHandle: cropHitResult.handle,\n x,\n y,\n selectedElement: selectedElement || null,\n activeChildElement,\n stateMachine,\n });\n forceUpdate({});\n return;\n } else if (cropHitResult.type === 'image-handle') {\n handleImageHandleClick({\n cropContext,\n imageHandle: cropHitResult.handle,\n x,\n y,\n selectedElement: selectedElement || null,\n activeChildElement,\n stateMachine,\n });\n forceUpdate({});\n return;\n } else if (cropHitResult.type === 'image-drag') {\n handleImageDragClick({\n cropContext,\n x,\n y,\n selectedElement: selectedElement || null,\n activeChildElement,\n stateMachine,\n dragOriginalElementsRef,\n setHoverState,\n });\n forceUpdate({});\n return;\n } else if (cropHitResult.type === 'exit-crop') {\n exitCropMode({\n cropContext,\n selectedElement: selectedElement || null,\n onElementUpdate,\n transformHandles,\n updateActiveChild,\n zoom,\n });\n setHandlesVersion((v) => v + 1);\n return;\n }\n }\n\n // Check rotation handle\n if (transformHandles.hitTestRotation(x, y, zoom)) {\n if (workingElement.locked) return;\n\n const bbox = workingElement.getBoundingBox();\n const center = getBoundingBoxCenter(bbox);\n const startAngle = calculateAngle(center.x, center.y, x, y);\n\n setIsRotating(true);\n setRotationStartAngle(startAngle);\n setCurrentRotation(workingElement.rotation);\n stateMachine.startRotate(workingElement, x, y, startAngle, workingElement.rotation);\n\n if (activeChildElement && selectedElement instanceof GroupElement) {\n stateMachine._activeChildContext = {\n group: selectedElement,\n childId: activeChildElement.id,\n };\n }\n\n // A tap (no movement past threshold) on the rotation handle\n // should still count as a tap for the cycle/deselect logic.\n // For small elements the handles extend well outside the\n // element's hit area; without this, those taps would just\n // get eaten by the handle hit-test and never reach pointer-\n // up's cycle path.\n pendingDeselectIdRef.current = workingElement.id;\n pendingDeselectStartPosRef.current = { x, y };\n\n forceUpdate({});\n return;\n }\n\n // Check resize handles\n const handle = transformHandles.hitTestResize(x, y, zoom);\n if (handle) {\n if (workingElement.locked) return;\n\n const bbox = workingElement.getBoundingBox();\n const visualBbox = workingElement.getVisualBoundingBox();\n stateMachine.startResize(workingElement, handle, x, y, bbox, visualBbox);\n\n // Same as the rotation handle: tap-without-drag on a resize\n // handle should fall through to the cycle/deselect logic on\n // pointerup. Resize itself fires only when the pointer moves\n // past the threshold.\n pendingDeselectIdRef.current = workingElement.id;\n pendingDeselectStartPosRef.current = { x, y };\n\n if (activeChildElement && selectedElement instanceof GroupElement) {\n stateMachine._activeChildContext = {\n group: selectedElement,\n childId: activeChildElement.id,\n };\n }\n\n forceUpdate({});\n return;\n }\n\n // Check if clicking on element itself (for drag).\n // No `isHitAllowedInFocus` gate here, unlike the cycle/select\n // paths: once an element is already selected, the user has\n // explicitly committed to it, and the 30%-blur overflow halo\n // is rendered specifically to let them grab the element from\n // outside the piece's clipped area. The focus filter is for\n // disambiguating *which* element to select among a stack —\n // it shouldn't block dragging the one the user already chose.\n if (workingElement.hitTest(x, y)) {\n if (workingElement.locked) return;\n\n // (We previously had a \"higher-element handoff\" here that\n // would auto-select the topmost visible element above the\n // selected one when the user clicked on a stack. That\n // intercepts the tap before the cycle in pointerup runs,\n // and short-circuits a [A, B, C] stack into A↔B oscillation\n // — the cycle never reaches C. Cycling-down via\n // `pendingDeselect`'s cycle handler is now the single\n // source of truth for tap-on-stack semantics. To grab a\n // hidden-under-stack element, the user taps to cycle.)\n\n // If selected element is a group without an active child, store pending child selection\n if (selectedElement instanceof GroupElement && !activeChildElement) {\n const clickedChild = selectedElement.hitTestChild(x, y);\n if (clickedChild) {\n stateMachine._pendingChildSelection = clickedChild;\n }\n }\n\n // Tap on already-selected non-text element → tag for\n // deselect on pointerup (cleared if the user drags). For\n // text elements the existing pending-text-edit path takes\n // precedence — tap-on-selected-text enters edit mode.\n if (\n selectedElement &&\n workingElement.id === selectedElement.id &&\n !(workingElement instanceof TextElement)\n ) {\n pendingDeselectIdRef.current = workingElement.id;\n pendingDeselectStartPosRef.current = { x, y };\n }\n\n // If clicking on an already-selected text element, store pending edit mode\n if (\n workingElement instanceof TextElement &&\n !(workingElement instanceof GroupElement) &&\n selectedElement &&\n workingElement.id === selectedElement.id &&\n !isEditing\n ) {\n stateMachine._pendingTextEditMode = {\n elementId: workingElement.id,\n originalX: workingElement.x,\n originalY: workingElement.y,\n clickX: x,\n clickY: y,\n };\n }\n\n stateMachine.startDrag(workingElement, x, y);\n\n dragOriginalElementsRef.current = new Map();\n dragOriginalElementsRef.current.set(workingElement.id, workingElement.clone());\n\n setHoverState({ type: null, data: null });\n\n if (workingElement instanceof GroupElement) {\n stateMachine._groupDragOriginalElement = workingElement.clone();\n }\n\n if (activeChildElement && selectedElement instanceof GroupElement) {\n stateMachine._activeChildContext = {\n group: selectedElement,\n childId: activeChildElement.id,\n };\n }\n\n forceUpdate({});\n return;\n }\n }\n\n // Handle group child selection\n if (activeChildElement && selectedElement instanceof GroupElement) {\n const siblingClicked = handleSiblingClick({\n x,\n y,\n activeChildElement,\n selectedElement,\n updateActiveChild,\n transformHandles,\n setHandlesVersion,\n stateMachine,\n dragOriginalElementsRef,\n setHoverState,\n forceUpdate,\n zoom,\n });\n if (siblingClicked) return;\n\n exitActiveChildMode({ updateActiveChild, selectedElement: selectedElement || null, transformHandles, setHandlesVersion, zoom });\n return;\n }\n\n if (activeChildElement) {\n exitActiveChildMode({ updateActiveChild, selectedElement: selectedElement || null, transformHandles, setHandlesVersion, zoom });\n return;\n }\n\n // Single-finger pan also covers landing on an *unselected*\n // element. Auto-select + drag was exactly the unintentional\n // move users were reporting. Drag on the *already-selected*\n // element above (workingElement.hitTest branch) is unchanged,\n // so explicit tap-then-drag on a logo still moves it.\n //\n // Tap-to-select still works because we record the would-be\n // selection in `pendingTap` and only commit a pan once the\n // pointer moves past a small threshold; a true tap commits\n // the selection on pointerup instead.\n //\n // Applies at ALL zoom levels for consistency — at fit zoom a\n // pan is just a no-op visually, but the user still doesn't\n // get an unintended element grab. Shift-drag bypasses this so\n // shift-click multi-select keeps working on desktop.\n if (!e.shiftKey) {\n // Hit-test under the cursor and collect ALL hits so we can\n // cycle through stacked elements on repeated taps. Locked\n // elements are still selectable — they just can't be\n // manipulated (gesture routing falls through to pan/zoom\n // when a locked element is the current selection).\n const activeArtboardElements = getActiveArtboardElements();\n const hits: typeof activeArtboardElements = [];\n for (let i = activeArtboardElements.length - 1; i >= 0; i--) {\n const el = activeArtboardElements[i];\n if (el.visible === false) continue;\n if (!el.hitTest(x, y)) continue;\n if (!isHitAllowedInFocus(el, x, y, focusedPieceRect)) continue;\n hits.push(el);\n }\n // Resolve the pendingTap action by walking the cycle:\n // no current selection in hits → topmost hit\n // current is in hits, more below → next one down\n // current is in hits, at the bottom → deselect (don't wrap\n // back to top — feels\n // like \"I'm done with\n // this stack\")\n // no hits, something is selected → deselect (tap empty)\n // no hits, nothing selected → no-op\n let pendingTap: NonNullable<typeof singleFingerPanRef.current>['pendingTap'] = null;\n if (hits.length === 0) {\n if (selectedElement) pendingTap = { kind: 'deselect' };\n } else {\n const currentIdx = selectedElement\n ? hits.findIndex((el) => el.id === selectedElement.id)\n : -1;\n if (currentIdx === -1) {\n pendingTap = {\n kind: 'select-element',\n elementId: hits[0].id,\n x,\n y,\n shiftKey: e.shiftKey,\n };\n } else if (currentIdx + 1 < hits.length) {\n pendingTap = {\n kind: 'select-element',\n elementId: hits[currentIdx + 1].id,\n x,\n y,\n shiftKey: e.shiftKey,\n };\n } else {\n pendingTap = { kind: 'deselect' };\n }\n }\n singleFingerPanRef.current = {\n pointerId: e.pointerId,\n startClientX: e.clientX,\n startClientY: e.clientY,\n startPanOffset: { ...panOffsetRef.current },\n movedPastThreshold: false,\n pendingTap,\n };\n try {\n canvas.setPointerCapture(e.pointerId);\n } catch {\n /* non-fatal */\n }\n e.preventDefault();\n return;\n }\n\n // Handle element selection\n const activeArtboardElements = getActiveArtboardElements();\n const elementClicked = handleElementSelection({\n x,\n y,\n shiftKey: e.shiftKey,\n activeArtboardElements,\n selectedElement: selectedElement || null,\n multiSelection,\n onSelectionChange,\n onMultiSelectionChange,\n stateMachine,\n dragOriginalElementsRef,\n setHoverState,\n forceUpdate,\n });\n if (elementClicked) return;\n\n // Clicking on empty space.\n // - Zoomed in (userZoom > 1): start a single-finger pan instead\n // of marquee. The user wants to navigate; marquee select on a\n // zoomed-in canvas would mostly miss elements anyway.\n // - At fit (userZoom ≤ 1): existing marquee select behaviour,\n // panning is a no-op there since the whole canvas is already\n // visible.\n const isProtected = clickProtection.shouldProtectClick();\n const shouldPreserve = shouldPreserveSelection(e.target);\n if (!isProtected && !shouldPreserve) {\n if (userZoomRef.current > 1.001) {\n // Don't deselect — preserving selection while panning means\n // the user can keep their selected element + pan to see\n // another part of the design. No `pendingTap` here because\n // there's nothing under the finger to select.\n singleFingerPanRef.current = {\n pointerId: e.pointerId,\n startClientX: e.clientX,\n startClientY: e.clientY,\n startPanOffset: { ...panOffsetRef.current },\n movedPastThreshold: false,\n pendingTap: null,\n };\n try {\n canvas.setPointerCapture(e.pointerId);\n } catch {\n /* non-fatal */\n }\n e.preventDefault();\n } else {\n onSelectionChange(null);\n onMultiSelectionChange([]);\n startMarqueeSelection(x, y);\n }\n }\n },\n [\n isEditing,\n selectedElement,\n getActiveArtboardElements,\n onSelectionChange,\n onMultiSelectionChange,\n transformHandles,\n activeChildElement,\n multiSelection,\n stateMachine,\n penTool,\n executeElementUpdate,\n setHandlesVersion,\n zoom,\n setCursorPosition,\n setSelectionStart,\n setSelectionEnd,\n // Canvas-zoom pinch entry uses these setters / refs.\n canvasRef,\n setElements,\n focusedPieceRect,\n ]\n );\n\n // ============================================\n // POINTER MOVE\n // ============================================\n const handlePointerMove = useCallback(\n (e: React.PointerEvent<HTMLCanvasElement>) => {\n // Right-button-only drag: inert (see the matching interception in\n // handlePointerDown). `buttons === 2` means the right button is the\n // ONLY one held — moves with the left button down (1 or 3) keep\n // flowing so an in-progress left-drag is never frozen.\n if (e.buttons === 2) return;\n\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const rect = canvasBoundsRef.current ?? canvas.getBoundingClientRect();\n const x = (e.clientX - rect.left) / zoom - paddingOffsetX;\n const y = (e.clientY - rect.top) / zoom - paddingOffsetY;\n\n lastMousePosRef.current = { x, y };\n\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n // PINCH GESTURE — track + apply\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n // Always update the live pointer position, whether or not we're\n // currently pinching. (A third finger landed before pinch\n // started, say, would still want its position tracked so when\n // the user lifts a finger we have an up-to-date map.)\n if (activePointersRef.current.has(e.pointerId)) {\n activePointersRef.current.set(e.pointerId, {\n x,\n y,\n clientX: e.clientX,\n clientY: e.clientY,\n });\n }\n\n // Canvas-zoom pinch update — applies scale + pan to the\n // viewport so the world point under the gesture's start\n // centroid stays stable as fingers move. Checked BEFORE the\n // single-finger pan branch as a safety net: if both refs\n // somehow ended up populated (e.g. timing oddity), the pinch\n // wins. The pinch entry path itself also clears\n // `singleFingerPanRef`, so this should normally be redundant.\n if (canvasZoomPinchRef.current) {\n const z = canvasZoomPinchRef.current;\n const a = activePointersRef.current.get(z.pointerA);\n const b = activePointersRef.current.get(z.pointerB);\n if (a && b) {\n const dx = b.clientX - a.clientX;\n const dy = b.clientY - a.clientY;\n const currentDistance = Math.hypot(dx, dy);\n const scale = currentDistance / z.startDistance;\n const newUserZoom = Math.min(\n Math.max(z.startUserZoom * scale, 0.25),\n 8,\n );\n // Centroid in container-relative pixels (pre-transform\n // wrapper origin). Same coord frame `panOffset` is applied\n // in, so the formula below stays consistent.\n const startCentroidContainerX =\n z.startCentroidClient.x - z.containerRect.left;\n const startCentroidContainerY =\n z.startCentroidClient.y - z.containerRect.top;\n const currentCentroidContainerX =\n (a.clientX + b.clientX) / 2 - z.containerRect.left;\n const currentCentroidContainerY =\n (a.clientY + b.clientY) / 2 - z.containerRect.top;\n // Effective-zoom ratio drives how much the canvas content\n // grows; calculated via userZoom because calculatedZoom\n // (auto-fit) doesn't change during a gesture.\n const ratio = newUserZoom / z.startUserZoom;\n // Combined formula:\n // (a) zoom around the gesture's start centroid (keep that\n // world point under that screen point as scale changes),\n // (b) pan by the centroid's drift since gesture start.\n const rawNewPanX =\n currentCentroidContainerX -\n (startCentroidContainerX - z.startPanOffset.x) * ratio;\n const rawNewPanY =\n currentCentroidContainerY -\n (startCentroidContainerY - z.startPanOffset.y) * ratio;\n // Clamp panOffset so the canvas always overlaps the\n // container by at least `minVisible` px in each direction.\n // Without this, pinching past the user-zoom max cap (8×)\n // keeps drifting the centroid → panOffset grows unbounded\n // and content vanishes off-screen.\n //\n // The canvas DOM at this moment is at `userZoomRef.current`;\n // the impending `setUserZoom(newUserZoom)` will scale it\n // by `growthRatio`. We predict the post-render canvas dims\n // and clamp the new pan so the canvas stays anchored in\n // view at those predicted dims.\n const canvas2 = canvasRef.current;\n if (canvas2) {\n const canvasRect = canvas2.getBoundingClientRect();\n const containerEl = canvas2.parentElement?.parentElement;\n const containerWidth = containerEl?.clientWidth ?? canvasRect.width;\n const containerHeight = containerEl?.clientHeight ?? canvasRect.height;\n const minVisible = 80;\n const growthRatio = newUserZoom / Math.max(userZoomRef.current, 0.0001);\n const predictedCanvasWidth = canvasRect.width * growthRatio;\n const predictedCanvasHeight = canvasRect.height * growthRatio;\n // canvas-left in container = effectiveMarginX + panOffset.x.\n // After this update: shift by (rawNewPanX - currentPanX).\n const currentPanX = panOffsetRef.current.x;\n const currentPanY = panOffsetRef.current.y;\n const currentLeftInContainer = canvasRect.left - z.containerRect.left;\n const currentTopInContainer = canvasRect.top - z.containerRect.top;\n const predictedLeft = currentLeftInContainer + (rawNewPanX - currentPanX);\n const predictedTop = currentTopInContainer + (rawNewPanY - currentPanY);\n const clampedLeft = Math.min(\n Math.max(predictedLeft, -predictedCanvasWidth + minVisible),\n containerWidth - minVisible,\n );\n const clampedTop = Math.min(\n Math.max(predictedTop, -predictedCanvasHeight + minVisible),\n containerHeight - minVisible,\n );\n setUserZoom(newUserZoom);\n setPanOffset({\n x: rawNewPanX + (clampedLeft - predictedLeft),\n y: rawNewPanY + (clampedTop - predictedTop),\n });\n } else {\n setUserZoom(newUserZoom);\n setPanOffset({ x: rawNewPanX, y: rawNewPanY });\n }\n }\n return;\n }\n\n // Single-finger pan update — translate panOffset by the\n // pointer's screen-pixel delta. Below threshold the gesture\n // could still land as a tap, so we hold off updating panOffset\n // (and crucially keep `pendingTap` live) until the user moves\n // far enough that the intent is clearly a drag.\n if (\n singleFingerPanRef.current &&\n singleFingerPanRef.current.pointerId === e.pointerId\n ) {\n const p = singleFingerPanRef.current;\n const dx = e.clientX - p.startClientX;\n const dy = e.clientY - p.startClientY;\n if (!p.movedPastThreshold) {\n if (Math.hypot(dx, dy) > 5) {\n p.movedPastThreshold = true;\n // Movement committed: this is a pan, not a tap.\n p.pendingTap = null;\n } else {\n // Still might be a tap — don't move the canvas yet.\n return;\n }\n }\n // Pan is a no-op at fit zoom (1×) — the entire canvas is\n // already visible, there's nothing to navigate to. We still\n // mark the gesture as \"moved past threshold\" above so the\n // pendingTap resolves correctly (no spurious tap-on-release\n // after a drag), we just don't apply the offset.\n if (userZoomRef.current <= 1.001) return;\n setPanOffset({\n x: p.startPanOffset.x + dx,\n y: p.startPanOffset.y + dy,\n });\n return;\n }\n\n if (\n stateMachine.getMode() === 'pinch' &&\n pinchPointerIdsRef.current\n ) {\n const { a, b } = pinchPointerIdsRef.current;\n const p1 = activePointersRef.current.get(a);\n const p2 = activePointersRef.current.get(b);\n if (p1 && p2) {\n const gesture = stateMachine.updatePinch(p1, p2);\n const { element, startData } = stateMachine.getPinchContext();\n PinchHandler.calculatePinch({\n element,\n startData,\n scale: gesture.scale,\n rotationDelta: gesture.rotationDelta,\n startCentroid: gesture.startCentroid,\n currentCentroid: gesture.currentCentroid,\n snapRotation: snapSystem\n ? (rot, el) => snapSystem.snapRotation(rot, el)\n : undefined,\n });\n // Live-update the elements array so the canvas paints the\n // gesture in progress. Commit happens on pointer-up via\n // `executeElementUpdate` for undo/redo.\n setElements((prev) =>\n prev.map((el) =>\n el.id === element.id ? (element as CanvasElement) : el,\n ),\n );\n }\n e.preventDefault();\n return;\n }\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n // Handle selection handle dragging (iOS-style)\n if (draggingSelectionHandleRef.current && selectedElement instanceof TextElement) {\n const visualBbox = selectedElement.getVisualBoundingBox();\n const center = selectedElement.getRotationAnchor();\n const rotationRad = RotationUtils.toRadiansInverse(selectedElement.rotation);\n const dx = x - center.x;\n const dy = y - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = dx * cos - dy * sin;\n const localRotatedY = dx * sin + dy * cos;\n\n const handlePositions = selectionHandlePositionsRef.current;\n let yOffset = 0;\n if (draggingSelectionHandleRef.current === 'start' && handlePositions?.start) {\n yOffset = handlePositions.start.textEdge.y - handlePositions.start.center.y;\n } else if (draggingSelectionHandleRef.current === 'end' && handlePositions?.end) {\n yOffset = handlePositions.end.textEdge.y - handlePositions.end.center.y;\n }\n\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = (localRotatedY + yOffset) + visualBbox.height / 2;\n\n const dragIndex = getCursorIndexFromPoint(selectedElement, { x: relativeX, y: relativeY });\n\n if (draggingSelectionHandleRef.current === 'start') {\n if (dragIndex <= selectionEnd) {\n setSelectionStart(dragIndex);\n setCursorPosition(dragIndex);\n } else {\n setSelectionStart(selectionEnd);\n setSelectionEnd(dragIndex);\n setCursorPosition(dragIndex);\n draggingSelectionHandleRef.current = 'end';\n }\n } else {\n if (dragIndex >= selectionStart) {\n setSelectionEnd(dragIndex);\n setCursorPosition(dragIndex);\n } else {\n setSelectionEnd(selectionStart);\n setSelectionStart(dragIndex);\n setCursorPosition(dragIndex);\n draggingSelectionHandleRef.current = 'start';\n }\n }\n\n if (inputRef.current) {\n const start = Math.min(selectionStart, selectionEnd);\n const end = Math.max(selectionStart, selectionEnd);\n inputRef.current.setSelectionRange(start, end);\n }\n return;\n }\n\n // Handle pen tool mode\n if (penTool.isActive() && selectedElement instanceof PathElement) {\n if (penTool.isDraggingHandle()) {\n penTool.updateHandle(x - selectedElement.x, y - selectedElement.y);\n setHandlesVersion((v) => v + 1);\n } else {\n penTool.setPreviewPoint(x - selectedElement.x, y - selectedElement.y);\n setHandlesVersion((v) => v + 1);\n }\n return;\n }\n\n // Update Alt key state from mouse event\n if (e.altKey !== isAltKeyPressed) {\n setIsAltKeyPressed(e.altKey);\n }\n\n // Handle marquee selection\n if (isMarqueeSelecting) {\n updateMarqueeSelection(x, y, elements);\n return;\n }\n\n // Check if dragging multi-selection group\n if (stateMachine.getMode() === 'drag') {\n const multiDragContext = stateMachine._multiSelectionDragContext;\n\n if (multiDragContext && multiSelection.length > 0) {\n // Shift axis-lock (read per move event — see GestureConstraints)\n const { dx, dy } = constrainDragDelta(\n x - multiDragContext.startX,\n y - multiDragContext.startY,\n { shift: e.shiftKey }\n );\n\n if (!multiDragContext.currentElements) {\n multiDragContext.currentElements = new Map();\n }\n\n const updatedElements: CanvasElement[] = [];\n multiDragContext.elementsStartData.forEach((elementData: MultiElementStartData) => {\n const element = elementData.element;\n const startData = elementData.startData;\n\n const updatedElement = element.clone();\n updatedElement.x = startData.x + dx;\n updatedElement.y = startData.y + dy;\n\n multiDragContext.currentElements!.set(updatedElement.id, updatedElement);\n updatedElements.push(updatedElement);\n });\n\n setElements((prevElements) => prevElements.map((el) => updatedElements.find((ue) => ue.id === el.id) || el));\n\n multiDragContext.currentDelta = { dx, dy };\n\n if (!multiDragContext.startOBB && multiSelectionOBBRef.current) {\n multiDragContext.startOBB = { ...multiSelectionOBBRef.current };\n }\n\n setHandlesVersion((v) => v + 1);\n forceUpdate({});\n return;\n }\n }\n\n if (stateMachine.getMode() === 'drag' && selectedElement) {\n const dragMode = stateMachine.getDragMode();\n\n // Special handling for crop image drag\n if (dragMode === 'crop-image-drag') {\n const context = stateMachine.getContext();\n const draggedImage = context.element as ImageElement;\n\n if (draggedImage instanceof ImageElement && draggedImage.isCropping) {\n const result = cropController.handleCropImageDrag(draggedImage, context, x, y);\n\n const cropGroupContext = stateMachine._cropGroupContext;\n const activeChildContext = stateMachine._activeChildContext;\n const groupContext = cropGroupContext || activeChildContext;\n\n if (groupContext && selectedElement instanceof GroupElement) {\n const updatedChild = draggedImage.clone();\n updatedChild.x = result.x;\n updatedChild.y = result.y;\n updatedChild.updateCrop(result.cropX, result.cropY, result.cropWidth, result.cropHeight, false);\n\n const updatedGroup = selectedElement.clone();\n const childIndex = cropGroupContext\n ? cropGroupContext.childIndex\n : updatedGroup.children.findIndex((c) => c.id === (groupContext as ActiveChildContext).childId);\n if (childIndex >= 0) {\n updatedGroup.children[childIndex] = updatedChild;\n updatedGroup.updateBoundsFromChildren(true);\n }\n\n setElements((prevElements) => prevElements.map((el) => (el.id === updatedGroup.id ? updatedGroup : el)));\n\n const dragContext = stateMachine.getContext() as ExtendedInteractionContext;\n if (!dragContext._cachedElement) {\n dragContext._cachedElement = {};\n }\n dragContext._cachedElement[updatedGroup.id] = updatedGroup;\n\n if (activeChildContext && childIndex >= 0) {\n updateActiveChild(updatedGroup.children[childIndex]);\n transformHandles.update(updatedChild, zoom);\n }\n\n setHandlesVersion((v) => v + 1);\n } else {\n const updatedElement = draggedImage.clone();\n updatedElement.x = result.x;\n updatedElement.y = result.y;\n updatedElement.updateCrop(result.cropX, result.cropY, result.cropWidth, result.cropHeight, false);\n\n setElements((prevElements) =>\n prevElements.map((el) => (el.id === updatedElement.id ? updatedElement : el))\n );\n\n const dragContext = stateMachine.getContext() as ExtendedInteractionContext;\n if (!dragContext._cachedElement) {\n dragContext._cachedElement = {};\n }\n dragContext._cachedElement[updatedElement.id] = updatedElement;\n\n transformHandles.update(updatedElement, zoom);\n setHandlesVersion((v) => v + 1);\n }\n return;\n }\n }\n\n // Normal drag handling\n const activeChildContext = stateMachine._activeChildContext;\n\n const activeArtboardElements = getActiveArtboardElements();\n const flattenedElements: CanvasElement[] = [];\n activeArtboardElements.forEach((s) => {\n if (s instanceof GroupElement) {\n flattenedElements.push(...s.children);\n } else {\n flattenedElements.push(s);\n }\n });\n\n // Modifier state is read PER MOVE EVENT (not captured at pointerdown):\n // users press/release Shift mid-gesture. Shift = axis-lock the drag.\n const dragModifiers = { shift: e.shiftKey };\n let newPos = stateMachine.updateDrag(x, y, flattenedElements, dragModifiers);\n\n const elementForSpacingSnap =\n activeChildContext && selectedElement instanceof GroupElement\n ? selectedElement.children.find((c) => c.id === activeChildContext.childId)\n : selectedElement;\n\n if (elementForSpacingSnap) {\n newPos = spacingSystem.snapToSpacing(newPos, elementForSpacingSnap, flattenedElements);\n\n // Shift axis-lock: clamp the locked axis back to gesture start so\n // spacing snap (like alignment snap inside updateDrag) only ever\n // moves the element along the free axis.\n if (dragModifiers.shift) {\n const dragCtx = stateMachine.getContext();\n const dragSD = dragCtx.startData as TransformStartData;\n const { lockedAxis } = constrainDragDelta(x - dragCtx.startX, y - dragCtx.startY, dragModifiers);\n if (lockedAxis === 'x') {\n newPos.x = dragSD.x;\n } else if (lockedAxis === 'y') {\n newPos.y = dragSD.y;\n }\n }\n }\n\n if (activeChildContext && selectedElement instanceof GroupElement) {\n const updatedGroup = selectedElement.clone();\n const childIndex = updatedGroup.children.findIndex((c) => c.id === activeChildContext.childId);\n if (childIndex >= 0) {\n const updatedChild = updatedGroup.children[childIndex].clone();\n updatedChild.x = newPos.x;\n updatedChild.y = newPos.y;\n updatedGroup.children[childIndex] = updatedChild;\n updatedGroup.updateBoundsFromChildren(true);\n }\n\n setElements((prevElements) => prevElements.map((el) => (el.id === updatedGroup.id ? updatedGroup : el)));\n\n const dragContext = stateMachine.getContext() as ExtendedInteractionContext;\n if (!dragContext._cachedElement) {\n dragContext._cachedElement = {};\n }\n dragContext._cachedElement[updatedGroup.id] = updatedGroup;\n\n if (childIndex >= 0) {\n const newActiveChild = updatedGroup.children[childIndex];\n updateActiveChild(newActiveChild);\n transformHandles.update(newActiveChild, zoom);\n }\n\n setHandlesVersion((v) => v + 1);\n forceUpdate({});\n } else {\n const groupDragOriginal = stateMachine._groupDragOriginalElement;\n const updatedElement =\n selectedElement instanceof GroupElement && groupDragOriginal\n ? groupDragOriginal.clone()\n : selectedElement.clone();\n\n if (updatedElement instanceof GroupElement) {\n const context = stateMachine.getContext();\n const dragStartData = context.startData as TransformStartData;\n const dx = newPos.x - dragStartData.x;\n const dy = newPos.y - dragStartData.y;\n updatedElement.move(dx, dy);\n } else {\n updatedElement.x = newPos.x;\n updatedElement.y = newPos.y;\n }\n\n setElements((prevElements) => prevElements.map((el) => (el.id === updatedElement.id ? updatedElement : el)));\n transformHandles.update(updatedElement, zoom);\n\n const dragContext = stateMachine.getContext() as ExtendedInteractionContext;\n if (!dragContext._cachedElement) {\n dragContext._cachedElement = {};\n }\n dragContext._cachedElement[updatedElement.id] = updatedElement;\n }\n\n setHandlesVersion((v) => v + 1);\n setSnapGuides(snapSystem.getGuides());\n\n let movingElement: CanvasElement | undefined;\n if (activeChildContext && selectedElement instanceof GroupElement) {\n movingElement = selectedElement.children.find((c) => c.id === activeChildContext.childId);\n } else {\n const dragContext = stateMachine.getContext() as ExtendedInteractionContext;\n if (dragContext._cachedElement && selectedElement) {\n movingElement = dragContext._cachedElement[selectedElement.id];\n } else {\n movingElement = selectedElement || undefined;\n }\n }\n\n if (movingElement) {\n const indicators = spacingSystem.detectSpacing(movingElement, flattenedElements, isAltKeyPressed);\n setSpacingIndicators(indicators);\n }\n } else if (stateMachine.getMode() === 'resize') {\n const { dx, dy } = stateMachine.updateResize(x, y);\n const resizeContext = stateMachine.getResizeContext();\n\n // Handle multi-selection resize\n const multiResizeContext = stateMachine._multiSelectionResizeContext;\n if (multiResizeContext && multiSelection.length > 0) {\n const startBounds = multiResizeContext.startBounds;\n const anchor = multiResizeContext.anchor;\n\n let fixedX: number, fixedY: number;\n let draggedStartX: number, draggedStartY: number;\n\n switch (anchor) {\n case 'top-left':\n fixedX = startBounds.maxX; fixedY = startBounds.maxY;\n draggedStartX = startBounds.minX; draggedStartY = startBounds.minY;\n break;\n case 'top-right':\n fixedX = startBounds.minX; fixedY = startBounds.maxY;\n draggedStartX = startBounds.maxX; draggedStartY = startBounds.minY;\n break;\n case 'bottom-left':\n fixedX = startBounds.maxX; fixedY = startBounds.minY;\n draggedStartX = startBounds.minX; draggedStartY = startBounds.maxY;\n break;\n case 'bottom-right':\n fixedX = startBounds.minX; fixedY = startBounds.minY;\n draggedStartX = startBounds.maxX; draggedStartY = startBounds.maxY;\n break;\n default:\n fixedX = startBounds.minX; fixedY = startBounds.minY;\n draggedStartX = startBounds.maxX; draggedStartY = startBounds.maxY;\n }\n\n const draggedCurrentX = draggedStartX + dx;\n const draggedCurrentY = draggedStartY + dy;\n\n const startDistX = Math.abs(draggedStartX - fixedX);\n const startDistY = Math.abs(draggedStartY - fixedY);\n const newDistX = Math.abs(draggedCurrentX - fixedX);\n const newDistY = Math.abs(draggedCurrentY - fixedY);\n\n const scaleX = startDistX > 0 ? newDistX / startDistX : 1;\n const scaleY = startDistY > 0 ? newDistY / startDistY : 1;\n const scale = (scaleX + scaleY) / 2;\n\n const startWidth = startBounds.maxX - startBounds.minX;\n const startHeight = startBounds.maxY - startBounds.minY;\n const newWidth = startWidth * scale;\n const newHeight = startHeight * scale;\n\n let newMinX: number = fixedX, newMinY: number = fixedY;\n let newMaxX: number = fixedX, newMaxY: number = fixedY;\n\n switch (anchor) {\n case 'top-left':\n newMaxX = fixedX; newMaxY = fixedY;\n newMinX = fixedX - newWidth; newMinY = fixedY - newHeight;\n break;\n case 'top-right':\n newMinX = fixedX; newMaxY = fixedY;\n newMaxX = fixedX + newWidth; newMinY = fixedY - newHeight;\n break;\n case 'bottom-left':\n newMaxX = fixedX; newMinY = fixedY;\n newMinX = fixedX - newWidth; newMaxY = fixedY + newHeight;\n break;\n case 'bottom-right':\n newMinX = fixedX; newMinY = fixedY;\n newMaxX = fixedX + newWidth; newMaxY = fixedY + newHeight;\n break;\n }\n\n multiResizeContext.elementsStartData.forEach((elementData: MultiElementStartData) => {\n const element = elementData.element;\n const startData = elementData.startData;\n\n const offsetX = startData.x - fixedX;\n const offsetY = startData.y - fixedY;\n\n const newX = fixedX + offsetX * scale;\n const newY = fixedY + offsetY * scale;\n\n const updatedElement = element.clone();\n\n if (updatedElement instanceof ImageElement) {\n updatedElement.x = newX;\n updatedElement.y = newY;\n const imageData = updatedElement.transformData;\n const startImageData = startData.transformData as ImageTransformData;\n imageData.width = startImageData.width * scale;\n imageData.height = startImageData.height * scale;\n } else if (updatedElement instanceof GroupElement) {\n updatedElement.x = newX;\n updatedElement.y = newY;\n const newChildWidth = startData.width * scale;\n const newChildHeight = startData.height * scale;\n updatedElement.resize(anchor as ResizeAnchor, newChildWidth, newChildHeight, startData as unknown as TransformStartData);\n } else {\n const newElementWidth = startData.width * scale;\n const newElementHeight = startData.height * scale;\n updatedElement.resize(anchor as ResizeAnchor, newElementWidth, newElementHeight, startData as unknown as TransformStartData);\n updatedElement.x = newX;\n updatedElement.y = newY;\n }\n\n onElementUpdate(updatedElement);\n });\n\n if (multiSelectionOBBRef.current) {\n const newCenterX = (newMinX + newMaxX) / 2;\n const newCenterY = (newMinY + newMaxY) / 2;\n multiSelectionOBBRef.current = {\n centerX: newCenterX,\n centerY: newCenterY,\n width: newMaxX - newMinX,\n height: newMaxY - newMinY,\n rotation: multiSelectionOBBRef.current.rotation,\n };\n }\n\n return;\n }\n\n // Handle crop box resize\n const resizedImage = resizeContext.element as ImageElement;\n if (\n resizedImage instanceof ImageElement &&\n resizedImage.isCropping &&\n resizeContext.handle.anchor &&\n resizeContext.handle.anchor.startsWith('crop-')\n ) {\n const cropAnchor = resizeContext.handle.anchor.replace('crop-', '');\n const context = stateMachine.getContext();\n\n const result = cropController.handleCropBoxResize(resizedImage, cropAnchor, resizeContext, context, x, y);\n\n canvas.style.cursor = result.cursor;\n\n const cropGroupContext = stateMachine._cropGroupContext;\n const activeChildCtx = stateMachine._activeChildContext;\n const groupContext = cropGroupContext || activeChildCtx;\n\n if (groupContext && selectedElement instanceof GroupElement) {\n const updatedChild = resizedImage.clone();\n updatedChild.x = result.x;\n updatedChild.y = result.y;\n updatedChild.rotation = result.rotation;\n updatedChild.transformData.width = result.width;\n updatedChild.transformData.height = result.height;\n updatedChild.updateCrop(result.cropX, result.cropY, result.cropWidth, result.cropHeight, false);\n\n const updatedGroup = selectedElement.clone();\n const childIndex = cropGroupContext\n ? cropGroupContext.childIndex\n : updatedGroup.children.findIndex((c) => c.id === (groupContext as ActiveChildContext).childId);\n if (childIndex >= 0) {\n updatedGroup.children[childIndex] = updatedChild;\n updatedGroup.updateBoundsFromChildren(true);\n }\n onElementUpdate(updatedGroup);\n\n if (activeChildCtx && childIndex >= 0) {\n updateActiveChild(updatedGroup.children[childIndex]);\n }\n } else {\n const updatedElement = resizedImage.clone();\n updatedElement.x = result.x;\n updatedElement.y = result.y;\n updatedElement.rotation = result.rotation;\n updatedElement.transformData.width = result.width;\n updatedElement.transformData.height = result.height;\n updatedElement.updateCrop(result.cropX, result.cropY, result.cropWidth, result.cropHeight, false);\n onElementUpdate(updatedElement);\n }\n return;\n }\n\n if (!selectedElement) return;\n\n const activeChildCtx = stateMachine._activeChildContext;\n const isResizingChildInGroup = activeChildCtx && selectedElement instanceof GroupElement;\n\n const elementsForSnap: CanvasElement[] = [];\n elements.forEach((s) => {\n if (s instanceof GroupElement) {\n elementsForSnap.push(...s.children);\n } else {\n elementsForSnap.push(s);\n }\n });\n\n const result = globalResizePipeline.executeResize({\n element: resizeContext.element as TextElement,\n handle: resizeContext.handle,\n dx,\n dy,\n startData: resizeContext.startData,\n allElements: elementsForSnap,\n snapSystem: snapSystem,\n // Read per move event: Shift = aspect-lock to gesture-start ratio\n modifiers: { shift: e.shiftKey },\n });\n\n let updatedElement = result.element;\n\n stateMachine.updateResizeElement(updatedElement);\n\n if (isResizingChildInGroup) {\n const updatedGroup = selectedElement.clone();\n const childIndex = updatedGroup.children.findIndex((c) => c.id === activeChildCtx.childId);\n if (childIndex >= 0) {\n updatedGroup.children[childIndex] = updatedElement;\n updatedGroup.updateBoundsFromChildren(true);\n }\n onElementUpdate(updatedGroup);\n\n if (childIndex >= 0) {\n updateActiveChild(updatedGroup.children[childIndex]);\n transformHandles.update(updatedGroup.children[childIndex], zoom);\n }\n } else {\n onElementUpdate(updatedElement);\n transformHandles.update(updatedElement, zoom);\n }\n\n setHandlesVersion((v) => v + 1);\n setSnapGuides(snapSystem.getGuides());\n } else if (stateMachine.getMode() === 'rotate') {\n canvas.style.cursor = 'grabbing';\n\n if (!isRotating) {\n setIsRotating(true);\n onRotationStateChange?.(true);\n }\n\n // Check multi-selection rotation\n const multiRotationContext = stateMachine._multiSelectionRotationContext;\n if (multiRotationContext && multiSelection.length > 0) {\n const bounds = multiRotationContext.startBounds;\n // Read per move event: Shift = snap rotation to 15° increments\n const deltaRotation = stateMachine.updateRotate(x, y, bounds.centerX, bounds.centerY, { shift: e.shiftKey });\n setCurrentRotation(deltaRotation);\n\n multiRotationContext.elementsStartData.forEach((elementData: MultiElementStartData) => {\n const element = elementData.element;\n const startData = elementData.startData;\n\n const rotatedPos = Transform.rotatePointAroundAnchor(\n startData.x, startData.y,\n bounds.centerX, bounds.centerY,\n deltaRotation\n );\n\n const updatedElement = element.clone();\n updatedElement.x = rotatedPos.x;\n updatedElement.y = rotatedPos.y;\n updatedElement.setRotation(startData.rotation + deltaRotation);\n\n onElementUpdate(updatedElement);\n });\n return;\n }\n\n const activeChildCtx = stateMachine._activeChildContext;\n const workingEl = activeChildCtx ? activeChildElement : selectedElement;\n\n if (!workingEl) return;\n\n const bbox = workingEl.getBoundingBox();\n const center = getBoundingBoxCenter(bbox);\n const centerXBefore = center.x;\n const centerYBefore = center.y;\n\n // Read per move event: Shift = snap rotation to 15° increments\n const newRotation = stateMachine.updateRotate(x, y, center.x, center.y, { shift: e.shiftKey });\n\n const updatedElement = workingEl.clone();\n updatedElement.setRotation(newRotation);\n\n if (!(updatedElement instanceof GroupElement)) {\n const newBbox = updatedElement.getBoundingBox();\n const newCenter = getBoundingBoxCenter(newBbox);\n const offsetX = centerXBefore - newCenter.x;\n const offsetY = centerYBefore - newCenter.y;\n updatedElement.x += offsetX;\n updatedElement.y += offsetY;\n }\n\n rotatingElementRef.current = updatedElement;\n setCurrentRotation(updatedElement.rotation);\n setRotatingElementVersion(v => v + 1);\n\n if (activeChildCtx && selectedElement instanceof GroupElement) {\n const updatedGroup = selectedElement.clone();\n const childIndex = updatedGroup.children.findIndex((c) => c.id === activeChildCtx.childId);\n if (childIndex >= 0) {\n updatedGroup.children[childIndex] = updatedElement;\n updatedGroup.updateBoundsFromChildren(true);\n }\n onElementUpdate(updatedGroup);\n\n if (childIndex >= 0) {\n updateActiveChild(updatedGroup.children[childIndex]);\n transformHandles.update(updatedGroup.children[childIndex], zoom);\n }\n } else {\n onElementUpdate(updatedElement);\n transformHandles.update(updatedElement, zoom);\n }\n\n setHandlesVersion((v) => v + 1);\n } else {\n // Idle mode: Update cursor and hover state\n\n // Check multi-selection handles\n if (multiSelection.length > 0 && multiSelectionGroupBoundsRef.current) {\n const bounds = multiSelectionGroupBoundsRef.current;\n const hitRadius = 16 / zoom;\n\n const corners = [\n { x: bounds.minX, y: bounds.minY, anchor: 'top-left', cursor: 'nwse-resize' },\n { x: bounds.maxX, y: bounds.minY, anchor: 'top-right', cursor: 'nesw-resize' },\n { x: bounds.minX, y: bounds.maxY, anchor: 'bottom-left', cursor: 'nesw-resize' },\n { x: bounds.maxX, y: bounds.maxY, anchor: 'bottom-right', cursor: 'nwse-resize' },\n ];\n\n const groupRotation = bounds.rotation || 0;\n const rotatedCorners = corners.map((corner) => {\n if (groupRotation !== 0) {\n const rotationRad = (-groupRotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const cdx = corner.x - bounds.centerX;\n const cdy = corner.y - bounds.centerY;\n return {\n x: bounds.centerX + (cdx * cos - cdy * sin),\n y: bounds.centerY + (cdx * sin + cdy * cos),\n anchor: corner.anchor,\n cursor: corner.cursor,\n };\n }\n return corner;\n });\n\n for (const corner of rotatedCorners) {\n const cdx = x - corner.x;\n const cdy = y - corner.y;\n const distance = Math.sqrt(cdx * cdx + cdy * cdy);\n\n if (distance <= hitRadius) {\n canvas.style.cursor = corner.cursor;\n setHoverState({ type: 'multi-selection-handle', data: corner });\n return;\n }\n }\n\n const rotationHitRadius = (16 + 2) / zoom;\n const rotationHandleDistance = Math.sqrt(\n Math.pow(x - bounds.rotationHandleX, 2) + Math.pow(y - bounds.rotationHandleY, 2)\n );\n\n if (rotationHandleDistance <= rotationHitRadius) {\n canvas.style.cursor = 'grab';\n setHoverState({ type: 'multi-selection-rotation-handle', data: null });\n return;\n }\n\n let localX = x;\n let localY = y;\n\n if (groupRotation !== 0) {\n const rotationRad = (groupRotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const cdx = x - bounds.centerX;\n const cdy = y - bounds.centerY;\n localX = bounds.centerX + (cdx * cos - cdy * sin);\n localY = bounds.centerY + (cdx * sin + cdy * cos);\n }\n\n if (localX >= bounds.minX && localX <= bounds.maxX && localY >= bounds.minY && localY <= bounds.maxY) {\n canvas.style.cursor = 'move';\n setHoverState({ type: 'multi-selection-bounds', data: bounds });\n return;\n }\n }\n\n // Alt+hover: Show spacing measurements\n if (e.altKey) {\n let hoveredElement: CanvasElement | null = null;\n const activeArtboardElements = getActiveArtboardElements();\n for (let i = activeArtboardElements.length - 1; i >= 0; i--) {\n const element = activeArtboardElements[i];\n if (element.visible === false) continue;\n\n if (element instanceof GroupElement) {\n for (let j = element.children.length - 1; j >= 0; j--) {\n const child = element.children[j];\n if (child.visible === false) continue;\n const bbox = child.getVisualBoundingBox();\n if (x >= bbox.x && x <= bbox.x + bbox.width && y >= bbox.y && y <= bbox.y + bbox.height) {\n hoveredElement = child;\n break;\n }\n }\n } else {\n const bbox = element.getVisualBoundingBox();\n if (x >= bbox.x && x <= bbox.x + bbox.width && y >= bbox.y && y <= bbox.y + bbox.height) {\n hoveredElement = element;\n break;\n }\n }\n if (hoveredElement) break;\n }\n\n if (hoveredElement) {\n const flattenedElements: CanvasElement[] = [];\n activeArtboardElements.forEach((s) => {\n if (s instanceof GroupElement) {\n flattenedElements.push(...s.children);\n } else {\n flattenedElements.push(s);\n }\n });\n\n const indicators = spacingSystem.detectSpacing(hoveredElement, flattenedElements, true);\n setSpacingIndicators(indicators);\n } else {\n setSpacingIndicators([]);\n }\n } else {\n if (spacingIndicators.length > 0) {\n setSpacingIndicators([]);\n }\n }\n\n // Handle single element hover\n const hoverCheckElement = activeChildElement || selectedElement;\n\n if (hoverCheckElement && hoverCheckElement.visible !== false) {\n if (hoverCheckElement instanceof ImageElement && hoverCheckElement.isCropping) {\n const cropHandle = hoverCheckElement.hitTestCropHandle(x, y, zoom);\n if (cropHandle) {\n const cursor = getCursorForWorldPosition(\n cropHandle.worldX, cropHandle.worldY,\n hoverCheckElement.x, hoverCheckElement.y,\n cropHandle.type, hoverCheckElement.rotation, cropHandle.anchor\n );\n canvas.style.cursor = cursor;\n setHoverState({ type: 'crop-handle', data: cropHandle });\n return;\n }\n\n const imageHandle = transformHandles.hitTestResize(x, y, zoom);\n if (imageHandle) {\n canvas.style.cursor = imageHandle.cursor;\n setHoverState({ type: 'resize-handle', data: imageHandle });\n return;\n }\n\n if (hoverCheckElement.hitTest(x, y)) {\n canvas.style.cursor = 'move';\n setHoverState({ type: 'element', data: hoverCheckElement });\n return;\n }\n } else {\n if (transformHandles.hitTestRotation(x, y, zoom)) {\n canvas.style.cursor = 'grab';\n setHoverState({ type: 'rotation-handle', data: null });\n return;\n }\n\n const resizeHandle = transformHandles.hitTestResize(x, y, zoom);\n if (resizeHandle) {\n canvas.style.cursor = resizeHandle.cursor;\n setHoverState({ type: 'resize-handle', data: resizeHandle });\n return;\n }\n\n if (hoverCheckElement.hitTest(x, y)) {\n canvas.style.cursor = 'move';\n setHoverState({ type: 'element', data: hoverCheckElement });\n return;\n }\n }\n }\n\n // Check group siblings hover\n if (activeChildElement && selectedElement instanceof GroupElement) {\n for (let i = selectedElement.children.length - 1; i >= 0; i--) {\n const child = selectedElement.children[i];\n if (child.visible === false || child.id === activeChildElement.id) continue;\n if (child.hitTest(x, y)) {\n canvas.style.cursor = 'pointer';\n setHoverState({ type: 'group-sibling', data: child });\n return;\n }\n }\n }\n\n // Check hovering over any element. Mirror the focus filter\n // applied to the click path (see `isHitAllowedInFocus` usage\n // around `pointerdown` line ~1207): if the cursor is over the\n // overflow region of a non-selected element (outside the\n // visible piece bounds) the click would be ignored, so the\n // cursor shouldn't promise that hovering anywhere is\n // interactive. Keep the cursor default in that case so the\n // affordance matches the behaviour. The selected element's\n // own drag IS allowed in overflow (see comment at line ~1075),\n // so the selected-element hover branch above intentionally\n // does NOT apply this filter.\n const activeArtboardElements = getActiveArtboardElements();\n for (let i = activeArtboardElements.length - 1; i >= 0; i--) {\n if (activeArtboardElements[i].visible === false) continue;\n if (activeArtboardElements[i].locked) continue;\n if (activeArtboardElements[i].hitTest(x, y)) {\n if (!isHitAllowedInFocus(activeArtboardElements[i], x, y, focusedPieceRect)) {\n continue;\n }\n canvas.style.cursor = 'pointer';\n setHoverState({ type: 'element', data: activeArtboardElements[i] });\n return;\n }\n }\n\n canvas.style.cursor = 'default';\n setHoverState({ type: null, data: null });\n }\n },\n [\n stateMachine,\n selectedElement,\n getActiveArtboardElements,\n onElementUpdate,\n transformHandles,\n isMarqueeSelecting,\n snapSystem,\n multiSelection,\n multiSelectionOBBRef,\n activeChildElement,\n penTool,\n setHandlesVersion,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n setHoverState,\n spacingSystem,\n setSpacingIndicators,\n spacingIndicators,\n cropController,\n // Canvas-zoom pinch update uses these viewport setters.\n setUserZoom,\n setPanOffset,\n ]\n );\n\n // ============================================\n // POINTER UP\n // ============================================\n const handlePointerUp = useCallback((e: React.PointerEvent<HTMLCanvasElement>) => {\n // Right-button release: inert — its pointerdown never entered the\n // pipeline (see the interception in handlePointerDown), so there is\n // no gesture state to commit and nothing below should run.\n if (e.button === 2) return;\n\n (e.target as HTMLCanvasElement).releasePointerCapture(e.pointerId);\n\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n // PINCH GESTURE — release + commit\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n activePointersRef.current.delete(e.pointerId);\n\n // Canvas-zoom pinch release — no commit step (zoom + pan live in\n // ViewportContext, already applied frame-by-frame). Just drop the\n // gesture state when either driving finger lifts.\n if (\n canvasZoomPinchRef.current &&\n (e.pointerId === canvasZoomPinchRef.current.pointerA ||\n e.pointerId === canvasZoomPinchRef.current.pointerB)\n ) {\n canvasZoomPinchRef.current = null;\n activePointersRef.current.clear();\n return;\n }\n\n // Single-finger pan release. If the user never moved past the\n // pan threshold and we have a pending tap action (typically\n // \"select element X\"), fire it here so tap-to-select still\n // works when panning is the default-on-empty behaviour.\n // Otherwise this was a real pan and the canvas is already where\n // the user left it.\n if (\n singleFingerPanRef.current &&\n singleFingerPanRef.current.pointerId === e.pointerId\n ) {\n const p = singleFingerPanRef.current;\n singleFingerPanRef.current = null;\n if (!p.movedPastThreshold) {\n // Pure setSelection / deselect. We avoid going through\n // `handleElementSelection` because that helper ALSO sets up\n // drag state — for a tap-to-select the state machine would\n // briefly enter DRAGGING then end(), and the residue caused\n // a small jump on the user's *next* drag. These direct calls\n // leave the state machine in IDLE so the next pointerdown\n // captures clean startData.\n if (p.pendingTap?.kind === 'select-element') {\n onSelectionChange(p.pendingTap.elementId);\n onMultiSelectionChange([]);\n } else if (p.pendingTap?.kind === 'deselect') {\n onSelectionChange(null);\n onMultiSelectionChange([]);\n }\n }\n return;\n }\n\n if (\n stateMachine.getMode() === 'pinch' &&\n pinchPointerIdsRef.current\n ) {\n const { a, b } = pinchPointerIdsRef.current;\n // If either of the two pinch-driving fingers lifts, the\n // gesture is over — commit the final element state via\n // executeElementUpdate so it lands in undo/redo, then drop\n // back to IDLE. Don't try to \"downgrade\" to single-finger\n // drag; that's a UX rabbit hole (accidental jumps when one\n // finger lifts mid-gesture).\n if (e.pointerId === a || e.pointerId === b) {\n const ctx = stateMachine.getContext();\n const finalElement = ctx.element;\n const startElement = pinchOriginalElementRef.current ?? undefined;\n if (finalElement) {\n executeElementUpdate(\n startElement as CanvasElement | undefined,\n finalElement as CanvasElement,\n );\n }\n stateMachine.end();\n pinchPointerIdsRef.current = null;\n pinchOriginalElementRef.current = null;\n // Drop any remaining pointers from the active map — the\n // gesture is over, and a stray pointerId hanging around\n // would re-trigger pinch when the next finger lands.\n activePointersRef.current.clear();\n return;\n }\n }\n // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n if (draggingSelectionHandleRef.current) {\n draggingSelectionHandleRef.current = null;\n }\n\n // Handle pen tool mode\n if (penTool.isActive() && penTool.isDraggingHandle()) {\n const wasClosing = penTool.isClosingPath();\n penTool.finishDraggingHandle();\n\n if (wasClosing && selectedElement instanceof PathElement) {\n const updatedElement = selectedElement.clone();\n executeElementUpdate(selectedElement, updatedElement);\n }\n\n setHandlesVersion((v) => v + 1);\n return;\n }\n\n // Handle marquee selection completion\n if (isMarqueeSelecting) {\n finishMarqueeSelection(onMultiSelectionChange);\n return;\n }\n\n // Commit drag changes to history\n if (dragOriginalElementsRef.current && stateMachine.getMode() === 'drag') {\n const originalElements = dragOriginalElementsRef.current;\n\n originalElements.forEach((originalElement, elementId) => {\n const finalElement = elements.find((el) => el.id === elementId);\n if (finalElement) {\n const hasChanged =\n originalElement.x !== finalElement.x ||\n originalElement.y !== finalElement.y ||\n (originalElement instanceof GroupElement &&\n finalElement instanceof GroupElement &&\n JSON.stringify(originalElement.children.map((c) => ({ x: c.x, y: c.y }))) !==\n JSON.stringify(finalElement.children.map((c) => ({ x: c.x, y: c.y }))));\n\n if (hasChanged) {\n executeElementUpdate(originalElement, finalElement);\n }\n }\n });\n\n dragOriginalElementsRef.current = null;\n }\n\n const canvas = canvasRef.current;\n if (canvas) {\n canvas.style.cursor = 'default';\n }\n\n snapSystem.clearGuides();\n snapSystem.clearSnapState();\n setSnapGuides([]);\n\n spacingSystem.clearIndicators();\n setSpacingIndicators([]);\n\n if (stateMachine._cropGroupContext) {\n delete stateMachine._cropGroupContext;\n }\n\n if (stateMachine._activeChildContext) {\n if (activeChildElement && selectedElement instanceof GroupElement) {\n const dragContext = stateMachine.getContext() as ExtendedInteractionContext;\n const cachedGroup = dragContext._cachedElement && dragContext._cachedElement[selectedElement.id];\n const groupToUse = (cachedGroup as GroupElement) || selectedElement;\n\n const updatedChild = groupToUse.children.find((c) => c.id === activeChildElement.id);\n if (updatedChild) {\n updateActiveChild(updatedChild as ImageElement | TextElement);\n transformHandles.update(updatedChild, zoom);\n }\n }\n delete stateMachine._activeChildContext;\n }\n\n if (stateMachine._multiSelectionResizeContext) {\n delete stateMachine._multiSelectionResizeContext;\n }\n\n elements.forEach((element) => {\n if (element instanceof CustomTransform && element._lockedLineCount !== undefined) {\n const updatedElement = element.clone();\n updatedElement._lockedLineCount = undefined;\n onElementUpdate(updatedElement);\n }\n });\n\n if (stateMachine._multiSelectionDragContext) {\n const multiDragContext = stateMachine._multiSelectionDragContext;\n\n if (multiDragContext.startOBB && multiDragContext.currentDelta) {\n const { dx, dy } = multiDragContext.currentDelta;\n const startOBB = multiDragContext.startOBB;\n multiSelectionOBBRef.current = {\n ...startOBB,\n centerX: startOBB.centerX + dx,\n centerY: startOBB.centerY + dy,\n };\n }\n\n delete stateMachine._multiSelectionDragContext;\n }\n\n if (stateMachine._multiSelectionRotationContext) {\n if (stateMachine.getMode() === 'rotate' && multiSelection.length > 0 && multiSelectionOBBRef.current) {\n const prevOBB = multiSelectionOBBRef.current;\n multiSelectionOBBRef.current = {\n ...prevOBB,\n rotation: prevOBB.rotation + stateMachine._multiSelectionRotationContext.currentRotation || prevOBB.rotation,\n };\n }\n delete stateMachine._multiSelectionRotationContext;\n }\n\n const dragContext = stateMachine.getContext() as ExtendedInteractionContext;\n if (dragContext._cachedElement) {\n delete dragContext._cachedElement;\n }\n\n // Handle pending child selection\n if (stateMachine._pendingChildSelection) {\n const pendingChild = stateMachine._pendingChildSelection;\n let shouldSelectChild = true;\n\n if (selectedElement instanceof GroupElement) {\n const originalGroup = stateMachine._groupDragOriginalElement;\n if (originalGroup) {\n const moved =\n Math.abs(selectedElement.x - originalGroup.x) > 0.1 ||\n Math.abs(selectedElement.y - originalGroup.y) > 0.1;\n shouldSelectChild = !moved;\n }\n }\n\n if (shouldSelectChild) {\n updateActiveChild(pendingChild);\n transformHandles.update(pendingChild, zoom);\n setHandlesVersion((v) => v + 1);\n }\n\n delete stateMachine._pendingChildSelection;\n }\n\n // Handle pending text edit mode\n if (stateMachine._pendingTextEditMode) {\n const pending = stateMachine._pendingTextEditMode;\n const currentElement = elements.find((el) => el.id === pending.elementId);\n\n const moved =\n currentElement &&\n (Math.abs(currentElement.x - pending.originalX) > 0.1 ||\n Math.abs(currentElement.y - pending.originalY) > 0.1);\n\n if (!moved && currentElement instanceof TextElement && !(currentElement instanceof ImageElement) && !(currentElement instanceof ShapeElement)) {\n enterTextEditMode(currentElement, pending.clickX, pending.clickY);\n }\n\n delete stateMachine._pendingTextEditMode;\n }\n\n // Handle pending tap-to-deselect: fire only if the gesture was a\n // genuine tap (no significant movement). The pinch path above\n // already cleared the candidate, so we only land here for\n // single-pointer taps. We compare the tapped element's current\n // position against its position at pointerdown — if it didn't\n // move it's a tap, not a drag.\n if (pendingDeselectIdRef.current) {\n const id = pendingDeselectIdRef.current;\n const startPos = pendingDeselectStartPosRef.current;\n const currentElement = elements.find((el) => el.id === id);\n const original = dragOriginalElementsRef.current?.get(id);\n const moved = !!(\n currentElement &&\n original &&\n (Math.abs(currentElement.x - original.x) > 0.1 ||\n Math.abs(currentElement.y - original.y) > 0.1)\n );\n // Belt-and-suspenders: also bail if pointer travelled far in\n // screen space (covers cases where drag started but element\n // didn't move yet — e.g. snapped back to origin).\n const screenMoved =\n startPos\n ? Math.hypot(\n (e.clientX - (canvasBoundsRef.current?.left ?? 0)) / zoom -\n paddingOffsetX -\n startPos.x,\n (e.clientY - (canvasBoundsRef.current?.top ?? 0)) / zoom -\n paddingOffsetY -\n startPos.y,\n ) > 4 / Math.max(zoom, 0.0001)\n : false;\n if (!moved && !screenMoved && id === selectedElement?.id && startPos) {\n // Tap-without-drag on the selected element. Walk the cycle:\n // collect every visible element under the tap point, find the\n // currently-selected one in the stack, and either advance to\n // the next layer below OR deselect when there's nothing else\n // to cycle to. Mirrors the cycle in the `pendingTap` (pan)\n // path so locked-and-unlocked selected elements behave\n // identically — tap-on-selected always cycles down, ending\n // in deselect at the bottom.\n const activeArtboardElements = getActiveArtboardElements();\n const hits: typeof activeArtboardElements = [];\n for (let i = activeArtboardElements.length - 1; i >= 0; i--) {\n const el = activeArtboardElements[i];\n if (el.visible === false) continue;\n if (!el.hitTest(startPos.x, startPos.y)) continue;\n if (!isHitAllowedInFocus(el, startPos.x, startPos.y, focusedPieceRect))\n continue;\n hits.push(el);\n }\n const currentIdx = hits.findIndex((el) => el.id === id);\n if (currentIdx !== -1 && currentIdx + 1 < hits.length) {\n onSelectionChange(hits[currentIdx + 1].id);\n } else {\n onSelectionChange(null);\n }\n }\n pendingDeselectIdRef.current = null;\n pendingDeselectStartPosRef.current = null;\n }\n\n if (stateMachine._groupDragOriginalElement) {\n delete stateMachine._groupDragOriginalElement;\n }\n\n stateMachine.end();\n setIsRotating(false);\n onRotationStateChange?.(false);\n rotatingElementRef.current = null;\n setRotatingElementVersion(0);\n forceUpdate({});\n }, [\n stateMachine,\n snapSystem,\n isMarqueeSelecting,\n onMultiSelectionChange,\n multiSelection,\n multiSelectionOBBRef,\n activeChildElement,\n selectedElement,\n updateActiveChild,\n transformHandles,\n canvasRef,\n penTool,\n setHandlesVersion,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n onRotationStateChange,\n enterTextEditMode,\n elements,\n onSelectionChange,\n executeElementUpdate,\n focusedPieceRect,\n ]);\n\n // ============================================\n // DOUBLE CLICK\n // ============================================\n const handleDoubleClick = useCallback(\n (e: React.MouseEvent<HTMLCanvasElement>) => {\n if (!selectedElement) return;\n\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const rect = canvasBoundsRef.current ?? canvas.getBoundingClientRect();\n const x = (e.clientX - rect.left) / zoom - paddingOffsetX;\n const y = (e.clientY - rect.top) / zoom - paddingOffsetY;\n\n if (selectedElement.hitTest(x, y)) {\n // Group element - check child\n if (selectedElement instanceof GroupElement) {\n const clickedChild = selectedElement.hitTestChild(x, y);\n if (clickedChild) {\n const isChildAlreadyActive = activeChildElement && activeChildElement.id === clickedChild.id;\n if (!isChildAlreadyActive) return;\n\n // Image children no longer enter crop mode on double-\n // click — too easy to trigger by accident. Users open\n // crop via the explicit toolbar control instead.\n\n // Handle text child - enter text edit mode\n if (clickedChild instanceof TextElement && !(clickedChild instanceof GroupElement) && !(clickedChild instanceof ShapeElement)) {\n const visualBbox = clickedChild.getVisualBoundingBox();\n\n const rotationRad = RotationUtils.toRadiansInverse(clickedChild.rotation);\n const center = clickedChild.getRotationAnchor();\n const cdx = x - center.x;\n const cdy = y - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = cdx * cos - cdy * sin;\n const localRotatedY = cdx * sin + cdy * cos;\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = localRotatedY + visualBbox.height / 2;\n\n const clickIndex = getCursorIndexFromPoint(clickedChild, { x: relativeX, y: relativeY });\n\n const contentHeight = visualBbox.height + 16;\n const offset = 6;\n\n setEditPosition({\n x: rect.left + (visualBbox.x + paddingOffsetX) * zoom - offset,\n y: rect.top + (visualBbox.y + paddingOffsetY) * zoom - offset,\n width: Math.max(20, visualBbox.width * zoom),\n height: Math.max(contentHeight * zoom, 50),\n });\n setEditText(getWrappedTextForEditing(clickedChild));\n const initialRichText = clickedChild.getRichText().clone();\n editRichTextRef.current = initialRichText;\n setEditRichText(initialRichText);\n const styleAtEnd = clickedChild.getRichText().getStyleAt(Math.max(0, clickedChild.text.length - 1));\n setCurrentFormattingStyle({ ...clickedChild.getDefaultStyle(), ...styleAtEnd });\n\n setCursorPosition(clickIndex);\n setSelectionStart(clickIndex);\n setSelectionEnd(clickIndex);\n\n setIsEditing(true);\n editModeEnteredAtRef.current = Date.now();\n editingChildIdRef.current = clickedChild.id;\n return;\n }\n }\n return;\n }\n\n // Image elements no longer enter crop mode on double-click\n // — too easy to trigger by accident. Users open crop via the\n // explicit toolbar control instead.\n\n // Handle path elements - enter path edit mode\n if (selectedElement instanceof PathElement) {\n penTool.editPath(selectedElement);\n setHandlesVersion((v) => v + 1);\n return;\n }\n\n // Handle shape elements - do nothing\n if (selectedElement instanceof ShapeElement) {\n return;\n }\n\n // Only text elements enter edit mode on double-click. Image elements\n // fall through here (no crop-on-double-click — see note above) and the\n // guard also narrows the TextElement | ImageElement union for the\n // text-only accessors below (getRichText, text, getDefaultStyle).\n if (!(selectedElement instanceof TextElement)) {\n return;\n }\n\n // Handle text elements - enter edit mode\n const visualBbox = selectedElement.getVisualBoundingBox();\n\n const rotationRad = RotationUtils.toRadiansInverse(selectedElement.rotation);\n const center = selectedElement.getRotationAnchor();\n const cdx = x - center.x;\n const cdy = y - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = cdx * cos - cdy * sin;\n const localRotatedY = cdx * sin + cdy * cos;\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = localRotatedY + visualBbox.height / 2;\n\n const clickIndex = getCursorIndexFromPoint(selectedElement as TextElement, { x: relativeX, y: relativeY });\n\n const contentHeight = visualBbox.height + 16;\n const offset = 6;\n\n setEditPosition({\n x: rect.left + (visualBbox.x + paddingOffsetX) * zoom - offset,\n y: rect.top + (visualBbox.y + paddingOffsetY) * zoom - offset,\n width: Math.max(20, visualBbox.width * zoom),\n height: Math.max(contentHeight * zoom, 50),\n });\n setEditText(getWrappedTextForEditing(selectedElement));\n const initialRichText = selectedElement.getRichText().clone();\n editRichTextRef.current = initialRichText;\n setEditRichText(initialRichText);\n const styleAtEnd = selectedElement.getRichText().getStyleAt(Math.max(0, selectedElement.text.length - 1));\n setCurrentFormattingStyle({ ...selectedElement.getDefaultStyle(), ...styleAtEnd });\n setCursorPosition(clickIndex);\n setSelectionStart(clickIndex);\n setSelectionEnd(clickIndex);\n setIsEditing(true);\n editModeEnteredAtRef.current = Date.now();\n }\n },\n [\n selectedElement,\n onElementUpdate,\n activeChildElement,\n transformHandles,\n penTool,\n setHandlesVersion,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n setEditPosition,\n setEditText,\n setEditRichText,\n setCurrentFormattingStyle,\n setCursorPosition,\n setSelectionStart,\n setSelectionEnd,\n setIsEditing,\n updateActiveChild,\n onCropModeEnter,\n getWrappedTextForEditing,\n ]\n );\n\n // ============================================\n // DOCUMENT-LEVEL EVENT FORWARDING\n // ============================================\n\n // Document-level mousemove handler\n const handleDocumentMouseMove = useCallback(\n (e: MouseEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const mode = stateMachine.getMode();\n if (mode === 'idle') return;\n\n const rect = canvasBoundsRef.current ?? canvas.getBoundingClientRect();\n const isInsideCanvas =\n e.clientX >= rect.left &&\n e.clientX <= rect.right &&\n e.clientY >= rect.top &&\n e.clientY <= rect.bottom;\n\n if (isInsideCanvas) return;\n\n const syntheticEvent = {\n clientX: e.clientX,\n clientY: e.clientY,\n altKey: e.altKey,\n shiftKey: e.shiftKey,\n ctrlKey: e.ctrlKey,\n metaKey: e.metaKey,\n bubbles: true,\n cancelable: true,\n currentTarget: canvas,\n target: canvas,\n nativeEvent: e,\n type: 'pointermove',\n pointerId: (e as PointerEvent).pointerId || 0,\n pointerType: (e as PointerEvent).pointerType || 'mouse',\n preventDefault: () => e.preventDefault(),\n stopPropagation: () => e.stopPropagation(),\n isPropagationStopped: () => false,\n isDefaultPrevented: () => e.defaultPrevented,\n } as unknown as React.PointerEvent<HTMLCanvasElement>;\n\n handlePointerMove(syntheticEvent);\n },\n [handlePointerMove, canvasRef, stateMachine]\n );\n\n // Document-level pointerup handler.\n //\n // This catches pointer-up that the canvas's own React `onPointerUp`\n // would miss — e.g. when the user drags an element off the canvas\n // before lifting, or releases over a sibling overlay. We MUST gate\n // on an active gesture though: without this filter, every\n // pointer-up anywhere on the page (including taps on chip / pill /\n // editor-header buttons) runs `handlePointerUp`, which fires any\n // pending side-effects from a prior canvas interaction (most\n // visibly: pendingDeselect → unselects on a tap that wasn't even\n // on the canvas).\n const handleDocumentPointerUp = useCallback(\n (e: PointerEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n // Only handle the event if it originated inside the canvas, OR\n // we're tracking an active gesture on this pointer (drag /\n // pinch / pan in progress whose finger may have travelled\n // outside the canvas DOM).\n const targetNode = e.target as Node | null;\n const insideCanvas = targetNode ? canvas.contains(targetNode) : false;\n const hasActiveGesture =\n activePointersRef.current.has(e.pointerId) ||\n canvasZoomPinchRef.current !== null ||\n singleFingerPanRef.current !== null ||\n pinchPointerIdsRef.current !== null ||\n stateMachine.isInteracting();\n if (!insideCanvas && !hasActiveGesture) return;\n\n const syntheticEvent = {\n clientX: e.clientX,\n clientY: e.clientY,\n // Forward the button so handlePointerUp's right-button guard\n // applies to document-forwarded releases too.\n button: e.button,\n currentTarget: canvas,\n target: canvas,\n pointerId: e.pointerId || 0,\n pointerType: e.pointerType || 'mouse',\n } as unknown as React.PointerEvent<HTMLCanvasElement>;\n\n handlePointerUp(syntheticEvent);\n },\n [handlePointerUp, canvasRef, stateMachine]\n );\n\n // Attach document listeners\n useEffect(() => {\n document.addEventListener('pointermove', handleDocumentMouseMove);\n document.addEventListener('pointerup', handleDocumentPointerUp);\n\n return () => {\n document.removeEventListener('pointermove', handleDocumentMouseMove);\n document.removeEventListener('pointerup', handleDocumentPointerUp);\n };\n }, [handleDocumentMouseMove, handleDocumentPointerUp]);\n\n // ============================================\n // CONTEXT MENU (right-click / long-press)\n // ============================================\n // The select/drag side of the gesture is neutralized by the button-2\n // interception at the top of handlePointerDown; this handler owns the\n // POSITIVE half: hit-test under the cursor with the SAME rules the\n // pointer-down tap path uses (topmost first, hidden skipped, locked\n // still selectable — gotcha #20, focus filter honoured), update the\n // selection Figma-style, and hand the request to the host's menu UI.\n const handleContextMenu = useCallback(\n (e: React.MouseEvent<HTMLCanvasElement>) => {\n // No host menu wired → leave the event alone entirely (native\n // browser menu shows). The drag pipeline is still protected by the\n // pointer-down interception regardless.\n if (!onContextMenuRequest) return;\n\n // The menu replaces the browser's native one on the canvas.\n e.preventDefault();\n\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n // Modal interaction modes own their pointer semantics — don't\n // fight text editing, crop mode, or the pen tool with a menu.\n const isModalMode =\n isEditing ||\n (selectedElement instanceof ImageElement && selectedElement.isCropping) ||\n penTool.isActive();\n if (isModalMode) return;\n\n const rect = canvas.getBoundingClientRect();\n const x = (e.clientX - rect.left) / zoom - paddingOffsetX;\n const y = (e.clientY - rect.top) / zoom - paddingOffsetY;\n\n // Same hit-test the pointer-down tap path uses: topmost element\n // first, hidden elements skipped, locked elements INCLUDED\n // (locking gates manipulation, not selection), clipped-zone\n // focus filter honoured.\n const activeArtboardElements = getActiveArtboardElements();\n let hit: CanvasElement | null = null;\n for (let i = activeArtboardElements.length - 1; i >= 0; i--) {\n const el = activeArtboardElements[i];\n if (el.visible === false) continue;\n if (!el.hitTest(x, y)) continue;\n if (!isHitAllowedInFocus(el, x, y, focusedPieceRect)) continue;\n hit = el;\n break;\n }\n\n if (hit) {\n const isInMultiSelection = multiSelection.includes(hit.id);\n const isCurrentSelection = selectedElement?.id === hit.id;\n // Figma behaviour: right-click selects the element under the\n // cursor — but preserves an existing multi-selection when the\n // target is already part of it (so bulk actions stay bulk).\n if (!isInMultiSelection && !isCurrentSelection) {\n onSelectionChange(hit.id);\n onMultiSelectionChange([]);\n }\n }\n // Empty canvas: leave the selection untouched — the host shows the\n // canvas-level menu (Paste / Select All).\n\n onContextMenuRequest({\n clientX: e.clientX,\n clientY: e.clientY,\n worldX: x,\n worldY: y,\n elementId: hit ? hit.id : null,\n });\n },\n [\n onContextMenuRequest,\n canvasRef,\n isEditing,\n selectedElement,\n penTool,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n getActiveArtboardElements,\n focusedPieceRect,\n multiSelection,\n onSelectionChange,\n onMultiSelectionChange,\n ]\n );\n\n return {\n handlePointerDown,\n handlePointerMove,\n handlePointerUp,\n handleDoubleClick,\n handleContextMenu,\n };\n}\n","/**\n * useTextEditingHandlers - Text editing event handlers and effects\n *\n * Extracted from CanvasEditor.tsx to isolate text editing logic including:\n * - Text edit completion (handleTextEditComplete)\n * - Blur handling (handleTextEditBlur)\n * - Keyboard handling during editing (handleTextKeyDown)\n * - Character-level formatting (applyFormattingToSelection, toggleFormattingProperty, getSelectionStyle)\n * - Cursor animation effects\n * - Formatting style tracking\n * - Edit position updates\n * - Textarea cursor sync\n * - Enter key to enter edit mode\n */\n\nimport { useCallback, useEffect, useRef } from 'react';\nimport { TextElement } from '../../../core/TextElement.js';\nimport { ImageElement } from '../../../core/ImageElement.js';\nimport { GroupElement } from '../../../core/GroupElement.js';\nimport { ShapeElement } from '../../../core/ShapeElement.js';\nimport type { TransformHandles } from '../../../core/TransformHandles.js';\nimport type { RichText, CharacterStyle } from '../../../types/index.js';\nimport { RotationUtils } from '../../../core/RotationUtils.js';\nimport { getCursorIndexFromPoint, moveCursorVertically } from '../../../utils/textCursorUtils.js';\nimport { wrapText } from '../../../core/TextMetrics.js';\nimport type { PathElement } from '../../../core/PathElement.js';\n\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\ntype ChildElement = TextElement | ImageElement | ShapeElement | PathElement | GroupElement;\n\n/**\n * Helper function to convert plain text to wrapped text with explicit newlines\n * that match the canvas wrapping behavior. This ensures textarea and canvas\n * wrap text identically, preventing cursor misalignment.\n */\nfunction getWrappedTextForEditing(element: TextElement): string {\n // For transforms that don't use standard wrapping (circle, arch, etc.),\n // return text as-is without wrapping\n if (element.transformType !== 'custom') {\n return element.text;\n }\n\n // Get visual bbox to determine wrapping width\n const visualBbox = element.getVisualBoundingBox();\n const maxWidth = visualBbox.width;\n\n // Use getText() method instead of .text property to get the latest value from richText\n const elementText = element.getText();\n\n // Use the same wrapText algorithm as canvas rendering\n const lines = wrapText(\n elementText,\n maxWidth,\n element.fontSize,\n element.fontFamily,\n element.bold,\n element.italic,\n undefined,\n false,\n { letterSpacing: element.letterSpacing }\n );\n\n // Join lines with newlines for textarea\n return lines.join('\\n');\n}\n\nexport interface UseTextEditingHandlersParams {\n // Refs\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n inputRef: React.RefObject<HTMLTextAreaElement | null>;\n editingChildIdRef: React.MutableRefObject<string | null>;\n editRichTextRef: React.MutableRefObject<RichText | null>;\n selectionAnchorRef: React.MutableRefObject<number>;\n cursorStartTimeRef: React.MutableRefObject<number>;\n lastMultiClickTimeRef: React.MutableRefObject<number>;\n multiClickSelectionRef: React.MutableRefObject<{ start: number; end: number; cursor: number } | null>;\n editPositionUpdateTimerRef: React.MutableRefObject<ReturnType<typeof setTimeout> | null>;\n draggingSelectionHandleRef: React.MutableRefObject<'start' | 'end' | null>;\n repositioningCursorRef: React.MutableRefObject<boolean>;\n editModeEnteredAtRef: React.MutableRefObject<number>;\n lastTouchTargetRef: React.MutableRefObject<HTMLElement | null>;\n\n // Constants\n MULTI_CLICK_DEBOUNCE: number;\n EDIT_MODE_BLUR_DEBOUNCE: number;\n\n // State values\n isEditing: boolean;\n editText: string;\n editRichText: RichText | null;\n cursorPosition: number;\n selectionStart: number;\n selectionEnd: number;\n selectedId: string | null;\n selectedElement: CanvasElement | undefined;\n activeChildElement: ChildElement | null;\n elements: CanvasElement[];\n zoom: number;\n paddingOffsetX: number;\n paddingOffsetY: number;\n // State setters\n setIsEditing: (v: boolean) => void;\n setEditText: (v: string) => void;\n setEditRichText: (v: RichText | null) => void;\n setCursorPosition: React.Dispatch<React.SetStateAction<number>>;\n setSelectionStart: React.Dispatch<React.SetStateAction<number>>;\n setSelectionEnd: React.Dispatch<React.SetStateAction<number>>;\n setEditPosition: (pos: { x: number; y: number; width: number; height: number }) => void;\n setCurrentFormattingStyle: React.Dispatch<React.SetStateAction<CharacterStyle | null>>;\n setCursorAnimationFrame: React.Dispatch<React.SetStateAction<number>>;\n setHandlesVersion: React.Dispatch<React.SetStateAction<number>>;\n setElements: React.Dispatch<React.SetStateAction<CanvasElement[]>>;\n\n // Callbacks\n onElementUpdate: (element: CanvasElement) => void;\n onTextSelectionChange?: () => void;\n transformHandles: TransformHandles;\n updateActiveChild: (child: ChildElement | null) => void;\n}\n\nexport interface TextEditingHandlersResult {\n handleTextEditComplete: () => void;\n handleTextEditBlur: () => void;\n handleTextKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;\n applyFormattingToSelection: (style: CharacterStyle) => void;\n getSelectionStyle: () => CharacterStyle | null;\n toggleFormattingProperty: (property: 'bold' | 'italic' | 'underline' | 'strikethrough') => void;\n enterTextEditMode: (element: TextElement, worldX: number, worldY: number) => void;\n getWrappedTextForEditing: (element: TextElement) => string;\n}\n\nexport function useTextEditingHandlers(params: UseTextEditingHandlersParams): TextEditingHandlersResult {\n const {\n canvasRef,\n inputRef,\n editingChildIdRef,\n editRichTextRef,\n selectionAnchorRef,\n cursorStartTimeRef,\n lastMultiClickTimeRef,\n multiClickSelectionRef,\n editPositionUpdateTimerRef,\n draggingSelectionHandleRef,\n repositioningCursorRef,\n editModeEnteredAtRef,\n lastTouchTargetRef,\n MULTI_CLICK_DEBOUNCE,\n EDIT_MODE_BLUR_DEBOUNCE,\n isEditing,\n editText,\n editRichText,\n cursorPosition,\n selectionStart,\n selectionEnd,\n selectedId,\n selectedElement,\n activeChildElement,\n elements,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n setIsEditing,\n setEditText,\n setEditRichText,\n setCursorPosition,\n setSelectionStart,\n setSelectionEnd,\n setEditPosition,\n setCurrentFormattingStyle,\n setCursorAnimationFrame,\n setHandlesVersion,\n setElements,\n onElementUpdate,\n onTextSelectionChange,\n transformHandles,\n updateActiveChild,\n } = params;\n\n // Helper to enter text edit mode for a text element\n const enterTextEditMode = useCallback(\n (element: TextElement, worldX: number, worldY: number) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const rect = canvas.getBoundingClientRect();\n const visualBbox = element.getVisualBoundingBox();\n\n // Calculate cursor position from click\n const rotationRad = RotationUtils.toRadiansInverse(element.rotation);\n const center = element.getRotationAnchor();\n const dx = worldX - center.x;\n const dy = worldY - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = dx * cos - dy * sin;\n const localRotatedY = dx * sin + dy * cos;\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = localRotatedY + visualBbox.height / 2;\n\n const clickIndex = getCursorIndexFromPoint(element, { x: relativeX, y: relativeY });\n\n // Convert world coordinates to screen coordinates with zoom\n const contentHeight = visualBbox.height + 16; // +16 for padding\n\n // Offset for textarea padding (4px) + border (2px) = 6px\n const offset = 6;\n\n setEditPosition({\n x: rect.left + (visualBbox.x + paddingOffsetX) * zoom - offset,\n y: rect.top + (visualBbox.y + paddingOffsetY) * zoom - offset,\n width: Math.max(20, visualBbox.width * zoom),\n height: Math.max(contentHeight * zoom, 50),\n });\n setEditText(getWrappedTextForEditing(element));\n const initialRichText = element.getRichText().clone();\n editRichTextRef.current = initialRichText;\n setEditRichText(initialRichText);\n // Get style at end of text, falling back to element defaults\n const styleAtEnd = element.getRichText().getStyleAt(Math.max(0, element.text.length - 1));\n setCurrentFormattingStyle({ ...element.getDefaultStyle(), ...styleAtEnd });\n setCursorPosition(clickIndex);\n setSelectionStart(clickIndex);\n setSelectionEnd(clickIndex);\n setIsEditing(true);\n // Record when we entered edit mode to prevent immediate blur from exiting (iOS fix)\n editModeEnteredAtRef.current = Date.now();\n },\n [zoom, setEditPosition, setEditText, setEditRichText, setCurrentFormattingStyle, setCursorPosition, setSelectionStart, setSelectionEnd, setIsEditing]\n );\n\n // Apply formatting to selected text\n const applyFormattingToSelection = useCallback(\n (style: CharacterStyle) => {\n if (!isEditing || !editRichText) {\n return;\n }\n\n const start = Math.min(selectionStart, selectionEnd);\n const end = Math.max(selectionStart, selectionEnd);\n\n if (start === end) {\n // No selection - apply to ALL characters\n const updated = editRichText.clone();\n const textLength = updated.getText().length;\n updated.applyStyle(0, textLength, style);\n\n editRichTextRef.current = updated;\n setEditRichText(updated);\n setEditText(updated.getText());\n\n setCurrentFormattingStyle((prevStyle) => {\n const newStyle = { ...prevStyle, ...style };\n return newStyle;\n });\n } else {\n // Apply to selection\n const updated = editRichText.clone();\n updated.applyStyle(start, end, style);\n\n editRichTextRef.current = updated;\n setEditRichText(updated);\n setEditText(updated.getText());\n\n setCurrentFormattingStyle((prevStyle) => {\n const newStyle = { ...prevStyle, ...style };\n return newStyle;\n });\n }\n },\n [isEditing, editRichText, selectionStart, selectionEnd]\n );\n\n // Get the style of the current selection (for toolbar state)\n const getSelectionStyle = useCallback((): CharacterStyle | null => {\n if (!isEditing || !editRichText || !selectedElement) return null;\n\n const start = Math.min(selectionStart, selectionEnd);\n const end = Math.max(selectionStart, selectionEnd);\n\n if (start === end) {\n if (start === 0) {\n return selectedElement instanceof TextElement ? selectedElement.getDefaultStyle() : {};\n }\n return editRichText.getStyleAt(Math.max(0, start - 1));\n }\n\n return editRichText.getStyleAt(start);\n }, [isEditing, editRichText, selectionStart, selectionEnd, selectedElement]);\n\n // Toggle boolean formatting properties\n const toggleFormattingProperty = useCallback(\n (property: 'bold' | 'italic' | 'underline' | 'strikethrough') => {\n if (!isEditing || !editRichText || !selectedElement) return;\n\n const currentStyle = getSelectionStyle();\n const defaultStyle = selectedElement instanceof TextElement ? selectedElement.getDefaultStyle() : {};\n const effectiveStyle = { ...defaultStyle, ...currentStyle };\n const newValue = !effectiveStyle[property];\n\n applyFormattingToSelection({ [property]: newValue });\n },\n [isEditing, editRichText, selectedElement, getSelectionStyle, applyFormattingToSelection]\n );\n\n // Handle finishing text edit\n const handleTextEditComplete = useCallback(() => {\n if (!isEditing) return;\n\n const latestRichText = editRichTextRef.current || editRichText;\n\n if (selectedElement && latestRichText) {\n const editingChildId = editingChildIdRef.current;\n\n if (editingChildId && selectedElement instanceof GroupElement) {\n const updatedGroup = selectedElement.clone();\n const childIndex = updatedGroup.children.findIndex((c) => c.id === editingChildId);\n if (childIndex >= 0) {\n const updatedChild = updatedGroup.children[childIndex];\n if (updatedChild instanceof TextElement) {\n updatedChild.setRichText(latestRichText);\n }\n updatedGroup.updateBoundsFromChildren(true);\n }\n onElementUpdate(updatedGroup);\n\n const updatedChild = updatedGroup.children[childIndex];\n if (updatedChild) {\n updateActiveChild(updatedChild);\n transformHandles.update(updatedChild, zoom);\n setHandlesVersion((v) => v + 1);\n }\n } else if (selectedElement instanceof TextElement) {\n const updatedElement = selectedElement.clone();\n updatedElement.setRichText(latestRichText);\n onElementUpdate(updatedElement);\n\n transformHandles.update(updatedElement, zoom);\n setHandlesVersion((v) => v + 1);\n }\n }\n\n multiClickSelectionRef.current = null;\n setIsEditing(false);\n setEditText('');\n setEditRichText(null);\n editRichTextRef.current = null;\n setCurrentFormattingStyle(null);\n editingChildIdRef.current = null;\n }, [isEditing, editText, editRichText, editRichTextRef, selectedElement, onElementUpdate, transformHandles, updateActiveChild, zoom]);\n\n // Handle textarea blur\n const handleTextEditBlur = useCallback(() => {\n const now = Date.now();\n const timeSinceMultiClick = now - lastMultiClickTimeRef.current;\n if (timeSinceMultiClick < MULTI_CLICK_DEBOUNCE) {\n if (inputRef.current) {\n inputRef.current.focus();\n const savedSelection = multiClickSelectionRef.current;\n if (savedSelection) {\n inputRef.current.setSelectionRange(savedSelection.start, savedSelection.end);\n }\n }\n return;\n }\n\n setTimeout(() => {\n const timeSinceEditStart = Date.now() - editModeEnteredAtRef.current;\n if (timeSinceEditStart < EDIT_MODE_BLUR_DEBOUNCE) {\n inputRef.current?.focus();\n return;\n }\n\n if (repositioningCursorRef.current) {\n repositioningCursorRef.current = false;\n inputRef.current?.focus();\n if (inputRef.current) {\n inputRef.current.setSelectionRange(selectionStart, selectionEnd);\n }\n return;\n }\n\n const activeElement = document.activeElement;\n let element = activeElement as HTMLElement | null;\n let isToolbarClick = false;\n\n while (element) {\n const classList = element.classList;\n const dataSlot = element.getAttribute('data-slot');\n if (dataSlot && (dataSlot.includes('popover') || dataSlot.includes('dialog'))) {\n isToolbarClick = true;\n break;\n }\n if (element.hasAttribute('data-preserve-selection')) {\n isToolbarClick = true;\n break;\n }\n if (classList) {\n if (\n classList.contains('sc-toolbar') ||\n classList.contains('sc-toolbar-items') ||\n classList.contains('sc-toolbar-btn') ||\n classList.contains('sc-toolbar-btn-sm') ||\n classList.contains('sc-toolbar-btn-icon') ||\n classList.contains('sc-toolbar-color') ||\n classList.contains('sc-toolbar-label') ||\n classList.contains('sc-toolbar-select') ||\n classList.contains('sc-toolbar-select-sm') ||\n classList.contains('sc-toolbar-group') ||\n classList.contains('sc-toolbar-scroll') ||\n classList.contains('sc-toolbar-color-label') ||\n classList.contains('sc-toolbar-color-square') ||\n classList.contains('sc-color-picker-dropdown') ||\n classList.contains('sc-text-color-panel') ||\n classList.contains('sc-tooltip-wrapper') ||\n classList.contains('sc-font-dropdown-with-label') ||\n classList.contains('sc-font-family-select-button') ||\n classList.contains('sc-more-menu-btn') ||\n classList.contains('sc-more-menu-toolbar') ||\n classList.contains('sc-font-drawer-overlay') ||\n classList.contains('sc-font-drawer-content') ||\n classList.contains('sc-font-drawer-handle') ||\n classList.contains('sc-font-drawer-handle-bar') ||\n classList.contains('sc-font-dropdown-list-container') ||\n classList.contains('sc-font-dropdown-search') ||\n classList.contains('sc-font-search-input') ||\n classList.contains('sc-font-category-filters') ||\n classList.contains('sc-font-category-btn') ||\n classList.contains('sc-font-grid-option') ||\n classList.contains('sc-font-section') ||\n classList.contains('sc-font-section-header') ||\n classList.contains('sc-font-section-used') ||\n classList.contains('sc-font-section-header-used') ||\n classList.contains('sc-font-dropdown-list-grid') ||\n classList.contains('sc-font-dropdown-list-grid-used') ||\n classList.contains('sc-font-preview') ||\n classList.contains('sc-font-name-label') ||\n classList.contains('sc-font-size-slider-container') ||\n classList.contains('sc-font-size-slider') ||\n classList.contains('sc-font-size-icon-small') ||\n classList.contains('sc-font-size-icon-large') ||\n classList.contains('sc-font-icon-toggle') ||\n classList.contains('sc-font-dropdown-empty') ||\n classList.contains('vaul-drawer-wrapper') ||\n classList.contains('vaul-overlay')\n ) {\n isToolbarClick = true;\n break;\n }\n }\n element = element.parentElement;\n }\n\n // iOS Safari fix: also check the tracked touch target\n if (!isToolbarClick && lastTouchTargetRef.current) {\n element = lastTouchTargetRef.current;\n while (element) {\n const classList = element.classList;\n const dataSlot = element.getAttribute('data-slot');\n if (dataSlot && (dataSlot.includes('popover') || dataSlot.includes('dialog'))) {\n isToolbarClick = true;\n break;\n }\n if (element.hasAttribute('data-preserve-selection')) {\n isToolbarClick = true;\n break;\n }\n if (classList) {\n if (\n classList.contains('sc-toolbar') ||\n classList.contains('sc-toolbar-items') ||\n classList.contains('sc-toolbar-btn') ||\n classList.contains('sc-toolbar-btn-sm') ||\n classList.contains('sc-toolbar-btn-icon') ||\n classList.contains('sc-toolbar-color') ||\n classList.contains('sc-toolbar-label') ||\n classList.contains('sc-toolbar-select') ||\n classList.contains('sc-toolbar-select-sm') ||\n classList.contains('sc-toolbar-group') ||\n classList.contains('sc-toolbar-scroll') ||\n classList.contains('sc-toolbar-color-label') ||\n classList.contains('sc-toolbar-color-square') ||\n classList.contains('sc-color-picker-dropdown') ||\n classList.contains('sc-text-color-panel') ||\n classList.contains('sc-tooltip-wrapper') ||\n classList.contains('sc-font-dropdown-with-label') ||\n classList.contains('sc-font-family-select-button') ||\n classList.contains('sc-more-menu-btn') ||\n classList.contains('sc-more-menu-toolbar') ||\n classList.contains('vaul-drawer-wrapper') ||\n classList.contains('vaul-overlay')\n ) {\n isToolbarClick = true;\n break;\n }\n }\n element = element.parentElement;\n }\n }\n\n if (!isToolbarClick) {\n handleTextEditComplete();\n } else {\n const savedStart = selectionStart;\n const savedEnd = selectionEnd;\n inputRef.current?.focus();\n if (inputRef.current) {\n inputRef.current.setSelectionRange(savedStart, savedEnd);\n }\n }\n }, 0);\n }, [handleTextEditComplete, selectionStart, selectionEnd]);\n\n // Handle keyboard during editing\n const handleTextKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n const textarea = e.currentTarget;\n\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleTextEditComplete();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n setIsEditing(false);\n setEditText('');\n setEditRichText(null);\n editRichTextRef.current = null;\n setCurrentFormattingStyle(null);\n editingChildIdRef.current = null;\n }\n else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'b') {\n e.preventDefault();\n toggleFormattingProperty('bold');\n } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'i') {\n e.preventDefault();\n toggleFormattingProperty('italic');\n } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'u') {\n e.preventDefault();\n toggleFormattingProperty('underline');\n }\n else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n setTimeout(() => {\n const newPos = textarea.selectionStart;\n setCursorPosition(newPos);\n setSelectionStart(newPos);\n setSelectionEnd(textarea.selectionEnd);\n selectionAnchorRef.current = newPos;\n }, 0);\n }\n else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n if (selectedElement && selectedElement instanceof TextElement) {\n e.preventDefault();\n\n const direction = e.key === 'ArrowUp' ? 'up' : 'down';\n const newPos = moveCursorVertically(selectedElement, cursorPosition, direction);\n\n setCursorPosition(newPos);\n setSelectionStart(newPos);\n setSelectionEnd(newPos);\n selectionAnchorRef.current = newPos;\n\n textarea.setSelectionRange(newPos, newPos);\n }\n }\n else if (e.key === 'Home' || e.key === 'End') {\n setTimeout(() => {\n const newPos = textarea.selectionStart;\n setCursorPosition(newPos);\n setSelectionStart(newPos);\n setSelectionEnd(textarea.selectionEnd);\n selectionAnchorRef.current = newPos;\n }, 0);\n }\n },\n [handleTextEditComplete, toggleFormattingProperty, setCursorPosition, setSelectionStart, setSelectionEnd, selectionAnchorRef, cursorPosition, selectedElement]\n );\n\n // ============================================\n // EFFECTS\n // ============================================\n\n // Reset cursor animation start time when entering edit mode or moving cursor\n useEffect(() => {\n if (isEditing) {\n cursorStartTimeRef.current = performance.now();\n }\n }, [isEditing, cursorPosition, cursorStartTimeRef]);\n\n // RAF loop to keep cursor animating during edit mode\n useEffect(() => {\n if (!isEditing) return;\n\n let rafId: number;\n let lastUpdate = 0;\n const FRAME_INTERVAL = 33; // ~30fps\n\n const animate = (timestamp: number) => {\n if (timestamp - lastUpdate >= FRAME_INTERVAL) {\n setCursorAnimationFrame((prev) => (prev + 1) % 1000);\n lastUpdate = timestamp;\n }\n rafId = requestAnimationFrame(animate);\n };\n\n rafId = requestAnimationFrame(animate);\n return () => cancelAnimationFrame(rafId);\n }, [isEditing]);\n\n // Load font and focus input when editing starts\n const prevIsEditingRef = useRef(false);\n useEffect(() => {\n if (isEditing && !prevIsEditingRef.current && inputRef.current) {\n inputRef.current.focus();\n const endPos = editText.length;\n setCursorPosition(endPos);\n setSelectionStart(endPos);\n setSelectionEnd(endPos);\n inputRef.current.setSelectionRange(endPos, endPos);\n }\n prevIsEditingRef.current = isEditing;\n }, [isEditing, editText]);\n\n // Ref to track last formatting style to prevent infinite loops\n const lastFormattingStyleRef = useRef<CharacterStyle | null>(null);\n\n const stylesAreEqual = (a: CharacterStyle | null, b: CharacterStyle | null): boolean => {\n if (a === b) return true;\n if (!a || !b) return false;\n return (\n a.color === b.color &&\n a.fontFamily === b.fontFamily &&\n a.fontSize === b.fontSize &&\n a.bold === b.bold &&\n a.italic === b.italic &&\n a.underline === b.underline &&\n a.strikethrough === b.strikethrough\n );\n };\n\n // Update current formatting style when cursor moves\n useEffect(() => {\n if (!isEditing || !editRichText) return;\n\n const start = Math.min(selectionStart, selectionEnd);\n const end = Math.max(selectionStart, selectionEnd);\n\n if (start === end) {\n let newStyle = null;\n\n if (start > 0) {\n newStyle = editRichText.getStyleAt(start - 1);\n } else {\n const element = elements.find((e) => e.id === selectedId);\n if (element instanceof TextElement) {\n newStyle = element.getDefaultStyle();\n }\n }\n\n if (newStyle && !stylesAreEqual(newStyle, lastFormattingStyleRef.current)) {\n lastFormattingStyleRef.current = newStyle;\n setCurrentFormattingStyle(newStyle);\n }\n }\n }, [isEditing, editRichText, selectionStart, selectionEnd, selectedId]);\n\n // Sync textarea cursor/selection position with state\n useEffect(() => {\n if (!isEditing || !inputRef.current) return;\n\n const textarea = inputRef.current;\n\n if (textarea.selectionStart !== selectionStart || textarea.selectionEnd !== selectionEnd) {\n if (selectionStart !== selectionEnd) {\n textarea.setSelectionRange(selectionStart, selectionEnd);\n } else {\n textarea.setSelectionRange(cursorPosition, cursorPosition);\n }\n }\n }, [editText, isEditing, cursorPosition, selectionStart, selectionEnd]);\n\n // Update edit position dynamically as text changes\n useEffect(() => {\n const selectedEl = elements.find((e) => e.id === selectedId);\n if (!isEditing || !selectedEl || !canvasRef.current || !inputRef.current) return;\n\n if (editPositionUpdateTimerRef.current) {\n clearTimeout(editPositionUpdateTimerRef.current);\n }\n\n editPositionUpdateTimerRef.current = setTimeout(() => {\n let editingElement: TextElement | ImageElement | GroupElement | ShapeElement | PathElement = selectedEl;\n if (editingChildIdRef.current && selectedEl instanceof GroupElement) {\n const child = selectedEl.children.find((c) => c.id === editingChildIdRef.current);\n if (child) {\n editingElement = child;\n }\n }\n\n if (!(editingElement instanceof TextElement)) return;\n\n const tempElement = editingElement.clone();\n if (editRichText) {\n tempElement.setRichText(editRichText);\n }\n\n const visualBbox = tempElement.getVisualBoundingBox();\n const rect = canvasRef.current!.getBoundingClientRect();\n const contentHeight = visualBbox.height + 16;\n const offset = 6;\n\n setEditPosition({\n x: rect.left + (visualBbox.x + paddingOffsetX) * zoom - offset,\n y: rect.top + (visualBbox.y + paddingOffsetY) * zoom - offset,\n width: Math.max(20, visualBbox.width * zoom),\n height: Math.max(contentHeight * zoom, 50),\n });\n\n if (editingChildIdRef.current && selectedEl instanceof GroupElement) {\n const updatedGroup = selectedEl.clone();\n const childIndex = updatedGroup.children.findIndex((c) => c.id === editingChildIdRef.current);\n if (childIndex >= 0) {\n updatedGroup.children[childIndex] = tempElement;\n updatedGroup.updateBoundsFromChildren(true);\n }\n setElements((prevElements) => prevElements.map((e) => (e.id === updatedGroup.id ? updatedGroup : e)));\n transformHandles.update(tempElement, zoom);\n } else {\n setElements((prevElements) => prevElements.map((e) => (e.id === tempElement.id ? tempElement : e)));\n transformHandles.update(tempElement, zoom);\n }\n\n setHandlesVersion((v) => v + 1);\n }, 50);\n\n return () => {\n if (editPositionUpdateTimerRef.current) {\n clearTimeout(editPositionUpdateTimerRef.current);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editText, editRichText, isEditing, selectedId, zoom]);\n\n // Update cursor position and selection when textarea selection changes\n useEffect(() => {\n if (!isEditing || !inputRef.current) return;\n\n const textarea = inputRef.current;\n const updateCursor = () => {\n if (draggingSelectionHandleRef.current) {\n return;\n }\n\n const timeSinceMultiClick = Date.now() - lastMultiClickTimeRef.current;\n if (timeSinceMultiClick < MULTI_CLICK_DEBOUNCE) {\n return;\n }\n\n const start = textarea.selectionStart || 0;\n const end = textarea.selectionEnd || 0;\n setCursorPosition((prev) => prev === end ? prev : end);\n setSelectionStart((prev) => prev === start ? prev : start);\n setSelectionEnd((prev) => prev === end ? prev : end);\n\n onTextSelectionChange?.();\n };\n\n textarea.addEventListener('click', updateCursor);\n textarea.addEventListener('keyup', updateCursor);\n textarea.addEventListener('select', updateCursor);\n textarea.addEventListener('mouseup', updateCursor);\n\n const handleSelectionChange = () => {\n if (document.activeElement === textarea) {\n updateCursor();\n }\n };\n document.addEventListener('selectionchange', handleSelectionChange);\n\n updateCursor();\n\n return () => {\n textarea.removeEventListener('click', updateCursor);\n textarea.removeEventListener('keyup', updateCursor);\n textarea.removeEventListener('select', updateCursor);\n textarea.removeEventListener('mouseup', updateCursor);\n document.removeEventListener('selectionchange', handleSelectionChange);\n };\n }, [isEditing]);\n\n // Handle Enter key to enter edit mode for selected text element\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n const target = e.target as HTMLElement;\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') return;\n if (e.key !== 'Enter') return;\n if (isEditing) return;\n if (!canvasRef.current) return;\n\n const rect = canvasRef.current.getBoundingClientRect();\n\n // Check if there's an active child element in a group (text element only)\n if (\n activeChildElement &&\n activeChildElement instanceof TextElement &&\n !(activeChildElement instanceof GroupElement) &&\n !(activeChildElement instanceof ImageElement) &&\n !(activeChildElement instanceof ShapeElement)\n ) {\n e.preventDefault();\n const visualBbox = activeChildElement.getVisualBoundingBox();\n const contentHeight = visualBbox.height + 16;\n const offset = 6;\n\n setEditPosition({\n x: rect.left + (visualBbox.x + paddingOffsetX) * zoom - offset,\n y: rect.top + (visualBbox.y + paddingOffsetY) * zoom - offset,\n width: Math.max(200, visualBbox.width * zoom),\n height: Math.max(contentHeight * zoom, 50),\n });\n setEditText(getWrappedTextForEditing(activeChildElement));\n setIsEditing(true);\n editModeEnteredAtRef.current = Date.now();\n editingChildIdRef.current = activeChildElement.id;\n return;\n }\n\n // Check if there's a selected text element\n if (\n selectedElement &&\n selectedElement instanceof TextElement &&\n !(selectedElement instanceof GroupElement) &&\n !(selectedElement instanceof ImageElement) &&\n !(selectedElement instanceof ShapeElement)\n ) {\n e.preventDefault();\n const visualBbox = selectedElement.getVisualBoundingBox();\n const contentHeight = visualBbox.height + 16;\n const offset = 6;\n\n setEditPosition({\n x: rect.left + (visualBbox.x + paddingOffsetX) * zoom - offset,\n y: rect.top + (visualBbox.y + paddingOffsetY) * zoom - offset,\n width: Math.max(200, visualBbox.width * zoom),\n height: Math.max(contentHeight * zoom, 50),\n });\n setEditText(getWrappedTextForEditing(selectedElement));\n setIsEditing(true);\n editModeEnteredAtRef.current = Date.now();\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [selectedElement, activeChildElement, isEditing]);\n\n return {\n handleTextEditComplete,\n handleTextEditBlur,\n handleTextKeyDown,\n applyFormattingToSelection,\n getSelectionStyle,\n toggleFormattingProperty,\n enterTextEditMode,\n getWrappedTextForEditing,\n };\n}\n","/**\n * CanvasEditor - Main component for text transformation canvas\n * Manages elements, selection, interaction, and rendering\n *\n * This is the orchestrator component. Heavy logic has been extracted into:\n * - useCanvasRenderLoop: Canvas rendering (DPR, zoom, artboards, elements, overlays)\n * - useCanvasInteraction: Pointer/mouse event handlers (drag, resize, rotate, selection)\n * - useTextEditingHandlers: Text editing (formatting, blur, keyboard, cursor sync)\n */\n\nimport React, { useRef, useState, useEffect, useCallback, useMemo, forwardRef, useImperativeHandle } from 'react';\nimport { createPortal } from 'react-dom';\nimport { TransformHandles } from '../core/TransformHandles.js';\nimport { globalResizePipeline } from '../core/ResizePipeline.js';\nimport { InteractionStateMachine } from '../core/InteractionStateMachine.js';\nimport { AlignmentSnapSystem } from '../core/AlignmentSnapSystem.js';\nimport { SpacingSystem } from '../core/SpacingSystem.js';\nimport { ArtboardRenderer } from '../core/ArtboardRenderer.js';\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport { GroupElement } from '../core/GroupElement.js';\nimport { PathElement } from '../core/PathElement.js';\nimport { ShapeElement } from '../core/ShapeElement.js';\nimport { ArtboardElement } from '../core/ArtboardElement.js';\nimport type { ArtboardManager } from '../core/ArtboardManager.js';\nimport type { CharacterStyle } from '../types/index.js';\nimport { RichText } from '../types/index.js';\nimport { useTheme } from '../contexts/ThemeContext.js';\nimport { RotationUtils } from '../core/RotationUtils.js';\nimport { getCursorIndexFromPoint, getWordBoundaryAt } from '../utils/textCursorUtils.js';\n\n/** Union type for all canvas element types */\ntype CanvasElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\n// Import custom hooks\nimport {\n useMarqueeSelection,\n usePenTool,\n useActiveChild,\n useHoverState,\n useCropMode,\n useMultiSelection,\n useTextEditing,\n useCanvasRenderLoop,\n useCanvasInteraction,\n useTextEditingHandlers,\n useCanvasLayout,\n useInteractionState,\n useRenderState,\n} from './CanvasEditor/hooks/index.js';\n\ninterface CanvasEditorProps {\n elements: CanvasElement[];\n artboards: ArtboardElement[];\n artboardManager: ArtboardManager;\n selectedId: string | null;\n multiSelection: string[];\n onElementUpdate: (element: CanvasElement) => void;\n setElements: React.Dispatch<React.SetStateAction<CanvasElement[]>>;\n executeElementUpdate: (\n oldElement: CanvasElement | undefined,\n newElement: CanvasElement\n ) => void;\n onSelectionChange: (id: string | null) => void;\n onMultiSelectionChange: (ids: string[]) => void;\n onActiveChildChange?: (child: CanvasElement | null) => void;\n onTextSelectionChange?: () => void;\n onHoverChange?: (elementId: string | null) => void;\n onRotationStateChange?: (isRotating: boolean) => void;\n onCropModeEnter?: () => void;\n /**\n * Right-click (contextmenu) on the canvas: fired AFTER the target under\n * the cursor has been hit-tested and selected (existing multi-selection\n * preserved when the target is part of it). The host renders the menu —\n * see `CanvasContextMenu`. When omitted, the browser's native menu is\n * left alone; either way right-click never starts a drag/marquee.\n */\n onContextMenuRequest?: (\n request: import('./CanvasEditor/hooks/useCanvasInteraction.js').CanvasContextMenuRequest\n ) => void;\n hideHandles?: boolean;\n canvasRef?: React.RefObject<HTMLCanvasElement | null>;\n width?: number;\n height?: number;\n zoom?: number;\n offsetX?: number;\n offsetY?: number;\n viewPadding?: number;\n artboardBorderRadius?: number;\n fixedMargin?: number;\n fixedMarginX?: number;\n fixedMarginY?: number;\n showRotationHandle?: boolean;\n /** ADR-0054 Phase 4: cutout shapes (artboard px) punched through the\n * `<canvas>` as truly transparent holes. Translated internally to a\n * CSS `clip-path: path(evenodd, ...)` that accounts for padding +\n * zoom, so consumers can think purely in artboard coordinates. */\n canvasCutouts?: import(\"./embed/SnowconeCanvas\").CanvasCutoutShape[];\n /** ADR-0054: piece-guide overlays drawn inside the canvas render loop. */\n pieceGuides?: import(\"./embed/SnowconeCanvas\").PieceGuidesConfig;\n /**\n * ADR-0060: which piece (if any) the editor is focused on inside a\n * multi-piece spread. `'spread'` (or undefined) shows every piece in\n * the layout at full size — the historical behaviour. `{ pieceId }`\n * fits the viewport to that piece (with a peek margin so neighbouring\n * pieces remain visible at reduced opacity for overflow feedback).\n * The pieces themselves come from `pieceGuides.pieces`.\n *\n * Spread-mode is derived from `pieces.length`: layouts with a single\n * piece are unaffected by this prop (the toggle is a no-op for them).\n */\n pieceFocus?: 'spread' | { pieceId: string };\n /**\n * ADR-0060: optional override for the focused-piece rect, used by\n * the parent (`Canvas.tsx`) to feed in an animated rect when\n * switching pieces. When provided, it replaces the rect that would\n * otherwise be derived from `pieceFocus` + `pieceGuides`. Lets the\n * zoom-fit math (lives in `Canvas.tsx`) and the layout/render-loop\n * (lives here) share one interpolating value during the dolly.\n */\n focusedPieceRect?: { x: number; y: number; width: number; height: number } | null;\n}\n\nexport interface CanvasEditorHandle {\n applyTextFormatting: (style: CharacterStyle) => void;\n toggleFormattingProperty: (property: 'bold' | 'italic' | 'underline' | 'strikethrough') => void;\n isEditingText: () => boolean;\n isRotating: () => boolean;\n getSelectionStyle: () => CharacterStyle | null;\n getZoom: () => number;\n forceRender: () => void;\n getEditingState: () => {\n isEditing: boolean;\n editText: string;\n cursorPosition: number;\n selectionStart: number;\n selectionEnd: number;\n };\n getCropState: () => {\n isCropping: boolean;\n elementId: string | null;\n };\n getPaddingOffset: () => { x: number; y: number };\n}\n\nconst CanvasEditor = forwardRef<CanvasEditorHandle, CanvasEditorProps>(\n (\n {\n elements,\n artboards,\n artboardManager,\n selectedId,\n multiSelection,\n onElementUpdate,\n setElements,\n executeElementUpdate,\n onSelectionChange,\n onMultiSelectionChange,\n onActiveChildChange,\n onTextSelectionChange,\n onHoverChange,\n onRotationStateChange,\n onCropModeEnter,\n onContextMenuRequest,\n hideHandles = false,\n canvasRef: externalCanvasRef,\n width,\n height,\n zoom = 1.0,\n offsetX = 0,\n offsetY = 0,\n viewPadding = 1.0,\n artboardBorderRadius = 0,\n fixedMargin,\n fixedMarginX,\n fixedMarginY,\n showRotationHandle = true,\n canvasCutouts,\n pieceGuides,\n pieceFocus,\n focusedPieceRect: focusedPieceRectOverride,\n },\n ref\n ) => {\n // ============================================\n // THEME\n // ============================================\n const { resolvedTheme } = useTheme();\n\n // ============================================\n // REFS\n // ============================================\n const internalCanvasRef = useRef<HTMLCanvasElement>(null);\n const canvasRef = externalCanvasRef || internalCanvasRef;\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n // Text editing coordination refs\n const repositioningCursorRef = useRef(false);\n const lastClickTimeRef = useRef<number>(0);\n const clickCountRef = useRef<number>(0);\n const editModeEnteredAtRef = useRef<number>(0);\n const skipDragSelectionRef = useRef<boolean>(false);\n const lastMultiClickTimeRef = useRef<number>(0);\n const multiClickSelectionRef = useRef<{ start: number; end: number; cursor: number } | null>(null);\n const lastProcessedClickTimeRef = useRef<number>(0);\n const draggingSelectionHandleRef = useRef<'start' | 'end' | null>(null);\n const selectionHandlePositionsRef = useRef<{\n start: { center: { x: number; y: number }; textEdge: { x: number; y: number } } | null;\n end: { center: { x: number; y: number }; textEdge: { x: number; y: number } } | null;\n }>({ start: null, end: null });\n const lastMousePosRef = useRef<{ x: number; y: number } | null>(null);\n const dragOriginalElementsRef = useRef<Map<string, CanvasElement> | null>(null);\n const rotatingElementRef = useRef<CanvasElement | null>(null);\n const editPositionUpdateTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Constants\n const MULTI_CLICK_THRESHOLD = 400;\n const MULTI_CLICK_DEBOUNCE = 300;\n const CLICK_DEDUP_THRESHOLD = 50;\n const EDIT_MODE_BLUR_DEBOUNCE = 200;\n\n // ============================================\n // COMPOSITE HOOKS: Layout, Interaction, Render\n // ============================================\n // ADR-0060: when `pieceFocus` names a piece in the active layout's\n // `pieces[]`, useCanvasLayout fits the viewport to that piece's\n // rect plus a peek margin (so neighbour pieces remain visible at\n // edges for overflow feedback). Spread mode (or unrecognised piece\n // id) falls back to artboard-sized viewport.\n // When the parent (Canvas.tsx) supplies an animated rect, defer to\n // it; otherwise derive from `pieceFocus` directly so this component\n // still works standalone (e.g. inside a host that doesn't animate).\n const derivedFocusedPieceRect = React.useMemo(() => {\n if (!pieceFocus || pieceFocus === 'spread') return null;\n const pieces = pieceGuides?.pieces ?? [];\n // Single-piece layouts ignore pieceFocus — focus toggle is a\n // no-op for them per ADR-0060.\n if (pieces.length <= 1) return null;\n const match = pieces.find((p) => p.id === pieceFocus.pieceId);\n if (!match) return null;\n return { x: match.x, y: match.y, width: match.width, height: match.height };\n }, [pieceFocus, pieceGuides]);\n const focusedPieceRect =\n focusedPieceRectOverride !== undefined\n ? focusedPieceRectOverride\n : derivedFocusedPieceRect;\n\n // Selection-clipping rect for the hit-test focus filter. Falls\n // back to the single piece's bounds for products with one piece\n // (phone cases, etc.) so clicks outside the clipped boundary\n // don't select elements that are primarily inside it.\n const selectionClipRect = React.useMemo(() => {\n if (focusedPieceRect) return focusedPieceRect;\n const pieces = pieceGuides?.pieces ?? [];\n if (pieces.length === 1) {\n const p = pieces[0];\n return { x: p.x, y: p.y, width: p.width, height: p.height };\n }\n return null;\n }, [focusedPieceRect, pieceGuides]);\n\n const {\n isTouchDevice,\n canvasSize,\n lastTouchTargetRef,\n effectiveViewPadding,\n paddedCanvasWidth,\n paddedCanvasHeight,\n paddingOffsetX,\n paddingOffsetY,\n zoomInvariantBorderRadius,\n } = useCanvasLayout({\n width, height, zoom, viewPadding, artboardBorderRadius,\n fixedMargin, fixedMarginX, fixedMarginY,\n artboards, artboardManager,\n focusedPieceRect,\n });\n\n const {\n snapGuides, setSnapGuides,\n spacingIndicators, setSpacingIndicators,\n isAltKeyPressed, setIsAltKeyPressed,\n isRotating, setIsRotating,\n setRotationStartAngle,\n currentRotation, setCurrentRotation,\n setRotatingElementVersion,\n } = useInteractionState();\n\n const {\n renderVersion,\n forceRender,\n handlesVersion, setHandlesVersion,\n forceUpdate,\n cursorAnimationFrame, setCursorAnimationFrame,\n } = useRenderState({ elements });\n\n // ============================================\n // CORE SYSTEMS (stable references)\n // ============================================\n const [transformHandles] = useState(() => new TransformHandles());\n const [artboardRenderer] = useState(() => new ArtboardRenderer({ showBorder: false, showLabel: false }));\n const [snapSystem] = useState(() => new AlignmentSnapSystem({ enabled: true, showGuides: true }));\n const [spacingSystem] = useState(() => new SpacingSystem({ enabled: true, showLabels: true }));\n const [stateMachine] = useState(() => {\n const machine = new InteractionStateMachine();\n machine.setResizePipeline(globalResizePipeline);\n machine.setSnapHandler(snapSystem);\n return machine;\n });\n\n // Update artboardRenderer borderRadius\n useEffect(() => {\n artboardRenderer.setOptions({ borderRadius: zoomInvariantBorderRadius });\n }, [zoomInvariantBorderRadius, artboardRenderer]);\n\n // ADR-0060: when a piece is focused inside a multi-piece spread,\n // expose the piece's bounds as additional snap targets. The snap\n // system then offers piece-center / piece-edge guides alongside\n // the regular element-to-element snapping, so dragging an element\n // to \"centre on Front\" feels right — it snaps to the visible\n // piece's centre, not the full 4042×3075 spread's centre. Cleared\n // when no piece is focused (spread/'All' view, single-piece\n // products) so single-piece editing keeps its old behaviour.\n useEffect(() => {\n const focused = focusedPieceRectOverride !== undefined\n ? focusedPieceRectOverride\n : derivedFocusedPieceRect;\n snapSystem.setExtraSnapRects(\n focused\n ? [{ id: 'focused-piece', ...focused }]\n : [],\n );\n }, [snapSystem, focusedPieceRectOverride, derivedFocusedPieceRect]);\n\n // ============================================\n // ELEMENT REFS (for use in callbacks)\n // ============================================\n const elementsRef = useRef(elements);\n elementsRef.current = elements;\n\n const selectedElement = elements.find((e) => e.id === selectedId);\n const selectedElementRef = useRef(selectedElement);\n selectedElementRef.current = selectedElement;\n\n // ============================================\n // ARIA ANNOUNCEMENTS\n // ============================================\n const [ariaAnnouncement, setAriaAnnouncement] = useState('');\n\n useEffect(() => {\n if (!selectedId) return;\n const el = elements.find((e) => e.id === selectedId);\n if (!el) return;\n if (el instanceof TextElement) {\n const label = (el as TextElement).text?.slice(0, 40) || 'empty';\n setAriaAnnouncement(`Selected text element: ${label}`);\n } else if (el instanceof ImageElement) {\n setAriaAnnouncement('Selected image element');\n } else if (el instanceof GroupElement) {\n setAriaAnnouncement('Selected group');\n } else {\n setAriaAnnouncement('Selected element');\n }\n }, [selectedId, elements]);\n\n const artboardManagerRef = useRef(artboardManager);\n artboardManagerRef.current = artboardManager;\n\n // ============================================\n // SNAP HOOK\n // ============================================\n const elementsByArtboardRef = useRef(new Map<string | null, CanvasElement[]>());\n\n useEffect(() => {\n const snapHook = (result: unknown) => {\n const resizeResult = result as { element: CanvasElement; handle: unknown; startData: unknown; [key: string]: unknown };\n const activeArtboardId = artboardManagerRef.current.getActiveArtboardId();\n const activeArtboardElements = elementsByArtboardRef.current.get(activeArtboardId) || [];\n const allElements: CanvasElement[] = [];\n activeArtboardElements.forEach((e) => {\n if (e instanceof GroupElement) {\n allElements.push(...e.children);\n } else {\n allElements.push(e);\n }\n });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ResizeResult type is internal to AlignmentSnapSystem\n return snapSystem.snapDimensions(resizeResult as any, allElements, (resizeResult as any).startData);\n };\n globalResizePipeline.addHook('afterResize', snapHook);\n return () => { globalResizePipeline.removeHook('afterResize', snapHook); };\n }, [snapSystem]);\n\n // ============================================\n // CUSTOM HOOKS (existing Phase 1-3 extractions)\n // ============================================\n const { cropController } = useCropMode();\n const { penTool, penCursor } = usePenTool({ selectedElement });\n\n const {\n isEditing, setIsEditing,\n editText, setEditText,\n editPosition, setEditPosition,\n cursorPosition, setCursorPosition,\n selectionStart, setSelectionStart,\n selectionEnd, setSelectionEnd,\n cursorOpacityRef, cursorStartTimeRef,\n selectionAnchorRef, didDragSelectRef,\n editRichText, setEditRichText, editRichTextRef,\n currentFormattingStyle: _currentFormattingStyle,\n setCurrentFormattingStyle,\n } = useTextEditing({ selectedElement, ref });\n\n // Multi-click selection force-restore\n useEffect(() => {\n const timeSinceMultiClick = Date.now() - lastMultiClickTimeRef.current;\n const savedSelection = multiClickSelectionRef.current;\n if (savedSelection && timeSinceMultiClick < MULTI_CLICK_DEBOUNCE) {\n if (selectionStart !== savedSelection.start || selectionEnd !== savedSelection.end) {\n setCursorPosition(savedSelection.cursor);\n setSelectionStart(savedSelection.start);\n setSelectionEnd(savedSelection.end);\n if (inputRef.current) {\n inputRef.current.setSelectionRange(savedSelection.start, savedSelection.end);\n }\n }\n } else if (savedSelection && timeSinceMultiClick >= MULTI_CLICK_DEBOUNCE) {\n multiClickSelectionRef.current = null;\n }\n }, [selectionStart, selectionEnd, cursorPosition]);\n\n // ============================================\n // TEXT-EDIT TEXTAREA SCROLL TRACKING (ADR-0085 §5, class-3 contract)\n // ============================================\n // The invisible edit textarea is a `position: fixed` portal to\n // document.body whose coordinates were computed from the canvas rect at\n // edit-entry. Page scroll and nested-container scroll move the canvas\n // without re-entering edit mode, so the hit-target drifts off the element\n // (taps land outside the textarea and blur edit mode). Same mechanism as\n // the floating contextual toolbar: capture-phase passive scroll listeners\n // (scroll doesn't bubble, but it does capture — nested scrollers\n // reposition too), shifting by the canvas-rect delta read at positioning\n // time. Direct DOM style writes on purpose — React only rewrites\n // left/top when `editPosition` changes, and any such recompute reads a\n // fresh rect, so the two writers can't fight.\n useEffect(() => {\n if (!isEditing) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n let lastRect = canvas.getBoundingClientRect();\n const reposition = (): void => {\n const el = inputRef.current;\n const liveCanvas = canvasRef.current;\n if (!el || !liveCanvas) return;\n const rect = liveCanvas.getBoundingClientRect();\n const dx = rect.left - lastRect.left;\n const dy = rect.top - lastRect.top;\n if (dx === 0 && dy === 0) return;\n lastRect = rect;\n el.style.left = `${parseFloat(el.style.left || '0') + dx}px`;\n el.style.top = `${parseFloat(el.style.top || '0') + dy}px`;\n };\n window.addEventListener('scroll', reposition, { passive: true, capture: true });\n window.addEventListener('resize', reposition);\n return () => {\n window.removeEventListener('scroll', reposition, { capture: true });\n window.removeEventListener('resize', reposition);\n };\n }, [isEditing]);\n\n const {\n isMarqueeSelecting, marqueeStart, marqueeEnd, marqueePreviewSelection,\n startMarqueeSelection, updateMarqueeSelection, finishMarqueeSelection,\n } = useMarqueeSelection();\n\n const {\n multiSelectionGroupBoundsRef, multiSelectionOBBRef,\n } = useMultiSelection(multiSelection);\n\n const {\n activeChildElement, updateActiveChild, editingChildIdRef,\n } = useActiveChild({ elements, selectedElement, transformHandles, onActiveChildChange });\n\n const { hoverState, setHoverState } = useHoverState({ onHoverChange });\n\n // ============================================\n // ELEMENT CACHE BY ARTBOARD\n // ============================================\n const elementsByArtboard = useMemo(() => {\n const cache = new Map<string | null, CanvasElement[]>();\n elements.forEach((element) => {\n const artboardId = artboardManager.getArtboardIdForElement(element.id);\n if (!cache.has(artboardId)) { cache.set(artboardId, []); }\n cache.get(artboardId)!.push(element);\n });\n return cache;\n }, [elements, artboardManager]);\n\n elementsByArtboardRef.current = elementsByArtboard;\n\n const getActiveArtboardElements = useCallback(() => {\n const activeArtboardId = artboardManager.getActiveArtboardId();\n return elementsByArtboard.get(activeArtboardId) || [];\n }, [elementsByArtboard, artboardManager]);\n\n // ============================================\n // ALT KEY + PEN TOOL SHORTCUTS\n // ============================================\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (isEditing) return;\n\n if (penTool.isActive() && selectedElement instanceof PathElement) {\n if (e.key === 'Escape') {\n e.preventDefault();\n penTool.finishPath();\n const updatedElement = selectedElement.clone();\n executeElementUpdate(selectedElement, updatedElement);\n setHandlesVersion((v) => v + 1);\n return;\n } else if (e.key === 'Backspace' || e.key === 'Delete') {\n e.preventDefault();\n penTool.deleteLastPoint();\n setHandlesVersion((v) => v + 1);\n return;\n }\n }\n\n if (e.altKey) { setIsAltKeyPressed(true); }\n };\n\n const handleKeyUp = (e: KeyboardEvent) => {\n if (!e.altKey) { setIsAltKeyPressed(false); }\n };\n\n const handleBlur = (e: FocusEvent) => {\n if (!e.relatedTarget || !(e.relatedTarget as Node).ownerDocument) {\n setIsAltKeyPressed(false);\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n window.addEventListener('keyup', handleKeyUp);\n window.addEventListener('blur', handleBlur as EventListener);\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n window.removeEventListener('keyup', handleKeyUp);\n window.removeEventListener('blur', handleBlur);\n };\n }, [penTool, selectedElement, executeElementUpdate, setHandlesVersion, isEditing]);\n\n // Re-detect spacing when Alt key is pressed/released\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas || !lastMousePosRef.current ||\n stateMachine.getMode() === 'drag' || stateMachine.getMode() === 'resize') {\n return;\n }\n\n const { x, y } = lastMousePosRef.current;\n\n if (isAltKeyPressed) {\n let hoveredElement: CanvasElement | null = null;\n const activeArtboardElements = getActiveArtboardElements();\n for (let i = activeArtboardElements.length - 1; i >= 0; i--) {\n const element = activeArtboardElements[i];\n if (element.visible === false) continue;\n if (element instanceof GroupElement) {\n for (let j = element.children.length - 1; j >= 0; j--) {\n const child = element.children[j];\n if (child.visible === false) continue;\n const bbox = child.getVisualBoundingBox();\n if (x >= bbox.x && x <= bbox.x + bbox.width && y >= bbox.y && y <= bbox.y + bbox.height) {\n hoveredElement = child;\n break;\n }\n }\n } else {\n const bbox = element.getVisualBoundingBox();\n if (x >= bbox.x && x <= bbox.x + bbox.width && y >= bbox.y && y <= bbox.y + bbox.height) {\n hoveredElement = element;\n break;\n }\n }\n if (hoveredElement) break;\n }\n\n if (hoveredElement) {\n const flattenedElements: CanvasElement[] = [];\n activeArtboardElements.forEach((e) => {\n if (e instanceof GroupElement) { flattenedElements.push(...e.children); }\n else { flattenedElements.push(e); }\n });\n const indicators = spacingSystem.detectSpacing(hoveredElement, flattenedElements, true);\n setSpacingIndicators(indicators);\n }\n } else {\n setSpacingIndicators([]);\n }\n }, [isAltKeyPressed, getActiveArtboardElements, spacingSystem, stateMachine]);\n\n // ============================================\n // TRANSFORM HANDLES UPDATE\n // ============================================\n const lastHandlesUpdateRef = useRef<string | null>(null);\n\n useEffect(() => {\n const selectedEl = elements.find((e) => e.id === selectedId);\n const elementToUpdate = activeChildElement || selectedEl;\n\n if (elementToUpdate) {\n const bbox = elementToUpdate.getBoundingBox();\n const elementKey = `${elementToUpdate.id}-${bbox.x}-${bbox.y}-${bbox.width}-${bbox.height}-${elementToUpdate.rotation}`;\n\n if (lastHandlesUpdateRef.current !== elementKey) {\n lastHandlesUpdateRef.current = elementKey;\n transformHandles.update(elementToUpdate, zoom);\n setHandlesVersion((v) => v + 1);\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [selectedId, activeChildElement, transformHandles, zoom, elements]);\n\n // ============================================\n // EXTRACTED HOOKS (Phase 4)\n // ============================================\n\n // Text editing handlers\n const {\n handleTextEditBlur,\n handleTextKeyDown,\n applyFormattingToSelection,\n getSelectionStyle,\n toggleFormattingProperty,\n enterTextEditMode,\n getWrappedTextForEditing,\n } = useTextEditingHandlers({\n canvasRef,\n inputRef,\n editingChildIdRef,\n editRichTextRef,\n selectionAnchorRef,\n cursorStartTimeRef,\n lastMultiClickTimeRef,\n multiClickSelectionRef,\n editPositionUpdateTimerRef,\n draggingSelectionHandleRef,\n repositioningCursorRef,\n editModeEnteredAtRef,\n lastTouchTargetRef,\n MULTI_CLICK_DEBOUNCE,\n EDIT_MODE_BLUR_DEBOUNCE,\n isEditing,\n editText,\n editRichText,\n cursorPosition,\n selectionStart,\n selectionEnd,\n selectedId,\n selectedElement,\n activeChildElement,\n elements,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n setIsEditing,\n setEditText,\n setEditRichText,\n setCursorPosition,\n setSelectionStart,\n setSelectionEnd,\n setEditPosition,\n setCurrentFormattingStyle,\n setCursorAnimationFrame,\n setHandlesVersion,\n setElements,\n onElementUpdate,\n onTextSelectionChange,\n transformHandles,\n updateActiveChild,\n });\n\n // Canvas interaction handlers\n const {\n handlePointerDown,\n handlePointerMove,\n handlePointerUp,\n handleDoubleClick,\n handleContextMenu,\n } = useCanvasInteraction({\n canvasRef,\n inputRef,\n repositioningCursorRef,\n lastClickTimeRef,\n clickCountRef,\n lastProcessedClickTimeRef,\n lastMultiClickTimeRef,\n multiClickSelectionRef,\n skipDragSelectionRef,\n draggingSelectionHandleRef,\n selectionHandlePositionsRef,\n lastMousePosRef,\n dragOriginalElementsRef,\n rotatingElementRef,\n editingChildIdRef,\n editRichTextRef,\n editModeEnteredAtRef,\n multiSelectionGroupBoundsRef,\n elementsRef,\n MULTI_CLICK_THRESHOLD,\n MULTI_CLICK_DEBOUNCE,\n CLICK_DEDUP_THRESHOLD,\n isEditing,\n editText,\n isTouchDevice,\n selectionStart,\n selectionEnd,\n isRotating,\n isMarqueeSelecting,\n isAltKeyPressed,\n selectedId,\n selectedElement,\n activeChildElement,\n elements,\n multiSelection,\n multiSelectionOBBRef,\n zoom,\n paddingOffsetX,\n paddingOffsetY,\n spacingIndicators,\n focusedPieceRect: selectionClipRect,\n setCursorPosition,\n setSelectionStart,\n setSelectionEnd,\n setIsRotating,\n setRotationStartAngle,\n setCurrentRotation,\n setRotatingElementVersion,\n setIsAltKeyPressed,\n setSnapGuides,\n setSpacingIndicators,\n setHandlesVersion,\n setHoverState,\n setElements,\n forceUpdate,\n stateMachine,\n transformHandles,\n snapSystem,\n spacingSystem,\n penTool,\n cropController,\n onElementUpdate,\n executeElementUpdate,\n onSelectionChange,\n onMultiSelectionChange,\n onRotationStateChange,\n onCropModeEnter,\n onContextMenuRequest,\n updateActiveChild,\n getActiveArtboardElements,\n startMarqueeSelection,\n updateMarqueeSelection,\n finishMarqueeSelection,\n enterTextEditMode,\n setEditPosition,\n setEditText,\n setEditRichText,\n setIsEditing,\n setCurrentFormattingStyle,\n getWrappedTextForEditing,\n });\n\n // Canvas render loop\n useCanvasRenderLoop({\n canvasRef,\n elements,\n elementsByArtboard,\n artboards,\n artboardManager,\n artboardRenderer,\n selectedId,\n selectedElement,\n activeChildElement,\n editingChildIdRef,\n transformHandles,\n stateMachine,\n canvasSize,\n paddedCanvasWidth,\n paddedCanvasHeight,\n paddingOffsetX,\n paddingOffsetY,\n zoom,\n zoomInvariantBorderRadius,\n effectiveViewPadding,\n showRotationHandle,\n isRotating,\n currentRotation,\n hoverState,\n isEditing,\n editText,\n editRichText,\n cursorPosition,\n selectionStart,\n selectionEnd,\n cursorOpacityRef,\n cursorStartTimeRef,\n isTouchDevice,\n selectionHandlePositionsRef,\n multiClickSelectionRef,\n lastMultiClickTimeRef,\n MULTI_CLICK_DEBOUNCE,\n snapGuides,\n spacingIndicators,\n isAltKeyPressed,\n multiSelection,\n multiSelectionOBBRef,\n multiSelectionGroupBoundsRef,\n isMarqueeSelecting,\n marqueeStart,\n marqueeEnd,\n marqueePreviewSelection,\n penTool,\n handlesVersion,\n hideHandles,\n resolvedTheme,\n cursorAnimationFrame,\n renderVersion,\n pieceGuides,\n focusedPieceId:\n pieceFocus && pieceFocus !== 'spread' ? pieceFocus.pieceId : null,\n focusedPieceRect,\n });\n\n // ============================================\n // IMPERATIVE HANDLE\n // ============================================\n useImperativeHandle(\n ref,\n () => ({\n applyTextFormatting: (style: CharacterStyle) => {\n applyFormattingToSelection(style);\n },\n toggleFormattingProperty: (property: 'bold' | 'italic' | 'underline' | 'strikethrough') => {\n toggleFormattingProperty(property);\n },\n isEditingText: () => isEditing,\n isRotating: () => isRotating,\n getSelectionStyle: () => getSelectionStyle(),\n getZoom: () => zoom,\n forceRender,\n getEditingState: () => ({\n isEditing,\n editText,\n cursorPosition,\n selectionStart,\n selectionEnd,\n }),\n getCropState: () => ({\n isCropping: selectedElement instanceof ImageElement && (selectedElement as ImageElement).isCropping,\n elementId: selectedElement?.id || null,\n }),\n getPaddingOffset: () => ({ x: paddingOffsetX, y: paddingOffsetY }),\n }),\n [applyFormattingToSelection, toggleFormattingProperty, isEditing, isRotating, getSelectionStyle, zoom, forceRender, editText, cursorPosition, selectionStart, selectionEnd, selectedElement, paddingOffsetX, paddingOffsetY]\n );\n\n // ============================================\n // ADR-0054 Phase 4: Canvas clip-path for true die-cut knockout.\n // ============================================\n // Translate artboard-space cutouts into a CSS clip-path in the\n // `<canvas>` element's own CSS-pixel coordinate system. The canvas\n // element's CSS size is `paddedCanvas{W,H} * zoom`; the artboard is\n // drawn at `(paddingOffset{X,Y}, paddingOffset{X,Y})` inside it. So\n // for every cutout at artboard `(cx, cy, cw, ch)` the equivalent\n // canvas-CSS position is `((paddingOffsetX + cx) * zoom, ... )`.\n // Evenodd fill rule punches the cutout shapes out of a full-canvas\n // outer rect.\n const canvasClipPathValue = useMemo(() => {\n if (!canvasCutouts || canvasCutouts.length === 0) return undefined;\n const cssW = paddedCanvasWidth * zoom;\n const cssH = paddedCanvasHeight * zoom;\n const outer = `M 0 0 h ${cssW} v ${cssH} h ${-cssW} Z`;\n const cutouts = canvasCutouts\n .map((c) => {\n if (c.kind === \"rect\" || c.kind === \"roundedRect\") {\n const x = (paddingOffsetX + c.x) * zoom;\n const y = (paddingOffsetY + c.y) * zoom;\n const w = c.width * zoom;\n const h = c.height * zoom;\n if (c.kind === \"roundedRect\") {\n const rx = (c.rx ?? c.ry ?? 0) * zoom;\n const ry = (c.ry ?? c.rx ?? 0) * zoom;\n if (rx > 0 || ry > 0) {\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 }\n return `M ${x} ${y} h ${w} v ${h} h ${-w} Z`;\n }\n // kind === \"path\": raw SVG path-data. Assumes the `d` string\n // is already in artboard-space px; we don't attempt to parse\n // and transform (Phase 5 territory). Shift via a wrapping\n // `translate` is not possible inside a CSS path() string — so\n // the consumer should provide rect/roundedRect for now.\n return \"\";\n })\n .filter(Boolean);\n if (cutouts.length === 0) return undefined;\n return `path(evenodd, \"${outer} ${cutouts.join(\" \")}\")`;\n }, [canvasCutouts, paddedCanvasWidth, paddedCanvasHeight, paddingOffsetX, paddingOffsetY, zoom]);\n\n // ============================================\n // JSX RENDER\n // ============================================\n return (\n <>\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n style={{ position: 'absolute', width: 1, height: 1, margin: -1, padding: 0, overflow: 'hidden', clip: 'rect(0,0,0,0)', whiteSpace: 'nowrap', borderWidth: 0 }}\n >\n {ariaAnnouncement}\n </div>\n <canvas\n role=\"application\"\n aria-label=\"Design editor canvas. Use mouse or touch to select, move, and resize elements.\"\n ref={canvasRef}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerUp}\n onDoubleClick={handleDoubleClick}\n onContextMenu={handleContextMenu}\n style={{\n display: 'block',\n // When pieceGuides are present (phone cases, anything with\n // a non-rectangular outer shape) the canvas must be fully\n // transparent: the artboard 2D fill is already rounded to\n // the piece's outer radius, and the bleed-ring vignette\n // uses `destination-out` to knock the corners out. An\n // opaque CSS bg here defeats both — the area outside the\n // rounded artboard reads as a white frame, and the\n // knocked-out corners reveal that same white instead of\n // whatever sits behind the canvas. Plain rectangular\n // products keep the legacy editor-surface fill so the\n // design canvas still reads as a distinct surface.\n background: pieceGuides ? 'transparent' : 'var(--color-canvas-bg)',\n width: `${paddedCanvasWidth * zoom}px`,\n height: `${paddedCanvasHeight * zoom}px`,\n cursor: penTool.isActive() && selectedElement instanceof PathElement ? penCursor : undefined,\n position: 'absolute',\n left: `${offsetX}px`,\n top: `${offsetY}px`,\n touchAction: 'none',\n clipPath: canvasClipPathValue,\n }}\n />\n {/* Render textarea via portal to document.body to escape any containing blocks\n created by backdrop-filter, transform, or filter on parent elements. */}\n {isEditing &&\n (() => {\n // Determine which element is being edited to get font properties\n let editingElement: CanvasElement | null | undefined = selectedElement;\n if (editingChildIdRef.current && selectedElement instanceof GroupElement) {\n const child = selectedElement.children.find((c) => c.id === editingChildIdRef.current);\n if (child) { editingElement = child; }\n }\n\n const textEl = editingElement instanceof TextElement ? editingElement : null;\n const fontSize = textEl?.fontSize || 24;\n const fontFamily = textEl?.fontFamily || 'Arial';\n const bold = textEl?.bold || false;\n const italic = textEl?.italic || false;\n const textAlign = textEl?.textAlign || 'center';\n\n const textareaElement = (\n <textarea\n ref={inputRef}\n data-preserve-selection=\"\"\n value={editText}\n onChange={(e) => {\n const newValue = (e.target as HTMLTextAreaElement).value;\n\n if (!editRichText) {\n const newRichText = RichText.fromPlainText(newValue, {});\n editRichTextRef.current = newRichText;\n setEditRichText(newRichText);\n setEditText(newValue);\n const newCursorPos = e.target.selectionEnd;\n setCursorPosition(newCursorPos);\n setSelectionStart(e.target.selectionStart);\n setSelectionEnd(e.target.selectionEnd);\n return;\n }\n\n const oldText = editText;\n const updatedRichText = editRichText.clone();\n\n let prefixLen = 0;\n while (prefixLen < oldText.length && prefixLen < newValue.length && oldText[prefixLen] === newValue[prefixLen]) {\n prefixLen++;\n }\n\n let suffixLen = 0;\n while (suffixLen < oldText.length - prefixLen && suffixLen < newValue.length - prefixLen &&\n oldText[oldText.length - 1 - suffixLen] === newValue[newValue.length - 1 - suffixLen]) {\n suffixLen++;\n }\n\n const deleteStart = prefixLen;\n const deleteEnd = oldText.length - suffixLen;\n const insertText = newValue.substring(prefixLen, newValue.length - suffixLen);\n\n if (deleteEnd > deleteStart) {\n updatedRichText.delete(deleteStart, deleteEnd);\n }\n\n if (insertText.length > 0) {\n let insertStyle: CharacterStyle;\n if (_currentFormattingStyle) {\n insertStyle = _currentFormattingStyle;\n } else if (deleteStart > 0) {\n insertStyle = updatedRichText.getStyleAt(deleteStart - 1);\n } else if (selectedElement instanceof TextElement) {\n insertStyle = selectedElement.getDefaultStyle();\n } else {\n insertStyle = {};\n }\n updatedRichText.insert(deleteStart, insertText, insertStyle);\n }\n\n editRichTextRef.current = updatedRichText;\n setEditRichText(updatedRichText);\n setEditText(newValue);\n\n const newCursorPos = e.target.selectionEnd;\n setCursorPosition(newCursorPos);\n setSelectionStart(e.target.selectionStart);\n setSelectionEnd(e.target.selectionEnd);\n selectionAnchorRef.current = newCursorPos;\n\n const textarea = e.target;\n setTimeout(() => {\n if (textarea && document.activeElement === textarea) {\n textarea.setSelectionRange(newCursorPos, newCursorPos);\n }\n }, 0);\n }}\n onBlur={handleTextEditBlur}\n onKeyDown={handleTextKeyDown}\n onPointerDown={(e) => {\n e.preventDefault();\n if (!selectedElement || !(selectedElement instanceof TextElement) || !canvasRef.current) return;\n\n const now = Date.now();\n if (now - lastProcessedClickTimeRef.current < CLICK_DEDUP_THRESHOLD) return;\n lastProcessedClickTimeRef.current = now;\n\n if (now - lastMultiClickTimeRef.current >= MULTI_CLICK_DEBOUNCE) {\n multiClickSelectionRef.current = null;\n }\n\n const canvasRect = canvasRef.current.getBoundingClientRect();\n const canvasX = (e.clientX - canvasRect.left) / zoom - paddingOffsetX;\n const canvasY = (e.clientY - canvasRect.top) / zoom - paddingOffsetY;\n\n const visualBbox = selectedElement.getVisualBoundingBox();\n const center = selectedElement.getRotationAnchor();\n const rotationRad = RotationUtils.toRadiansInverse(selectedElement.rotation);\n const dx = canvasX - center.x;\n const dy = canvasY - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = dx * cos - dy * sin;\n const localRotatedY = dx * sin + dy * cos;\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = localRotatedY + visualBbox.height / 2;\n\n const clickIndex = getCursorIndexFromPoint(selectedElement, { x: relativeX, y: relativeY });\n\n const timeSinceLastClick = now - lastClickTimeRef.current;\n if (timeSinceLastClick < MULTI_CLICK_THRESHOLD) {\n clickCountRef.current += 1;\n } else {\n clickCountRef.current = 1;\n }\n lastClickTimeRef.current = now;\n\n const currentText = editText;\n didDragSelectRef.current = false;\n\n if (clickCountRef.current >= 3) {\n e.preventDefault();\n lastMultiClickTimeRef.current = now;\n multiClickSelectionRef.current = { start: 0, end: currentText.length, cursor: currentText.length };\n skipDragSelectionRef.current = true;\n clickCountRef.current = 0;\n selectionAnchorRef.current = 0;\n setCursorPosition(currentText.length);\n setSelectionStart(0);\n setSelectionEnd(currentText.length);\n if (inputRef.current) {\n inputRef.current.focus();\n inputRef.current.setSelectionRange(0, currentText.length);\n }\n } else if (clickCountRef.current === 2) {\n e.preventDefault();\n const wordBounds = getWordBoundaryAt(currentText, clickIndex);\n lastMultiClickTimeRef.current = now;\n multiClickSelectionRef.current = { start: wordBounds.start, end: wordBounds.end, cursor: wordBounds.end };\n skipDragSelectionRef.current = true;\n selectionAnchorRef.current = wordBounds.start;\n setCursorPosition(wordBounds.end);\n setSelectionStart(wordBounds.start);\n setSelectionEnd(wordBounds.end);\n if (inputRef.current) {\n inputRef.current.focus();\n inputRef.current.setSelectionRange(wordBounds.start, wordBounds.end);\n }\n } else {\n multiClickSelectionRef.current = null;\n selectionAnchorRef.current = clickIndex;\n setSelectionStart(clickIndex);\n setCursorPosition(clickIndex);\n setSelectionEnd(clickIndex);\n if (inputRef.current) {\n inputRef.current.focus();\n inputRef.current.setSelectionRange(clickIndex, clickIndex);\n }\n skipDragSelectionRef.current = false;\n }\n }}\n onPointerMove={(e) => {\n if (e.buttons !== 1) return;\n\n // Handle selection handle dragging\n if (draggingSelectionHandleRef.current && selectedElement instanceof TextElement && canvasRef.current) {\n const canvasRect = canvasRef.current.getBoundingClientRect();\n const canvasX = (e.clientX - canvasRect.left) / zoom - paddingOffsetX;\n const canvasY = (e.clientY - canvasRect.top) / zoom - paddingOffsetY;\n\n const visualBbox = selectedElement.getVisualBoundingBox();\n const center = selectedElement.getRotationAnchor();\n const rotationRad = RotationUtils.toRadiansInverse(selectedElement.rotation);\n const dx = canvasX - center.x;\n const dy = canvasY - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = dx * cos - dy * sin;\n const localRotatedY = dx * sin + dy * cos;\n\n const handlePositions = selectionHandlePositionsRef.current;\n let yOffset = 0;\n if (draggingSelectionHandleRef.current === 'start' && handlePositions?.start) {\n yOffset = handlePositions.start.textEdge.y - handlePositions.start.center.y;\n } else if (draggingSelectionHandleRef.current === 'end' && handlePositions?.end) {\n yOffset = handlePositions.end.textEdge.y - handlePositions.end.center.y;\n }\n\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = (localRotatedY + yOffset) + visualBbox.height / 2;\n\n const dragIndex = getCursorIndexFromPoint(selectedElement, { x: relativeX, y: relativeY });\n\n if (draggingSelectionHandleRef.current === 'start') {\n if (dragIndex <= selectionEnd) {\n setSelectionStart(dragIndex);\n setCursorPosition(dragIndex);\n } else {\n setSelectionStart(selectionEnd);\n setSelectionEnd(dragIndex);\n setCursorPosition(dragIndex);\n draggingSelectionHandleRef.current = 'end';\n }\n } else {\n if (dragIndex >= selectionStart) {\n setSelectionEnd(dragIndex);\n setCursorPosition(dragIndex);\n } else {\n setSelectionEnd(selectionStart);\n setSelectionStart(dragIndex);\n setCursorPosition(dragIndex);\n draggingSelectionHandleRef.current = 'start';\n }\n }\n\n if (inputRef.current) {\n const start = Math.min(selectionStart, selectionEnd);\n const end = Math.max(selectionStart, selectionEnd);\n inputRef.current.setSelectionRange(start, end);\n }\n return;\n }\n\n if (skipDragSelectionRef.current) return;\n if (!selectedElement || !(selectedElement instanceof TextElement) || !canvasRef.current) return;\n\n const canvasRect = canvasRef.current.getBoundingClientRect();\n const canvasX = (e.clientX - canvasRect.left) / zoom - paddingOffsetX;\n const canvasY = (e.clientY - canvasRect.top) / zoom - paddingOffsetY;\n\n const visualBbox = selectedElement.getVisualBoundingBox();\n const center = selectedElement.getRotationAnchor();\n const rotationRad = RotationUtils.toRadiansInverse(selectedElement.rotation);\n const dx = canvasX - center.x;\n const dy = canvasY - center.y;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const localRotatedX = dx * cos - dy * sin;\n const localRotatedY = dx * sin + dy * cos;\n const relativeX = localRotatedX + visualBbox.width / 2;\n const relativeY = localRotatedY + visualBbox.height / 2;\n\n const dragIndex = getCursorIndexFromPoint(selectedElement, { x: relativeX, y: relativeY });\n const anchor = selectionAnchorRef.current;\n\n if (anchor !== dragIndex) {\n didDragSelectRef.current = true;\n }\n\n setSelectionEnd(dragIndex);\n setCursorPosition(dragIndex);\n setSelectionStart(anchor);\n\n if (inputRef.current) {\n const start = Math.min(anchor, dragIndex);\n const end = Math.max(anchor, dragIndex);\n inputRef.current.setSelectionRange(start, end);\n }\n }}\n onPointerUp={() => {\n if (draggingSelectionHandleRef.current) {\n draggingSelectionHandleRef.current = null;\n }\n }}\n onClick={() => {\n didDragSelectRef.current = false;\n skipDragSelectionRef.current = false;\n }}\n onSelect={() => {\n // Selection handled manually in onPointerDown/onPointerMove\n }}\n autoFocus\n spellCheck={false}\n style={{\n position: 'fixed',\n left: `${editPosition.x}px`,\n top: `${editPosition.y}px`,\n width: `${editPosition.width}px`,\n height: `${editPosition.height || 50}px`,\n fontSize: `${fontSize * zoom}px`,\n fontFamily: `\"${fontFamily}\", Arial, sans-serif`,\n fontWeight: bold ? 700 : 400,\n fontStyle: italic ? 'italic' : 'normal',\n textAlign: textAlign,\n lineHeight: 1.2,\n opacity: 0,\n zIndex: 1001,\n resize: 'none',\n border: '2px solid transparent',\n outline: 'none',\n padding: '4px',\n margin: '0',\n overflow: 'hidden',\n color: 'transparent',\n background: 'transparent',\n caretColor: 'transparent',\n whiteSpace: 'pre-wrap',\n wordBreak: 'normal',\n overflowWrap: 'normal',\n }}\n />\n );\n\n return createPortal(textareaElement, document.body);\n })()}\n </>\n );\n }\n);\n\nCanvasEditor.displayName = 'CanvasEditor';\n\nexport default CanvasEditor;\n","'use client';\n\nimport * as React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/utils';\nimport { useSurface } from './surface';\n\nconst badgeVariants = cva(\n 'sc:inline-flex sc:items-center sc:rounded-badge sc:border sc:px-2.5 sc:py-0.5 sc:text-xs sc:transition-colors sc:focus:outline-none sc:focus:ring-2 sc:focus:ring-ring sc:focus:ring-offset-2 sc:font-caption',\n {\n variants: {\n variant: {\n default: 'sc:border-transparent sc:bg-primary sc:text-primary-foreground',\n secondary: 'sc:border-transparent sc:bg-default sc:text-default-foreground',\n destructive: 'sc:border-transparent sc:bg-danger sc:text-danger-foreground',\n success: 'sc:border-transparent sc:bg-success sc:text-success-foreground',\n warning: 'sc:border-transparent sc:bg-warning sc:text-warning-foreground',\n outline: 'sc:border-divider sc:text-foreground',\n gradient: 'sc:border-transparent bg-gradient-primary text-gradient-foreground',\n },\n size: {\n default: 'sc:px-2.5 sc:py-0.5 sc:text-xs',\n sm: 'sc:px-2 sc:py-0.5 sc:text-[10px]',\n lg: 'sc:px-3 sc:py-1 sc:text-sm',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n }\n);\n\nexport interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {\n // HeroUI Chip compatibility props\n color?: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger';\n /** Whether to show a dot indicator */\n dot?: boolean;\n /** Content to show on the left */\n startContent?: React.ReactNode;\n /** Content to show on the right */\n endContent?: React.ReactNode;\n /** Close button click handler (makes badge dismissible) */\n onClose?: () => void;\n}\n\nfunction Badge({\n className,\n variant,\n size,\n color,\n dot,\n startContent,\n endContent,\n onClose,\n children,\n ...props\n}: BadgeProps) {\n const { variant: surfaceVariant } = useSurface();\n const isOnDefaultSurface = surfaceVariant === 'default';\n const isOnSecondarySurface = surfaceVariant === 'secondary';\n\n // Map HeroUI color prop to variant\n const effectiveVariant = color\n ? (\n {\n default: 'secondary',\n primary: 'default',\n secondary: 'secondary',\n success: 'success',\n warning: 'warning',\n danger: 'destructive',\n } as const\n )[color]\n : variant;\n\n // Surface-aware variant overrides\n // - On default surface (white card): dark mode needs lighter bg, light mode base color is fine\n // - On secondary surface: both modes need the raised color for contrast\n const surfaceOverrides = {\n secondary:\n effectiveVariant === 'secondary' &&\n (isOnSecondarySurface\n ? 'sc:bg-[var(--color-default-on-surface)] sc:hover:bg-[var(--color-default-on-surface)]/80'\n : isOnDefaultSurface\n ? 'sc:dark:bg-[var(--color-default-on-surface)] sc:dark:hover:bg-[var(--color-default-on-surface)]/80'\n : false),\n outline:\n effectiveVariant === 'outline' &&\n (isOnDefaultSurface || isOnSecondarySurface) &&\n 'sc:border-muted-foreground/30',\n };\n\n return (\n <div\n className={cn(\n badgeVariants({ variant: effectiveVariant, size }),\n surfaceOverrides.secondary,\n surfaceOverrides.outline,\n className\n )}\n {...props}\n >\n {dot && <span className=\"sc:mr-1.5 sc:size-1.5 sc:rounded-full sc:bg-current\" aria-hidden=\"true\" />}\n {startContent && <span className=\"sc:mr-1\">{startContent}</span>}\n {children}\n {endContent && <span className=\"sc:ml-1\">{endContent}</span>}\n {onClose && (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"sc:ml-1 sc:inline-flex sc:size-3.5 sc:items-center sc:justify-center sc:rounded-full sc:hover:bg-current/20 sc:focus:outline-none\"\n aria-label=\"Remove\"\n >\n <svg\n className=\"sc:size-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n )}\n </div>\n );\n}\n\n// HeroUI Chip alias\nconst Chip = Badge;\n\nexport { Badge, Chip, badgeVariants };\n","'use client';\n\nimport * as React from 'react';\nimport { cn } from '../lib/utils';\n\nexport interface FieldsetProps extends React.FieldsetHTMLAttributes<HTMLFieldSetElement> {}\n\n/**\n * Fieldset - A semantic grouping element for form controls.\n *\n * Use this to group related form elements together with an optional legend.\n *\n * @example\n * ```tsx\n * <Fieldset>\n * <legend className=\"text-sm font-medium\">Personal Info</legend>\n * <TextField>\n * <Label>Name</Label>\n * <Input placeholder=\"Enter name...\" />\n * </TextField>\n * </Fieldset>\n * ```\n */\nconst Fieldset = React.forwardRef<HTMLFieldSetElement, FieldsetProps>(\n ({ className, disabled, children, ...props }, ref) => {\n return (\n <fieldset\n ref={ref}\n disabled={disabled}\n className={cn(\n 'sc:flex sc:flex-col sc:gap-3',\n // Disabled styles\n disabled && 'sc:opacity-60 sc:cursor-not-allowed',\n className\n )}\n {...props}\n >\n {children}\n </fieldset>\n );\n }\n);\nFieldset.displayName = 'Fieldset';\n\nexport interface LegendProps extends React.HTMLAttributes<HTMLLegendElement> {}\n\n/**\n * Legend - A caption for a fieldset.\n *\n * @example\n * ```tsx\n * <Fieldset>\n * <Legend>Account Settings</Legend>\n * ...\n * </Fieldset>\n * ```\n */\nconst Legend = React.forwardRef<HTMLLegendElement, LegendProps>(\n ({ className, children, ...props }, ref) => {\n return (\n <legend\n ref={ref}\n className={cn('sc:text-base sc:text-foreground sc:mb-3 sc:font-heading', className)}\n {...props}\n >\n {children}\n </legend>\n );\n }\n);\nLegend.displayName = 'Legend';\n\nexport { Fieldset, Legend };\n","'use client';\n\nimport * as React from 'react';\nimport * as RadioGroupPrimitive from '@radix-ui/react-radio-group';\nimport { cn } from '../lib/utils';\nimport { useSurface } from './surface';\n\nconst RadioGroup = React.forwardRef<\n React.ElementRef<typeof RadioGroupPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <RadioGroupPrimitive.Root className={cn('sc:grid sc:gap-2', className)} {...props} ref={ref} />\n));\nRadioGroup.displayName = RadioGroupPrimitive.Root.displayName;\n\ninterface RadioGroupItemProps extends React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> {\n /** Visual variant - auto-detected from Surface context, or can be set explicitly */\n variant?: 'default' | 'filled';\n}\n\nconst RadioGroupItem = React.forwardRef<\n React.ElementRef<typeof RadioGroupPrimitive.Item>,\n RadioGroupItemProps\n>(({ className, variant, ...props }, ref) => {\n // Auto-detect variant from Surface context\n const surface = useSurface();\n const effectiveVariant = variant ?? (surface.variant === 'default' ? 'filled' : 'default');\n\n return (\n <RadioGroupPrimitive.Item\n ref={ref}\n className={cn(\n 'sc:aspect-square sc:size-4 sc:rounded-full sc:border-none sc:transition-colors',\n 'sc:focus-visible:outline-none',\n 'sc:disabled:cursor-not-allowed sc:disabled:opacity-50',\n // Variant styles - semantic: field on page vs field on surface\n effectiveVariant === 'default' && 'sc:bg-field sc:shadow-soft sc:dark:shadow-none',\n effectiveVariant === 'filled' && 'sc:bg-[var(--color-field-on-surface)]',\n 'sc:data-[state=checked]:bg-primary sc:data-[state=checked]:shadow-none',\n className\n )}\n {...props}\n >\n <RadioGroupPrimitive.Indicator className=\"sc:flex sc:items-center sc:justify-center\">\n <div className=\"sc:size-1.5 sc:rounded-full sc:bg-primary-foreground\" />\n </RadioGroupPrimitive.Indicator>\n </RadioGroupPrimitive.Item>\n );\n});\nRadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;\n\n// Radio component - wraps RadioGroupItem with label pattern\ninterface RadioProps extends React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> {\n children?: React.ReactNode;\n /** Visual variant - auto-detected from Surface context, or can be set explicitly */\n variant?: 'default' | 'filled';\n}\n\nconst Radio = React.forwardRef<React.ElementRef<typeof RadioGroupPrimitive.Item>, RadioProps>(\n ({ children, className, variant, ...props }, ref) => {\n if (children) {\n return (\n <div className=\"sc:flex sc:items-center sc:space-x-2\">\n <RadioGroupItem ref={ref} variant={variant} {...props} />\n <label\n htmlFor={props.id}\n className=\"sc:text-sm sc:leading-none sc:text-foreground sc:peer-disabled:cursor-not-allowed sc:peer-disabled:opacity-70 sc:font-label\"\n >\n {children}\n </label>\n </div>\n );\n }\n return <RadioGroupItem ref={ref} className={className} variant={variant} {...props} />;\n }\n);\nRadio.displayName = 'Radio';\n\nexport { RadioGroup, RadioGroupItem, Radio };\n","'use client';\n\nimport * as React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '../lib/utils';\n\nconst spinnerVariants = cva('sc:pointer-events-none sc:relative sc:origin-center sc:animate-spin sc:size-6 sc:text-primary', {\n variants: {\n size: {\n sm: 'sc:size-4',\n md: 'sc:size-6',\n lg: 'sc:size-8',\n xl: 'sc:size-10',\n },\n color: {\n primary: 'sc:text-primary',\n secondary: 'sc:text-foreground/60',\n success: 'sc:text-success',\n warning: 'sc:text-warning',\n danger: 'sc:text-danger',\n current: 'sc:text-current',\n },\n },\n});\n\nexport interface SpinnerProps\n extends Omit<React.ComponentProps<'span'>, 'color'>,\n VariantProps<typeof spinnerVariants> {\n /** Label for screen readers */\n label?: string;\n}\n\nfunction Spinner({ className, size, color, label = 'Loading', ...props }: SpinnerProps) {\n const id = React.useId();\n\n return (\n <span\n role=\"status\"\n aria-label={label}\n className={cn(spinnerVariants({ size, color }), className)}\n {...props}\n >\n <svg viewBox=\"0 0 24 24\" className=\"sc:size-full\" aria-hidden>\n <defs>\n <linearGradient id={`spinner-grad-1-${id}`} x1=\"50%\" x2=\"50%\" y1=\"5.271%\" y2=\"91.793%\">\n <stop offset=\"0%\" stopColor=\"currentColor\" />\n <stop offset=\"100%\" stopColor=\"currentColor\" stopOpacity={0.55} />\n </linearGradient>\n <linearGradient id={`spinner-grad-2-${id}`} x1=\"50%\" x2=\"50%\" y1=\"15.24%\" y2=\"87.15%\">\n <stop offset=\"0%\" stopColor=\"currentColor\" stopOpacity={0} />\n <stop offset=\"100%\" stopColor=\"currentColor\" stopOpacity={0.55} />\n </linearGradient>\n </defs>\n <g fill=\"none\">\n <path d=\"m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z\" />\n <path\n d=\"M8.749.021a1.5 1.5 0 0 1 .497 2.958A7.5 7.5 0 0 0 3 10.375a7.5 7.5 0 0 0 7.5 7.5v3c-5.799 0-10.5-4.7-10.5-10.5C0 5.23 3.726.865 8.749.021\"\n fill={`url(#spinner-grad-1-${id})`}\n transform=\"translate(1.5 1.625)\"\n />\n <path\n d=\"M15.392 2.673a1.5 1.5 0 0 1 2.119-.115A10.48 10.48 0 0 1 21 10.375c0 5.8-4.701 10.5-10.5 10.5v-3a7.5 7.5 0 0 0 5.007-13.084a1.5 1.5 0 0 1-.115-2.118\"\n fill={`url(#spinner-grad-2-${id})`}\n transform=\"translate(1.5 1.625)\"\n />\n </g>\n </svg>\n </span>\n );\n}\n\nexport { Spinner, spinnerVariants };\n","/**\n * CanvasContextMenu - Right-click context menu for the canvas surface.\n *\n * The canvas counterpart of the LayersPanel context menu (sweep finding #4:\n * the layers panel had one, the canvas didn't). Same primitives + styling\n * (`MenuItem`, the fixed positioned `sc:*` menu chrome), and the SAME action\n * implementations the layers panel / keyboard shortcuts use — nothing is\n * reimplemented here:\n *\n * - Copy / Paste → `handleCopyElements` / `handlePasteElements`\n * (EditorContext — the ⌘C/⌘V path)\n * - Duplicate → `useLayers().duplicateLayer` (the ⌘D path)\n * - Delete → `useLayers().deleteLayer`, with the same\n * bound-layer suppression the Delete key applies\n * - Lock / Unlock → `useLayers().toggleLock`\n * - Bring/Send ordering → `useLayers().moveLayerForward/-Backward/\n * bringToFront/sendToBack`\n * - Group / Ungroup → `useLayers().groupLayers/ungroupLayer`\n * - Select All → `selectAllOnActiveArtboard` (the ⌘A implementation)\n *\n * Lock semantics (gotcha #20): a locked element can be right-click-selected\n * and deleted; entries that would MOVE it (reorder, group/ungroup) are\n * disabled — mirroring the layers panel, which hides the drag affordance for\n * locked layers but keeps delete available.\n *\n * Rendered through a body portal (the canvas sits inside a transformed\n * pan/zoom wrapper, which would break `position: fixed`); the `.sc-root`\n * wrapper is load-bearing for the scoped theme tokens — see Dropdown.tsx.\n */\n\nimport React, { useEffect, useMemo, useRef } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Icon } from '@iconify/react';\nimport { useEditor } from '../contexts/EditorContext.js';\nimport { useIsLayerBound } from '../contexts/BoundLayerGuardContext.js';\nimport { useLayers } from '../hooks/useLayers.js';\nimport { selectAllOnActiveArtboard } from '../hooks/useKeyboardShortcuts.js';\nimport { GroupElement } from '../core/GroupElement.js';\nimport { icons } from './ui';\nimport MenuItem from './primitives/MenuItem.js';\n\n/** Where (viewport px) and on what the menu was requested. */\nexport interface CanvasContextMenuState {\n /** Viewport coords of the right-click — the menu's top-left corner. */\n x: number;\n y: number;\n /** Topmost hit element id, or null for the empty-canvas menu. */\n targetId: string | null;\n}\n\nexport interface CanvasContextMenuProps {\n menu: CanvasContextMenuState;\n onClose: () => void;\n}\n\n/** ⌘ on Apple platforms, Ctrl+ elsewhere — matches the actual bindings in\n * useKeyboardShortcuts (metaKey || ctrlKey). */\nconst IS_APPLE =\n typeof navigator !== 'undefined' && /Mac|iP(hone|ad|od)/.test(navigator.platform ?? '');\nconst MOD = IS_APPLE ? '⌘' : 'Ctrl+';\n\nconst Divider = () => <div className=\"sc:h-[1px] sc:bg-border-primary sc:my-xs\" />;\n\nexport const CanvasContextMenu: React.FC<CanvasContextMenuProps> = ({ menu, onClose }) => {\n const {\n multiSelection,\n handleCopyElements,\n handlePasteElements,\n hasClipboardContent,\n elements,\n artboardManager,\n handleSelectionChange,\n setMultiSelection,\n } = useEditor();\n\n const {\n duplicateLayer,\n deleteLayer,\n toggleLock,\n moveLayerForward,\n moveLayerBackward,\n bringToFront,\n sendToBack,\n groupLayers,\n ungroupLayer,\n findElementById,\n } = useLayers();\n\n // Same bound-layer suppression the Delete key applies (ADR-0085 §3).\n const isLayerBound = useIsLayerBound();\n\n const menuRef = useRef<HTMLDivElement>(null);\n\n // Focus the menu on open: Escape lands here (and useKeyboardShortcuts\n // already ignores keys originating inside a [role=\"menu\"], so closing the\n // menu doesn't ALSO deselect).\n useEffect(() => {\n menuRef.current?.focus();\n }, []);\n\n // Close on any pointerdown outside the menu (covers canvas clicks, other\n // chrome, and a second right-click elsewhere) — same outside-dismiss\n // contract as the LayersPanel context menu, on pointerdown so it also\n // fires for interactions that never produce a `click`.\n useEffect(() => {\n const handlePointerDown = (e: PointerEvent) => {\n if (menuRef.current && e.target instanceof Node && menuRef.current.contains(e.target)) {\n return;\n }\n onClose();\n };\n document.addEventListener('pointerdown', handlePointerDown);\n return () => document.removeEventListener('pointerdown', handlePointerDown);\n }, [onClose]);\n\n const target = menu.targetId ? findElementById(menu.targetId) : null;\n\n // The ids an action applies to: the preserved multi-selection when the\n // target is part of it, otherwise just the target.\n const scopeIds = useMemo(() => {\n if (!menu.targetId) return [];\n if (multiSelection.length > 1 && multiSelection.includes(menu.targetId)) {\n return multiSelection;\n }\n return [menu.targetId];\n }, [menu.targetId, multiSelection]);\n\n const isLocked = Boolean(target?.locked);\n const anyInScopeLocked = scopeIds.some((id) => Boolean(findElementById(id)?.locked));\n const isGroup = target instanceof GroupElement;\n const isMulti = scopeIds.length > 1;\n\n const run = (action: () => void) => () => {\n action();\n onClose();\n };\n\n const content = (\n <div\n ref={menuRef}\n role=\"menu\"\n tabIndex={-1}\n aria-label={target ? 'Element actions' : 'Canvas actions'}\n data-testid=\"canvas-context-menu\"\n data-context={target ? 'element' : 'canvas'}\n className=\"sc:fixed sc:bg-bg-primary sc:border sc:border-border-secondary sc:rounded-lg sc:p-xs sc:min-w-[200px] sc:shadow-lg sc:outline-none\"\n style={{ left: menu.x, top: menu.y, zIndex: 'var(--sc-z-modal)' }}\n onClick={(e) => e.stopPropagation()}\n onContextMenu={(e) => {\n // Right-clicking the menu itself is inert (no native menu, no re-open).\n e.preventDefault();\n e.stopPropagation();\n }}\n onKeyDown={(e) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n onClose();\n }\n }}\n >\n {target ? (\n <>\n <MenuItem\n data-testid=\"context-menu-copy\"\n icon={<Icon icon={icons.copy} className=\"sc:size-3.5\" />}\n label=\"Copy\"\n shortcut={`${MOD}C`}\n onClick={run(() => handleCopyElements())}\n />\n <MenuItem\n data-testid=\"context-menu-paste\"\n icon={<Icon icon={icons.paste} className=\"sc:size-3.5\" />}\n label=\"Paste\"\n shortcut={`${MOD}V`}\n disabled={!hasClipboardContent}\n onClick={run(() => handlePasteElements())}\n />\n <MenuItem\n data-testid=\"context-menu-duplicate\"\n icon={<Icon icon={icons.copy} className=\"sc:size-3.5\" />}\n label=\"Duplicate\"\n shortcut={`${MOD}D`}\n onClick={run(() => scopeIds.forEach((id) => duplicateLayer(id)))}\n />\n <Divider />\n <MenuItem\n data-testid=\"context-menu-lock\"\n icon={\n <Icon icon={isLocked ? icons.lockOpen : icons.lock} className=\"sc:size-3.5\" />\n }\n label={isLocked ? 'Unlock' : 'Lock'}\n onClick={run(() => scopeIds.forEach((id) => toggleLock(id)))}\n />\n <Divider />\n <MenuItem\n data-testid=\"context-menu-bring-to-front\"\n icon={<Icon icon={icons.layerForward} className=\"sc:size-3.5\" />}\n label=\"Bring to Front\"\n disabled={isLocked}\n onClick={run(() => bringToFront(menu.targetId!))}\n />\n <MenuItem\n data-testid=\"context-menu-bring-forward\"\n icon={<Icon icon={icons.arrowUp} className=\"sc:size-3.5\" />}\n label=\"Bring Forward\"\n disabled={isLocked}\n onClick={run(() => moveLayerForward(menu.targetId!))}\n />\n <MenuItem\n data-testid=\"context-menu-send-backward\"\n icon={<Icon icon={icons.arrowDown} className=\"sc:size-3.5\" />}\n label=\"Send Backward\"\n disabled={isLocked}\n onClick={run(() => moveLayerBackward(menu.targetId!))}\n />\n <MenuItem\n data-testid=\"context-menu-send-to-back\"\n icon={<Icon icon={icons.layerBackward} className=\"sc:size-3.5\" />}\n label=\"Send to Back\"\n disabled={isLocked}\n onClick={run(() => sendToBack(menu.targetId!))}\n />\n {(isMulti || isGroup) && <Divider />}\n {isMulti && (\n <MenuItem\n data-testid=\"context-menu-group\"\n icon={<Icon icon={icons.group} className=\"sc:size-3.5\" />}\n label=\"Group\"\n disabled={anyInScopeLocked}\n onClick={run(() => groupLayers(scopeIds))}\n />\n )}\n {isGroup && (\n <MenuItem\n data-testid=\"context-menu-ungroup\"\n icon={<Icon icon={icons.ungroup} className=\"sc:size-3.5\" />}\n label=\"Ungroup\"\n disabled={isLocked}\n onClick={run(() => ungroupLayer(menu.targetId!))}\n />\n )}\n <Divider />\n <MenuItem\n data-testid=\"context-menu-delete\"\n icon={<Icon icon={icons.trash} className=\"sc:size-3.5\" />}\n label=\"Delete\"\n shortcut=\"Del\"\n className=\"sc:text-[#d32f2f] sc:hover:bg-[rgba(211,47,47,0.1)] sc:hover:text-[#c62828]\"\n onClick={run(() => {\n // Locked elements ARE deletable (gotcha #20); bound layers are\n // not — same filter the Delete key applies.\n scopeIds\n .filter((id) => !isLayerBound(findElementById(id)?.name))\n .forEach((id) => deleteLayer(id));\n })}\n />\n </>\n ) : (\n <>\n <MenuItem\n data-testid=\"context-menu-paste\"\n icon={<Icon icon={icons.paste} className=\"sc:size-3.5\" />}\n label=\"Paste\"\n shortcut={`${MOD}V`}\n disabled={!hasClipboardContent}\n onClick={run(() => handlePasteElements())}\n />\n <MenuItem\n data-testid=\"context-menu-select-all\"\n icon={<Icon icon={icons.selectAll} className=\"sc:size-3.5\" />}\n label=\"Select All\"\n shortcut={`${MOD}A`}\n onClick={run(() =>\n selectAllOnActiveArtboard({\n elements,\n artboardManager,\n handleSelectionChange,\n setMultiSelection,\n })\n )}\n />\n </>\n )}\n </div>\n );\n\n // Body portal: the canvas lives inside a transformed (pan/zoom) wrapper\n // that would turn `position: fixed` into transform-relative positioning.\n // The .sc-root wrapper keeps the scoped theme tokens resolving (see the\n // matching comment in primitives/Dropdown.tsx).\n return createPortal(\n <div className=\"sc-root\" style={{ display: 'contents' }}>\n {content}\n </div>,\n document.body\n );\n};\n\nexport default CanvasContextMenu;\n","/**\n * Canvas - Simple wrapper around CanvasEditor for embedding\n * Extracted from working App.tsx - no fancy logic, just props passthrough\n *\n * Interactive mode: Enables infinite canvas with pan/zoom:\n * - Scroll wheel: zoom in/out (centered on cursor)\n * - Middle mouse drag OR Space+drag: pan the canvas\n * - Pinch gesture: zoom on touch devices\n */\n\nimport React from 'react';\nimport { useEditor } from '../../contexts/EditorContext.js';\nimport { useViewportContext } from '../../contexts/ViewportContext.js';\nimport { useSelectionContext } from '../../contexts/SelectionContext.js';\nimport { useToolStateContext } from '../../contexts/ToolStateContext.js';\nimport { useKeyboardShortcuts } from '../../hooks/useKeyboardShortcuts.js';\nimport { useAnimatedFocusRect } from '../CanvasEditor/hooks/useAnimatedFocusRect.js';\nimport CanvasEditor from '../CanvasEditor.jsx';\nimport CanvasContextMenu, {\n type CanvasContextMenuState,\n} from '../CanvasContextMenu.jsx';\nimport type { CanvasContextMenuRequest } from '../CanvasEditor/hooks/useCanvasInteraction.js';\n\n/** Bounds for the editor-mode user zoom multiplier. 0.25× lets the\n * user zoom out enough to overview a wide spread; 8× covers fine-\n * detail editing on logos / small text. */\nconst USER_MIN_ZOOM = 0.25;\nconst USER_MAX_ZOOM = 8;\n\n/** Zoom limits for interactive mode */\nconst MIN_ZOOM = 0.1;\nconst MAX_ZOOM = 4.0;\nconst ZOOM_SENSITIVITY = 0.001; // How fast wheel zoom responds\n\nexport interface CanvasProps {\n /** CSS class name */\n className?: string;\n /** Inline styles */\n style?: React.CSSProperties;\n /** Viewport width (container size) */\n width?: number;\n /** Viewport height (container size) */\n height?: number;\n /** Zoom level - if not provided, automatically fits artboard to container */\n zoom?: number;\n /** Padding percentage when auto-fitting (0-1, default 0.95 = 95% of container) */\n fitPadding?: number;\n /**\n * Enable interactive pan/zoom mode (infinite canvas)\n * - Scroll wheel: zoom in/out\n * - Space+drag or middle mouse: pan\n */\n interactive?: boolean;\n /** Border radius for artboard corners (in pixels) */\n artboardBorderRadius?: number;\n /**\n * Fixed screen-space margin in pixels (overrides fitPadding for positioning)\n * When set, the artboard will have this exact margin regardless of zoom level.\n * The fitPadding still controls the zoom calculation, but positioning uses this fixed margin.\n */\n fixedMargin?: number;\n /**\n * Maximum HTML height in pixels. When the artboard (with vertical fixedMargin padding)\n * would exceed this height, horizontal padding is increased to shrink the artboard\n * until it fits within this constraint while still respecting vertical fixedMargin.\n */\n maxHeight?: number;\n /**\n * Whether to show the rotation handle on canvas (defaults to true)\n * Set to false for embedded mode where rotation is handled via toolbar panel\n */\n showRotationHandle?: boolean;\n /**\n * Whether keyboard shortcuts (copy/paste, delete, undo/redo, etc.) are enabled.\n * @default true\n */\n enableShortcuts?: boolean;\n /**\n * ADR-0054 Phase 4: cutout shapes (artboard px) punched through the\n * `<canvas>` as truly transparent holes. See SnowconeCanvasProps.\n */\n canvasCutouts?: import(\"./SnowconeCanvas\").CanvasCutoutShape[];\n /**\n * ADR-0054: piece-guide overlays drawn directly into the canvas\n * render loop (boundary, safe-area wash, zones, die-cut, labels).\n */\n pieceGuides?: import(\"./SnowconeCanvas\").PieceGuidesConfig;\n /**\n * ADR-0060: viewport focus mode for multi-piece spread layouts. See\n * `SnowconeCanvasProps.pieceFocus` for the full contract.\n */\n pieceFocus?: 'spread' | { pieceId: string };\n}\n\n/**\n * Canvas component - renders CanvasEditor without UI chrome\n * Exactly like App.tsx but as a reusable component\n *\n * Auto-fit behavior: If no zoom is provided, the canvas will automatically\n * calculate the zoom level to fit the artboard within the container with padding.\n *\n * Interactive mode: Enables infinite canvas with pan/zoom controls.\n */\nexport const Canvas: React.FC<CanvasProps> = ({\n className,\n style,\n width,\n height,\n zoom: propZoom,\n fitPadding = 0.95,\n interactive = false,\n artboardBorderRadius = 0,\n fixedMargin,\n maxHeight,\n showRotationHandle = true,\n enableShortcuts = true,\n canvasCutouts,\n pieceGuides,\n pieceFocus,\n}) => {\n // Debug: log artboardBorderRadius\n // --- Core editor state (elements, artboards, refs, command history) ---\n const {\n canvasEditorRef,\n canvasRef,\n elements,\n artboards,\n artboardManager,\n handleElementUpdate,\n setElements,\n executeElementUpdate,\n } = useEditor();\n\n // --- Viewport state (zoom, pan) ---\n const {\n zoom: contextZoom,\n setZoom,\n panOffset,\n setPanOffset,\n isPanning,\n setIsPanning,\n userZoom,\n setUserZoom,\n resetUserView,\n } = useViewportContext();\n\n // --- Selection state ---\n const {\n selectedId,\n multiSelection,\n handleSelectionChange,\n setMultiSelection,\n handleActiveChildChange,\n setHoveredElementId,\n hideHandles,\n } = useSelectionContext();\n\n // --- Tool state ---\n const {\n setTextSelectionVersion,\n setExpandedPanelType,\n } = useToolStateContext();\n\n // Handler to open crop panel when entering crop mode via double-click\n const handleCropModeEnter = React.useCallback(() => {\n setExpandedPanelType('crop');\n }, [setExpandedPanelType]);\n\n const containerRef = React.useRef<HTMLDivElement>(null);\n // Start with 0 zoom - will be calculated after layout\n const [calculatedZoom, setCalculatedZoom] = React.useState<number>(0);\n // Track container width for margin calculations\n const [containerWidth, setContainerWidth] = React.useState<number>(0);\n // Track whether maxHeight is constraining (set during zoom calculation)\n const [isHeightConstrained, setIsHeightConstrained] = React.useState<boolean>(false);\n // The last zoom THIS component pushed into ViewportContext. A context\n // zoom that differs from it is, by definition, an external write (host\n // `setZoom` / `zoomIn` / `zoomOut` / `zoomToFit` via the public\n // Editor.* / useViewport surface) and must be adopted as the new\n // baseline. Initialized to the context's CURRENT zoom — never 0 — so\n // the provider's initial value can't masquerade as an external write\n // and stomp a prop-seeded zoom during mount (DX finding #14).\n const lastSyncedZoomRef = React.useRef<number>(contextZoom);\n // Whether we've already done the initial post-layout fit calc.\n // First run uses a double-rAF so CSS layout has settled; later\n // runs (driven by animation, dep changes, etc.) compute\n // synchronously to keep up with 60fps tweens.\n const hasFitOnceRef = React.useRef(false);\n\n // Track if space key is held for pan mode\n const [isSpaceHeld, setIsSpaceHeld] = React.useState(false);\n // Track pan start position\n const panStartRef = React.useRef<{ x: number; y: number; panX: number; panY: number } | null>(null);\n\n // Set up keyboard shortcuts\n useKeyboardShortcuts({ enabled: enableShortcuts });\n\n // Right-click context menu state. CanvasEditor has already hit-tested and\n // selected the target by the time the request arrives — this component\n // only positions and renders the menu (default ON, like the layers panel's\n // own context menu).\n const [contextMenu, setContextMenu] = React.useState<CanvasContextMenuState | null>(null);\n const handleContextMenuRequest = React.useCallback(\n (request: CanvasContextMenuRequest) => {\n setContextMenu({\n x: request.clientX,\n y: request.clientY,\n targetId: request.elementId,\n });\n },\n []\n );\n const closeContextMenu = React.useCallback(() => setContextMenu(null), []);\n\n // Get active artboard for sizing\n const activeArtboard = artboardManager.getActiveArtboard();\n\n // ============================================================================\n // INTERACTIVE MODE: Wheel zoom and space+drag pan\n // ============================================================================\n\n // Handle wheel zoom (centered on cursor position)\n const handleWheel = React.useCallback(\n (e: WheelEvent) => {\n // Interactive mode: any wheel zooms (infinite-canvas feel).\n // Non-interactive (editor) mode: only zoom on `ctrlKey` (Mac\n // trackpad pinch fires this) or `metaKey` (Cmd+scroll explicit\n // zoom). Ordinary scroll passes through to the page.\n const isZoomGesture = interactive || e.ctrlKey || e.metaKey;\n if (!isZoomGesture) return;\n\n // Prevent page scroll\n e.preventDefault();\n\n const container = containerRef.current;\n if (!container) return;\n\n // Get cursor position relative to container\n const rect = container.getBoundingClientRect();\n const cursorX = e.clientX - rect.left;\n const cursorY = e.clientY - rect.top;\n\n // Calculate zoom delta\n const delta = -e.deltaY * ZOOM_SENSITIVITY;\n\n if (interactive) {\n // Interactive mode operates directly on calculatedZoom (no\n // separate auto-fit baseline).\n const oldZoom = calculatedZoom;\n const newZoom = Math.min(Math.max(oldZoom * (1 + delta), MIN_ZOOM), MAX_ZOOM);\n if (newZoom === oldZoom) return;\n const zoomRatio = newZoom / oldZoom;\n const newPanX = cursorX - (cursorX - panOffset.x) * zoomRatio;\n const newPanY = cursorY - (cursorY - panOffset.y) * zoomRatio;\n setCalculatedZoom(newZoom);\n setPanOffset({ x: newPanX, y: newPanY });\n } else {\n // Non-interactive mode keeps `calculatedZoom` as the auto-fit\n // baseline and zooms via `userZoom` on top. This way switching\n // pieces / resizing the container still re-fits cleanly even\n // after the user has zoomed in.\n const oldUserZoom = userZoom;\n const newUserZoom = Math.min(\n Math.max(oldUserZoom * (1 + delta), USER_MIN_ZOOM),\n USER_MAX_ZOOM,\n );\n if (newUserZoom === oldUserZoom) return;\n // Effective zoom changes by ratio = newUserZoom / oldUserZoom\n // (calculatedZoom is constant during this gesture).\n const ratio = newUserZoom / oldUserZoom;\n const newPanX = cursorX - (cursorX - panOffset.x) * ratio;\n const newPanY = cursorY - (cursorY - panOffset.y) * ratio;\n setUserZoom(newUserZoom);\n setPanOffset({ x: newPanX, y: newPanY });\n }\n },\n [interactive, calculatedZoom, panOffset, setPanOffset, userZoom, setUserZoom]\n );\n\n // Attach wheel listener (needs to be non-passive to preventDefault).\n // Both interactive and non-interactive modes attach — the handler\n // itself decides whether a given wheel event is a zoom gesture.\n React.useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n container.addEventListener('wheel', handleWheel, { passive: false });\n return () => container.removeEventListener('wheel', handleWheel);\n }, [handleWheel]);\n\n // Track space key for pan mode\n React.useEffect(() => {\n if (!interactive) return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.code === 'Space' && !e.repeat) {\n setIsSpaceHeld(true);\n }\n };\n\n const handleKeyUp = (e: KeyboardEvent) => {\n if (e.code === 'Space') {\n setIsSpaceHeld(false);\n panStartRef.current = null;\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n window.addEventListener('keyup', handleKeyUp);\n\n return () => {\n window.removeEventListener('keydown', handleKeyDown);\n window.removeEventListener('keyup', handleKeyUp);\n };\n }, [interactive]);\n\n // Pan handlers (using pointer events for unified mouse/touch/pen support)\n const handlePanStart = React.useCallback(\n (e: React.PointerEvent) => {\n if (!interactive) return;\n\n // Start pan on middle mouse button OR space+left click\n // For touch, two-finger pan could be added in the future\n const isMiddleMouse = e.button === 1;\n const isSpacePan = isSpaceHeld && e.button === 0;\n\n if (isMiddleMouse || isSpacePan) {\n e.preventDefault();\n (e.target as HTMLElement).setPointerCapture(e.pointerId);\n setIsPanning(true);\n panStartRef.current = {\n x: e.clientX,\n y: e.clientY,\n panX: panOffset.x,\n panY: panOffset.y,\n };\n }\n },\n [interactive, isSpaceHeld, panOffset, setIsPanning]\n );\n\n const handlePanMove = React.useCallback(\n (e: React.PointerEvent) => {\n if (!isPanning || !panStartRef.current) return;\n\n const dx = e.clientX - panStartRef.current.x;\n const dy = e.clientY - panStartRef.current.y;\n\n setPanOffset({\n x: panStartRef.current.panX + dx,\n y: panStartRef.current.panY + dy,\n });\n },\n [isPanning, setPanOffset]\n );\n\n const handlePanEnd = React.useCallback((e?: React.PointerEvent) => {\n if (isPanning) {\n if (e) {\n (e.target as HTMLElement).releasePointerCapture(e.pointerId);\n }\n setIsPanning(false);\n panStartRef.current = null;\n }\n }, [isPanning, setIsPanning]);\n\n // Global pointer up to catch releases outside container\n React.useEffect(() => {\n if (!interactive || !isPanning) return;\n\n const handleGlobalPointerUp = () => {\n setIsPanning(false);\n panStartRef.current = null;\n };\n\n window.addEventListener('pointerup', handleGlobalPointerUp);\n return () => window.removeEventListener('pointerup', handleGlobalPointerUp);\n }, [interactive, isPanning, setIsPanning]);\n\n // ADR-0060: when `pieceFocus` names a piece, the canvas's visual\n // subject is THAT piece, not the full artboard. Width / height for\n // CSS sizing + centring math should follow the focused piece. Falls\n // back to full artboard for spread mode / single-piece layouts.\n //\n // The target rect comes from `pieceGuides.pieces`; the animated\n // value smoothly interpolates between targets when the user\n // switches pieces (single source of truth for both the zoom-fit\n // calc here and the layout / render-loop downstream — keeps zoom\n // and pan in lock-step during the dolly).\n const targetFocusedRect = React.useMemo(() => {\n if (!pieceFocus || !pieceGuides) return null;\n const pieces = pieceGuides.pieces ?? [];\n if (pieces.length <= 1) return null;\n // 'spread' (= the picker's \"All\" view): animate to/from a\n // full-artboard rect so the camera dollies smoothly between a\n // piece and the all-pieces view. Returning `null` here would\n // make the animation hook snap (no `from`/`to` to interpolate),\n // producing the same instant jump the user wants to avoid.\n if (pieceFocus === 'spread') {\n if (!activeArtboard) return null;\n return {\n x: 0,\n y: 0,\n width: activeArtboard.width,\n height: activeArtboard.height,\n };\n }\n const match = pieces.find((p) => p.id === pieceFocus.pieceId);\n if (!match) return null;\n return { x: match.x, y: match.y, width: match.width, height: match.height };\n }, [pieceFocus, pieceGuides, activeArtboard]);\n const focusedPiece = useAnimatedFocusRect(targetFocusedRect);\n\n // Calculate zoom to fit artboard width in container\n // Height is determined by artboard aspect ratio, not container\n React.useEffect(() => {\n if (propZoom !== undefined) {\n // Use explicit zoom from props\n setCalculatedZoom(propZoom);\n return;\n }\n\n if (!containerRef.current || !activeArtboard) {\n return;\n }\n\n let rafId: number = 0;\n\n const calculateFitZoom = () => {\n if (!containerRef.current || !activeArtboard) return;\n\n // Only measure width - height will be determined by artboard aspect ratio\n const containerWidth = containerRef.current.clientWidth;\n\n\n if (containerWidth === 0) {\n return;\n }\n\n // ADR-0060: when `pieceFocus` names a piece in the active layout,\n // fit the zoom to THAT piece's dimensions instead of the full\n // artboard. Otherwise a focused 1500-wide piece on a 3100-wide\n // spread gets squashed because the zoom assumes the full 3100\n // needs to fit the container — the focused piece ends up at <50%\n // of the container width with neighbours visible everywhere.\n // Falling back to artboard dims when no focus or unknown piece.\n let fitWidth = activeArtboard.width;\n let fitHeight = activeArtboard.height;\n if (pieceFocus && pieceFocus !== 'spread' && pieceGuides) {\n const pieces = pieceGuides.pieces ?? [];\n if (pieces.length > 1) {\n const match = pieces.find((p) => p.id === pieceFocus.pieceId);\n if (match) {\n fitWidth = match.width;\n fitHeight = match.height;\n }\n }\n }\n\n // Calculate zoom to fit artboard width with padding\n // When fixedMargin is provided, use it for both left and right margins (fixed pixels)\n // Otherwise, use fitPadding as a percentage\n let fitZoom: number;\n let constrainedByHeight = false;\n\n if (fixedMargin !== undefined) {\n // Fixed margin mode: artboard fills container minus 2x fixed margin\n const availableWidth = containerWidth - (fixedMargin * 2);\n const widthFitZoom = Math.min(availableWidth / fitWidth, 1.0);\n\n if (focusedPiece && maxHeight !== undefined) {\n // ADR-0060: in focused mode, fit the piece by whichever\n // axis is tighter. On a narrow viewport with a wide piece\n // (e.g. 1500×2000 Front in a 400px-wide container), pure\n // height-fit would make the canvas wider than the container\n // and clip the focused piece horizontally. Width-fit keeps\n // the piece fully visible — the canvas ends up shorter than\n // the panel slot, which the consumer's flex layout absorbs\n // by vertically centring the canvas inside the slot.\n const availableHeight = maxHeight - (fixedMargin * 2);\n const heightFitZoom = Math.min(availableHeight / fitHeight, 1.0);\n fitZoom = Math.min(widthFitZoom, heightFitZoom);\n constrainedByHeight = heightFitZoom <= widthFitZoom;\n } else {\n fitZoom = widthFitZoom;\n if (maxHeight !== undefined) {\n const artboardHeightAtZoom = fitHeight * fitZoom;\n const totalHeightAtZoom = artboardHeightAtZoom + (fixedMargin * 2);\n if (totalHeightAtZoom > maxHeight) {\n const availableHeight = maxHeight - (fixedMargin * 2);\n const heightConstrainedZoom = availableHeight / fitHeight;\n fitZoom = Math.min(fitZoom, heightConstrainedZoom, 1.0);\n constrainedByHeight = true;\n }\n }\n }\n } else {\n // Percentage mode: artboard fills fitPadding% of container\n fitZoom = Math.min((containerWidth * fitPadding) / fitWidth, 1.0);\n }\n\n setCalculatedZoom(fitZoom);\n setContainerWidth(containerWidth);\n setIsHeightConstrained(constrainedByHeight);\n };\n\n // First run: double-RAF so CSS layout has settled before we\n // measure containerWidth. Subsequent runs (animation, dep\n // changes) compute synchronously — re-scheduling rAFs at 60fps\n // would lag the focused-piece dolly by 2 frames.\n let cancelled = false;\n if (hasFitOnceRef.current) {\n calculateFitZoom();\n } else {\n rafId = requestAnimationFrame(() => {\n rafId = requestAnimationFrame(() => {\n if (cancelled) return;\n calculateFitZoom();\n hasFitOnceRef.current = true;\n });\n });\n }\n\n // Watch for container width changes\n const resizeObserver = new ResizeObserver(() => {\n calculateFitZoom();\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n cancelled = true;\n cancelAnimationFrame(rafId);\n resizeObserver.disconnect();\n };\n }, [propZoom, activeArtboard, fitPadding, fixedMargin, maxHeight, pieceFocus, pieceGuides, focusedPiece]);\n\n // The zoom that the rest of the canvas system sees. `calculatedZoom`\n // is the auto-fit value driven by container size; `userZoom` is the\n // user's zoom-in / zoom-out multiplier (pinch, wheel, pill). Multiply\n // them so element rendering, hit-testing, and pointer math all stay\n // in sync with what the user sees.\n const effectiveZoom =\n calculatedZoom > 0 && userZoom > 0 ? calculatedZoom * userZoom : calculatedZoom;\n\n // ---------------------------------------------------------------------\n // Zoom ownership handshake — ViewportContext.zoom is the ONE public zoom\n // value: hosts read it AND write it. Internally the canvas composes zoom\n // from two pieces (layout-driven `calculatedZoom` × gesture-driven\n // `userZoom`), so it must both PUSH its composed value into the context\n // and ADOPT any context value it didn't push itself. `lastSyncedZoomRef`\n // is the handshake: equality means \"that's our own echo\", inequality\n // means \"the host wrote zoom — make it the new gesture baseline\".\n // ---------------------------------------------------------------------\n\n // ADOPT external zoom writes (BOTH modes). Declared before the push\n // effect below so that when a re-fit changes `calculatedZoom`, this\n // effect re-evaluates against the not-yet-updated handshake value and\n // correctly sees \"nothing external happened\" instead of mistaking the\n // stale context echo for a host write.\n React.useEffect(() => {\n if (contextZoom === lastSyncedZoomRef.current || contextZoom <= 0) return;\n if (interactive) {\n // Interactive mode renders `calculatedZoom` directly — adopt as-is.\n // The next wheel multiplies from here (and clamps, as always).\n setCalculatedZoom(contextZoom);\n } else {\n // Editor mode: `calculatedZoom` is the auto-fit baseline owned by\n // layout, so route the absolute external zoom through the same\n // `userZoom` multiplier the wheel / pinch / pill paths use. Re-fits\n // keep honoring the host's intent (fit × multiplier) and the next\n // ctrl-wheel composes on top of the external value instead of\n // clobbering it from a stale baseline. `calculatedZoom` is a dep on\n // purpose: a write landing before layout has produced a fit baseline\n // is adopted as soon as the baseline exists.\n if (calculatedZoom <= 0) return;\n setUserZoom(contextZoom / calculatedZoom);\n }\n lastSyncedZoomRef.current = contextZoom;\n }, [interactive, contextZoom, calculatedZoom, setUserZoom]);\n\n // PUSH the composed zoom into the context (and record it as our own).\n // Never push the pre-layout 0 — it isn't a zoom, and recording it would\n // make the provider's initial value look like an external write.\n React.useEffect(() => {\n if (effectiveZoom <= 0) return;\n lastSyncedZoomRef.current = effectiveZoom;\n setZoom(effectiveZoom);\n }, [effectiveZoom, setZoom]);\n\n // Reset user zoom + pan when the focused piece changes — switching\n // to a different piece (Front → Back, etc.) should land on fit-to-\n // piece, not preserve a previous zoom that won't make sense for\n // the new framing. Skip on the initial mount (`focusedPiece`\n // becomes non-null then; resetting an already-default state is a\n // no-op but feels cleaner without a guard).\n const lastFocusedPieceIdRef = React.useRef<string | null>(null);\n React.useEffect(() => {\n const id = focusedPiece && 'id' in focusedPiece ? (focusedPiece as { id?: string }).id ?? null : null;\n if (id !== lastFocusedPieceIdRef.current) {\n if (lastFocusedPieceIdRef.current !== null) {\n // Don't reset on the first focus event — only on transitions.\n resetUserView();\n }\n lastFocusedPieceIdRef.current = id;\n }\n }, [focusedPiece, resetUserView]);\n\n // `focusedPiece` itself is hoisted above the `calculateFitZoom`\n // useEffect — these helpers stay co-located with the dimension math\n // that uses them.\n const subjectWidth = focusedPiece ? focusedPiece.width : activeArtboard?.width ?? 0;\n const subjectHeight = focusedPiece ? focusedPiece.height : activeArtboard?.height ?? 0;\n\n const canvasWidth = subjectWidth * effectiveZoom;\n const canvasHeight = subjectHeight * effectiveZoom;\n\n // The canvas's *layout* width / height at fit zoom (userZoom = 1).\n // Used for centring math so the canvas's left/top edge stays\n // anchored when the user zooms in — we then translate via\n // `panOffset` to recentre around the gesture centroid. Without\n // this anchoring, `effectiveMarginX = (container - canvasWidth)/2`\n // would shrink (and go negative) as zoom grows, fighting the pan\n // adjustment.\n const fitCanvasWidth = subjectWidth * calculatedZoom;\n\n // Calculate padded canvas dimensions (what CanvasEditor will render)\n // When fixedMargin is provided, add fixed margins to artboard size\n // Otherwise use fitPadding as a percentage multiplier\n let paddedCanvasHeight = activeArtboard\n ? (fixedMargin !== undefined ? canvasHeight + (fixedMargin * 2) : canvasHeight / fitPadding)\n : 0;\n\n // Enforce maxHeight constraint on display height\n if (maxHeight !== undefined && paddedCanvasHeight > maxHeight) {\n paddedCanvasHeight = maxHeight;\n }\n\n // Calculate effective margins for CanvasEditor\n // When maxHeight constrains (tracked via isHeightConstrained state),\n // horizontal margin increases to center artboard at full container width\n let effectiveMarginX = fixedMargin;\n let effectiveMarginY = fixedMargin;\n\n // Use current container width from ref (more reliable than state which may be stale)\n const currentContainerWidth = containerRef.current?.clientWidth || containerWidth;\n\n if (fixedMargin !== undefined && isHeightConstrained && currentContainerWidth > 0 && fitCanvasWidth > 0) {\n // Center horizontally based on fit-zoom canvas width, NOT the\n // current (possibly-zoomed) width — see `fitCanvasWidth` comment\n // above. PanOffset compensates around the user's gesture centroid\n // when they zoom in.\n effectiveMarginX = (currentContainerWidth - fitCanvasWidth) / 2;\n }\n\n // Display height matches padded canvas height\n const displayHeight = paddedCanvasHeight;\n\n // Cache container dims via ResizeObserver instead of reading\n // `clientWidth/Height` per pan update. During a drag the user\n // can fire dozens of `panOffset` updates per second, and\n // synchronous layout reads on every one cause forced reflows\n // (visible iOS Safari memory pressure → page refresh under\n // sustained drag).\n const containerSizeRef = React.useRef({ w: 0, h: 0 });\n React.useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n containerSizeRef.current = {\n w: container.clientWidth,\n h: container.clientHeight,\n };\n const observer = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (!entry) return;\n containerSizeRef.current = {\n w: entry.contentRect.width,\n h: entry.contentRect.height,\n };\n });\n observer.observe(container);\n return () => observer.disconnect();\n }, []);\n\n // Clamp panOffset so at least 25% of the canvas's smaller axis stays\n // visible on each side. Without this, a single-finger pan at fit\n // zoom can shove the canvas off-screen to the point where the user\n // can \"lose\" it. Only fires when the current panOffset would leave\n // less than 25% visible — at fit (panOffset {0,0}) this is a no-op.\n // We do this in `Canvas.tsx` instead of inside each gesture handler\n // so all pan paths (pinch, single-finger pan, pill, wheel) share\n // the same clamp.\n React.useEffect(() => {\n if (interactive) return; // interactive mode is intentional infinite canvas\n\n // Belt-and-suspenders to the per-gesture guards: at fit zoom the\n // entire canvas is in view, so any non-zero pan is meaningless.\n // Pinch / wheel / programmatic-set paths can briefly leave a\n // residual offset; snap it to zero here so the next paint is\n // clean. The single-finger pan path already short-circuits at\n // userZoom <= 1.001 (see useCanvasInteraction).\n if (userZoom <= 1.001) {\n if (panOffset.x !== 0 || panOffset.y !== 0) {\n setPanOffset({ x: 0, y: 0 });\n }\n return;\n }\n\n const containerW = containerSizeRef.current.w;\n const containerH = containerSizeRef.current.h;\n if (containerW === 0 || containerH === 0) return;\n if (canvasWidth === 0 || canvasHeight === 0) return;\n\n const minVisibleX = 0.25 * Math.min(canvasWidth, containerW);\n const minVisibleY = 0.25 * Math.min(canvasHeight, containerH);\n\n // Canvas screen-left in container coords = effectiveMarginX + panOffset.x.\n // Constraints to keep `minVisibleX` of the canvas inside the container:\n // canvas right edge ≥ minVisibleX → panOffset.x ≥ minVisibleX - effectiveMarginX - canvasWidth\n // canvas left edge ≤ containerW - minVisibleX → panOffset.x ≤ containerW - minVisibleX - effectiveMarginX\n const minPanX = minVisibleX - (effectiveMarginX ?? 0) - canvasWidth;\n const maxPanX = containerW - minVisibleX - (effectiveMarginX ?? 0);\n const minPanY = minVisibleY - (effectiveMarginY ?? 0) - canvasHeight;\n const maxPanY = containerH - minVisibleY - (effectiveMarginY ?? 0);\n\n const clampedX = Math.max(minPanX, Math.min(maxPanX, panOffset.x));\n const clampedY = Math.max(minPanY, Math.min(maxPanY, panOffset.y));\n\n if (clampedX !== panOffset.x || clampedY !== panOffset.y) {\n setPanOffset({ x: clampedX, y: clampedY });\n }\n }, [\n interactive,\n userZoom,\n panOffset,\n canvasWidth,\n canvasHeight,\n effectiveMarginX,\n effectiveMarginY,\n setPanOffset,\n ]);\n\n // Container style - different for interactive vs non-interactive mode\n // Both modes include overscroll-behavior-x: none to prevent iOS Safari edge swipe navigation\n const containerStyle: React.CSSProperties = interactive\n ? {\n // Interactive mode: full viewport with overflow hidden for infinite canvas feel\n position: 'relative',\n width: width ? `${width}px` : '100%',\n height: height ? `${height}px` : '100%',\n overflow: 'hidden',\n cursor: isPanning ? 'grabbing' : isSpaceHeld ? 'grab' : 'default',\n touchAction: 'none', // Disable browser touch gestures for proper canvas interaction\n overscrollBehaviorX: 'none', // Prevent iOS Safari edge swipe navigation\n ...style,\n }\n : {\n // Standard mode: width fills parent, height calculated from artboard aspect ratio\n // Canvas is absolutely positioned, so no flexbox centering needed - we use offsets\n position: 'relative',\n width: width ? `${width}px` : '100%',\n height: height ? `${height}px` : displayHeight > 0 ? `${displayHeight}px` : 'auto',\n overflow: 'hidden', // Prevent canvas content from causing horizontal scroll\n touchAction: 'none', // Disable browser touch gestures for canvas control\n overscrollBehaviorX: 'none', // Prevent iOS Safari edge swipe navigation\n ...style,\n };\n\n // Inner wrapper style. Both modes apply the pan offset as a CSS\n // translate so the user's \"drag to see another part of the\n // zoomed-in design\" works in non-interactive mode too. Interactive\n // mode adds absolute positioning for the infinite-canvas feel.\n const innerWrapperStyle: React.CSSProperties = interactive\n ? {\n position: 'absolute',\n left: 0,\n top: 0,\n transform: `translate(${panOffset.x}px, ${panOffset.y}px)`,\n transformOrigin: '0 0',\n // Prevent pointer events during pan for smoother experience\n pointerEvents: isPanning ? 'none' : 'auto',\n }\n : {\n transform:\n panOffset.x !== 0 || panOffset.y !== 0\n ? `translate(${panOffset.x}px, ${panOffset.y}px)`\n : undefined,\n transformOrigin: '0 0',\n // Critical: `will-change: transform` keeps the wrapper on its\n // own compositor layer so panning feels smooth even at high\n // zoom (canvas DOM is bigger than container).\n willChange: panOffset.x !== 0 || panOffset.y !== 0 ? 'transform' : undefined,\n };\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={containerStyle}\n onPointerDown={interactive ? handlePanStart : undefined}\n onPointerMove={interactive ? handlePanMove : undefined}\n onPointerUp={interactive ? handlePanEnd : undefined}\n onPointerLeave={interactive ? handlePanEnd : undefined}\n onPointerCancel={interactive ? handlePanEnd : undefined}\n >\n <div style={innerWrapperStyle}>\n <CanvasEditor\n ref={canvasEditorRef}\n elements={elements}\n artboards={artboards}\n artboardManager={artboardManager}\n selectedId={selectedId}\n multiSelection={multiSelection}\n onElementUpdate={handleElementUpdate}\n setElements={setElements}\n executeElementUpdate={executeElementUpdate}\n onSelectionChange={handleSelectionChange}\n onMultiSelectionChange={setMultiSelection}\n onActiveChildChange={handleActiveChildChange}\n onTextSelectionChange={() => setTextSelectionVersion((v) => v + 1)}\n onHoverChange={setHoveredElementId}\n onCropModeEnter={handleCropModeEnter}\n onContextMenuRequest={handleContextMenuRequest}\n hideHandles={hideHandles}\n canvasRef={canvasRef}\n width={activeArtboard?.width}\n height={activeArtboard?.height}\n zoom={effectiveZoom}\n viewPadding={fitPadding}\n artboardBorderRadius={artboardBorderRadius}\n fixedMargin={fixedMargin}\n fixedMarginX={effectiveMarginX}\n fixedMarginY={effectiveMarginY}\n showRotationHandle={showRotationHandle}\n canvasCutouts={canvasCutouts}\n pieceGuides={pieceGuides}\n pieceFocus={pieceFocus}\n focusedPieceRect={focusedPiece}\n />\n </div>\n {contextMenu && <CanvasContextMenu menu={contextMenu} onClose={closeContextMenu} />}\n {!interactive && (userZoom < 0.999 || userZoom > 1.001) && (\n <ZoomPill\n userZoom={userZoom}\n onSetZoom={(target) => {\n // 1× is the auto-fit baseline → reset both zoom AND pan\n // (no useful pan at fit; reset gives a predictable home).\n if (target <= 1.001) {\n resetUserView();\n return;\n }\n const container = containerRef.current;\n if (!container) {\n setUserZoom(target);\n return;\n }\n const rect = container.getBoundingClientRect();\n // Anchor the zoom on the container centre when picked\n // from the menu (no cursor or pinch centroid to use).\n const cx = rect.width / 2;\n const cy = rect.height / 2;\n const ratio = target / Math.max(userZoom, 0.0001);\n setUserZoom(target);\n setPanOffset({\n x: cx - (cx - panOffset.x) * ratio,\n y: cy - (cy - panOffset.y) * ratio,\n });\n }}\n />\n )}\n </div>\n );\n};\n\n// ZoomPill — small floating control rendered inside the canvas\n// container. Lives bottom-right; only appears in non-interactive\n// (editor) mode where the user explicitly drives zoom. Interactive\n// mode has its own infinite-canvas wheel-based UX and doesn't need\n// a discrete pill. Tapping the percentage opens a menu of preset\n// zoom levels — no separate +/- since pinch / wheel handle granular\n// zooming and the presets cover the meaningful steps for editing.\nconst ZOOM_PRESETS = [1, 2, 4, 8] as const;\n\nfunction ZoomPill({\n userZoom,\n onSetZoom,\n}: {\n userZoom: number;\n onSetZoom: (target: number) => void;\n}) {\n const pct = Math.round(userZoom * 100);\n const [menuOpen, setMenuOpen] = React.useState(false);\n return (\n <div\n style={{\n position: 'absolute',\n bottom: 12,\n right: 12,\n zIndex: 30,\n pointerEvents: 'auto',\n }}\n // Tapping the zoom pill / its menu shouldn't deselect — see\n // SelectionChip's matching marker for the rationale.\n data-preserve-selection\n >\n {menuOpen && (\n <>\n {/* Click-outside scrim. Fixed-positioned full-viewport so\n the menu closes on any tap outside it, including\n outside the canvas. */}\n <div\n onClick={() => setMenuOpen(false)}\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 29,\n }}\n />\n {/* Preset menu, anchored above the trigger. */}\n <div\n role=\"menu\"\n style={{\n position: 'absolute',\n bottom: 'calc(100% + 6px)',\n right: 0,\n zIndex: 31,\n minWidth: 96,\n backdropFilter: 'blur(6px)',\n }}\n className=\"sc:flex sc:flex-col sc:rounded-2xl sc:border sc:border-border/40 sc:bg-popover/95 sc:p-1 sc:shadow-lg\"\n >\n {ZOOM_PRESETS.map((preset) => {\n const presetPct = preset * 100;\n const active = Math.abs(userZoom - preset) < 0.05;\n return (\n <button\n key={preset}\n type=\"button\"\n role=\"menuitem\"\n onClick={() => {\n setMenuOpen(false);\n onSetZoom(preset);\n }}\n className={\n active\n ? 'sc:rounded-xl sc:bg-foreground/10 sc:px-4 sc:py-2 sc:text-base sc:font-medium sc:tabular-nums sc:text-foreground sc:text-right'\n : 'sc:rounded-xl sc:px-4 sc:py-2 sc:text-base sc:font-medium sc:tabular-nums sc:text-foreground/80 sc:hover:bg-foreground/5 sc:hover:text-foreground sc:text-right'\n }\n >\n {presetPct}%\n </button>\n );\n })}\n </div>\n </>\n )}\n <div\n className=\"sc:flex sc:items-center sc:gap-1 sc:rounded-full sc:border sc:border-border/40 sc:bg-popover/85 sc:p-1 sc:shadow-sm\"\n style={{ backdropFilter: 'blur(6px)' }}\n >\n <button\n type=\"button\"\n onClick={() => setMenuOpen((open) => !open)}\n aria-haspopup=\"menu\"\n aria-expanded={menuOpen}\n aria-label=\"Zoom level\"\n className=\"sc:rounded-full sc:px-4 sc:py-1.5 sc:text-base sc:font-medium sc:tabular-nums sc:text-foreground/80 sc:hover:text-foreground\"\n style={{ minWidth: 72 }}\n >\n {pct}%\n </button>\n </div>\n </div>\n );\n}\n","/**\n * Section Registry\n *\n * A registry for mapping section IDs (used in kit layout slots) to their\n * React component implementations and metadata. Sections are the building\n * blocks of the editor layout — each slot in a kit's layout references\n * section IDs that are resolved to components at render time.\n *\n * Built-in sections (layers-panel, effects-panel, etc.) are registered\n * by calling `registerBuiltinSections()` during editor initialization.\n * Custom sections can be registered by consumers for custom editor layouts.\n *\n * ONE CATALOG, TWO ACCESS STYLES (ADR-0085, D6): the sections that have an\n * `Editor.*` counterpart resolve to the SAME component references the public\n * parts export — `layers-panel` IS `Editor.Layers`, `menu-button` IS\n * `Editor.Menu`, `undo-redo-controls` IS `Editor.History`, `add-element-menu`\n * IS `Editor.Add` (pinned by the D6 identity test). A kit slot string and a\n * JSX part are two spellings of one catalog entry; `registerSection` keeps\n * working and registers into the same world. The exception is\n * `contextual-toolbar`: in the part model the FLOATING contextual zone ships\n * with `Editor.Canvas` (ADR-0085 §5 — `Editor.Controls` is the docked zone /\n * an explicit floating declaration), so the legacy floating-toolbar section\n * keeps pointing at `ContextualToolbars` itself.\n *\n * @example Register a custom section\n * ```ts\n * import { registerSection } from '@snowcone-app/canvas/advanced';\n *\n * registerSection('my-custom-panel', {\n * component: MyCustomPanel,\n * label: 'Custom Panel',\n * position: 'right',\n * });\n * ```\n *\n * @example Look up a registered section\n * ```ts\n * import { getSection } from '@snowcone-app/canvas/advanced';\n *\n * const section = getSection('layers-panel');\n * if (section) {\n * const Component = section.component;\n * return <Component />;\n * }\n * ```\n */\n\nimport type React from 'react';\n\n// The part-backed catalog entries (D6). These four modules are LIGHT —\n// statically renderer-free by the import-graph gate (STATE_ENTRIES), so the\n// registry can hold the real references without dragging the paint pipeline\n// into this module's graph. Heavy chrome (EffectsPanel, ContextualToolbars,\n// …) stays lazily imported / caller-provided below.\nimport { EditorLayers } from '../components/editor/EditorLayers.js';\nimport { EditorMenu } from '../components/editor/EditorMenu.js';\nimport { EditorHistory } from '../components/editor/EditorHistory.js';\nimport { EditorAdd } from '../components/editor/EditorAdd.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Configuration for a registered section.\n *\n * Each section maps a string ID to a React component, a display label,\n * and the layout position where it is typically rendered.\n */\n/**\n * Type alias for section components in the registry.\n *\n * Section components read their state from context providers,\n * so they can be rendered with no props: `<Component />`.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type -- Section components read state from context; consumers render them with no props via <Component />\nexport type SectionComponent = React.ComponentType<{}>;\n\nexport interface SectionConfig {\n /**\n * The React component to render for this section.\n *\n * Section components read their state from EditorContext and other context\n * providers, so they are rendered with no explicit props: `<Component />`.\n *\n * If registering a component that has required props (e.g. for direct use),\n * cast it: `component: MyPanel as SectionComponent`.\n */\n component: SectionComponent;\n /** Human-readable label for UI display (e.g., panel headers, settings) */\n label: string;\n /** Optional icon identifier (e.g., an Iconify icon string) */\n icon?: string;\n /** The layout position where this section is typically placed */\n position: 'topbar' | 'left' | 'canvas' | 'right' | 'bottombar';\n}\n\n// ---------------------------------------------------------------------------\n// Registry\n// ---------------------------------------------------------------------------\n\n/** Internal registry map — section ID to config */\nconst sectionRegistry = new Map<string, SectionConfig>();\n\n/**\n * Register a section in the global section registry.\n *\n * If a section with the same ID is already registered, it will be\n * silently overwritten. This allows consumers to replace built-in\n * sections with custom implementations.\n *\n * @param id - Unique section identifier (e.g., 'layers-panel')\n * @param config - Section configuration including component, label, and position\n */\nexport function registerSection(id: string, config: SectionConfig): void {\n sectionRegistry.set(id, config);\n}\n\n/**\n * Retrieve a registered section by ID.\n *\n * @param id - The section identifier to look up\n * @returns The section config if found, or undefined\n */\nexport function getSection(id: string): SectionConfig | undefined {\n return sectionRegistry.get(id);\n}\n\n/**\n * Get all registered section IDs.\n *\n * @returns An array of all registered section ID strings\n */\nexport function getSectionIds(): string[] {\n return Array.from(sectionRegistry.keys());\n}\n\n/**\n * Get all registered sections as `[id, config]` pairs.\n *\n * Useful for iterating over the full registry to build dynamic layouts,\n * settings UIs, or debug views.\n *\n * @returns An array of `[sectionId, SectionConfig]` tuples\n *\n * @example List all registered sections\n * ```ts\n * for (const [id, config] of getAllSections()) {\n * console.log(`${id}: ${config.label} (${config.position})`);\n * }\n * ```\n */\nexport function getAllSections(): Array<[string, SectionConfig]> {\n return Array.from(sectionRegistry.entries());\n}\n\n/**\n * Remove a section from the registry.\n *\n * @param id - The section identifier to unregister\n */\nexport function unregisterSection(id: string): void {\n sectionRegistry.delete(id);\n}\n\n// ---------------------------------------------------------------------------\n// Built-in Section Registration\n// ---------------------------------------------------------------------------\n\n/** Whether built-in sections have been registered */\nlet builtinsRegistered = false;\n\n/**\n * Register all built-in sections from the editor's component library.\n *\n * This function lazily imports the component modules to avoid circular\n * dependencies and is idempotent — calling it multiple times has no\n * additional effect after the first call.\n *\n * Built-in sections registered:\n * - `layers-panel` — `Editor.Layers` (left; D6 part-backed)\n * - `menu-button` — `Editor.Menu` (topbar; D6 part-backed)\n * - `undo-redo-controls` — `Editor.History` (topbar; D6 part-backed)\n * - `add-element-menu` — `Editor.Add` (topbar; D6 part-backed)\n * - `effects-panel` — EffectsPanel (right)\n * - `export-panel` — ExportPanel (right)\n * - `images-panel` — ImagePanel (right)\n * - `glyph-panel` — GlyphPanel (right)\n * - `artboard-tabs` — ArtboardTabs (topbar)\n * - `contextual-toolbar` — ContextualToolbars (topbar; the legacy floating\n * zone — see the module doc for why this one is not `Editor.Controls`)\n * - `zoom-controls` — ZoomControls (bottombar)\n * - `crop-panel` — CropPanel (right)\n */\nexport async function registerBuiltinSections(): Promise<void> {\n if (builtinsRegistered) return;\n builtinsRegistered = true;\n\n registerPartBackedSections();\n\n const [\n { EffectsPanel },\n { ExportPanel },\n { ImagePanel },\n { GlyphPanel },\n { ArtboardTabs },\n { ContextualToolbars },\n { ZoomControls },\n { CropPanel },\n ] = await Promise.all([\n import('../components/embed/EffectsPanel.js'),\n import('../components/embed/ExportPanel.js'),\n import('../components/embed/ImagePanel.js'),\n import('../components/embed/GlyphPanel.js'),\n import('../components/embed/ArtboardTabs.js'),\n import('../components/ContextualToolbars.js'),\n import('../components/embed/ZoomControls.js'),\n import('../components/CropPanel.js'),\n ]);\n\n registerSection('effects-panel', {\n component: EffectsPanel as SectionComponent,\n label: 'Effects',\n icon: 'gravity-ui:sparkles',\n position: 'right',\n });\n\n registerSection('export-panel', {\n component: ExportPanel,\n label: 'Export',\n icon: 'gravity-ui:arrow-down-to-square',\n position: 'right',\n });\n\n registerSection('images-panel', {\n component: ImagePanel as SectionComponent,\n label: 'Images',\n icon: 'gravity-ui:picture',\n position: 'right',\n });\n\n registerSection('glyph-panel', {\n component: GlyphPanel as SectionComponent,\n label: 'Glyphs',\n icon: 'gravity-ui:text',\n position: 'right',\n });\n\n registerSection('artboard-tabs', {\n component: ArtboardTabs,\n label: 'Artboards',\n icon: 'gravity-ui:copy',\n position: 'topbar',\n });\n\n registerSection('contextual-toolbar', {\n component: ContextualToolbars,\n label: 'Contextual Toolbar',\n position: 'topbar',\n });\n\n registerSection('zoom-controls', {\n component: ZoomControls,\n label: 'Zoom',\n icon: 'gravity-ui:magnifier',\n position: 'bottombar',\n });\n\n registerSection('crop-panel', {\n component: CropPanel as SectionComponent,\n label: 'Crop',\n icon: 'gravity-ui:scissors',\n position: 'right',\n });\n}\n\n/**\n * Register the part-backed catalog entries (D6): these resolve to the SAME\n * component references as the public `Editor.*` parts — one catalog, two\n * access styles. Shared by both registration paths; the modules are light\n * (statically renderer-free, import-graph-gated), so this never needs to be\n * lazy.\n */\nfunction registerPartBackedSections(): void {\n registerSection('layers-panel', {\n component: EditorLayers as SectionComponent,\n label: 'Layers',\n icon: 'gravity-ui:layers',\n position: 'left',\n });\n\n registerSection('menu-button', {\n component: EditorMenu as SectionComponent,\n label: 'Menu',\n icon: 'gravity-ui:bars',\n position: 'topbar',\n });\n\n registerSection('undo-redo-controls', {\n component: EditorHistory as SectionComponent,\n label: 'Undo / Redo',\n icon: 'lucide:undo-2',\n position: 'topbar',\n });\n\n registerSection('add-element-menu', {\n component: EditorAdd as SectionComponent,\n label: 'Add Element',\n icon: 'gravity-ui:plus',\n position: 'topbar',\n });\n}\n\n/**\n * Synchronously register built-in sections using direct component references.\n *\n * Unlike `registerBuiltinSections()` which uses lazy imports, this function\n * requires that the HEAVY chrome components be passed in directly (callers —\n * KitLayout — already import them statically). The part-backed entries\n * (`layers-panel`, `menu-button`, `undo-redo-controls`, `add-element-menu`)\n * are registered from the `Editor.*` references directly (D6) and are no\n * longer parameters.\n *\n * @param components - Object mapping section IDs to their component references\n */\nexport function registerBuiltinSectionsSync(components: {\n EffectsPanel: SectionComponent;\n ExportPanel: SectionComponent;\n ImagePanel: SectionComponent;\n GlyphPanel: SectionComponent;\n ArtboardTabs: SectionComponent;\n ContextualToolbars: SectionComponent;\n ZoomControls: SectionComponent;\n CropPanel: SectionComponent;\n}): void {\n if (builtinsRegistered) return;\n builtinsRegistered = true;\n\n registerPartBackedSections();\n\n registerSection('effects-panel', {\n component: components.EffectsPanel,\n label: 'Effects',\n icon: 'gravity-ui:sparkles',\n position: 'right',\n });\n\n registerSection('export-panel', {\n component: components.ExportPanel,\n label: 'Export',\n icon: 'gravity-ui:arrow-down-to-square',\n position: 'right',\n });\n\n registerSection('images-panel', {\n component: components.ImagePanel,\n label: 'Images',\n icon: 'gravity-ui:picture',\n position: 'right',\n });\n\n registerSection('glyph-panel', {\n component: components.GlyphPanel,\n label: 'Glyphs',\n icon: 'gravity-ui:text',\n position: 'right',\n });\n\n registerSection('artboard-tabs', {\n component: components.ArtboardTabs,\n label: 'Artboards',\n icon: 'gravity-ui:copy',\n position: 'topbar',\n });\n\n registerSection('contextual-toolbar', {\n component: components.ContextualToolbars,\n label: 'Contextual Toolbar',\n position: 'topbar',\n });\n\n registerSection('zoom-controls', {\n component: components.ZoomControls,\n label: 'Zoom',\n icon: 'gravity-ui:magnifier',\n position: 'bottombar',\n });\n\n registerSection('crop-panel', {\n component: components.CropPanel,\n label: 'Crop',\n icon: 'gravity-ui:scissors',\n position: 'right',\n });\n}\n","/**\n * StrokePanel - UI component for configuring stroke properties\n * Provides controls for universal stroke system\n *\n * Migrated to Shadcn/Radix:\n * - Disclosure (Collapsible)\n * - RadioGroup\n * - Slider\n * - Fieldset\n */\n\nimport { useState } from 'react';\nimport type { StrokeConfig } from '../types/index.js';\nimport ColorPickerDropdown from './ColorPickerDropdown.js';\nimport {\n Disclosure,\n DisclosureTrigger,\n DisclosureContent,\n Switch,\n Label,\n RadioGroup,\n Radio,\n Slider,\n Fieldset,\n Tooltip,\n TooltipTrigger,\n TooltipContent,\n TooltipProvider,\n} from './ui';\n\ninterface StrokePanelProps {\n stroke?: StrokeConfig;\n onChange: (stroke: StrokeConfig | undefined) => void;\n}\n\nconst StrokePanel = ({ stroke, onChange }: StrokePanelProps) => {\n const [isExpanded, setIsExpanded] = useState(stroke?.enabled || false);\n\n const enabled = stroke?.enabled || false;\n const color = stroke?.color || '#000000';\n const width = stroke?.width || 5;\n const lineCap = stroke?.lineCap || 'butt';\n const lineJoin = stroke?.lineJoin || 'miter';\n const opacity = stroke?.opacity !== undefined ? stroke.opacity : 1;\n\n const handleEnabledChange = (newEnabled: boolean) => {\n if (newEnabled) {\n onChange({\n enabled: true,\n color,\n width,\n lineCap,\n lineJoin,\n opacity,\n });\n } else {\n onChange(undefined);\n }\n setIsExpanded(newEnabled);\n };\n\n const handleColorChange = (e: { target: { value: string } }) => {\n if (!stroke) return;\n onChange({\n ...stroke,\n color: e.target.value,\n });\n };\n\n const handleWidthChange = (newWidth: number[]) => {\n if (!stroke) return;\n onChange({\n ...stroke,\n width: newWidth[0],\n });\n };\n\n const handleOpacityChange = (newOpacity: number[]) => {\n if (!stroke) return;\n onChange({\n ...stroke,\n opacity: newOpacity[0],\n });\n };\n\n const handleLineCapChange = (newLineCap: string) => {\n if (!stroke) return;\n onChange({\n ...stroke,\n lineCap: newLineCap as 'butt' | 'round' | 'square',\n });\n };\n\n const handleLineJoinChange = (newLineJoin: string) => {\n if (!stroke) return;\n onChange({\n ...stroke,\n lineJoin: newLineJoin as 'miter' | 'round' | 'bevel',\n });\n };\n\n return (\n <Disclosure open={isExpanded} onOpenChange={setIsExpanded} className=\"sc:mb-xl\">\n <DisclosureTrigger className=\"sc:w-full\">\n <div className=\"sc:flex sc:items-center sc:justify-between sc:w-full\">\n <Label>Stroke</Label>\n <Switch checked={enabled} onCheckedChange={handleEnabledChange} />\n </div>\n </DisclosureTrigger>\n <DisclosureContent className=\"sc:flex sc:flex-col sc:gap-lg sc:pt-lg\">\n {/* Color picker */}\n <Fieldset className=\"sc:flex sc:flex-col sc:gap-sm\">\n <div className=\"sc:flex sc:items-center sc:justify-between\">\n <Label>Color</Label>\n <ColorPickerDropdown value={color} onChange={handleColorChange} />\n </div>\n </Fieldset>\n\n {/* Width slider */}\n <div className=\"sc:flex sc:flex-col sc:gap-sm\">\n <Label>Width</Label>\n <Slider\n value={[width]}\n onValueChange={handleWidthChange}\n min={1}\n max={50}\n step={1}\n />\n <div className=\"sc:text-sm sc:text-text-muted\">{width}px</div>\n </div>\n\n {/* Opacity slider */}\n <div className=\"sc:flex sc:flex-col sc:gap-sm\">\n <Label>Opacity</Label>\n <Slider\n value={[opacity]}\n onValueChange={handleOpacityChange}\n min={0}\n max={1}\n step={0.01}\n />\n <div className=\"sc:text-sm sc:text-text-muted\">{Math.round(opacity * 100)}%</div>\n </div>\n\n {/* Line cap options */}\n <TooltipProvider delayDuration={300}>\n <Fieldset className=\"sc:flex sc:flex-col sc:gap-sm\">\n <Label>Line Cap</Label>\n <RadioGroup value={lineCap} onValueChange={handleLineCapChange} orientation=\"horizontal\">\n <Tooltip>\n <TooltipTrigger asChild>\n <div>\n <Radio value=\"butt\" id=\"cap-butt\">Butt</Radio>\n </div>\n </TooltipTrigger>\n <TooltipContent>Butt - Flat edges</TooltipContent>\n </Tooltip>\n <Tooltip>\n <TooltipTrigger asChild>\n <div>\n <Radio value=\"round\" id=\"cap-round\">Round</Radio>\n </div>\n </TooltipTrigger>\n <TooltipContent>Round - Rounded edges</TooltipContent>\n </Tooltip>\n <Tooltip>\n <TooltipTrigger asChild>\n <div>\n <Radio value=\"square\" id=\"cap-square\">Square</Radio>\n </div>\n </TooltipTrigger>\n <TooltipContent>Square - Extended square edges</TooltipContent>\n </Tooltip>\n </RadioGroup>\n </Fieldset>\n\n {/* Line join options */}\n <Fieldset className=\"sc:flex sc:flex-col sc:gap-sm\">\n <Label>Line Join</Label>\n <RadioGroup value={lineJoin} onValueChange={handleLineJoinChange} orientation=\"horizontal\">\n <Tooltip>\n <TooltipTrigger asChild>\n <div>\n <Radio value=\"miter\" id=\"join-miter\">Miter</Radio>\n </div>\n </TooltipTrigger>\n <TooltipContent>Miter - Sharp corners</TooltipContent>\n </Tooltip>\n <Tooltip>\n <TooltipTrigger asChild>\n <div>\n <Radio value=\"round\" id=\"join-round\">Round</Radio>\n </div>\n </TooltipTrigger>\n <TooltipContent>Round - Rounded corners</TooltipContent>\n </Tooltip>\n <Tooltip>\n <TooltipTrigger asChild>\n <div>\n <Radio value=\"bevel\" id=\"join-bevel\">Bevel</Radio>\n </div>\n </TooltipTrigger>\n <TooltipContent>Bevel - Beveled corners</TooltipContent>\n </Tooltip>\n </RadioGroup>\n </Fieldset>\n </TooltipProvider>\n </DisclosureContent>\n </Disclosure>\n );\n};\n\nexport default StrokePanel;\n","/**\n * MaskItem - Individual mask item in the masks list\n * Displays mask properties and controls\n */\n\nimport { useState } from 'react';\nimport { Button, Tooltip, TooltipTrigger, TooltipContent, Slider, Label } from './ui';\nimport type { MaskDefinition, ShapeType, ShapeTransformData, BaseTextElementConfig, ImageElementConfig, ImageTransformData } from '../types/index.js';\n\ninterface MaskItemProps {\n mask: MaskDefinition;\n index: number;\n isFirst: boolean;\n isLast: boolean;\n onUpdate: (updates: Partial<MaskDefinition>) => void;\n onRemove: () => void;\n onReorder: (direction: 'up' | 'down') => void;\n}\n\nconst MaskItem = ({ mask, index, isFirst, isLast, onUpdate, onRemove, onReorder }: MaskItemProps) => {\n const [isExpanded, setIsExpanded] = useState(false);\n\n const getMaskIcon = (type: string): string => {\n switch (type) {\n case 'clip':\n return '🎭';\n case 'alpha':\n return '🌫️';\n case 'luma':\n return '🌗';\n case 'distress':\n return '⚡';\n default:\n return '🎭';\n }\n };\n\n const getMaskLabel = (type: string): string => {\n switch (type) {\n case 'clip':\n return 'Clip Mask';\n case 'alpha':\n return 'Alpha Mask';\n case 'luma':\n return 'Luma Mask';\n case 'distress':\n return 'Distress Mask';\n default:\n return 'Unknown Mask';\n }\n };\n\n const handleOpacityChange = (value: number) => {\n onUpdate({ opacity: value });\n };\n\n const handleFeatherChange = (value: number) => {\n onUpdate({ feather: value });\n };\n\n const handleInvertedToggle = () => {\n onUpdate({ inverted: !mask.inverted });\n };\n\n const handleMaskElementTypeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {\n const transformType = e.target.value as 'custom' | 'shape' | 'image';\n\n if (transformType === 'image') {\n // Create a new image element config\n const existingUrl = mask.maskElement.transformType === 'image'\n ? (mask.maskElement as ImageElementConfig).imageUrl || ''\n : '';\n onUpdate({\n maskElement: {\n transformType: 'image',\n imageUrl: existingUrl,\n x: mask.maskElement.x || 0,\n y: mask.maskElement.y || 0,\n rotation: mask.maskElement.rotation || 0,\n transformData: {\n type: 'image',\n width: 200,\n height: 200,\n cropX: 0,\n cropY: 0,\n cropWidth: 1,\n cropHeight: 1,\n flipHorizontal: false,\n flipVertical: false,\n borderRadius: 0,\n },\n },\n });\n } else if (transformType === 'shape') {\n // Create a completely new shape element config (don't spread old properties)\n onUpdate({\n maskElement: {\n transformType: 'shape',\n x: mask.maskElement.x || 0,\n y: mask.maskElement.y || 0,\n rotation: mask.maskElement.rotation || 0,\n transformData: {\n type: 'shape',\n shapeType: 'star',\n width: 200,\n height: 200,\n fillColor: '#000000',\n fillOpacity: 1,\n points: 5,\n innerRadius: 0.4,\n },\n },\n });\n } else {\n // Create a completely new text element config (don't spread old properties)\n onUpdate({\n maskElement: {\n transformType: 'custom',\n x: mask.maskElement.x || 0,\n y: mask.maskElement.y || 0,\n rotation: mask.maskElement.rotation || 0,\n text: 'MASK',\n fontSize: 80,\n fontFamily: 'Anton',\n color: '#000000',\n },\n });\n }\n };\n\n const handleShapeTypeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {\n const shapeType = e.target.value as ShapeType;\n if (mask.maskElement.transformType === 'shape') {\n onUpdate({\n maskElement: {\n transformType: 'shape',\n x: mask.maskElement.x || 0,\n y: mask.maskElement.y || 0,\n rotation: mask.maskElement.rotation || 0,\n transformData: {\n ...(mask.maskElement.transformData as Partial<ShapeTransformData>),\n shapeType,\n },\n },\n });\n }\n };\n\n const handleTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (mask.maskElement.transformType !== 'shape') {\n const textConfig = mask.maskElement as BaseTextElementConfig & { transformType: string; x?: number; y?: number; rotation?: number };\n onUpdate({\n maskElement: {\n transformType: mask.maskElement.transformType,\n x: mask.maskElement.x || 0,\n y: mask.maskElement.y || 0,\n rotation: mask.maskElement.rotation || 0,\n text: e.target.value,\n fontSize: textConfig.fontSize || 80,\n fontFamily: textConfig.fontFamily || 'Anton',\n color: textConfig.color || '#000000',\n },\n });\n }\n };\n\n const handleImageUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (mask.maskElement.transformType === 'image') {\n const imgConfig = mask.maskElement as ImageElementConfig;\n onUpdate({\n maskElement: {\n transformType: 'image',\n imageUrl: e.target.value,\n x: mask.maskElement.x || 0,\n y: mask.maskElement.y || 0,\n rotation: mask.maskElement.rotation || 0,\n transformData: imgConfig.transformData || {\n type: 'image',\n width: 200,\n height: 200,\n cropX: 0,\n cropY: 0,\n cropWidth: 1,\n cropHeight: 1,\n flipHorizontal: false,\n flipVertical: false,\n borderRadius: 0,\n },\n },\n });\n }\n };\n\n const handleSizeChange = (size: number) => {\n if (mask.maskElement.transformType === 'image') {\n const imgConfig = mask.maskElement as ImageElementConfig;\n onUpdate({\n maskElement: {\n transformType: 'image',\n imageUrl: imgConfig.imageUrl || '',\n x: mask.maskElement.x || 0,\n y: mask.maskElement.y || 0,\n rotation: mask.maskElement.rotation || 0,\n transformData: {\n ...(imgConfig.transformData as Partial<ImageTransformData>),\n width: size,\n height: size,\n },\n },\n });\n } else if (mask.maskElement.transformType === 'shape') {\n onUpdate({\n maskElement: {\n transformType: 'shape',\n x: mask.maskElement.x || 0,\n y: mask.maskElement.y || 0,\n rotation: mask.maskElement.rotation || 0,\n transformData: {\n ...(mask.maskElement.transformData as Partial<ShapeTransformData>),\n width: size,\n height: size,\n },\n },\n });\n } else {\n const textConfig = mask.maskElement as BaseTextElementConfig & { transformType: string; x?: number; y?: number; rotation?: number };\n onUpdate({\n maskElement: {\n transformType: mask.maskElement.transformType,\n x: mask.maskElement.x || 0,\n y: mask.maskElement.y || 0,\n rotation: mask.maskElement.rotation || 0,\n text: textConfig.text || 'MASK',\n fontSize: size,\n fontFamily: textConfig.fontFamily || 'Anton',\n color: textConfig.color || '#000000',\n },\n });\n }\n };\n\n const getMaskElementSize = (): number => {\n if (mask.maskElement.transformType === 'image') {\n return (mask.maskElement as ImageElementConfig).transformData?.width || 200;\n }\n if (mask.maskElement.transformType === 'shape') {\n return (mask.maskElement.transformData as Partial<ShapeTransformData>)?.width || 200;\n }\n return (mask.maskElement as BaseTextElementConfig).fontSize || 80;\n };\n\n return (\n <div className=\"sc:rounded-md sc:border sc:border-border-secondary sc:bg-bg-tertiary sc:p-lg\">\n {/* Mask header */}\n <div className=\"sc:flex sc:items-center sc:justify-between\">\n <div className=\"sc:flex sc:items-center sc:gap-lg\">\n <span className=\"sc:text-lg\">{getMaskIcon(mask.type)}</span>\n <div className=\"sc:flex sc:flex-col sc:gap-xs\">\n <strong className=\"sc:text-lg sc:text-text-primary\">{getMaskLabel(mask.type)}</strong>\n <small className=\"sc:text-sm sc:text-text-muted\">#{index + 1}</small>\n </div>\n </div>\n <div className=\"sc:flex sc:gap-1\">\n <Tooltip delay={0}>\n <TooltipTrigger asChild>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n aria-label=\"Toggle mask details\"\n aria-expanded={isExpanded}\n onClick={() => setIsExpanded(!isExpanded)}\n >\n {isExpanded ? '▼' : '▶'}\n </Button>\n </TooltipTrigger>\n <TooltipContent>\n <p>{isExpanded ? 'Collapse' : 'Expand'}</p>\n </TooltipContent>\n </Tooltip>\n </div>\n </div>\n\n {/* Mask controls (when expanded) */}\n {isExpanded && (\n <div className=\"sc:mt-xl sc:flex sc:flex-col sc:gap-xl sc:border-t sc:border-border-secondary sc:pt-xl\">\n {/* Opacity slider */}\n <Slider\n className=\"slider-no-fill\"\n value={[mask.opacity || 1]}\n onValueChange={(val) => handleOpacityChange(val[0])}\n min={0}\n max={1}\n step={0.01}\n >\n <Label>Opacity</Label>\n <Slider.Output>{({ state }) => `${Math.round(state.values[0] * 100)}%`}</Slider.Output>\n <Slider.Track />\n <Slider.Thumb />\n </Slider>\n\n {/* Feather slider */}\n <Slider\n className=\"slider-no-fill\"\n value={[mask.feather || 0]}\n onValueChange={(val) => handleFeatherChange(val[0])}\n min={0}\n max={50}\n step={1}\n >\n <Label>Feather</Label>\n <Slider.Output>{({ state }) => `${state.values[0]}px`}</Slider.Output>\n <Slider.Track />\n <Slider.Thumb />\n </Slider>\n\n {/* Inverted toggle */}\n <label className=\"sc:flex sc:cursor-pointer sc:items-center sc:gap-md sc:text-base sc:text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={mask.inverted || false}\n onChange={handleInvertedToggle}\n className=\"sc:h-4 sc:w-4 sc:cursor-pointer\"\n />\n <span>Invert Mask</span>\n </label>\n\n {/* Mask Element Configuration */}\n <div className=\"sc:flex sc:flex-col sc:gap-lg sc:rounded-md sc:border sc:border-border-dark sc:bg-bg-secondary sc:p-xl\">\n <div className=\"sc:mb-xs sc:text-md sc:font-semibold sc:uppercase sc:tracking-wide sc:text-accent-primary\">Mask Shape</div>\n\n {/* Element Type Selector */}\n <div className=\"sc:flex sc:flex-col sc:gap-sm\">\n <label htmlFor={`mask-type-${mask.id}`} className=\"sc:text-base sc:text-text-secondary\">Type</label>\n <select\n id={`mask-type-${mask.id}`}\n value={mask.maskElement.transformType === 'shape' ? 'shape' : mask.maskElement.transformType === 'image' ? 'image' : 'custom'}\n onChange={handleMaskElementTypeChange}\n className=\"sc:w-full sc:cursor-pointer sc:rounded-md sc:border sc:border-border-dark sc:bg-bg-tertiary sc:px-md sc:py-sm sc:text-base sc:text-text-primary sc:transition-all sc:duration-base sc:hover:border-accent-primary sc:focus:border-accent-primary sc:focus:outline-none\"\n >\n <option value=\"custom\">Text</option>\n <option value=\"shape\">Shape</option>\n <option value=\"image\">Image</option>\n </select>\n </div>\n\n {/* Shape-specific controls */}\n {mask.maskElement.transformType === 'shape' && (\n <div className=\"sc:flex sc:flex-col sc:gap-sm\">\n <label htmlFor={`mask-shape-${mask.id}`} className=\"sc:text-base sc:text-text-secondary\">Shape</label>\n <select\n id={`mask-shape-${mask.id}`}\n value={(mask.maskElement.transformData as Partial<ShapeTransformData>)?.shapeType || 'star'}\n onChange={handleShapeTypeChange}\n className=\"sc:w-full sc:cursor-pointer sc:rounded-md sc:border sc:border-border-dark sc:bg-bg-tertiary sc:px-md sc:py-sm sc:text-base sc:text-text-primary sc:transition-all sc:duration-base sc:hover:border-accent-primary sc:focus:border-accent-primary sc:focus:outline-none\"\n >\n <option value=\"rectangle\">Rectangle</option>\n <option value=\"circle\">Circle</option>\n <option value=\"ellipse\">Ellipse</option>\n <option value=\"star\">Star</option>\n </select>\n </div>\n )}\n\n {/* Image-specific controls */}\n {mask.maskElement.transformType === 'image' && (\n <div className=\"sc:flex sc:flex-col sc:gap-sm\">\n <label htmlFor={`mask-image-${mask.id}`} className=\"sc:text-base sc:text-text-secondary\">Image URL</label>\n <input\n id={`mask-image-${mask.id}`}\n type=\"text\"\n value={(mask.maskElement as ImageElementConfig).imageUrl || ''}\n onChange={handleImageUrlChange}\n className=\"sc:w-full sc:rounded-md sc:border sc:border-border-dark sc:bg-bg-tertiary sc:px-md sc:py-sm sc:text-base sc:text-text-primary sc:transition-all sc:duration-base sc:hover:border-accent-primary sc:focus:border-accent-primary sc:focus:outline-none\"\n placeholder=\"https://... or data:image/...\"\n />\n </div>\n )}\n\n {/* Text-specific controls */}\n {mask.maskElement.transformType !== 'shape' && mask.maskElement.transformType !== 'image' && (\n <div className=\"sc:flex sc:flex-col sc:gap-sm\">\n <label htmlFor={`mask-text-${mask.id}`} className=\"sc:text-base sc:text-text-secondary\">Text</label>\n <input\n id={`mask-text-${mask.id}`}\n type=\"text\"\n value={(mask.maskElement as BaseTextElementConfig).text || 'MASK'}\n onChange={handleTextChange}\n className=\"sc:w-full sc:rounded-md sc:border sc:border-border-dark sc:bg-bg-tertiary sc:px-md sc:py-sm sc:text-base sc:text-text-primary sc:transition-all sc:duration-base sc:hover:border-accent-primary sc:focus:border-accent-primary sc:focus:outline-none\"\n placeholder=\"Mask text\"\n />\n </div>\n )}\n\n {/* Size control (works for both text and shapes) */}\n <Slider\n className=\"slider-no-fill\"\n value={[getMaskElementSize()]}\n onValueChange={(val) => handleSizeChange(val[0])}\n min={20}\n max={500}\n step={10}\n >\n <Label>Size</Label>\n <Slider.Output />\n <Slider.Track />\n <Slider.Thumb />\n </Slider>\n </div>\n\n {/* Action buttons */}\n <div className=\"sc:mt-md sc:flex sc:gap-sm\">\n <button\n onClick={() => onReorder('up')}\n disabled={isFirst}\n title=\"Move Up\"\n aria-label=\"Move mask up\"\n className=\"sc:flex-1 sc:cursor-pointer sc:rounded-md sc:border sc:border-border-dark sc:bg-bg-hover sc:px-md sc:py-sm sc:text-xl sc:text-text-secondary sc:transition-all sc:duration-slow sc:hover:border-accent-primary sc:hover:bg-bg-dark-hover sc:hover:text-text-primary sc:disabled:cursor-not-allowed sc:disabled:opacity-disabled\"\n >\n ↑\n </button>\n <button\n onClick={() => onReorder('down')}\n disabled={isLast}\n title=\"Move Down\"\n aria-label=\"Move mask down\"\n className=\"sc:flex-1 sc:cursor-pointer sc:rounded-md sc:border sc:border-border-dark sc:bg-bg-hover sc:px-md sc:py-sm sc:text-xl sc:text-text-secondary sc:transition-all sc:duration-slow sc:hover:border-accent-primary sc:hover:bg-bg-dark-hover sc:hover:text-text-primary sc:disabled:cursor-not-allowed sc:disabled:opacity-disabled\"\n >\n ↓\n </button>\n <button\n onClick={onRemove}\n title=\"Remove Mask\"\n aria-label=\"Remove mask\"\n className=\"sc:flex-[2] sc:cursor-pointer sc:rounded-md sc:border sc:border-error-light sc:bg-error sc:px-md sc:py-sm sc:text-base sc:font-semibold sc:text-text-on-dark sc:transition-all sc:duration-slow sc:hover:bg-error-light\"\n >\n ✕ Remove\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n\nexport default MaskItem;\n","/**\n * MasksPanel - UI component for managing element masks\n * Displays and controls the mask stack for selected elements\n *\n * Migrated to use primitives:\n * - Custom panel structure (masks don't have enable/disable, only collapsible)\n * - Button primitive for add button and mask type buttons\n * - Tailwind utilities for all styling\n */\n\nimport { useState } from 'react';\nimport type { MaskDefinition, MaskType, AnyElementConfig } from '../types/index.js';\nimport MaskItem from './MaskItem.js';\nimport { Button } from './ui';\n\ninterface MasksPanelProps {\n masks?: MaskDefinition[];\n onChange: (masks: MaskDefinition[]) => void;\n}\n\nconst MasksPanel = ({ masks = [], onChange }: MasksPanelProps) => {\n const [isExpanded, setIsExpanded] = useState(masks.length > 0);\n const [showAddMenu, setShowAddMenu] = useState(false);\n\n const handleAddMask = (type: MaskType) => {\n const newMask: MaskDefinition = {\n id: `mask-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n type,\n maskElement: createDefaultMaskElement(type),\n inverted: false,\n feather: 0,\n opacity: 1.0,\n };\n\n onChange([...masks, newMask]);\n setShowAddMenu(false);\n };\n\n const handleRemoveMask = (maskId: string) => {\n onChange(masks.filter((m) => m.id !== maskId));\n };\n\n const handleUpdateMask = (maskId: string, updates: Partial<MaskDefinition>) => {\n onChange(masks.map((m) => (m.id === maskId ? { ...m, ...updates } : m)));\n };\n\n const handleReorderMask = (maskId: string, direction: 'up' | 'down') => {\n const index = masks.findIndex((m) => m.id === maskId);\n if (index === -1) return;\n\n const newMasks = [...masks];\n if (direction === 'up' && index > 0) {\n [newMasks[index], newMasks[index - 1]] = [newMasks[index - 1], newMasks[index]];\n } else if (direction === 'down' && index < masks.length - 1) {\n [newMasks[index], newMasks[index + 1]] = [newMasks[index + 1], newMasks[index]];\n }\n\n onChange(newMasks);\n };\n\n return (\n <div className=\"sc:mb-xl sc:rounded-xl sc:border sc:border-border-primary sc:bg-bg-secondary sc:p-xl\">\n {/* Header */}\n <div className=\"sc:mb-md sc:flex sc:items-center sc:justify-between\">\n <div className=\"sc:flex sc:items-center sc:gap-md sc:text-xl sc:font-semibold sc:text-text-primary\">\n <span>Masks ({masks.length})</span>\n {masks.length > 0 && (\n <button\n onClick={() => setIsExpanded(!isExpanded)}\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={isExpanded ? 'Collapse' : 'Expand'}\n >\n {isExpanded ? '▼' : '▶'}\n </button>\n )}\n </div>\n <div className=\"sc:flex sc:gap-sm\">\n <Button\n variant=\"default\"\n onClick={() => setShowAddMenu(!showAddMenu)}\n className=\"sc:rounded-lg sc:bg-accent sc:px-xl sc:py-sm sc:font-semibold sc:text-text-on-accent sc:transition-all sc:duration-medium sc:hover:bg-accent-medium\"\n >\n + Add\n </Button>\n </div>\n </div>\n\n {/* Add mask dropdown menu */}\n {showAddMenu && (\n <div className=\"sc:mb-md sc:mt-md sc:flex sc:flex-col sc:gap-sm sc:rounded-lg sc:border sc:border-border-medium sc:bg-surface sc:p-md\">\n <button\n onClick={() => handleAddMask('clip')}\n className=\"sc:flex sc:cursor-pointer sc:items-center sc:gap-xl sc:rounded-lg sc:border sc:border-border-light sc:bg-bg-secondary sc:p-lg sc:text-left sc:text-text-primary sc:transition-all sc:duration-medium sc:hover:border-accent sc:hover:bg-bg-hover\"\n >\n <span className=\"sc:shrink-0 sc:text-xl\">🎭</span>\n <div className=\"sc:flex sc:flex-col sc:gap-xs\">\n <strong className=\"sc:text-lg sc:text-text-primary\">Clip Mask</strong>\n <small className=\"sc:text-base sc:text-text-secondary\">Hard edge clipping (text, shapes)</small>\n </div>\n </button>\n <button\n onClick={() => handleAddMask('alpha')}\n className=\"sc:flex sc:cursor-pointer sc:items-center sc:gap-xl sc:rounded-lg sc:border sc:border-border-light sc:bg-bg-secondary sc:p-lg sc:text-left sc:text-text-primary sc:transition-all sc:duration-medium sc:hover:border-accent sc:hover:bg-bg-hover\"\n >\n <span className=\"sc:shrink-0 sc:text-xl\">🌫️</span>\n <div className=\"sc:flex sc:flex-col sc:gap-xs\">\n <strong className=\"sc:text-lg sc:text-text-primary\">Alpha Mask</strong>\n <small className=\"sc:text-base sc:text-text-secondary\">Transparency-based masking</small>\n </div>\n </button>\n <button\n onClick={() => handleAddMask('luma')}\n className=\"sc:flex sc:cursor-pointer sc:items-center sc:gap-xl sc:rounded-lg sc:border sc:border-border-light sc:bg-bg-secondary sc:p-lg sc:text-left sc:text-text-primary sc:transition-all sc:duration-medium sc:hover:border-accent sc:hover:bg-bg-hover\"\n >\n <span className=\"sc:shrink-0 sc:text-xl\">🌗</span>\n <div className=\"sc:flex sc:flex-col sc:gap-xs\">\n <strong className=\"sc:text-lg sc:text-text-primary\">Luma Mask</strong>\n <small className=\"sc:text-base sc:text-text-secondary\">Luminosity-based masking</small>\n </div>\n </button>\n <button\n onClick={() => handleAddMask('distress')}\n className=\"sc:flex sc:cursor-pointer sc:items-center sc:gap-xl sc:rounded-lg sc:border sc:border-border-light sc:bg-bg-secondary sc:p-lg sc:text-left sc:text-text-primary sc:transition-all sc:duration-medium sc:hover:border-accent sc:hover:bg-bg-hover\"\n >\n <span className=\"sc:shrink-0 sc:text-xl\">⚡</span>\n <div className=\"sc:flex sc:flex-col sc:gap-xs\">\n <strong className=\"sc:text-lg sc:text-text-primary\">Distress Mask</strong>\n <small className=\"sc:text-base sc:text-text-secondary\">Vintage/grunge texture effects</small>\n </div>\n </button>\n </div>\n )}\n\n {/* Mask list */}\n {isExpanded && masks.length > 0 && (\n <div className=\"sc:mt-xl sc:flex sc:flex-col sc:gap-md\">\n {masks.map((mask, index) => (\n <MaskItem\n key={mask.id}\n mask={mask}\n index={index}\n isFirst={index === 0}\n isLast={index === masks.length - 1}\n onUpdate={(updates) => handleUpdateMask(mask.id, updates)}\n onRemove={() => handleRemoveMask(mask.id)}\n onReorder={(direction) => handleReorderMask(mask.id, direction)}\n />\n ))}\n </div>\n )}\n\n {masks.length === 0 && (\n <div className=\"sc:p-3xl sc:text-center sc:text-text-secondary\">\n <p className=\"sc:mb-xs sc:text-lg\">No masks applied</p>\n <small className=\"sc:text-base sc:text-border-medium\">Click \"+ Add\" to add a mask</small>\n </div>\n )}\n </div>\n );\n};\n\n/**\n * Create default mask element based on mask type\n */\nfunction createDefaultMaskElement(type: MaskType): AnyElementConfig {\n switch (type) {\n case 'clip':\n return {\n transformType: 'custom',\n text: 'MASK',\n fontSize: 80,\n fontFamily: 'Anton',\n x: 0,\n y: 0,\n rotation: 0,\n color: '#000000',\n };\n\n case 'alpha':\n case 'luma':\n return {\n transformType: 'custom',\n text: 'FADE',\n fontSize: 100,\n fontFamily: 'Arial',\n x: 0,\n y: 0,\n rotation: 0,\n color: '#888888',\n };\n\n case 'distress':\n // Placeholder - will be replaced with actual texture\n return {\n transformType: 'image',\n imageUrl: '', // Will be set when texture is loaded\n x: 0,\n y: 0,\n rotation: 0,\n transformData: {\n type: 'image',\n width: 500,\n height: 500,\n cropX: 0,\n cropY: 0,\n cropWidth: 1,\n cropHeight: 1,\n flipHorizontal: false,\n flipVertical: false,\n borderRadius: 0,\n },\n };\n\n default:\n return {\n transformType: 'custom',\n text: 'MASK',\n fontSize: 80,\n x: 0,\n y: 0,\n rotation: 0,\n color: '#000000',\n };\n }\n}\n\nexport default MasksPanel;\n","/**\n * Distress Effect Presets\n * Predefined configurations for common distress styles\n */\n\nimport type { DistressEffect } from '../types/index.js';\n\n/**\n * Vintage worn preset - Light edge wear\n */\nexport const WORN_LIGHT: DistressEffect = {\n enabled: true,\n style: 'worn',\n intensity: 30,\n fadeAmount: 20,\n grainAmount: 15,\n edgeWear: 40,\n seed: undefined, // Will be generated when applied\n};\n\n/**\n * Vintage worn preset - Medium wear\n */\nexport const WORN_MEDIUM: DistressEffect = {\n enabled: true,\n style: 'worn',\n intensity: 55,\n fadeAmount: 35,\n grainAmount: 25,\n edgeWear: 60,\n seed: undefined,\n};\n\n/**\n * Vintage worn preset - Heavy wear\n */\nexport const WORN_HEAVY: DistressEffect = {\n enabled: true,\n style: 'worn',\n intensity: 75,\n fadeAmount: 50,\n grainAmount: 40,\n edgeWear: 80,\n seed: undefined,\n};\n\n/**\n * Cracked preset - Light cracks\n */\nexport const CRACKED_LIGHT: DistressEffect = {\n enabled: true,\n style: 'cracked',\n intensity: 25,\n scratchAmount: 30,\n edgeWear: 20,\n seed: undefined,\n};\n\n/**\n * Cracked preset - Medium cracks\n */\nexport const CRACKED_MEDIUM: DistressEffect = {\n enabled: true,\n style: 'cracked',\n intensity: 50,\n scratchAmount: 50,\n edgeWear: 35,\n seed: undefined,\n};\n\n/**\n * Cracked preset - Heavy cracks\n */\nexport const CRACKED_HEAVY: DistressEffect = {\n enabled: true,\n style: 'cracked',\n intensity: 80,\n scratchAmount: 75,\n edgeWear: 60,\n seed: undefined,\n};\n\n/**\n * Grunge preset - Light texture\n */\nexport const GRUNGE_LIGHT: DistressEffect = {\n enabled: true,\n style: 'grunge',\n intensity: 35,\n grainAmount: 40,\n fadeAmount: 25,\n seed: undefined,\n};\n\n/**\n * Grunge preset - Medium texture\n */\nexport const GRUNGE_MEDIUM: DistressEffect = {\n enabled: true,\n style: 'grunge',\n intensity: 60,\n grainAmount: 60,\n fadeAmount: 40,\n seed: undefined,\n};\n\n/**\n * Grunge preset - Heavy texture\n */\nexport const GRUNGE_HEAVY: DistressEffect = {\n enabled: true,\n style: 'grunge',\n intensity: 85,\n grainAmount: 80,\n fadeAmount: 55,\n seed: undefined,\n};\n\n/**\n * Retro preset - Light halftone\n */\nexport const RETRO_LIGHT: DistressEffect = {\n enabled: true,\n style: 'retro',\n intensity: 30,\n fadeAmount: 30,\n grainAmount: 25,\n seed: undefined,\n};\n\n/**\n * Retro preset - Medium halftone\n */\nexport const RETRO_MEDIUM: DistressEffect = {\n enabled: true,\n style: 'retro',\n intensity: 55,\n fadeAmount: 45,\n grainAmount: 40,\n seed: undefined,\n};\n\n/**\n * Retro preset - Heavy halftone\n */\nexport const RETRO_HEAVY: DistressEffect = {\n enabled: true,\n style: 'retro',\n intensity: 75,\n fadeAmount: 60,\n grainAmount: 55,\n seed: undefined,\n};\n\n/**\n * All presets organized by category\n */\nexport const DISTRESS_PRESETS = {\n worn: {\n light: WORN_LIGHT,\n medium: WORN_MEDIUM,\n heavy: WORN_HEAVY,\n },\n cracked: {\n light: CRACKED_LIGHT,\n medium: CRACKED_MEDIUM,\n heavy: CRACKED_HEAVY,\n },\n grunge: {\n light: GRUNGE_LIGHT,\n medium: GRUNGE_MEDIUM,\n heavy: GRUNGE_HEAVY,\n },\n retro: {\n light: RETRO_LIGHT,\n medium: RETRO_MEDIUM,\n heavy: RETRO_HEAVY,\n },\n};\n\n/**\n * Get preset by name\n */\nexport function getPreset(\n category: 'worn' | 'cracked' | 'grunge' | 'retro',\n level: 'light' | 'medium' | 'heavy'\n): DistressEffect {\n const preset = { ...DISTRESS_PRESETS[category][level] };\n // Generate unique seed for this application\n preset.seed = Date.now();\n return preset;\n}\n\n/**\n * Get all preset names\n */\nexport function getAllPresetNames(): Array<{ category: string; level: string; name: string }> {\n return [\n { category: 'worn', level: 'light', name: 'Vintage Worn (Light)' },\n { category: 'worn', level: 'medium', name: 'Vintage Worn (Medium)' },\n { category: 'worn', level: 'heavy', name: 'Vintage Worn (Heavy)' },\n { category: 'cracked', level: 'light', name: 'Cracked (Light)' },\n { category: 'cracked', level: 'medium', name: 'Cracked (Medium)' },\n { category: 'cracked', level: 'heavy', name: 'Cracked (Heavy)' },\n { category: 'grunge', level: 'light', name: 'Grunge (Light)' },\n { category: 'grunge', level: 'medium', name: 'Grunge (Medium)' },\n { category: 'grunge', level: 'heavy', name: 'Grunge (Heavy)' },\n { category: 'retro', level: 'light', name: 'Retro Halftone (Light)' },\n { category: 'retro', level: 'medium', name: 'Retro Halftone (Medium)' },\n { category: 'retro', level: 'heavy', name: 'Retro Halftone (Heavy)' },\n ];\n}\n","/**\n * DistressPanel - UI component for distress effects\n * HIGH PRIORITY for t-shirt design\n *\n * Provides quick access to vintage/grunge effects:\n * - Worn, Cracked, Grunge, Retro presets\n * - Intensity and parameter controls\n * - Preview functionality\n *\n * Migrated to Shadcn/Radix:\n * - Disclosure (Collapsible)\n * - RadioGroup\n * - Slider\n * - Button\n */\n\nimport type React from 'react';\nimport { useState } from 'react';\nimport type { DistressEffect, DistressStyle } from '../types/index.js';\nimport { getPreset } from '../effects/distress-presets.js';\nimport { DISTRESS_TEXTURE_PRESETS, ensureBuiltinTexturesReady } from '../effects/distress-textures.js';\nimport { preloadTexture } from '../effects/DistressTextureCache.js';\nimport {\n Disclosure,\n DisclosureTrigger,\n DisclosureContent,\n Switch,\n Label,\n RadioGroup,\n Radio,\n Slider,\n Button,\n Fieldset,\n} from './ui';\n\ninterface DistressPanelProps {\n distressEffect?: DistressEffect;\n onChange: (distress: DistressEffect | undefined) => void;\n}\n\nconst DistressPanel = ({ distressEffect, onChange }: DistressPanelProps) => {\n ensureBuiltinTexturesReady();\n const [showPresets, setShowPresets] = useState(false);\n\n const enabled = distressEffect?.enabled || false;\n const style = distressEffect?.style || 'worn';\n const intensity = distressEffect?.intensity || 50;\n const fadeAmount = distressEffect?.fadeAmount || 30;\n const grainAmount = distressEffect?.grainAmount || 30;\n const scratchAmount = distressEffect?.scratchAmount || 30;\n const edgeWear = distressEffect?.edgeWear || 40;\n\n const handleEnabledChange = (newEnabled: boolean) => {\n if (newEnabled) {\n onChange({\n enabled: true,\n style,\n intensity,\n fadeAmount,\n grainAmount,\n scratchAmount,\n edgeWear,\n seed: Date.now(),\n });\n } else {\n onChange(undefined);\n }\n };\n\n const handlePresetApply = (category: string, level: string) => {\n const preset = getPreset(category as 'worn' | 'cracked' | 'grunge' | 'retro', level as 'light' | 'medium' | 'heavy');\n onChange(preset);\n setShowPresets(false);\n };\n\n const handleStyleChange = (newStyle: string) => {\n if (!distressEffect) return;\n if (newStyle === 'custom') {\n // Switch to custom texture mode\n const url = distressEffect.textureUrl || DISTRESS_TEXTURE_PRESETS[0].textureUrl;\n preloadTexture(url);\n onChange({\n ...distressEffect,\n style: 'custom',\n textureUrl: url,\n textureOpacity: distressEffect.textureOpacity ?? (distressEffect.intensity / 100),\n });\n } else {\n onChange({\n ...distressEffect,\n style: newStyle as DistressStyle,\n seed: Date.now(), // New seed for new style\n });\n }\n };\n\n const handleTextureSelect = (url: string) => {\n if (!distressEffect) return;\n preloadTexture(url);\n onChange({\n ...distressEffect,\n style: 'custom',\n textureUrl: url,\n });\n };\n\n const handleTextureOpacityChange = (value: number[]) => {\n if (!distressEffect) return;\n onChange({\n ...distressEffect,\n textureOpacity: value[0] / 100,\n });\n };\n\n const handleIntensityChange = (value: number[]) => {\n if (!distressEffect) return;\n onChange({\n ...distressEffect,\n intensity: value[0],\n });\n };\n\n const handleFadeChange = (value: number[]) => {\n if (!distressEffect) return;\n onChange({\n ...distressEffect,\n fadeAmount: value[0],\n });\n };\n\n const handleGrainChange = (value: number[]) => {\n if (!distressEffect) return;\n onChange({\n ...distressEffect,\n grainAmount: value[0],\n });\n };\n\n const handleScratchChange = (value: number[]) => {\n if (!distressEffect) return;\n onChange({\n ...distressEffect,\n scratchAmount: value[0],\n });\n };\n\n const handleEdgeWearChange = (value: number[]) => {\n if (!distressEffect) return;\n onChange({\n ...distressEffect,\n edgeWear: value[0],\n });\n };\n\n const handleRandomize = () => {\n if (!distressEffect) return;\n onChange({\n ...distressEffect,\n seed: Date.now(), // Generate new random seed\n });\n };\n\n return (\n <Disclosure open={enabled} onOpenChange={handleEnabledChange} className=\"sc:mb-xl\">\n <DisclosureTrigger className=\"sc:w-full\">\n <div className=\"sc:flex sc:items-center sc:justify-between sc:w-full\">\n <Label>Distress Effect</Label>\n <Switch\n checked={enabled}\n onCheckedChange={handleEnabledChange}\n onClick={(e: React.MouseEvent) => e.stopPropagation()}\n />\n </div>\n </DisclosureTrigger>\n <DisclosureContent className=\"sc:flex sc:flex-col sc:gap-lg sc:pt-lg\">\n {/* Presets Button */}\n <div className=\"sc:flex sc:gap-sm\">\n <Button\n variant=\"outline\"\n onClick={() => setShowPresets(!showPresets)}\n className=\"sc:flex-1\"\n >\n Presets\n </Button>\n </div>\n\n {/* Presets menu */}\n {showPresets && (\n <div className=\"sc:flex sc:flex-col sc:gap-xl sc:rounded-md sc:border sc:border-border-primary sc:bg-bg-tertiary sc:p-xl\">\n {/* Vintage Worn */}\n <div>\n <h4 className=\"sc:mb-sm sc:text-base sc:font-semibold sc:text-accent-primary\">Vintage Worn</h4>\n <div className=\"sc:flex sc:gap-sm\">\n <Button\n variant=\"outline\"\n onClick={() => handlePresetApply('worn', 'light')}\n className=\"sc:flex-1\"\n >\n Light\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => handlePresetApply('worn', 'medium')}\n className=\"sc:flex-1\"\n >\n Medium\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => handlePresetApply('worn', 'heavy')}\n className=\"sc:flex-1\"\n >\n Heavy\n </Button>\n </div>\n </div>\n\n {/* Cracked */}\n <div>\n <h4 className=\"sc:mb-sm sc:text-base sc:font-semibold sc:text-accent-primary\">Cracked</h4>\n <div className=\"sc:flex sc:gap-sm\">\n <Button\n variant=\"outline\"\n onClick={() => handlePresetApply('cracked', 'light')}\n className=\"sc:flex-1\"\n >\n Light\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => handlePresetApply('cracked', 'medium')}\n className=\"sc:flex-1\"\n >\n Medium\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => handlePresetApply('cracked', 'heavy')}\n className=\"sc:flex-1\"\n >\n Heavy\n </Button>\n </div>\n </div>\n\n {/* Grunge */}\n <div>\n <h4 className=\"sc:mb-sm sc:text-base sc:font-semibold sc:text-accent-primary\">Grunge</h4>\n <div className=\"sc:flex sc:gap-sm\">\n <Button\n variant=\"outline\"\n onClick={() => handlePresetApply('grunge', 'light')}\n className=\"sc:flex-1\"\n >\n Light\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => handlePresetApply('grunge', 'medium')}\n className=\"sc:flex-1\"\n >\n Medium\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => handlePresetApply('grunge', 'heavy')}\n className=\"sc:flex-1\"\n >\n Heavy\n </Button>\n </div>\n </div>\n\n {/* Retro Halftone */}\n <div>\n <h4 className=\"sc:mb-sm sc:text-base sc:font-semibold sc:text-accent-primary\">Retro Halftone</h4>\n <div className=\"sc:flex sc:gap-sm\">\n <Button\n variant=\"outline\"\n onClick={() => handlePresetApply('retro', 'light')}\n className=\"sc:flex-1\"\n >\n Light\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => handlePresetApply('retro', 'medium')}\n className=\"sc:flex-1\"\n >\n Medium\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => handlePresetApply('retro', 'heavy')}\n className=\"sc:flex-1\"\n >\n Heavy\n </Button>\n </div>\n </div>\n </div>\n )}\n\n {/* Style selector */}\n <Fieldset className=\"sc:flex sc:flex-col sc:gap-sm\">\n <Label>Style</Label>\n <RadioGroup value={style} onValueChange={handleStyleChange} orientation=\"horizontal\">\n <Radio value=\"worn\" id=\"style-worn\">Worn</Radio>\n <Radio value=\"cracked\" id=\"style-cracked\">Cracked</Radio>\n <Radio value=\"grunge\" id=\"style-grunge\">Grunge</Radio>\n <Radio value=\"retro\" id=\"style-retro\">Retro</Radio>\n <Radio value=\"custom\" id=\"style-custom\">Custom</Radio>\n </RadioGroup>\n </Fieldset>\n\n {/* Custom texture controls */}\n {style === 'custom' && (\n <>\n {/* Texture grid */}\n <div className=\"sc:grid sc:grid-cols-4 sc:gap-1.5\">\n {DISTRESS_TEXTURE_PRESETS.map((preset) => (\n <button\n key={preset.id}\n onClick={() => handleTextureSelect(preset.textureUrl)}\n className={`sc:aspect-square sc:rounded-md sc:border-2 sc:overflow-hidden sc:transition-colors ${\n distressEffect?.textureUrl === preset.textureUrl\n ? 'sc:border-accent-primary'\n : 'sc:border-border-primary sc:hover:border-border-focus'\n }`}\n title={preset.name}\n >\n <img\n src={preset.thumbnailUrl}\n alt={preset.name}\n className=\"sc:w-full sc:h-full sc:object-cover\"\n loading=\"lazy\"\n />\n </button>\n ))}\n </div>\n\n {/* Texture opacity slider */}\n <div className=\"sc:flex sc:flex-col sc:gap-sm\">\n <Label>Texture Opacity</Label>\n <Slider\n value={[Math.round((distressEffect?.textureOpacity ?? 0.5) * 100)]}\n onValueChange={handleTextureOpacityChange}\n min={0}\n max={100}\n step={1}\n />\n <div className=\"sc:text-sm sc:text-text-muted\">{Math.round((distressEffect?.textureOpacity ?? 0.5) * 100)}%</div>\n </div>\n </>\n )}\n\n {/* Intensity slider (for procedural styles) */}\n {style !== 'custom' && (\n <div className=\"sc:flex sc:flex-col sc:gap-sm\">\n <Label>Intensity</Label>\n <Slider\n value={[intensity]}\n onValueChange={handleIntensityChange}\n min={0}\n max={100}\n step={1}\n />\n <div className=\"sc:text-sm sc:text-text-muted\">{intensity}%</div>\n </div>\n )}\n\n {/* Style-specific parameters */}\n {(style === 'worn' || style === 'grunge' || style === 'retro') && (\n <div className=\"sc:flex sc:flex-col sc:gap-sm\">\n <Label>Fade</Label>\n <Slider\n value={[fadeAmount]}\n onValueChange={handleFadeChange}\n min={0}\n max={100}\n step={1}\n />\n <div className=\"sc:text-sm sc:text-text-muted\">{fadeAmount}%</div>\n </div>\n )}\n\n {(style === 'worn' || style === 'grunge' || style === 'retro') && (\n <div className=\"sc:flex sc:flex-col sc:gap-sm\">\n <Label>Grain</Label>\n <Slider\n value={[grainAmount]}\n onValueChange={handleGrainChange}\n min={0}\n max={100}\n step={1}\n />\n <div className=\"sc:text-sm sc:text-text-muted\">{grainAmount}%</div>\n </div>\n )}\n\n {style === 'cracked' && (\n <div className=\"sc:flex sc:flex-col sc:gap-sm\">\n <Label>Scratches</Label>\n <Slider\n value={[scratchAmount]}\n onValueChange={handleScratchChange}\n min={0}\n max={100}\n step={1}\n />\n <div className=\"sc:text-sm sc:text-text-muted\">{scratchAmount}%</div>\n </div>\n )}\n\n {(style === 'worn' || style === 'cracked') && (\n <div className=\"sc:flex sc:flex-col sc:gap-sm\">\n <Label>Edge Wear</Label>\n <Slider\n value={[edgeWear]}\n onValueChange={handleEdgeWearChange}\n min={0}\n max={100}\n step={1}\n />\n <div className=\"sc:text-sm sc:text-text-muted\">{edgeWear}%</div>\n </div>\n )}\n\n {/* Randomize button */}\n <Button variant=\"outline\" onClick={handleRandomize} className=\"sc:w-full\">\n Randomize Pattern\n </Button>\n </DisclosureContent>\n </Disclosure>\n );\n};\n\nexport default DistressPanel;\n","/**\n * Compositing utility functions\n * Helper functions for knockout/clip compositing and scope management\n */\n\nimport type { BlendMode, CompositingConfig, CompositingScope, KnockoutConfig, KnockoutScope } from '../types/index.js';\n\n/**\n * Create default knockout configuration\n */\nexport function createKnockoutConfig(\n fill: boolean = false,\n stroke: boolean = false,\n scope: CompositingScope = 'group'\n): CompositingConfig {\n return { fill, stroke, scope };\n}\n\n/**\n * Create stroke knockout configuration (common for t-shirt design)\n */\nexport function createStrokeKnockout(scope: CompositingScope = 'artboard'): CompositingConfig {\n return {\n fill: false,\n stroke: true,\n scope,\n };\n}\n\n/**\n * Create fill knockout configuration\n */\nexport function createFillKnockout(scope: CompositingScope = 'artboard'): CompositingConfig {\n return {\n fill: true,\n stroke: false,\n scope,\n };\n}\n\n/**\n * Check if element has any compositing parts enabled (knockout or clip)\n */\nexport function hasCompositing(config: CompositingConfig | undefined): boolean {\n if (!config) return false;\n return !!(config.fill || config.stroke);\n}\n\n/** @deprecated Use hasCompositing instead */\nexport function hasKnockout(config: KnockoutConfig | undefined): boolean {\n return hasCompositing(config);\n}\n\n/**\n * Get compositing parts as array of strings\n */\nexport function getCompositingParts(config: CompositingConfig | undefined): string[] {\n if (!config) return [];\n\n const parts: string[] = [];\n if (config.fill) parts.push('fill');\n if (config.stroke) parts.push('stroke');\n return parts;\n}\n\n/** @deprecated Use getCompositingParts instead */\nexport function getKnockoutParts(config: KnockoutConfig | undefined): string[] {\n return getCompositingParts(config);\n}\n\n/**\n * Get human-readable description of compositing configuration\n */\nexport function describeCompositing(config: CompositingConfig | undefined, mode: BlendMode = 'knockout'): string {\n if (!config || !hasCompositing(config)) {\n return 'No compositing';\n }\n\n const parts = getCompositingParts(config);\n const scope = config.scope || 'group';\n const modeLabel = mode === 'clip' ? 'clip' : 'knockout';\n\n if (parts.length === 0) return 'No compositing';\n if (parts.length === 1) return `${parts[0]} ${modeLabel} (${scope})`;\n\n const partsList = parts.slice(0, -1).join(', ') + ' & ' + parts[parts.length - 1];\n return `${partsList} ${modeLabel} (${scope})`;\n}\n\n/** @deprecated Use describeCompositing instead */\nexport function describeKnockout(config: KnockoutConfig | undefined): string {\n if (!config || !hasCompositing(config)) {\n return 'No knockout';\n }\n return describeCompositing(config, 'knockout');\n}\n\n/**\n * Validate knockout configuration\n */\nexport function isValidKnockoutConfig(config: KnockoutConfig): boolean {\n // At least one part must be enabled\n if (!config.fill && !config.stroke) {\n return false;\n }\n\n // Scope must be valid\n if (config.scope && !['group', 'artboard'].includes(config.scope)) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Merge knockout configurations\n * Combines two configs, with priority given to the second one\n */\nexport function mergeKnockoutConfigs(\n config1: KnockoutConfig | undefined,\n config2: KnockoutConfig | undefined\n): KnockoutConfig | undefined {\n if (!config1) return config2;\n if (!config2) return config1;\n\n return {\n fill: config2.fill !== undefined ? config2.fill : config1.fill,\n stroke: config2.stroke !== undefined ? config2.stroke : config1.stroke,\n scope: config2.scope || config1.scope,\n };\n}\n\n/**\n * Clone knockout configuration\n */\nexport function cloneKnockoutConfig(config: KnockoutConfig): KnockoutConfig {\n return { ...config };\n}\n\n/**\n * Get recommended scope for use case\n */\nexport function getRecommendedScope(useCase: 'tshirt' | 'pillow' | 'pattern' | 'general'): KnockoutScope {\n switch (useCase) {\n case 'tshirt':\n return 'artboard'; // Cut through to transparent background\n case 'pillow':\n case 'pattern':\n return 'group'; // Knockout within design group only\n default:\n return 'group'; // Default to safer group scope\n }\n}\n","/**\n * CompositingPanel - UI component for knockout compositing\n * Controls blend mode and knockout configuration with dual scope support\n *\n * Migrated to Shadcn/Radix:\n * - Disclosure (Collapsible)\n * - RadioGroup\n * - Fieldset\n */\n\nimport { useState } from 'react';\nimport type { BlendMode, KnockoutConfig, KnockoutScope } from '../types/index.js';\nimport { describeKnockout } from '../rendering/knockout-utils.js';\nimport {\n Disclosure,\n DisclosureTrigger,\n DisclosureContent,\n Label,\n RadioGroup,\n Radio,\n Fieldset,\n} from './ui';\n\ninterface CompositingPanelProps {\n blendMode?: BlendMode;\n knockoutParts?: KnockoutConfig;\n onChange: (blendMode: BlendMode, knockoutParts?: KnockoutConfig) => void;\n}\n\nconst CompositingPanel = ({ blendMode = 'normal', knockoutParts, onChange }: CompositingPanelProps) => {\n const isCompositing = blendMode === 'knockout' || blendMode === 'clip';\n const [isExpanded, setIsExpanded] = useState(isCompositing);\n\n const isKnockout = isCompositing;\n const fillEnabled = knockoutParts?.fill || false;\n const strokeEnabled = knockoutParts?.stroke || false;\n const scope: KnockoutScope = knockoutParts?.scope || 'group';\n\n const handleBlendModeChange = (newMode: string) => {\n if (newMode === 'knockout' || newMode === 'clip') {\n // Enable stroke knockout by default (common for t-shirt design)\n onChange(newMode as BlendMode, {\n fill: false,\n stroke: true,\n scope: 'artboard',\n });\n setIsExpanded(true);\n } else {\n onChange(newMode as BlendMode, undefined);\n setIsExpanded(false);\n }\n };\n\n const handleFillToggle = () => {\n if (!knockoutParts) return;\n onChange(blendMode, {\n ...knockoutParts,\n fill: !fillEnabled,\n });\n };\n\n const handleStrokeToggle = () => {\n if (!knockoutParts) return;\n onChange(blendMode, {\n ...knockoutParts,\n stroke: !strokeEnabled,\n });\n };\n\n const handleScopeChange = (newScope: KnockoutScope) => {\n if (!knockoutParts) return;\n onChange(blendMode, {\n ...knockoutParts,\n scope: newScope,\n });\n };\n\n return (\n <Disclosure\n open={isExpanded}\n onOpenChange={isKnockout ? setIsExpanded : undefined}\n className=\"sc:mb-xl\"\n >\n <DisclosureTrigger className=\"sc:w-full\">\n <div className=\"sc:flex sc:items-center sc:justify-between sc:w-full\">\n <Label>Compositing</Label>\n </div>\n </DisclosureTrigger>\n <DisclosureContent className=\"sc:flex sc:flex-col sc:gap-lg sc:pt-lg\">\n {/* Blend Mode Selector */}\n <Fieldset className=\"sc:flex sc:flex-col sc:gap-sm\">\n <Label>Blend Mode</Label>\n <RadioGroup value={blendMode} onValueChange={handleBlendModeChange} orientation=\"horizontal\">\n <Radio value=\"normal\" id=\"blend-normal\">Normal</Radio>\n <Radio value=\"knockout\" id=\"blend-knockout\">Knockout</Radio>\n </RadioGroup>\n </Fieldset>\n\n {/* Knockout Configuration (shown when knockout mode is active) */}\n {isKnockout && (\n <>\n {/* Knockout Parts */}\n <div className=\"sc:flex sc:flex-col sc:gap-md sc:border-t sc:border-border-primary sc:pt-2xl\">\n <label className=\"sc:text-base sc:font-medium sc:text-text-secondary\">Knockout Parts</label>\n <div className=\"sc:flex sc:flex-col sc:gap-sm\">\n <label\n className=\"sc:flex sc:cursor-pointer sc:flex-col sc:gap-xs sc:rounded-lg sc:border sc:border-border-primary sc:bg-bg-tertiary sc:p-md sc:transition-all sc:duration-fast sc:hover:border-border-secondary sc:hover:bg-bg-hover\"\n onClick={handleFillToggle}\n >\n <span className=\"sc:flex sc:items-center sc:gap-sm sc:text-lg sc:font-semibold sc:text-text-primary\">\n <span className=\"sc:text-base\">{fillEnabled ? '☑' : '☐'}</span>\n Fill\n </span>\n <small className=\"sc:ml-[22px] sc:text-sm sc:text-text-muted\">Element content cuts through</small>\n </label>\n <label\n className=\"sc:flex sc:cursor-pointer sc:flex-col sc:gap-xs sc:rounded-lg sc:border sc:border-border-primary sc:bg-bg-tertiary sc:p-md sc:transition-all sc:duration-fast sc:hover:border-border-secondary sc:hover:bg-bg-hover\"\n onClick={handleStrokeToggle}\n >\n <span className=\"sc:flex sc:items-center sc:gap-sm sc:text-lg sc:font-semibold sc:text-text-primary\">\n <span className=\"sc:text-base\">{strokeEnabled ? '☑' : '☐'}</span>\n Stroke\n </span>\n <small className=\"sc:ml-[22px] sc:text-sm sc:text-text-muted\">Border/outline cuts through</small>\n </label>\n </div>\n </div>\n\n {/* Knockout Scope */}\n <Fieldset className=\"sc:flex sc:flex-col sc:gap-sm\">\n <Label>Knockout Scope</Label>\n <div className=\"sc:flex sc:gap-sm\">\n <button\n className={`sc:flex sc:flex-1 sc:cursor-pointer sc:flex-col sc:gap-xs sc:rounded-lg sc:border sc:p-lg sc:text-center sc:transition-all sc:duration-fast ${\n scope === 'group'\n ? 'sc:border-accent-primary sc:bg-accent-primary'\n : 'sc:border-border-primary sc:bg-bg-tertiary sc:hover:border-border-secondary sc:hover:bg-bg-hover'\n }`}\n onClick={() => handleScopeChange('group')}\n title=\"Knockout within group siblings only\"\n >\n <strong className={`sc:text-lg ${scope === 'group' ? 'sc:text-text-on-accent' : 'sc:text-text-primary'}`}>\n Group\n </strong>\n <small className={`sc:text-sm ${scope === 'group' ? 'sc:text-bg-tertiary' : 'sc:text-text-muted'}`}>\n Affects siblings in group\n </small>\n </button>\n <button\n className={`sc:flex sc:flex-1 sc:cursor-pointer sc:flex-col sc:gap-xs sc:rounded-lg sc:border sc:p-lg sc:text-center sc:transition-all sc:duration-fast ${\n scope === 'artboard'\n ? 'sc:border-accent-primary sc:bg-accent-primary'\n : 'sc:border-border-primary sc:bg-bg-tertiary sc:hover:border-border-secondary sc:hover:bg-bg-hover'\n }`}\n onClick={() => handleScopeChange('artboard')}\n title=\"Knockout cuts through to artboard\"\n >\n <strong className={`sc:text-lg ${scope === 'artboard' ? 'sc:text-text-on-accent' : 'sc:text-text-primary'}`}>\n Artboard\n </strong>\n <small className={`sc:text-sm ${scope === 'artboard' ? 'sc:text-bg-tertiary' : 'sc:text-text-muted'}`}>\n Cuts to transparent background\n </small>\n </button>\n </div>\n </Fieldset>\n\n {/* Description/Preview */}\n <div className=\"sc:flex sc:flex-col sc:gap-md\">\n <div className=\"sc:rounded-lg sc:border sc:border-border-primary sc:bg-bg-tertiary sc:p-md\">\n <strong className=\"sc:text-sm sc:font-semibold sc:uppercase sc:tracking-wide sc:text-accent-primary\">Effect:</strong>\n <p className=\"sc:mt-xs sc:text-base sc:text-text-secondary\">{describeKnockout(knockoutParts)}</p>\n </div>\n <div className=\"sc:rounded-sm sc:border-l-2 sc:border-accent-primary sc:bg-accent-light sc:p-md\">\n <small className=\"sc:text-sm sc:leading-relaxed sc:text-text-secondary\">\n <strong>Tip:</strong> Use <em className=\"sc:font-semibold sc:not-italic sc:text-accent-primary\">Artboard</em>{' '}\n scope for t-shirt designs, <em className=\"sc:font-semibold sc:not-italic sc:text-accent-primary\">Group</em> scope\n for pillow patterns.\n </small>\n </div>\n </div>\n </>\n )}\n </DisclosureContent>\n </Disclosure>\n );\n};\n\nexport default CompositingPanel;\n","/**\n * GlyphPicker - Interactive picker for selecting glyph alternates\n *\n * Displays available alternates for a selected character with visual previews\n */\n\nimport { useState, useEffect } from 'react';\nimport { Drawer, DrawerContent, DrawerTitle } from '../vendor/ui';\nimport { Icon } from '@iconify/react';\nimport { Button, icons, SparklesIcon } from './ui';\nimport { FontAnalyzer } from '../utils/FontAnalyzer';\nimport { createLogger } from '../utils/logger.js';\nimport type { GlyphAlternate, GlyphOverride } from '../types';\n\nconst logger = createLogger('GlyphPicker');\n\ninterface GlyphPickerProps {\n isOpen: boolean;\n onClose: () => void;\n character: string;\n charIndex: number;\n fontFamily: string;\n fontWeight?: number;\n currentGlyphOverride?: GlyphOverride;\n onSelectGlyph: (override: GlyphOverride | null) => void;\n browseAll?: boolean; // If true, show all glyphs in the font instead of just alternates for the character\n}\n\nexport const GlyphPicker = ({\n isOpen,\n onClose,\n character,\n charIndex,\n fontFamily,\n fontWeight = 400,\n currentGlyphOverride,\n onSelectGlyph,\n browseAll = false,\n}: GlyphPickerProps) => {\n const [alternates, setAlternates] = useState<GlyphAlternate[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Load glyph alternates when picker opens\n useEffect(() => {\n if (!isOpen) return;\n\n setLoading(true);\n setError(null);\n\n const loadPromise = browseAll\n ? FontAnalyzer.getAllGlyphs(fontFamily, fontWeight)\n : FontAnalyzer.getGlyphAlternates(fontFamily, character, fontWeight);\n\n loadPromise\n .then((alts) => {\n if (alts.length === 0) {\n setError(\n browseAll ? `No glyphs found in ${fontFamily}` : `No alternates found for \"${character}\" in ${fontFamily}`\n );\n }\n setAlternates(alts);\n setLoading(false);\n })\n .catch((err) => {\n logger.error('Error loading glyphs:', err);\n setError(browseAll ? 'Failed to load glyphs' : 'Failed to load glyph alternates');\n setLoading(false);\n });\n }, [isOpen, character, fontFamily, fontWeight, browseAll]);\n\n const handleSelectGlyph = (alternate: GlyphAlternate) => {\n // In browse all mode, always apply the selection\n // In character-specific mode, check if it's the default glyph\n if (!browseAll && alternate.category === 'default') {\n onSelectGlyph(null);\n } else {\n onSelectGlyph({\n charIndex,\n glyphIndex: alternate.glyphIndex,\n unicode: browseAll ? alternate.unicode : character,\n alternateName: alternate.name,\n });\n }\n onClose();\n };\n\n const title = browseAll ? `Browse All Glyphs - ${fontFamily}` : `Alternates for \"${character}\"`;\n\n return (\n <Drawer open={isOpen} onOpenChange={(open) => !open && onClose()}>\n <DrawerContent\n placement=\"bottom\"\n overlayOpacity={0.4}\n className=\"sc:flex sc:flex-col sc:overflow-hidden\"\n style={{ zIndex: 'var(--sc-z-drawer, 10002)' }}\n >\n {/* Accessibility title (sr-only) */}\n <DrawerTitle className=\"sc:sr-only\">{title}</DrawerTitle>\n\n {/* Header */}\n <div className=\"sc:flex sc:items-center sc:justify-between sc:px-3xl sc:pb-2xl sc:pt-2xl sc:border-b sc:border-border-primary\">\n <div className=\"sc:flex sc:items-center sc:gap-md\">\n <SparklesIcon size={24} className=\"sc:text-text-muted\" />\n <h3 className=\"sc:m-0 sc:text-3xl sc:font-semibold sc:text-text-primary\">\n {title}\n </h3>\n </div>\n <Button size=\"icon\" variant=\"ghost\" onClick={onClose}>\n <Icon icon={icons.close} className=\"sc:size-6\" />\n </Button>\n </div>\n\n {/* Content */}\n <div className=\"sc:flex-1 sc:overflow-y-auto sc:p-3xl\">\n {loading && (\n <div className=\"sc:text-center sc:py-6xl sc:text-text-muted\">Loading alternates...</div>\n )}\n\n {error && (\n <div className=\"sc:text-center sc:px-3xl sc:py-6xl sc:text-text-placeholder sc:text-xl\">\n {error}\n <div className=\"sc:mt-md sc:text-base\">\n This font may not have alternate glyphs for this character.\n </div>\n </div>\n )}\n\n {!loading && !error && alternates.length > 0 && (\n <div className=\"sc:grid sc:grid-cols-[repeat(auto-fill,minmax(100px,1fr))] sc:gap-xl\">\n {alternates.map((alternate, index) => {\n const isSelected = currentGlyphOverride?.glyphIndex === alternate.glyphIndex;\n const isDefault = alternate.category === 'default';\n\n return (\n <button\n key={`${alternate.glyphIndex}-${index}`}\n onClick={() => handleSelectGlyph(alternate)}\n className={`\n sc:flex sc:flex-col sc:items-center sc:gap-md sc:p-xl sc:rounded-xl\n sc:border-2 sc:cursor-pointer sc:transition-all sc:duration-base\n sc:relative sc:aspect-square\n ${isSelected\n ? 'sc:bg-accent-primary sc:border-accent-primary'\n : 'sc:bg-bg-secondary sc:border-border-primary sc:hover:bg-accent-light sc:hover:border-accent-primary'\n }\n `.trim()}\n >\n {/* Preview */}\n {alternate.previewDataUrl ? (\n <img\n src={alternate.previewDataUrl}\n alt={alternate.name}\n className={`sc:w-full sc:h-auto sc:object-contain ${isSelected ? 'sc:brightness-0 sc:invert' : ''}`}\n />\n ) : (\n <div\n style={{ fontFamily }}\n className={`sc:text-[48px] sc:leading-none ${isSelected ? 'sc:text-text-on-dark' : 'sc:text-text-primary'}`}\n >\n {character}\n </div>\n )}\n\n {/* Label */}\n <div\n className={`\n sc:text-sm sc:text-center sc:whitespace-nowrap sc:overflow-hidden\n sc:text-ellipsis sc:w-full\n ${isSelected ? 'sc:text-text-on-dark' : 'sc:text-text-muted'}\n `.trim()}\n >\n {isDefault ? 'Default' : alternate.name}\n </div>\n\n {/* Badge for default */}\n {isDefault && (\n <div className=\"sc:absolute sc:top-xs sc:right-xs sc:bg-accent-primary sc:text-text-on-dark sc:text-xs sc:px-sm sc:py-[2px] sc:rounded-md sc:font-semibold\">\n DEFAULT\n </div>\n )}\n </button>\n );\n })}\n </div>\n )}\n </div>\n\n {/* Footer */}\n {!loading && !error && alternates.length > 1 && (\n <div className=\"sc:px-3xl sc:py-xl sc:border-t sc:border-border-primary sc:bg-bg-secondary sc:text-base sc:text-text-muted sc:text-center\">\n {alternates.length} alternate{alternates.length !== 1 ? 's' : ''} available\n </div>\n )}\n </DrawerContent>\n </Drawer>\n );\n};\n","/**\n * OpenTypeFeaturesPanel - UI component for configuring OpenType features\n * Provides toggles for advanced typography features\n *\n * Migrated to use Tailwind utilities:\n * - Removed all inline styles (100+ style objects)\n * - Pure Tailwind classes with design tokens\n * - Button primitive for Browse All Glyphs button\n * - Maintains exact visual appearance\n */\n\nimport { useState, useEffect } from 'react';\nimport { Icon } from '@iconify/react';\nimport type { OpenTypeFeatures, GlyphOverride } from '../types/index.js';\nimport { FontAnalyzer } from '../utils/FontAnalyzer';\nimport { createLogger } from '../utils/logger.js';\n\nconst logger = createLogger('OpenTypeFeaturesPanel');\nimport { GlyphPicker } from './GlyphPicker';\nimport { Button, icons, SparklesIcon } from './ui';\n\ninterface OpenTypeFeaturesPanelProps {\n fontFamily?: string;\n fontWeight?: number;\n text?: string;\n openTypeFeatures?: OpenTypeFeatures;\n glyphOverrides?: GlyphOverride[];\n onChange: (features: OpenTypeFeatures | undefined) => void;\n onGlyphOverridesChange?: (overrides: GlyphOverride[] | undefined) => void;\n}\n\ninterface FeatureDefinition {\n tag: keyof OpenTypeFeatures;\n label: string;\n description: string;\n category: 'ligatures' | 'alternates' | 'numerals' | 'stylistic';\n}\n\nconst FEATURE_DEFINITIONS: FeatureDefinition[] = [\n // Ligatures\n { tag: 'liga', label: 'Standard Ligatures', description: 'Common ligatures like fi, fl', category: 'ligatures' },\n {\n tag: 'dlig',\n label: 'Discretionary Ligatures',\n description: 'Optional ligatures like ct, st',\n category: 'ligatures',\n },\n {\n tag: 'calt',\n label: 'Contextual Alternates',\n description: 'Context-aware glyph substitution',\n category: 'ligatures',\n },\n { tag: 'ccmp', label: 'Glyph Composition', description: 'Combine glyphs for proper display', category: 'ligatures' },\n { tag: 'locl', label: 'Localized Forms', description: 'Language-specific glyph variants', category: 'ligatures' },\n\n // Alternates\n { tag: 'swsh', label: 'Swashes', description: 'Decorative flourishes', category: 'alternates' },\n { tag: 'smcp', label: 'Small Caps', description: 'Small capital letters', category: 'alternates' },\n { tag: 'c2sc', label: 'Caps to Small Caps', description: 'Convert caps to small caps', category: 'alternates' },\n\n // Numerals\n { tag: 'onum', label: 'Old Style Figures', description: 'Varying height numbers', category: 'numerals' },\n { tag: 'lnum', label: 'Lining Figures', description: 'Uniform height numbers', category: 'numerals' },\n { tag: 'tnum', label: 'Tabular Figures', description: 'Fixed-width numbers', category: 'numerals' },\n { tag: 'pnum', label: 'Proportional Figures', description: 'Variable-width numbers', category: 'numerals' },\n\n // Stylistic Sets (first 10)\n { tag: 'ss01', label: 'Stylistic Set 1', description: 'Alternate character set 1', category: 'stylistic' },\n { tag: 'ss02', label: 'Stylistic Set 2', description: 'Alternate character set 2', category: 'stylistic' },\n { tag: 'ss03', label: 'Stylistic Set 3', description: 'Alternate character set 3', category: 'stylistic' },\n { tag: 'ss04', label: 'Stylistic Set 4', description: 'Alternate character set 4', category: 'stylistic' },\n { tag: 'ss05', label: 'Stylistic Set 5', description: 'Alternate character set 5', category: 'stylistic' },\n { tag: 'ss06', label: 'Stylistic Set 6', description: 'Alternate character set 6', category: 'stylistic' },\n { tag: 'ss07', label: 'Stylistic Set 7', description: 'Alternate character set 7', category: 'stylistic' },\n { tag: 'ss08', label: 'Stylistic Set 8', description: 'Alternate character set 8', category: 'stylistic' },\n { tag: 'ss09', label: 'Stylistic Set 9', description: 'Alternate character set 9', category: 'stylistic' },\n { tag: 'ss10', label: 'Stylistic Set 10', description: 'Alternate character set 10', category: 'stylistic' },\n\n // Technical features (usually enabled by default)\n { tag: 'kern', label: 'Kerning', description: 'Adjust spacing between letter pairs', category: 'stylistic' },\n { tag: 'mark', label: 'Mark Positioning', description: 'Position diacritical marks', category: 'stylistic' },\n { tag: 'mkmk', label: 'Mark to Mark', description: 'Position marks relative to marks', category: 'stylistic' },\n];\n\nconst OpenTypeFeaturesPanel = ({\n fontFamily,\n fontWeight = 400,\n text = '',\n openTypeFeatures,\n glyphOverrides = [],\n onChange,\n onGlyphOverridesChange,\n}: OpenTypeFeaturesPanelProps) => {\n const [isExpanded, setIsExpanded] = useState(false);\n const [availableFeatures, setAvailableFeatures] = useState<string[]>([]);\n const [loading, setLoading] = useState(false);\n\n // Glyph picker state\n const [glyphPickerOpen, setGlyphPickerOpen] = useState(false);\n const [selectedCharIndex, setSelectedCharIndex] = useState(0);\n const [selectedCharacter, setSelectedCharacter] = useState('');\n const [browseAllMode, setBrowseAllMode] = useState(false);\n\n // Alternate counts for each unique character\n const [alternateCounts, setAlternateCounts] = useState<Map<string, number>>(new Map());\n const [_loadingCounts, setLoadingCounts] = useState(false);\n\n // Load available features when font changes\n useEffect(() => {\n if (!fontFamily) {\n setAvailableFeatures([]);\n return;\n }\n\n setLoading(true);\n FontAnalyzer.getAvailableFeatures(fontFamily, fontWeight)\n .then((features) => {\n setAvailableFeatures(features);\n setLoading(false);\n })\n .catch((err) => {\n logger.error('Error loading OpenType features:', err);\n setAvailableFeatures([]);\n setLoading(false);\n });\n }, [fontFamily, fontWeight]);\n\n // Load alternate counts for each unique character when text or font changes\n useEffect(() => {\n if (!text || !fontFamily) {\n setAlternateCounts(new Map());\n return;\n }\n\n // Get unique characters (excluding spaces)\n const uniqueChars = Array.from(new Set(Array.from(text).filter((c) => c !== ' ')));\n\n if (uniqueChars.length === 0) {\n setAlternateCounts(new Map());\n return;\n }\n\n setLoadingCounts(true);\n\n const loadCounts = async () => {\n const counts = new Map<string, number>();\n\n for (const char of uniqueChars) {\n try {\n const alternates = await FontAnalyzer.getGlyphAlternates(fontFamily, char, fontWeight);\n // Count only the alternates (exclude the default glyph which is first)\n const alternateCount = alternates.length > 0 ? alternates.length - 1 : 0;\n counts.set(char, alternateCount);\n } catch (err) {\n logger.error(`Error loading alternates for '${char}':`, err);\n counts.set(char, 0);\n }\n }\n\n setAlternateCounts(counts);\n setLoadingCounts(false);\n };\n\n loadCounts();\n }, [text, fontFamily, fontWeight]);\n\n const handleToggleFeature = (tag: keyof OpenTypeFeatures) => {\n const currentValue = openTypeFeatures?.[tag];\n const newFeatures = {\n ...openTypeFeatures,\n [tag]: !currentValue,\n };\n\n // Remove undefined/false values to keep the object clean\n Object.keys(newFeatures).forEach((key) => {\n if (!newFeatures[key as keyof OpenTypeFeatures]) {\n delete newFeatures[key as keyof OpenTypeFeatures];\n }\n });\n\n // Pre-load font to ensure it's in cache for rendering\n if (fontFamily && Object.keys(newFeatures).length > 0) {\n FontAnalyzer.loadFont(fontFamily, fontWeight).catch((err) => {\n logger.error('Error pre-loading font for OpenType features:', err);\n });\n }\n\n // If no features enabled, set to undefined\n onChange(Object.keys(newFeatures).length > 0 ? newFeatures : undefined);\n };\n\n const handleCharacterClick = (charIndex: number, character: string) => {\n setSelectedCharIndex(charIndex);\n setSelectedCharacter(character);\n setBrowseAllMode(false);\n setGlyphPickerOpen(true);\n };\n\n const handleBrowseAllGlyphs = () => {\n setBrowseAllMode(true);\n setGlyphPickerOpen(true);\n };\n\n const handleSelectGlyph = (override: GlyphOverride | null) => {\n if (!onGlyphOverridesChange) return;\n\n let newOverrides = [...glyphOverrides];\n\n if (override) {\n // Add or update the override\n const existingIndex = newOverrides.findIndex((o) => o.charIndex === override.charIndex);\n if (existingIndex >= 0) {\n newOverrides[existingIndex] = override;\n } else {\n newOverrides.push(override);\n }\n } else {\n // Remove override\n newOverrides = newOverrides.filter((o) => o.charIndex !== selectedCharIndex);\n }\n\n onGlyphOverridesChange(newOverrides.length > 0 ? newOverrides : undefined);\n };\n\n const enabledCount = openTypeFeatures ? Object.values(openTypeFeatures).filter(Boolean).length : 0;\n\n const glyphOverridesCount = glyphOverrides.length;\n\n // Group features by category\n const groupedFeatures = FEATURE_DEFINITIONS.reduce(\n (acc, feature) => {\n if (!acc[feature.category]) {\n acc[feature.category] = [];\n }\n acc[feature.category].push(feature);\n return acc;\n },\n {} as Record<string, FeatureDefinition[]>\n );\n\n return (\n <div className=\"sc:mb-lg\">\n {/* Header */}\n <div\n className={`sc:flex sc:cursor-pointer sc:items-center sc:justify-between sc:py-sm ${isExpanded ? 'sc:border-b sc:border-border-light' : ''}`}\n onClick={() => setIsExpanded(!isExpanded)}\n >\n <div className=\"sc:flex sc:items-center sc:gap-sm\">\n <Icon icon={icons.text} className=\"sc:size-4\" />\n <span className=\"sc:text-xl sc:font-medium\">OpenType Features</span>\n {enabledCount > 0 && (\n <span className=\"sc:rounded-full sc:bg-info sc:px-xs sc:py-[2px] sc:text-2xs sc:font-semibold sc:text-info-foreground\">\n {enabledCount}\n </span>\n )}\n </div>\n <button className=\"sc:cursor-pointer sc:border-none sc:bg-transparent sc:p-xs sc:text-xs\">\n {isExpanded ? '▼' : '▶'}\n </button>\n </div>\n\n {/* Content */}\n {isExpanded && (\n <div className=\"sc:py-md\">\n {loading && <div className=\"sc:py-md sc:text-sm sc:text-text-secondary\">Loading available features...</div>}\n\n {!loading && !fontFamily && (\n <div className=\"sc:py-md sc:text-sm sc:text-text-muted\">Select a text element to configure OpenType features</div>\n )}\n\n {!loading && fontFamily && availableFeatures.length === 0 && (\n <div className=\"sc:py-md sc:text-sm sc:text-text-muted\">\n This font does not support advanced OpenType features\n </div>\n )}\n\n {/* Glyph Picker Section */}\n {!loading && fontFamily && text && onGlyphOverridesChange && (\n <div className=\"sc:mb-lg sc:border-b sc:border-border-light sc:pb-lg\">\n <div className=\"sc:mb-md sc:flex sc:items-center sc:justify-between\">\n <div className=\"sc:flex sc:items-center sc:gap-sm\">\n <SparklesIcon size={14} className=\"sc:text-text-secondary\" />\n <span className=\"sc:text-2xs sc:font-semibold sc:uppercase sc:tracking-wide sc:text-text-secondary\">\n Alternate Glyphs\n </span>\n {glyphOverridesCount > 0 && (\n <span className=\"sc:rounded-full sc:bg-success sc:px-xs sc:py-[2px] sc:text-3xs sc:font-semibold sc:text-success-foreground\">\n {glyphOverridesCount}\n </span>\n )}\n </div>\n </div>\n\n <div className=\"sc:mb-md sc:flex sc:items-center sc:justify-between sc:gap-md\">\n <div className=\"sc:text-xs sc:text-text-secondary\">Click on any letter to pick alternate glyphs</div>\n <Button\n variant=\"ghost\"\n onClick={handleBrowseAllGlyphs}\n className=\"sc:whitespace-nowrap sc:rounded-md sc:border sc:border-info sc:bg-info/10 sc:px-md sc:py-xs sc:text-xs sc:font-medium sc:text-info sc:transition-all sc:duration-fast sc:hover:bg-info sc:hover:text-info-foreground\"\n >\n Browse All Glyphs\n </Button>\n </div>\n\n {/* Text preview with clickable characters */}\n <div className=\"sc:flex sc:flex-wrap sc:gap-xs sc:rounded-md sc:border sc:border-border-medium sc:bg-surface sc:p-md\">\n {Array.from(text).map((char, index) => {\n const hasOverride = glyphOverrides.some((o) => o.charIndex === index);\n const isSpace = char === ' ';\n\n if (isSpace) {\n return <div key={index} className=\"sc:h-4xl sc:w-sm\" />;\n }\n\n const alternateCount = alternateCounts.get(char) || 0;\n\n return (\n <button\n key={index}\n onClick={() => handleCharacterClick(index, char)}\n className={`\n sc:relative sc:min-w-4xl sc:cursor-pointer sc:rounded sc:border-2 sc:p-xs sc:text-center sc:text-3xl sc:transition-all sc:duration-fast\n ${\n hasOverride\n ? 'sc:border-success sc:bg-success sc:text-success-foreground'\n : 'sc:border-border-medium sc:bg-surface sc:text-text-primary sc:hover:border-info sc:hover:bg-info/10'\n }\n `}\n style={{ fontFamily }}\n >\n {char}\n {/* Show count badge if there are alternates */}\n {alternateCount > 0 && (\n <span\n className={`sc:absolute sc:-right-[4px] sc:-top-[4px] sc:min-w-lg sc:rounded-full sc:border-2 sc:border-surface sc:px-xs sc:py-[2px] sc:text-center sc:text-3xs sc:font-semibold sc:leading-none sc:shadow-md ${\n hasOverride ? 'sc:bg-success sc:text-success-foreground' : 'sc:bg-info sc:text-info-foreground'\n }`}\n >\n {alternateCount}\n </span>\n )}\n </button>\n );\n })}\n </div>\n </div>\n )}\n\n {!loading && fontFamily && availableFeatures.length > 0 && (\n <>\n {Object.entries(groupedFeatures).map(([category, features]) => {\n // Filter to only show features available in the font\n const availableFeaturesInCategory = features.filter((f) => availableFeatures.includes(f.tag));\n\n if (availableFeaturesInCategory.length === 0) return null;\n\n return (\n <div key={category} className=\"sc:mb-lg\">\n <div className=\"sc:mb-sm sc:text-2xs sc:font-semibold sc:uppercase sc:tracking-wide sc:text-text-secondary\">\n {category}\n </div>\n <div className=\"sc:flex sc:flex-col sc:gap-sm\">\n {availableFeaturesInCategory.map((feature) => (\n <label\n key={feature.tag}\n className=\"sc:flex sc:cursor-pointer sc:items-start sc:gap-sm sc:rounded sc:p-xs sc:transition-colors sc:duration-fast sc:hover:bg-surface\"\n >\n <input\n type=\"checkbox\"\n checked={openTypeFeatures?.[feature.tag] || false}\n onChange={() => handleToggleFeature(feature.tag)}\n className=\"sc:mt-[2px] 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 <div className=\"sc:flex-1\">\n <div className=\"sc:text-sm sc:font-medium\">{feature.label}</div>\n <div className=\"sc:mt-[2px] sc:text-2xs sc:text-text-secondary\">{feature.description}</div>\n </div>\n </label>\n ))}\n </div>\n </div>\n );\n })}\n </>\n )}\n </div>\n )}\n\n {/* Glyph Picker Drawer */}\n {fontFamily && (\n <GlyphPicker\n isOpen={glyphPickerOpen}\n onClose={() => setGlyphPickerOpen(false)}\n character={selectedCharacter}\n charIndex={selectedCharIndex}\n fontFamily={fontFamily}\n fontWeight={fontWeight}\n currentGlyphOverride={glyphOverrides.find((o) => o.charIndex === selectedCharIndex)}\n onSelectGlyph={handleSelectGlyph}\n browseAll={browseAllMode}\n />\n )}\n </div>\n );\n};\n\nexport default OpenTypeFeaturesPanel;\n","/**\n * @deprecated Use the contextual toolbar secondary panels instead.\n * Stroke, masks, distress, and knockout are now available as inline secondary panels\n * in the contextual toolbar (see SecondaryPanels.tsx).\n *\n * EffectsPanel - Unified panel for all layer effects\n * Provides tabbed interface for Stroke, Masks, Distress, and Compositing (Knockout)\n */\n\nimport { useState } from 'react';\nimport { Icon } from '@iconify/react';\nimport { Tabs, Badge, icons } from './ui';\nimport { Drawer } from './Drawer';\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport { GroupElement } from '../core/GroupElement.js';\nimport { ShapeElement } from '../core/ShapeElement.js';\nimport { PathElement } from '../core/PathElement.js';\nimport StrokePanel from './StrokePanel.js';\nimport MasksPanel from './MasksPanel.js';\nimport DistressPanel from './DistressPanel.js';\nimport CompositingPanel from './CompositingPanel.js';\nimport OpenTypeFeaturesPanel from './OpenTypeFeaturesPanel.js';\n\ntype EditorElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\ninterface EffectsPanelProps {\n element: EditorElement | null;\n onElementUpdate: (element: EditorElement) => void;\n isOpen: boolean;\n onClose: () => void;\n}\n\ntype EffectTab = 'stroke' | 'masks' | 'distress' | 'compositing' | 'typography';\n\nconst EffectsPanel = ({ element, onElementUpdate, isOpen, onClose }: EffectsPanelProps) => {\n const [activeTab, setActiveTab] = useState<EffectTab>('stroke');\n\n if (!element) return null;\n\n // Check if element has any effects enabled\n const hasStroke = element.stroke?.enabled;\n const hasMasks = element.masks && element.masks.length > 0;\n const hasDistress = element.distressEffect?.enabled;\n const hasKnockout = element.blendMode === 'knockout' || element.blendMode === 'clip';\n\n // Check if element is a text element with typography features\n const isTextElement = element instanceof TextElement;\n const hasTypography =\n isTextElement &&\n ((element.glyphOverrides && element.glyphOverrides.length > 0) ||\n (element.openTypeFeatures && Object.keys(element.openTypeFeatures).length > 0));\n\n return (\n <Drawer\n isOpen={isOpen}\n onClose={onClose}\n title=\"Layer Effects\"\n description=\"Configure stroke, masks, distress, and knockout effects\"\n maxHeight=\"90vh\"\n placement=\"right\"\n maxWidth=\"600px\"\n >\n <Tabs\n value={activeTab}\n onValueChange={(value) => setActiveTab(value as EffectTab)}\n className=\"sc:flex sc:flex-col sc:h-full\"\n >\n <Tabs.ListContainer className=\"sc:px-6\">\n <Tabs.List aria-label=\"Effect types\">\n <Tabs.Tab id=\"stroke\">\n <Icon icon={icons.minus} className=\"sc:size-4\" />\n Stroke\n {hasStroke && <Badge variant=\"default\" className=\"sc:ml-2 sc:size-2 sc:rounded-full sc:p-0\" />}\n <Tabs.Indicator />\n </Tabs.Tab>\n <Tabs.Tab id=\"masks\">\n <Icon icon={icons.group} className=\"sc:size-4\" />\n Masks\n {hasMasks && <Badge variant=\"default\" className=\"sc:ml-2 sc:size-2 sc:rounded-full sc:p-0\" />}\n <Tabs.Indicator />\n </Tabs.Tab>\n <Tabs.Tab id=\"distress\">\n <Icon icon=\"lucide:cloud\" className=\"sc:size-4\" />\n Distress\n {hasDistress && <Badge variant=\"default\" className=\"sc:ml-2 sc:size-2 sc:rounded-full sc:p-0\" />}\n <Tabs.Indicator />\n </Tabs.Tab>\n <Tabs.Tab id=\"compositing\">\n <Icon icon={icons.scissors} className=\"sc:size-4\" />\n Knockout\n {hasKnockout && <Badge variant=\"default\" className=\"sc:ml-2 sc:size-2 sc:rounded-full sc:p-0\" />}\n <Tabs.Indicator />\n </Tabs.Tab>\n {isTextElement && (\n <Tabs.Tab id=\"typography\">\n <Icon icon={icons.text} className=\"sc:size-4\" />\n Typography\n {hasTypography && <Badge variant=\"default\" className=\"sc:ml-2 sc:size-2 sc:rounded-full sc:p-0\" />}\n <Tabs.Indicator />\n </Tabs.Tab>\n )}\n </Tabs.List>\n </Tabs.ListContainer>\n\n {/* Tab Panels */}\n <Tabs.Panel id=\"stroke\" className=\"sc:flex-1 sc:overflow-y-auto sc:p-6\">\n <StrokePanel\n stroke={element.stroke}\n onChange={(stroke) => {\n const updated = element.clone();\n updated.stroke = stroke;\n onElementUpdate(updated);\n }}\n />\n <div className=\"sc:mt-6 sc:pt-4 sc:border-t sc:border-black/10 sc:dark:border-white/10\">\n <div className=\"sc:flex sc:items-start sc:gap-2 sc:text-xs sc:text-muted\">\n <Icon icon=\"lucide:lightbulb\" className=\"sc:size-4 sc:shrink-0 sc:mt-0.5\" />\n <span>Add outlines to text and shapes</span>\n </div>\n </div>\n </Tabs.Panel>\n\n <Tabs.Panel id=\"masks\" className=\"sc:flex-1 sc:overflow-y-auto sc:p-6\">\n <MasksPanel\n masks={element.masks}\n onChange={(masks) => {\n const updated = element.clone();\n updated.masks = masks;\n onElementUpdate(updated);\n }}\n />\n <div className=\"sc:mt-6 sc:pt-4 sc:border-t sc:border-black/10 sc:dark:border-white/10\">\n <div className=\"sc:flex sc:items-start sc:gap-2 sc:text-xs sc:text-muted\">\n <Icon icon=\"lucide:lightbulb\" className=\"sc:size-4 sc:shrink-0 sc:mt-0.5\" />\n <span>Use masks to control visibility</span>\n </div>\n </div>\n </Tabs.Panel>\n\n <Tabs.Panel id=\"distress\" className=\"sc:flex-1 sc:overflow-y-auto sc:p-6\">\n <DistressPanel\n distressEffect={element.distressEffect}\n onChange={(distressEffect) => {\n const updated = element.clone();\n updated.distressEffect = distressEffect;\n onElementUpdate(updated);\n }}\n />\n <div className=\"sc:mt-6 sc:pt-4 sc:border-t sc:border-black/10 sc:dark:border-white/10\">\n <div className=\"sc:flex sc:items-start sc:gap-2 sc:text-xs sc:text-muted\">\n <Icon icon=\"lucide:lightbulb\" className=\"sc:size-4 sc:shrink-0 sc:mt-0.5\" />\n <span>Add vintage/grunge effects for t-shirt designs</span>\n </div>\n </div>\n </Tabs.Panel>\n\n <Tabs.Panel id=\"compositing\" className=\"sc:flex-1 sc:overflow-y-auto sc:p-6\">\n <CompositingPanel\n blendMode={element.blendMode}\n knockoutParts={element.knockoutParts}\n onChange={(blendMode, knockoutParts) => {\n const updated = element.clone();\n updated.blendMode = blendMode;\n updated.knockoutParts = knockoutParts;\n onElementUpdate(updated);\n }}\n />\n <div className=\"sc:mt-6 sc:pt-4 sc:border-t sc:border-black/10 sc:dark:border-white/10\">\n <div className=\"sc:flex sc:items-start sc:gap-2 sc:text-xs sc:text-muted\">\n <Icon icon=\"lucide:lightbulb\" className=\"sc:size-4 sc:shrink-0 sc:mt-0.5\" />\n <span>Use Artboard scope for t-shirts, Group scope for patterns</span>\n </div>\n </div>\n </Tabs.Panel>\n\n {isTextElement && (\n <Tabs.Panel id=\"typography\" className=\"sc:flex-1 sc:overflow-y-auto sc:p-6\">\n <OpenTypeFeaturesPanel\n fontFamily={element.fontFamily}\n fontWeight={element.bold ? 700 : 400}\n text={element.text}\n openTypeFeatures={element.openTypeFeatures}\n glyphOverrides={element.glyphOverrides}\n onChange={(features) => {\n const updated = element.clone();\n updated.openTypeFeatures = features;\n onElementUpdate(updated);\n }}\n onGlyphOverridesChange={(overrides) => {\n const updated = element.clone();\n updated.glyphOverrides = overrides;\n onElementUpdate(updated);\n }}\n />\n <div className=\"sc:mt-6 sc:pt-4 sc:border-t sc:border-black/10 sc:dark:border-white/10\">\n <div className=\"sc:flex sc:items-start sc:gap-2 sc:text-xs sc:text-muted\">\n <Icon icon=\"lucide:lightbulb\" className=\"sc:size-4 sc:shrink-0 sc:mt-0.5\" />\n <span>Enable advanced OpenType features like ligatures and stylistic sets</span>\n </div>\n </div>\n </Tabs.Panel>\n )}\n </Tabs>\n </Drawer>\n );\n};\n\nexport default EffectsPanel;\n","/**\n * @deprecated Use the contextual toolbar secondary panels instead.\n * Stroke, masks, distress, and knockout are now available as inline secondary panels\n * in the contextual toolbar (see SecondaryPanels.tsx).\n *\n * EffectsPanel - Standalone effects panel component for embedding\n * Provides tabbed interface for Stroke, Masks, Distress, Knockout, and Typography\n *\n * This is a wrapper around the main EffectsPanel that integrates with EditorContext\n */\n\nimport React, { useCallback } from 'react';\nimport { useEditor } from '../../contexts/EditorContext.js';\nimport EffectsPanelCore from '../EffectsPanel.js';\nimport { TextElement } from '../../core/TextElement.js';\nimport { ImageElement } from '../../core/ImageElement.js';\nimport { GroupElement } from '../../core/GroupElement.js';\nimport { ShapeElement } from '../../core/ShapeElement.js';\nimport { PathElement } from '../../core/PathElement.js';\n\ntype EditorElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\nexport interface EffectsPanelProps {\n /**\n * Whether the panel is open\n */\n isOpen: boolean;\n\n /**\n * Callback when panel should close\n */\n onClose: () => void;\n\n /**\n * Element to edit effects for\n * If not provided, uses the selected element from context\n */\n element?: EditorElement | null;\n\n /**\n * Callback when element is updated\n * If not provided, uses the command history from context\n */\n onElementUpdate?: (element: EditorElement) => void;\n\n /**\n * Custom CSS styles\n */\n style?: React.CSSProperties;\n\n /**\n * Custom class name\n */\n className?: string;\n}\n\n/**\n * Standalone effects panel component\n *\n * @example Using with context (automatic)\n * ```tsx\n * function App() {\n * const [isOpen, setIsOpen] = useState(false);\n *\n * return (\n * <EditorProvider>\n * <Canvas />\n * <button onClick={() => setIsOpen(true)}>Edit Effects</button>\n * <EffectsPanel\n * isOpen={isOpen}\n * onClose={() => setIsOpen(false)}\n * />\n * </EditorProvider>\n * );\n * }\n * ```\n *\n * @example Using with custom element (manual)\n * ```tsx\n * function App() {\n * const [element, setElement] = useState(myElement);\n *\n * return (\n * <EditorProvider>\n * <EffectsPanel\n * isOpen={true}\n * onClose={() => {}}\n * element={element}\n * onElementUpdate={setElement}\n * />\n * </EditorProvider>\n * );\n * }\n * ```\n */\nexport const EffectsPanel: React.FC<EffectsPanelProps> = ({\n isOpen,\n onClose,\n element: externalElement,\n onElementUpdate: externalOnElementUpdate,\n style,\n className,\n}) => {\n const { selectedElement, executeElementUpdate } = useEditor();\n\n // Use external element if provided, otherwise use selected element from context\n const element = externalElement !== undefined ? externalElement : (selectedElement || null);\n\n // Use external update handler if provided, otherwise use command history\n const handleElementUpdate = useCallback(\n (updatedElement: EditorElement) => {\n if (externalOnElementUpdate) {\n externalOnElementUpdate(updatedElement);\n } else if (element) {\n executeElementUpdate(element, updatedElement);\n }\n },\n [externalOnElementUpdate, executeElementUpdate, element]\n );\n\n return (\n <div style={style} className={className}>\n <EffectsPanelCore\n element={element}\n onElementUpdate={handleElementUpdate}\n isOpen={isOpen}\n onClose={onClose}\n />\n </div>\n );\n};\n","/**\n * ExportPanel - Standalone export controls component\n * Uses useExport hook for export operations\n */\n\nimport React, { useState } from 'react';\nimport { useExport } from '../../hooks/useExport.js';\nimport { useArtboards } from '../../hooks/useArtboards.js';\nimport { Icon } from '@iconify/react';\nimport { icons } from '../ui';\nimport { createLogger } from '../../utils/logger.js';\n\nconst logger = createLogger('ExportPanel');\n\nexport interface ExportPanelProps {\n /**\n * Show continuous export controls\n */\n showContinuousExport?: boolean;\n\n /**\n * Default export scale\n */\n defaultScale?: number;\n\n /**\n * Default export format\n */\n defaultFormat?: 'png' | 'jpg';\n\n /**\n * Callback when export is complete\n */\n onExportComplete?: (exports: Record<string, string>) => void;\n\n /**\n * Custom CSS styles\n */\n style?: React.CSSProperties;\n\n /**\n * Custom class name\n */\n className?: string;\n}\n\n/**\n * Standalone export panel component\n *\n * @example\n * ```tsx\n * function EditorWithExport() {\n * return (\n * <EditorProvider>\n * <div style={{ display: 'flex' }}>\n * <Canvas />\n * <ExportPanel\n * showContinuousExport={true}\n * defaultScale={3}\n * onExportComplete={(exports) => {\n * console.log('Exports:', exports);\n * }}\n * />\n * </div>\n * </EditorProvider>\n * );\n * }\n * ```\n */\nexport const ExportPanel: React.FC<ExportPanelProps> = ({\n showContinuousExport = true,\n defaultScale = 3,\n defaultFormat = 'png',\n onExportComplete,\n style = {},\n className = '',\n}) => {\n const {\n exportArtboard,\n exportAllArtboards,\n startContinuousExport,\n stopContinuousExport,\n isExporting,\n isContinuousExporting,\n progress,\n supportsWorkerExport,\n workerStats,\n } = useExport();\n\n const { artboardsInfo, activeArtboardId } = useArtboards();\n\n const [scale, setScale] = useState(defaultScale);\n const [format, setFormat] = useState<'png' | 'jpg'>(defaultFormat);\n const [interval, setInterval] = useState(500);\n\n // Export active artboard\n const handleExportActive = async () => {\n if (!activeArtboardId) return;\n\n try {\n const dataUrl = await exportArtboard(activeArtboardId, { scale, format });\n\n // Download the image with timestamp to avoid overwriting\n const link = document.createElement('a');\n const artboard = artboardsInfo.find((a) => a.id === activeArtboardId);\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);\n const baseName = artboard?.name || 'artboard';\n link.download = `${baseName}-${timestamp}.${format}`;\n link.href = dataUrl;\n link.click();\n\n if (onExportComplete) {\n onExportComplete({ [activeArtboardId]: dataUrl });\n }\n } catch (error) {\n logger.error('Export failed:', error);\n alert('Export failed. Please try again.');\n }\n };\n\n // Export all artboards\n const handleExportAll = async () => {\n try {\n const exports = await exportAllArtboards({ scale, format });\n\n // Download all images with timestamp to avoid overwriting\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);\n Object.entries(exports).forEach(([artboardId, dataUrl]) => {\n const artboard = artboardsInfo.find((a) => a.id === artboardId);\n const baseName = artboard?.name || artboardId;\n const link = document.createElement('a');\n link.download = `${baseName}-${timestamp}.${format}`;\n link.href = dataUrl;\n link.click();\n });\n\n if (onExportComplete) {\n onExportComplete(exports);\n }\n } catch (error) {\n logger.error('Export all failed:', error);\n alert('Export failed. Please try again.');\n }\n };\n\n // Toggle continuous export\n const handleToggleContinuous = () => {\n if (isContinuousExporting) {\n stopContinuousExport();\n } else {\n startContinuousExport({\n interval,\n scale,\n format,\n onChange: (exports) => {\n if (onExportComplete) {\n onExportComplete(exports);\n }\n },\n });\n }\n };\n\n return (\n <div\n className={`sc-export-panel ${className}`}\n style={{\n width: 300,\n padding: 16,\n backgroundColor: 'var(--bg-primary)',\n border: '1px solid var(--border-primary)',\n borderRadius: 8,\n ...style,\n }}\n >\n {/* Header */}\n <h3\n style={{\n margin: '0 0 16px 0',\n fontSize: 16,\n fontWeight: 600,\n }}\n >\n Export Settings\n </h3>\n\n {/* Export settings */}\n <div style={{ marginBottom: 16 }}>\n {/* Scale */}\n <label\n htmlFor=\"export-scale\"\n style={{\n display: 'block',\n fontSize: 13,\n marginBottom: 4,\n fontWeight: 500,\n }}\n >\n Scale\n </label>\n <select\n id=\"export-scale\"\n value={scale}\n onChange={(e) => setScale(Number(e.target.value))}\n style={{\n width: '100%',\n padding: 8,\n fontSize: 13,\n border: '1px solid var(--border-primary)',\n borderRadius: 4,\n marginBottom: 12,\n }}\n >\n <option value={1}>1x (Original)</option>\n <option value={2}>2x (Retina)</option>\n <option value={3}>3x (High-Res)</option>\n <option value={4}>4x (Ultra)</option>\n </select>\n\n {/* Format */}\n <label\n htmlFor=\"export-format\"\n style={{\n display: 'block',\n fontSize: 13,\n marginBottom: 4,\n fontWeight: 500,\n }}\n >\n Format\n </label>\n <select\n id=\"export-format\"\n value={format}\n onChange={(e) => setFormat(e.target.value as 'png' | 'jpg')}\n style={{\n width: '100%',\n padding: 8,\n fontSize: 13,\n border: '1px solid var(--border-primary)',\n borderRadius: 4,\n marginBottom: 12,\n }}\n >\n <option value=\"png\">PNG (Transparent)</option>\n <option value=\"jpg\">JPEG (Smaller)</option>\n </select>\n\n {/* Interval (for continuous export) */}\n {showContinuousExport && (\n <>\n <label\n htmlFor=\"export-interval\"\n style={{\n display: 'block',\n fontSize: 13,\n marginBottom: 4,\n fontWeight: 500,\n }}\n >\n Continuous Export Interval\n </label>\n <select\n id=\"export-interval\"\n value={interval}\n onChange={(e) => setInterval(Number(e.target.value))}\n style={{\n width: '100%',\n padding: 8,\n fontSize: 13,\n border: '1px solid var(--border-primary)',\n borderRadius: 4,\n marginBottom: 12,\n }}\n >\n <option value={250}>250ms (Fast)</option>\n <option value={500}>500ms (Recommended)</option>\n <option value={1000}>1s (Slow)</option>\n <option value={2000}>2s (Very Slow)</option>\n </select>\n </>\n )}\n </div>\n\n {/* Export buttons */}\n <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 16 }}>\n <button\n onClick={handleExportActive}\n disabled={isExporting || !activeArtboardId}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 8,\n padding: 12,\n backgroundColor: 'var(--accent-primary)',\n color: 'var(--primary-foreground)',\n border: 'none',\n borderRadius: 6,\n cursor: isExporting || !activeArtboardId ? 'not-allowed' : 'pointer',\n fontSize: 14,\n fontWeight: 500,\n opacity: isExporting || !activeArtboardId ? 0.5 : 1,\n }}\n >\n <Icon icon={icons.download} className=\"sc:size-4\" />\n {isExporting ? 'Exporting...' : 'Export Active Artboard'}\n </button>\n\n <button\n onClick={handleExportAll}\n disabled={isExporting || artboardsInfo.length === 0}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 8,\n padding: 12,\n backgroundColor: 'var(--bg-secondary)',\n color: 'var(--text-primary)',\n border: '1px solid var(--border-primary)',\n borderRadius: 6,\n cursor: isExporting || artboardsInfo.length === 0 ? 'not-allowed' : 'pointer',\n fontSize: 14,\n fontWeight: 500,\n opacity: isExporting || artboardsInfo.length === 0 ? 0.5 : 1,\n }}\n >\n <Icon icon={icons.image} className=\"sc:size-4\" />\n {isExporting ? 'Exporting...' : 'Export All Artboards'}\n </button>\n\n {showContinuousExport && (\n <button\n onClick={handleToggleContinuous}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 8,\n padding: 12,\n backgroundColor: isContinuousExporting ? 'var(--danger)' : 'var(--success)',\n color: isContinuousExporting ? 'var(--danger-foreground)' : 'var(--success-foreground)',\n border: 'none',\n borderRadius: 6,\n cursor: 'pointer',\n fontSize: 14,\n fontWeight: 500,\n }}\n >\n {isContinuousExporting ? <Icon icon={icons.pause} className=\"sc:size-4\" /> : <Icon icon={icons.play} className=\"sc:size-4\" />}\n {isContinuousExporting ? 'Stop Continuous Export' : 'Start Continuous Export'}\n </button>\n )}\n </div>\n\n {/* Export status */}\n {progress.length > 0 && (\n <div\n style={{\n padding: 12,\n backgroundColor: 'var(--bg-secondary)',\n borderRadius: 6,\n fontSize: 12,\n marginBottom: 16,\n }}\n >\n <div style={{ fontWeight: 600, marginBottom: 8 }}>Export Progress</div>\n {progress.slice(-3).map((p) => (\n <div\n key={p.artboardId}\n style={{\n display: 'flex',\n justifyContent: 'space-between',\n marginBottom: 4,\n }}\n >\n <span>{p.artboardName}</span>\n <span style={{ color: p.status === 'complete' ? 'var(--success)' : 'var(--text-secondary)' }}>\n {p.status === 'complete' ? '✓' : p.status === 'error' ? '✗' : '...'}\n </span>\n </div>\n ))}\n </div>\n )}\n\n {/* Worker stats */}\n {supportsWorkerExport && (\n <div\n style={{\n padding: 12,\n backgroundColor: 'var(--bg-secondary)',\n borderRadius: 6,\n fontSize: 11,\n }}\n >\n <div style={{ fontWeight: 600, marginBottom: 4 }}>Worker Export Enabled ✓</div>\n <div style={{ opacity: 0.7 }}>\n Total Exports: {workerStats.totalExports}\n <br />\n Avg Time: {workerStats.avgExportTime.toFixed(0)}ms\n </div>\n </div>\n )}\n </div>\n );\n};\n","/**\n * GlyphBrowserDrawer - Browse and insert all glyphs from the current font\n * Opens as a bottom drawer and inserts glyphs at cursor position or appends to text\n */\n\nimport { useState, useEffect } from 'react';\nimport { Icon } from '@iconify/react';\nimport { Input, Badge, Spinner, icons } from './ui';\nimport { Drawer } from './Drawer';\nimport { FontAnalyzer } from '../utils/FontAnalyzer';\nimport { createLogger } from '../utils/logger.js';\nimport type { GlyphAlternate } from '../types';\n\nconst logger = createLogger('GlyphBrowserDrawer');\n\ninterface GlyphBrowserDrawerProps {\n isOpen: boolean;\n onClose: () => void;\n fontFamily: string;\n fontWeight?: number;\n onInsertGlyph: (unicode: string) => void;\n}\n\nexport const GlyphBrowserDrawer = ({\n isOpen,\n onClose,\n fontFamily,\n fontWeight = 400,\n onInsertGlyph,\n}: GlyphBrowserDrawerProps) => {\n const [glyphs, setGlyphs] = useState<GlyphAlternate[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [searchQuery, setSearchQuery] = useState('');\n\n // Load all glyphs when drawer opens\n useEffect(() => {\n if (!isOpen) return;\n\n setLoading(true);\n setError(null);\n setSearchQuery('');\n\n FontAnalyzer.getAllGlyphs(fontFamily, fontWeight)\n .then((glyphList) => {\n if (glyphList.length === 0) {\n setError(`No alternate glyphs found in ${fontFamily}`);\n }\n setGlyphs(glyphList);\n setLoading(false);\n })\n .catch((err) => {\n logger.error('Error loading glyphs:', err);\n setError('Failed to load glyphs');\n setLoading(false);\n });\n }, [isOpen, fontFamily, fontWeight]);\n\n const handleSelectGlyph = (glyph: GlyphAlternate) => {\n // Calculate the character to insert\n // If glyph has a real Unicode codepoint, use it\n // Otherwise, map to PUA (Private Use Area) using: U+F0000 + glyphIndex\n let charToInsert = glyph.unicode;\n\n // Check if this is already a valid single character with a codepoint\n if (!charToInsert || charToInsert.length === 0) {\n // No unicode - use PUA mapping\n charToInsert = String.fromCodePoint(0xf0000 + glyph.glyphIndex);\n } else {\n // Check if the unicode is actually mapped (not just extracted from name)\n // If unicode is a single basic letter like \"T\", this might be an alternate\n // without its own codepoint, so use PUA mapping\n const codePoint = charToInsert.codePointAt(0);\n if (codePoint && codePoint < 0xe000) {\n // This is a basic character (A-Z, etc), likely extracted from glyph name\n // Use PUA mapping instead\n charToInsert = String.fromCodePoint(0xf0000 + glyph.glyphIndex);\n }\n }\n\n onInsertGlyph(charToInsert);\n // Don't close the drawer - allow multiple insertions\n };\n\n // Filter glyphs based on search\n const filteredGlyphs = searchQuery\n ? glyphs.filter(\n (g) =>\n g.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n g.unicode.toLowerCase().includes(searchQuery.toLowerCase())\n )\n : glyphs;\n\n return (\n <Drawer\n isOpen={isOpen}\n onClose={onClose}\n title={`Browse Glyphs - ${fontFamily}`}\n description={`Browse and select alternate glyphs for the ${fontFamily} font`}\n maxHeight=\"75vh\"\n showCloseButton={true}\n >\n {/* Search Header */}\n <div className=\"sc:px-6 sc:py-4 sc:border-b sc:border-black/10 sc:dark:border-white/10\">\n <div className=\"sc:relative\">\n <Icon\n icon={icons.search}\n className=\"sc:absolute sc:left-3 sc:top-1/2 sc:-translate-y-1/2 sc:text-muted sc:pointer-events-none sc:size-4 sc:z-10\"\n />\n <Input\n type=\"text\"\n placeholder=\"Search glyphs...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"sc:pl-10\"\n />\n </div>\n </div>\n\n {/* Content */}\n <div className=\"sc:flex-1 sc:overflow-y-auto sc:p-6\">\n {loading && (\n <div className=\"sc:py-12 sc:text-center sc:text-muted\">\n <Spinner size=\"lg\" className=\"sc:mx-auto sc:mb-4\" />\n <div className=\"sc:text-sm\">Loading glyphs...</div>\n </div>\n )}\n\n {error && (\n <div className=\"sc:py-12 sc:text-center\">\n <Icon icon={icons.alertCircle} className=\"sc:size-12 sc:mx-auto sc:mb-4 sc:text-danger\" />\n <div className=\"sc:text-base sc:font-medium sc:mb-2\">{error}</div>\n <div className=\"sc:text-sm sc:text-muted\">This font may not have alternate glyphs.</div>\n </div>\n )}\n\n {!loading && !error && filteredGlyphs.length === 0 && searchQuery && (\n <div className=\"sc:py-12 sc:text-center\">\n <Icon icon={icons.search} className=\"sc:size-12 sc:mx-auto sc:mb-4 sc:text-muted\" />\n <div className=\"sc:text-base sc:font-medium\">No glyphs found matching \"{searchQuery}\"</div>\n </div>\n )}\n\n {!loading && !error && filteredGlyphs.length > 0 && (\n <div className=\"sc:grid sc:grid-cols-[repeat(auto-fill,minmax(100px,1fr))] sc:gap-4\">\n {filteredGlyphs.map((glyph, index) => (\n <button\n key={`${glyph.glyphIndex}-${index}`}\n onClick={() => handleSelectGlyph(glyph)}\n className=\"sc:group sc:relative sc:flex sc:aspect-square sc:cursor-pointer sc:flex-col sc:items-center sc:gap-2 sc:rounded-xl sc:border-2 sc:border-black/10 sc:bg-black/5 sc:p-3 sc:transition-all sc:hover:border-accent sc:hover:bg-black/10 sc:hover:shadow-lg sc:dark:border-white/10 sc:dark:bg-white/5 sc:dark:hover:border-accent sc:dark:hover:bg-white/10\"\n >\n {/* Preview */}\n {glyph.previewDataUrl ? (\n <img src={glyph.previewDataUrl} alt={glyph.name} className=\"sc:h-auto sc:w-full sc:object-contain\" />\n ) : (\n <div className=\"sc:text-5xl sc:leading-none\" style={{ fontFamily }}>\n {glyph.unicode}\n </div>\n )}\n\n {/* Label */}\n <div className=\"sc:w-full sc:overflow-hidden sc:text-ellipsis sc:whitespace-nowrap sc:text-center sc:text-xs sc:text-muted\">\n {glyph.name}\n </div>\n\n {/* Category badge */}\n {glyph.category !== 'default' && (\n <Badge\n size=\"sm\"\n color={\n glyph.category === 'swash'\n ? 'warning'\n : glyph.category === 'stylistic'\n ? 'primary'\n : 'success'\n }\n className=\"sc:absolute sc:right-1 sc:top-1 sc:text-[10px] sc:font-semibold sc:uppercase\"\n >\n {glyph.category === 'swash'\n ? 'SW'\n : glyph.category === 'stylistic'\n ? 'ALT'\n : glyph.category === 'ligature'\n ? 'LIG'\n : ''}\n </Badge>\n )}\n </button>\n ))}\n </div>\n )}\n\n {/* Footer Info */}\n {!loading && !error && filteredGlyphs.length > 0 && (\n <div className=\"sc:mt-6 sc:pt-4 sc:border-t sc:border-black/10 sc:dark:border-white/10 sc:text-center sc:text-sm sc:text-muted\">\n {filteredGlyphs.length} glyph{filteredGlyphs.length !== 1 ? 's' : ''} available\n {searchQuery && ` matching \"${searchQuery}\"`}\n </div>\n )}\n </div>\n </Drawer>\n );\n};\n","/**\n * GlyphPanel - Standalone glyph browser component for embedding\n * Browses and inserts font glyphs (alternates, ligatures, swashes) into text elements\n */\n\nimport React, { useCallback } from 'react';\nimport { useEditor } from '../../contexts/EditorContext.js';\nimport { GlyphBrowserDrawer } from '../GlyphBrowserDrawer.js';\nimport { TextElement } from '../../core/TextElement.js';\nimport { FontAnalyzer } from '../../utils/FontAnalyzer.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst logger = createLogger('GlyphPanel');\n\nexport interface GlyphPanelProps {\n /**\n * Whether the panel is open\n */\n isOpen: boolean;\n\n /**\n * Callback when panel should close\n */\n onClose: () => void;\n\n /**\n * Font family to browse glyphs for\n * If not provided, uses the selected text element's font\n */\n fontFamily?: string;\n\n /**\n * Font weight to browse glyphs for\n * If not provided, uses the selected text element's weight\n */\n fontWeight?: number;\n\n /**\n * Custom glyph insertion handler\n * If not provided, uses the default handler that updates the selected element\n */\n onInsertGlyph?: (unicode: string) => void;\n\n /**\n * Text element to insert glyphs into\n * If not provided, uses the selected element from context\n */\n element?: TextElement | null;\n}\n\n/**\n * Standalone glyph browser panel component\n *\n * @example Using with context (automatic)\n * ```tsx\n * function App() {\n * const [isOpen, setIsOpen] = useState(false);\n *\n * return (\n * <EditorProvider>\n * <Canvas />\n * <button onClick={() => setIsOpen(true)}>Browse Glyphs</button>\n * <GlyphPanel\n * isOpen={isOpen}\n * onClose={() => setIsOpen(false)}\n * />\n * </EditorProvider>\n * );\n * }\n * ```\n *\n * @example Using with custom font\n * ```tsx\n * function App() {\n * return (\n * <EditorProvider>\n * <GlyphPanel\n * isOpen={true}\n * onClose={() => {}}\n * fontFamily=\"Playfair Display\"\n * fontWeight={700}\n * />\n * </EditorProvider>\n * );\n * }\n * ```\n */\nexport const GlyphPanel: React.FC<GlyphPanelProps> = ({\n isOpen,\n onClose,\n fontFamily: externalFontFamily,\n fontWeight: externalFontWeight,\n onInsertGlyph: externalOnInsertGlyph,\n element: externalElement,\n}) => {\n const { selectedElement, executeElementUpdate } = useEditor();\n\n // Use external element if provided, otherwise use selected element\n const element = externalElement !== undefined ? externalElement : (selectedElement || null);\n\n // Get font family from props or element\n const fontFamily = externalFontFamily || (element instanceof TextElement ? element.fontFamily : 'Arial');\n\n // Get font weight from props or element\n const fontWeight = externalFontWeight || (element instanceof TextElement && element.bold ? 700 : 400);\n\n // Handle glyph insertion\n const handleInsertGlyph = useCallback(\n async (unicode: string) => {\n if (externalOnInsertGlyph) {\n // Use external handler if provided\n externalOnInsertGlyph(unicode);\n return;\n }\n\n // Default behavior: insert into selected text element\n if (!element || !(element instanceof TextElement)) {\n logger.warn('No text element selected for glyph insertion');\n return;\n }\n\n // Pre-load the font to ensure rendering works\n try {\n await FontAnalyzer.loadFont(fontFamily, fontWeight);\n } catch (err) {\n logger.error('Error pre-loading font for glyph rendering:', err);\n }\n\n const updatedElement = element.clone();\n\n // Simply append the unicode/PUA character to the text\n updatedElement.text = updatedElement.text + unicode;\n\n executeElementUpdate(element, updatedElement);\n },\n [externalOnInsertGlyph, element, fontFamily, fontWeight, executeElementUpdate]\n );\n\n return (\n <GlyphBrowserDrawer\n isOpen={isOpen}\n onClose={onClose}\n fontFamily={fontFamily}\n fontWeight={fontWeight}\n onInsertGlyph={handleInsertGlyph}\n />\n );\n};\n","/**\n * ArtboardTabs - Standalone artboard switcher component\n * Uses useArtboards hook for data and operations\n */\n\nimport React, { useState } from 'react';\nimport { useArtboards } from '../../hooks/useArtboards.js';\nimport { Icon } from '@iconify/react';\nimport { icons } from '../ui';\n\nexport interface ArtboardTabsProps {\n /**\n * Tab orientation\n */\n orientation?: 'horizontal' | 'vertical';\n\n /**\n * Show \"Add Artboard\" button\n */\n showAddButton?: boolean;\n\n /**\n * Show close buttons on tabs\n */\n showCloseButtons?: boolean;\n\n /**\n * Maximum tab width (for horizontal tabs)\n */\n maxTabWidth?: number;\n\n /**\n * Custom CSS styles\n */\n style?: React.CSSProperties;\n\n /**\n * Custom class name\n */\n className?: string;\n\n /**\n * Callback when a new artboard is created\n */\n onArtboardCreate?: (artboardId: string) => void;\n\n /**\n * Callback when an artboard is deleted\n */\n onArtboardDelete?: (artboardId: string) => void;\n}\n\n/**\n * Standalone artboard tabs component\n *\n * @example Horizontal tabs\n * ```tsx\n * function EditorWithTabs() {\n * return (\n * <EditorProvider>\n * <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>\n * <ArtboardTabs\n * orientation=\"horizontal\"\n * showAddButton={true}\n * showCloseButtons={true}\n * />\n * <Canvas />\n * </div>\n * </EditorProvider>\n * );\n * }\n * ```\n *\n * @example Vertical tabs\n * ```tsx\n * function EditorWithSideTabs() {\n * return (\n * <EditorProvider>\n * <div style={{ display: 'flex' }}>\n * <ArtboardTabs\n * orientation=\"vertical\"\n * showAddButton={true}\n * />\n * <Canvas />\n * </div>\n * </EditorProvider>\n * );\n * }\n * ```\n */\nexport const ArtboardTabs: React.FC<ArtboardTabsProps> = ({\n orientation = 'horizontal',\n showAddButton = true,\n showCloseButtons = false,\n maxTabWidth = 200,\n style = {},\n className = '',\n onArtboardCreate,\n onArtboardDelete,\n}) => {\n const {\n artboardsInfo,\n selectArtboard,\n createArtboard,\n deleteArtboard,\n renameArtboard,\n } = useArtboards();\n\n const [renamingId, setRenamingId] = useState<string | null>(null);\n const [newName, setNewName] = useState('');\n\n // Start renaming an artboard\n const startRename = (id: string, currentName: string) => {\n setRenamingId(id);\n setNewName(currentName);\n };\n\n // Finish renaming\n const finishRename = () => {\n if (renamingId && newName.trim()) {\n renameArtboard(renamingId, newName.trim());\n }\n setRenamingId(null);\n setNewName('');\n };\n\n // Cancel renaming\n const cancelRename = () => {\n setRenamingId(null);\n setNewName('');\n };\n\n // Handle add artboard\n const handleAddArtboard = () => {\n createArtboard(1200, 1200, {\n name: `Artboard ${artboardsInfo.length + 1}`,\n });\n\n // Get the newly created artboard (last in the list)\n // Note: In a real implementation, createArtboard should return the new artboard ID\n // For now, we'll call the callback after a short delay\n if (onArtboardCreate) {\n setTimeout(() => {\n const newArtboard = artboardsInfo[artboardsInfo.length];\n if (newArtboard) {\n onArtboardCreate(newArtboard.id);\n }\n }, 10);\n }\n };\n\n // Handle delete artboard\n const handleDeleteArtboard = (id: string, e: React.MouseEvent) => {\n e.stopPropagation();\n\n // Prevent deleting the last artboard\n if (artboardsInfo.length <= 1) {\n alert('Cannot delete the last artboard');\n return;\n }\n\n if (confirm('Delete this artboard?')) {\n deleteArtboard(id);\n if (onArtboardDelete) {\n onArtboardDelete(id);\n }\n }\n };\n\n // Container styles based on orientation\n const containerStyle: React.CSSProperties =\n orientation === 'horizontal'\n ? {\n display: 'flex',\n flexDirection: 'row',\n gap: 4,\n padding: 8,\n backgroundColor: 'var(--bg-secondary)',\n borderBottom: '1px solid var(--border-primary)',\n overflowX: 'auto',\n overflowY: 'hidden',\n ...style,\n }\n : {\n display: 'flex',\n flexDirection: 'column',\n gap: 4,\n padding: 8,\n backgroundColor: 'var(--bg-secondary)',\n borderRight: '1px solid var(--border-primary)',\n overflowY: 'auto',\n overflowX: 'hidden',\n minWidth: 200,\n ...style,\n };\n\n // Tab styles\n const tabStyle = (isActive: boolean): React.CSSProperties => ({\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '8px 12px',\n backgroundColor: isActive ? 'var(--accent-primary)' : 'var(--bg-primary)',\n color: isActive ? 'var(--primary-foreground)' : 'var(--text-primary)',\n border: isActive ? 'none' : '1px solid var(--border-primary)',\n borderRadius: 6,\n cursor: 'pointer',\n fontSize: 13,\n fontWeight: isActive ? 600 : 400,\n whiteSpace: 'nowrap',\n maxWidth: orientation === 'horizontal' ? maxTabWidth : undefined,\n minWidth: orientation === 'horizontal' ? 120 : undefined,\n transition: 'all 0.2s',\n });\n\n return (\n <div className={`artboard-tabs ${className}`} style={containerStyle}>\n {/* Artboard tabs */}\n {artboardsInfo.map((artboard) => {\n const isRenaming = renamingId === artboard.id;\n\n return (\n <div\n key={artboard.id}\n style={tabStyle(artboard.isActive)}\n onClick={() => !isRenaming && selectArtboard(artboard.id)}\n onDoubleClick={() => startRename(artboard.id, artboard.name)}\n >\n {/* Artboard name or rename input */}\n {isRenaming ? (\n <input\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n onBlur={finishRename}\n onKeyDown={(e) => {\n if (e.key === 'Enter') finishRename();\n if (e.key === 'Escape') cancelRename();\n }}\n autoFocus\n onClick={(e) => e.stopPropagation()}\n style={{\n flex: 1,\n padding: '2px 4px',\n border: '1px solid var(--border-primary)',\n borderRadius: 4,\n fontSize: 13,\n minWidth: 80,\n }}\n />\n ) : (\n <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis' }}>\n {artboard.name}\n </span>\n )}\n\n {/* Element count badge */}\n <span\n style={{\n fontSize: 11,\n opacity: 0.7,\n padding: '2px 6px',\n borderRadius: 10,\n backgroundColor: artboard.isActive ? 'color-mix(in srgb, var(--primary-foreground) 20%, transparent)' : 'var(--bg-secondary)',\n }}\n >\n {artboard.elementCount}\n </span>\n\n {/* Close button */}\n {showCloseButtons && (\n <button\n onClick={(e) => handleDeleteArtboard(artboard.id, e)}\n style={{\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n padding: 2,\n display: 'flex',\n alignItems: 'center',\n opacity: 0.7,\n transition: 'opacity 0.2s',\n }}\n onMouseEnter={(e) => (e.currentTarget.style.opacity = '1')}\n onMouseLeave={(e) => (e.currentTarget.style.opacity = '0.7')}\n title=\"Delete artboard\"\n >\n <Icon icon={icons.close} className=\"sc:size-3.5\" />\n </button>\n )}\n </div>\n );\n })}\n\n {/* Add artboard button */}\n {showAddButton && (\n <button\n onClick={handleAddArtboard}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 6,\n padding: '8px 12px',\n backgroundColor: 'var(--bg-primary)',\n border: '1px dashed var(--border-primary)',\n borderRadius: 6,\n cursor: 'pointer',\n fontSize: 13,\n color: 'var(--text-secondary)',\n minWidth: orientation === 'horizontal' ? 120 : undefined,\n transition: 'all 0.2s',\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = 'var(--bg-tertiary)';\n e.currentTarget.style.borderColor = 'var(--accent-primary)';\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = 'var(--bg-primary)';\n e.currentTarget.style.borderColor = 'var(--border-primary)';\n }}\n title=\"Add new artboard\"\n >\n <Icon icon={icons.plus} className=\"sc:size-3.5\" />\n <span>New Artboard</span>\n </button>\n )}\n </div>\n );\n};\n","/**\n * ZoomControls - Floating zoom control panel for interactive canvas mode\n *\n * Provides:\n * - Zoom in/out buttons\n * - Current zoom percentage display\n * - Fit to screen button\n * - Reset view (100%) button\n */\n\nimport React from 'react';\nimport { Icon } from '@iconify/react';\nimport { useViewportContext } from '../../contexts/ViewportContext.js';\nimport { Button, icons } from '../ui';\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../ui';\n\nexport interface ZoomControlsProps {\n /** CSS class name for the container */\n className?: string;\n /** Position of the controls */\n position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';\n /** Whether to show the fit-to-screen button */\n showFitButton?: boolean;\n /** Whether to show the reset button */\n showResetButton?: boolean;\n}\n\n/**\n * Floating zoom controls for interactive canvas mode\n */\nexport const ZoomControls: React.FC<ZoomControlsProps> = ({\n className,\n position = 'bottom-right',\n showFitButton = true,\n showResetButton = true,\n}) => {\n const { zoom, zoomIn, zoomOut, zoomToFit, resetView } = useViewportContext();\n\n // Format zoom as percentage\n const zoomPercentage = Math.round(zoom * 100);\n\n // Position styles\n const positionStyles: Record<string, React.CSSProperties> = {\n 'bottom-left': { bottom: 16, left: 16 },\n 'bottom-right': { bottom: 16, right: 16 },\n 'top-left': { top: 16, left: 16 },\n 'top-right': { top: 16, right: 16 },\n };\n\n return (\n <div\n className={className}\n style={{\n position: 'absolute',\n ...positionStyles[position],\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n padding: 4,\n backgroundColor: 'var(--surface)',\n borderRadius: 'var(--radius-lg)',\n boxShadow: 'var(--shadow-md)',\n zIndex: 'var(--sc-z-sticky)',\n }}\n >\n {/* Zoom Out */}\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={zoomOut}\n disabled={zoom <= 0.1}\n aria-label=\"Zoom out\"\n >\n <Icon icon={icons.minus} className=\"sc:size-4\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent>Zoom out</TooltipContent>\n </Tooltip>\n\n {/* Zoom Percentage */}\n <div\n style={{\n minWidth: 48,\n textAlign: 'center',\n fontSize: 13,\n fontWeight: 500,\n color: 'var(--foreground)',\n fontVariantNumeric: 'tabular-nums',\n }}\n >\n {zoomPercentage}%\n </div>\n\n {/* Zoom In */}\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={zoomIn}\n disabled={zoom >= 4}\n aria-label=\"Zoom in\"\n >\n <Icon icon={icons.plus} className=\"sc:size-4\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent>Zoom in</TooltipContent>\n </Tooltip>\n\n {/* Separator */}\n {(showFitButton || showResetButton) && (\n <div\n style={{\n width: 1,\n height: 20,\n backgroundColor: 'var(--divider)',\n margin: '0 4px',\n }}\n />\n )}\n\n {/* Fit to Screen */}\n {showFitButton && (\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={zoomToFit}\n aria-label=\"Fit to screen\"\n >\n <Icon icon={icons.fitScreen} className=\"sc:size-4\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent>Fit to screen</TooltipContent>\n </Tooltip>\n )}\n\n {/* Reset to 100% */}\n {showResetButton && (\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={resetView}\n aria-label=\"Reset to 100%\"\n >\n <Icon icon={icons.maximize} className=\"sc:size-4\" />\n </Button>\n </TooltipTrigger>\n <TooltipContent>Reset to 100%</TooltipContent>\n </Tooltip>\n )}\n </div>\n );\n};\n\nexport default ZoomControls;\n","import { debounce } from 'lodash-es';\nimport { createLogger } from '../utils/logger.js';\n\nconst logger = createLogger('AutoExportManager');\n\n/**\n * Configuration for auto-export behavior\n */\nexport interface AutoExportConfig {\n /** Enable/disable auto-export */\n enabled: boolean;\n\n /** Debounce delay - wait this long after last change before exporting (ms) */\n debounceMs: number;\n\n /** Maximum wait time - force export after this duration even if changes keep coming (ms) */\n maxWaitMs: number;\n}\n\n/**\n * Default configuration values\n */\nexport const DEFAULT_AUTO_EXPORT_CONFIG: AutoExportConfig = {\n enabled: true,\n debounceMs: 100,\n maxWaitMs: 1000,\n};\n\n/**\n * Statistics about auto-export behavior\n */\nexport interface AutoExportStats {\n /** Total number of exports triggered */\n totalExports: number;\n\n /** Number of exports skipped due to no changes */\n skippedExports: number;\n\n /** Last export timestamp */\n lastExportTime: number | null;\n\n /** Average time between exports (ms) */\n avgExportInterval: number;\n}\n\n/**\n * Manages automatic export triggered by editor state changes.\n *\n * Uses lodash debounce with `maxWait` to batch rapid changes (drag operations,\n * continuous typing) into a single export while guaranteeing a maximum latency.\n * Performs deep state comparison to skip no-op exports.\n *\n * Typically used via the `useAutoExport` hook rather than directly.\n *\n * @example\n * ```ts\n * const manager = new AutoExportManager({ enabled: true, debounceMs: 100, maxWaitMs: 1000 });\n * manager.onExport(async () => { await exportAllArtboards(); });\n * manager.scheduleExport(); // debounced\n * manager.destroy(); // cleanup\n * ```\n */\nexport class AutoExportManager {\n private config: AutoExportConfig;\n private debouncedExport: ReturnType<typeof debounce>;\n private onExportCallback: (() => void | Promise<void>) | null = null;\n private onExportScheduledCallback: (() => void) | null = null;\n // getStateCallback removed — no longer needed with revision counter\n\n // Change detection via revision counter (replaces expensive JSON.stringify)\n private revision = 0;\n private lastExportedRevision = -1;\n\n // Statistics\n private stats: AutoExportStats = {\n totalExports: 0,\n skippedExports: 0,\n lastExportTime: null,\n avgExportInterval: 0,\n };\n private exportTimestamps: number[] = [];\n\n constructor(config: Partial<AutoExportConfig> = {}) {\n this.config = { ...DEFAULT_AUTO_EXPORT_CONFIG, ...config };\n\n // Create debounced export with maxWait\n this.debouncedExport = debounce(\n () => this.performExport(),\n this.config.debounceMs,\n {\n maxWait: this.config.maxWaitMs,\n leading: false,\n trailing: true,\n }\n );\n }\n\n /**\n * Schedule an export (will be debounced)\n * Call this whenever any change happens in the editor\n */\n scheduleExport(): void {\n const timestamp = Date.now();\n\n if (!this.config.enabled) {\n return;\n }\n\n // Always bump revision so no changes are lost\n this.revision++;\n\n // Ignore scheduling within 200ms of last export (likely re-render artifacts)\n // But the revision was still bumped above, so the next scheduled export\n // will pick up this change — it won't be silently dropped.\n const timeSinceLastExport = this.stats.lastExportTime ? timestamp - this.stats.lastExportTime : Infinity;\n if (timeSinceLastExport < 200) {\n return;\n }\n\n // Notify immediately that export is scheduled (for shimmer feedback)\n // This fires when a change is detected, not after debounce, so UI can\n // immediately stop showing stale mockups and show loading state instead\n if (this.onExportScheduledCallback) {\n this.onExportScheduledCallback();\n }\n\n // Trigger debounced export\n this.debouncedExport();\n }\n\n /**\n * Force an immediate export (bypassing debounce)\n */\n async forceExport(): Promise<void> {\n // Cancel any pending debounced export\n this.debouncedExport.cancel();\n\n // Bump revision so performExport sees a change\n this.revision++;\n\n // Perform export immediately\n await this.performExport();\n }\n\n /**\n * Internal: Perform the actual export\n *\n * PERF: Uses a revision counter instead of JSON.stringify for change detection.\n * scheduleExport() bumps `this.revision`; we compare it to `lastExportedRevision`.\n * This eliminates the expensive JSON.stringify of all elements+artboards that was\n * causing ~50-200ms of main-thread work every time the debounce fired (~1s).\n *\n * The export callback itself (element serialization + worker postMessage) is\n * deferred via setTimeout(0) so it doesn't block the current animation frame.\n */\n private async performExport(): Promise<void> {\n // CRITICAL: Check if disabled BEFORE doing any work\n // This catches the case where a debounced call was already scheduled\n // but we've since disabled exports (e.g., user pressed \"Done\" during drag)\n if (!this.config.enabled) {\n return;\n }\n\n // Check if anything actually changed since last export\n if (this.revision === this.lastExportedRevision) {\n this.stats.skippedExports++;\n return;\n }\n\n // Capture the revision we're about to export\n const exportingRevision = this.revision;\n\n const now = Date.now();\n\n // Update statistics\n this.exportTimestamps.push(now);\n\n // Keep only last 10 timestamps for averaging\n if (this.exportTimestamps.length > 10) {\n this.exportTimestamps.shift();\n }\n\n // Calculate average interval\n if (this.exportTimestamps.length > 1) {\n const intervals = [];\n for (let i = 1; i < this.exportTimestamps.length; i++) {\n intervals.push(this.exportTimestamps[i] - this.exportTimestamps[i - 1]);\n }\n this.stats.avgExportInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;\n }\n\n this.stats.totalExports++;\n this.stats.lastExportTime = now;\n\n // Trigger export callback — deferred to next macrotask so it doesn't\n // block the current animation frame (prevents drag stutter).\n // Fire-and-forget: stats are already updated above, and the actual\n // export work (worker postMessage) is inherently async.\n if (this.onExportCallback) {\n // Deferred to next macrotask so it doesn't block the current animation frame.\n // CRITICAL: Read this.onExportCallback INSIDE the timeout, not before it.\n // Reading it before creates a race condition: React may update the callback\n // (via useAutoExport re-registration) between capture and execution.\n setTimeout(async () => {\n const callback = this.onExportCallback;\n if (!callback) return;\n try {\n await callback();\n } catch (error) {\n logger.error('Export failed:', error);\n }\n this.lastExportedRevision = exportingRevision;\n // If new changes arrived during the export, schedule another one\n if (this.revision > exportingRevision && this.config.enabled) {\n this.debouncedExport();\n }\n }, 0);\n } else {\n logger.warn('No export callback registered');\n }\n }\n\n // getCurrentStateSnapshot removed — replaced with revision counter\n // (JSON.stringify was causing 50-200ms main-thread stutter per export cycle)\n\n /**\n * Register callback to be called when export should happen\n */\n onExport(callback: () => void | Promise<void>): void {\n this.onExportCallback = callback;\n }\n\n /**\n * Register callback to be notified when an export is scheduled (before debounce)\n * This is called immediately when a change is detected, allowing consumers\n * to show loading indicators before the actual export completes.\n */\n onExportScheduled(callback: () => void): void {\n this.onExportScheduledCallback = callback;\n }\n\n // onGetState removed — no longer needed with revision counter\n\n /**\n * Update configuration (can be called while running)\n */\n updateConfig(newConfig: Partial<AutoExportConfig>): void {\n const oldConfig = { ...this.config };\n\n // IMPORTANT: If disabling, flush pending exports BEFORE updating config\n // This ensures the user's last edits are exported before we stop\n // (e.g., when clicking \"Done\" shortly after making edits)\n // We must flush before setting enabled=false, otherwise performExport()\n // will see enabled=false and return early, losing the final export.\n if (oldConfig.enabled && newConfig.enabled === false) {\n this.debouncedExport.flush();\n }\n\n this.config = { ...this.config, ...newConfig };\n\n // If timing changed, recreate debounced function\n if (oldConfig.debounceMs !== this.config.debounceMs || oldConfig.maxWaitMs !== this.config.maxWaitMs) {\n // Cancel old debounced function\n this.debouncedExport.cancel();\n\n // Create new debounced function with updated timings\n this.debouncedExport = debounce(\n () => this.performExport(),\n this.config.debounceMs,\n {\n maxWait: this.config.maxWaitMs,\n leading: false,\n trailing: true,\n }\n );\n\n }\n }\n\n /**\n * Get current statistics\n */\n getStats(): AutoExportStats {\n return { ...this.stats };\n }\n\n /**\n * Reset statistics\n */\n resetStats(): void {\n this.stats = {\n totalExports: 0,\n skippedExports: 0,\n lastExportTime: null,\n avgExportInterval: 0,\n };\n this.exportTimestamps = [];\n }\n\n /**\n * Reset change detection (next export will always trigger)\n */\n resetChangeDetection(): void {\n this.lastExportedRevision = -1;\n }\n\n /**\n * Cleanup resources\n */\n destroy(): void {\n // Cancel any pending exports\n this.debouncedExport.cancel();\n\n // Clear callbacks\n this.onExportCallback = null;\n this.onExportScheduledCallback = null;\n\n // Reset revision tracking\n this.lastExportedRevision = -1;\n\n }\n}\n","/**\n * LoadingStates - Loading and skeleton components for async operations\n * Provides loading states for editor components\n *\n * @example\n * ```tsx\n * import { CanvasSkeleton, useProjectLoader } from '@snowcone-app/canvas/embed';\n *\n * function ProjectLoader({ projectId }) {\n * const { loadFromJSON, isLoading } = useProjectLoader();\n *\n * useEffect(() => {\n * loadProject(projectId);\n * }, [projectId]);\n *\n * if (isLoading) {\n * return <CanvasSkeleton width={800} height={600} />;\n * }\n *\n * return <Canvas />;\n * }\n * ```\n */\n\nimport { CSSProperties } from 'react';\n\nexport interface CanvasSkeletonProps {\n width?: number | string;\n height?: number | string;\n style?: CSSProperties;\n}\n\nexport interface LayersPanelSkeletonProps {\n itemCount?: number;\n style?: CSSProperties;\n}\n\nexport interface ToolbarSkeletonProps {\n style?: CSSProperties;\n}\n\n/**\n * Canvas loading skeleton\n */\nexport function CanvasSkeleton({ width = 800, height = 600, style }: CanvasSkeletonProps) {\n return (\n <div\n className=\"canvas-skeleton\"\n style={{\n width,\n height,\n backgroundColor: 'var(--divider)',\n borderRadius: '8px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n position: 'relative',\n overflow: 'hidden',\n ...style,\n }}\n >\n {/* Shimmer effect */}\n <div\n style={{\n position: 'absolute',\n top: 0,\n left: '-100%',\n width: '100%',\n height: '100%',\n background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent)',\n animation: 'shimmer 2s infinite',\n }}\n />\n\n {/* Loading text */}\n <div style={{ fontSize: '14px', color: 'var(--muted)', fontFamily: 'system-ui, sans-serif' }}>\n Loading canvas...\n </div>\n\n <style>{`\n @keyframes shimmer {\n 0% { transform: translateX(0); }\n 100% { transform: translateX(200%); }\n }\n `}</style>\n </div>\n );\n}\n\n/**\n * Layers panel loading skeleton\n */\nexport function LayersPanelSkeleton({ itemCount = 5, style }: LayersPanelSkeletonProps) {\n return (\n <div\n className=\"layers-panel-skeleton\"\n style={{\n width: '100%',\n padding: '16px',\n backgroundColor: 'var(--surface)',\n borderRadius: '8px',\n ...style,\n }}\n >\n {Array.from({ length: itemCount }).map((_, index) => (\n <div\n key={index}\n className=\"layer-item-skeleton\"\n style={{\n height: '48px',\n backgroundColor: 'var(--divider)',\n borderRadius: '6px',\n marginBottom: index < itemCount - 1 ? '8px' : 0,\n position: 'relative',\n overflow: 'hidden',\n }}\n >\n {/* Shimmer effect */}\n <div\n style={{\n position: 'absolute',\n top: 0,\n left: '-100%',\n width: '100%',\n height: '100%',\n background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent)',\n animation: 'shimmer 2s infinite',\n animationDelay: `${index * 0.1}s`,\n }}\n />\n </div>\n ))}\n\n <style>{`\n @keyframes shimmer {\n 0% { transform: translateX(0); }\n 100% { transform: translateX(200%); }\n }\n `}</style>\n </div>\n );\n}\n\n/**\n * Toolbar loading skeleton\n */\nexport function ToolbarSkeleton({ style }: ToolbarSkeletonProps) {\n return (\n <div\n className=\"toolbar-skeleton\"\n style={{\n height: '56px',\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n padding: '0 16px',\n backgroundColor: 'var(--surface)',\n borderBottom: '1px solid var(--divider)',\n ...style,\n }}\n >\n {/* Button skeletons */}\n {[40, 60, 80, 100, 60, 40].map((width, index) => (\n <div\n key={index}\n style={{\n width: `${width}px`,\n height: '32px',\n backgroundColor: 'var(--divider)',\n borderRadius: '6px',\n position: 'relative',\n overflow: 'hidden',\n }}\n >\n {/* Shimmer effect */}\n <div\n style={{\n position: 'absolute',\n top: 0,\n left: '-100%',\n width: '100%',\n height: '100%',\n background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent)',\n animation: 'shimmer 2s infinite',\n animationDelay: `${index * 0.15}s`,\n }}\n />\n </div>\n ))}\n\n <style>{`\n @keyframes shimmer {\n 0% { transform: translateX(0); }\n 100% { transform: translateX(200%); }\n }\n `}</style>\n </div>\n );\n}\n\n/**\n * Generic loading spinner\n */\nexport function Spinner({ size = 32, color = 'var(--primary)' }: { size?: number; color?: string }) {\n return (\n <div\n className=\"spinner\"\n style={{\n width: `${size}px`,\n height: `${size}px`,\n border: `3px solid var(--divider)`,\n borderTopColor: color,\n borderRadius: '50%',\n animation: 'spin 0.8s linear infinite',\n }}\n >\n <style>{`\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n `}</style>\n </div>\n );\n}\n","import type { KitDefinition } from './types.js';\nimport type { PanelType } from '../types/capabilities.js';\n\n/**\n * Result of validating a {@link KitDefinition}.\n */\nexport interface KitValidationResult {\n /** Whether the kit is valid (no errors). Warnings do not affect validity. */\n valid: boolean;\n /** Fatal configuration errors that would prevent the editor from functioning. */\n errors: string[];\n /** Non-fatal issues that may produce unexpected behavior. */\n warnings: string[];\n}\n\n/**\n * Validates a {@link KitDefinition} for consistency and completeness.\n *\n * Use this to catch configuration errors before passing a kit to `KitRenderer`.\n * Errors indicate fatal issues (editor won't work), while warnings indicate\n * potential misconfigurations that may produce unexpected behavior.\n *\n * @param kit - The kit definition to validate.\n * @returns A {@link KitValidationResult} with errors and warnings.\n *\n * @example\n * ```ts\n * import { validateKit, COMPACT_CUSTOMIZER } from '@snowcone-app/canvas';\n *\n * const result = validateKit(COMPACT_CUSTOMIZER);\n * if (!result.valid) {\n * console.error('Kit errors:', result.errors);\n * }\n * if (result.warnings.length > 0) {\n * console.warn('Kit warnings:', result.warnings);\n * }\n * ```\n */\nexport function validateKit(kit: KitDefinition): KitValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // --- Name ---\n if (!kit.name || kit.name.trim() === '') {\n errors.push('Kit name is required');\n }\n\n // --- Layout / Behavior consistency ---\n if (kit.behavior.toolbar === 'none' && kit.layout.slots.topbar?.length) {\n warnings.push('Toolbar mode is \"none\" but topbar has sections — topbar sections will be ignored');\n }\n\n // --- Capabilities / Behavior alignment ---\n if (kit.behavior.selection === 'multi' && !kit.capabilities.tools.includes('select')) {\n warnings.push('Multi-selection enabled but \"select\" tool not in capabilities');\n }\n\n // --- Capabilities minimums ---\n if (kit.capabilities.elements.length === 0) {\n errors.push('At least one element type must be enabled in capabilities.elements');\n }\n\n if (kit.capabilities.transforms.length === 0) {\n errors.push('At least one transform type must be enabled in capabilities.transforms');\n }\n\n // --- Auto-export without export capability ---\n if (kit.behavior.autoExport && !kit.capabilities.features.export) {\n warnings.push('autoExport is enabled but features.export is false — exports will not be produced');\n }\n\n // --- Panel layout references unknown panels ---\n const panelSlots = [...(kit.layout.slots.left ?? []), ...(kit.layout.slots.right ?? [])];\n const knownPanelSections = ['layers-panel', 'effects-panel', 'export-panel', 'images-panel', 'artboard-tabs'];\n for (const section of panelSlots) {\n if (knownPanelSections.includes(section)) {\n // Check that the corresponding panel capability is enabled\n const sectionToPanelType: Record<string, string> = {\n 'layers-panel': 'layers',\n 'effects-panel': 'effects',\n 'export-panel': 'export',\n 'images-panel': 'images',\n 'artboard-tabs': 'artboards',\n };\n const panelType = sectionToPanelType[section];\n if (panelType && !kit.capabilities.panels.includes(panelType as PanelType)) {\n warnings.push(`Layout references \"${section}\" but \"${panelType}\" is not in capabilities.panels`);\n }\n }\n }\n\n return { valid: errors.length === 0, errors, warnings };\n}\n"],"names":["ALL_CAPABILITIES","MINIMAL_CAPABILITIES","COMPACT_CUSTOMIZER","PRO_STUDIO","EMBED_ONLY","KIT_PRESETS","resolveKit","kit","preset","validIds","KitContext","createContext","KitProvider","initialKit","children","currentKit","setCurrentKit","useState","useEffect","setKit","useCallback","resolved","value","useMemo","jsx","useKit","ctx","useContext","useCapabilities","isAnyCroppingActive","selectedElement","activeChildElement","ImageElement","GroupElement","child","selectAllOnActiveArtboard","opts","elements","artboardManager","handleSelectionChange","setMultiSelection","activeArtboardId","ids","el","useKeyboardShortcuts","options","enabled","multiSelection","handleElementUpdate","handleCopyElements","handlePasteElements","executeRemoveElement","undoActiveArtboard","redoActiveArtboard","setHideHandles","handleAddElement","isToolbarMenuOpen","useEditor","isLayerBound","useIsLayerBound","duplicateLayer","useLayers","handleKeyDown","e","target","_a","id","s","element","increment","dx","dy","updatedElement","handleKeyUp","DEFAULT_DURATION_MS","easeOutCubic","t","useAnimatedFocusRect","durationMs","rect","setRect","rectRef","useRef","rafRef","targetKey","from","start","tick","now","eased","getWordBoundaryAt","text","index","clampedIndex","wordChars","rightStart","rightEnd","leftEnd","leftStart","end","getCursorLineInfo","cursorIndex","fontSize","fontFamily","bold","italic","textAlign","elementLetterSpacing","richText","maxWidth","wrappingWidth","horizontalPadding","HORIZONTAL_PADDING","lines","wrapText","charStartIndex","lineIndex","i","lineEndIndex","line","posInLine","visibleLineText","leadingSpacesCount","isParagraphStart","isParagraphEnd","leadingSpacesMatch","trailingSpacesMatch","trailingSpacesCount","lineWidth","measureTextWidth","lineStartX","xPosition","measureEnd","char","globalIndex","style","charFontSize","charFontFamily","charBold","charItalic","charSpacing","moveCursorVertically","direction","lineHeight","lineSpacing","info","targetLineIndex","targetY","getCursorIndexFromPoint","point","currentY","relativeX","currentX","charWidth","useMarqueeSelection","isMarqueeSelecting","setIsMarqueeSelecting","marqueeStart","setMarqueeStart","marqueeEnd","setMarqueeEnd","marqueePreviewSelection","setMarqueePreviewSelection","x","y","minX","maxX","minY","maxY","previewSelectedIds","bbox","elementLeft","elementRight","elementTop","elementBottom","onMultiSelectionChange","selectedIds","PenToolManager","pathElement","p","newPoint","handleX","handleY","offsetX","offsetY","firstPoint","threshold","clickX","clickY","centerX","centerY","getThemeShapeFillColor","points","previewPoint","_currentPoint","getThemePenPathColor","prevPoint","currentPoint","cp1x","cp1y","_b","cp2x","_c","cp2y","_d","isClosed","lastPoint","_e","_f","getThemeAccentColor","getThemePenAnchorFillColor","handleInX","handleInY","getThemePenHandleColor","handleOutX","handleOutY","themeColor","hex","r","g","b","logger","createLogger","usePenTool","penTool","penCursor","setPenCursor","penIconContent","renderToStaticMarkup","Icon","cursorSvg","dataUrl","error","PathElement","useActiveChild","transformHandles","onActiveChildChange","setActiveChildElement","editingChildIdRef","updateActiveChild","updatedChild","c","useHoverState","onHoverChange","hoverState","setHoverStateInternal","setHoverState","newState","prevState","isNewHover","hoveredElementId","hoveredElement","CoordinateTransform","rotation","flipH","flipV","worldX","worldY","rotRad","RotationUtils","cos","sin","localX","localY","rotatedX","rotatedY","inverse","CropModeController","_element","context","startX","startY","td","startWidth","startHeight","startCropX","startCropY","startCropWidth","startCropHeight","localDelta","Transform","localDx","localDy","normalizedDx","normalizedDy","newCropX","newCropY","maxCropX","maxCropY","anchor","resizeContext","stateMachineContext","cursor","startRotation","startTransform","flipHMult","flipVMult","dxCurrent","dyCurrent","localCurrentX","localCurrentY","dxStart","dyStart","localStartX","localStartY","result","positionOffset","cursorAnchor","minSize","MIN_CROP_SIZE","newCropWidth","newCropHeight","cropX","cropY","cropWidth","cropHeight","oldCropX","oldCropY","oldCropWidth","oldCropHeight","width","height","oldCropCenterX","oldCropCenterY","newCropCenterX","newCropCenterY","localOffsetX","localOffsetY","worldOffset","worldOffsetX","worldOffsetY","transform","useCropMode","cropController","useMultiSelection","multiSelectionGroupBoundsRef","multiSelectionOBBRef","hasTextCapabilities","TextElement","useTextEditing","ref","isEditing","setIsEditing","editText","setEditText","editPosition","setEditPosition","cursorPosition","setCursorPosition","selectionStart","setSelectionStart","selectionEnd","setSelectionEnd","cursorOpacityRef","cursorStartTimeRef","selectionAnchorRef","didDragSelectRef","editRichText","setEditRichText","currentFormattingStyle","setCurrentFormattingStyle","editRichTextRef","applyFormattingToSelection","updated","textLength","prevStyle","getSelectionStyle","toggleFormattingProperty","property","currentStyle","newValue","useImperativeHandle","useCanvasLayout","zoom","viewPadding","artboardBorderRadius","fixedMargin","fixedMarginX","fixedMarginY","artboards","focusedPieceRect","isTouchDevice","setIsTouchDevice","hasTouch","lastTouchTargetRef","handleTouchStart","canvasSize","setCanvasSize","activeArtboard","handleResize","effectiveViewPadding","effectiveMarginX","effectiveMarginY","worldMarginX","worldMarginY","paddedCanvasWidth","paddedCanvasHeight","baseOffsetX","baseOffsetY","paddingOffsetX","paddingOffsetY","zoomInvariantBorderRadius","useInteractionState","snapGuides","setSnapGuides","spacingIndicators","setSpacingIndicators","isAltKeyPressed","setIsAltKeyPressed","isRotating","setIsRotating","_rotationStartAngle","setRotationStartAngle","currentRotation","setCurrentRotation","setRotatingElementVersion","useRenderState","forceUpdate","handlesVersion","setHandlesVersion","renderVersion","setRenderVersion","cursorAnimationFrame","setCursorAnimationFrame","forceRender","v","subscribeToImageLoads","elementId","DEFAULTS","cachedSnapshotEl","snapshotCanvas","source","w","h","sctx","resolveCssColor","input","trimmed","inner","name","rest","varName","fallback","isDocumentDark","_target","html","pathToCanvas","path","rx","ry","p2d","fillSvgPath","fillRule","strokeSvgPath","addSvgPathSubpath","addToPath2D","resolveBoundary","guide","naturalW","naturalH","resolveSafeArea","piece","inset","renderPieceGuides","params","pieces","pieceGuides","artboardOrigin","show","canvasEl","domTarget","isDark","_g","bleedFillCss","dieCutTintCss","boundaryStrokeCss","isInkDark","isInkLight","backdrop","z","zones","labels","applyPieceRotation","naturalDims","boundary","safeArea","isFocusedPiece","outerRadius","pathBoundary","outerShape","clipToOuterShape","wantInner","bleedBlend","effectiveBleedOpacity","drawBleedRingSolid","drawBleedRing","zone","setZoomInvariantStroke","drawDieCutKnockout","drawDieCut","drawLabels","fill","opacity","blurScale","blendMode","blurWorldPx","blurCssPx","dpr","pad","paddedW","paddedH","physW","physH","off","acquireOffscreen","octx","outerPath","boundaryPath","prevOp","compound","cachedBleedOffscreen","tint","tintAlpha","stroke","strokeWidthPx","baseAlpha","INTERACTION_PARTIAL_KNOCKOUT_RETAIN","interacting","fontWorldPx","haloWidth","haloWorldPx","label","anchorToAlign","anchorToBaseline","a","renderMultiSelectionIndicators","displaySelection","activeArtboardElementIds","stateMachine","multiSelectionOBB","filteredDisplaySelection","multiDragContext","isCurrentlyDraggingMulti","cachedElement","rotationAnchor","getLineWidth","selectedElements","corners","rotationRad","corner","groupWidth","groupHeight","obb","dragContext","groupRotation","renderWidth","renderHeight","renderCenterX","renderCenterY","renderMinX","renderMinY","renderMaxX","renderMaxY","rotationHandleX","rotationHandleY","ROTATION_HANDLE_DISTANCE","groupBounds","getDashPattern","renderMultiSelectionHandles","handleRadius","getHandleRadius","isResizingMultiSelection","activeHandle","cornerX","cornerY","renderRotationHandle","renderDimensionLabel","infoText","tooltipX","tooltipY","tooltipWidth","tooltipHeight","renderMarqueeSelection","getThemeAccentColorWithAlpha","renderGroupSiblingHover","hoveredChild","getThemeHoverBorderColor","ROLLING_WINDOW_SIZE","PerformanceMonitor","ms","count","rate","values","sum","buildSpreadClipShape","guides","focusedPieceId","focused","rot","natural","useCanvasRenderLoop","paramsRef","dirtyRef","editCacheRef","canvasBgColorRef","targetElement","color","frameId","renderLoop","t0","renderFrame","canvasRef","elementsByArtboard","artboardRenderer","selectedId","showRotationHandle","selectionHandlePositionsRef","multiClickSelectionRef","lastMultiClickTimeRef","MULTI_CLICK_DEBOUNCE","hideHandles","canvas","easeInOutCubic","progress","displayWidth","displayHeight","MAX_BUFFER_DIM","rawBufferWidth","rawBufferHeight","longest","scaleFactor","effectiveDpr","canvasBgColor","effectiveClipShape","effectiveBorderRadius","firstPieceId","guideAny","explicit","inferred","candidate","elementToRender","elementsForRender","cachedElements","childId","cache","clonedElement","sourceGroup","clonedGroup","clonedChild","cloned","shouldHideHandles","CanvasRenderer","savedSelection","withinDebounce","useRefSelection","selectedOverlapsDieCut","interactingElement","outer","zx","zy","zw","zh","guidesNeedRoundedClip","preservedRect","hasSelection","HUGE","updatedOBB","PinchHandler","startData","scale","rotationDelta","startCentroid","currentCentroid","snapRotation","newWidth","newHeight","sdx","sdy","newRotation","hitTestMultiSelection","bounds","hitRadius","CORNER_HANDLE_HIT_RADIUS","rotatedCorners","rotationHitRadius","startMultiSelectionResize","startMultiSelectionRotate","startAngle","calculateAngle","startMultiSelectionDrag","dragOriginalElementsRef","findCroppingImage","croppingChild","croppingChildIndex","croppingImage","hitTestCropMode","cropContext","cropHandle","imageHandle","handleCropHandleClick","cropBox","handleImageHandleClick","visualBbox","handleImageDragClick","exitCropMode","onElementUpdate","updatedGroup","handleSiblingClick","exitActiveChildMode","handleElementSelection","shiftKey","activeArtboardElements","onSelectionChange","clickedId","newSelection","HIT_FILTER_MIN_VISIBLE_AREA","isHitAllowedInFocus","pointX","pointY","ix","iy","ix2","iy2","useCanvasInteraction","inputRef","repositioningCursorRef","lastClickTimeRef","clickCountRef","lastProcessedClickTimeRef","skipDragSelectionRef","draggingSelectionHandleRef","lastMousePosRef","rotatingElementRef","editModeEnteredAtRef","MULTI_CLICK_THRESHOLD","CLICK_DEDUP_THRESHOLD","setElements","snapSystem","spacingSystem","executeElementUpdate","onRotationStateChange","onCropModeEnter","onContextMenuRequest","getActiveArtboardElements","startMarqueeSelection","updateMarqueeSelection","finishMarqueeSelection","enterTextEditMode","getWrappedTextForEditing","canvasBoundsRef","activePointersRef","pinchPointerIdsRef","pinchOriginalElementRef","selectedIdAtGestureStartRef","pendingDeselectIdRef","pendingDeselectStartPosRef","canvasZoomPinchRef","singleFingerPanRef","userZoom","setUserZoom","panOffset","setPanOffset","useViewportContext","userZoomRef","panOffsetRef","updateBounds","handlePointerDown","isModalMode","wasSelectedBefore","originals","prev","original","distance","containerRect","p1","p2","handlePositions","center","localRotatedX","localRotatedY","relativeY","clickIndex","currentText","wordBounds","relX","relY","hitResult","workingElement","cropHitResult","getBoundingBoxCenter","handle","clickedChild","hits","pendingTap","currentIdx","isProtected","clickProtection","shouldPreserve","shouldPreserveSelection","handlePointerMove","newUserZoom","startCentroidContainerX","startCentroidContainerY","currentCentroidContainerX","currentCentroidContainerY","ratio","rawNewPanX","rawNewPanY","canvas2","canvasRect","containerEl","containerWidth","containerHeight","minVisible","growthRatio","predictedCanvasWidth","predictedCanvasHeight","currentPanX","currentPanY","currentLeftInContainer","currentTopInContainer","predictedLeft","predictedTop","clampedLeft","clampedTop","gesture","yOffset","dragIndex","constrainDragDelta","updatedElements","elementData","prevElements","ue","draggedImage","cropGroupContext","activeChildContext","groupContext","childIndex","flattenedElements","dragModifiers","newPos","elementForSpacingSnap","dragCtx","dragSD","lockedAxis","newActiveChild","groupDragOriginal","dragStartData","movingElement","indicators","multiResizeContext","startBounds","fixedX","fixedY","draggedStartX","draggedStartY","draggedCurrentX","draggedCurrentY","startDistX","startDistY","newDistX","newDistY","scaleX","scaleY","newMinX","newMinY","newMaxX","newMaxY","newX","newY","imageData","startImageData","newChildWidth","newChildHeight","newElementWidth","newElementHeight","newCenterX","newCenterY","resizedImage","cropAnchor","activeChildCtx","isResizingChildInGroup","elementsForSnap","globalResizePipeline","multiRotationContext","deltaRotation","rotatedPos","workingEl","centerXBefore","centerYBefore","newBbox","newCenter","cdx","cdy","j","hoverCheckElement","getCursorForWorldPosition","resizeHandle","handlePointerUp","finalElement","startElement","wasClosing","originalElement","CustomTransform","startOBB","prevOBB","pendingChild","shouldSelectChild","originalGroup","pending","currentElement","ShapeElement","startPos","moved","screenMoved","handleDoubleClick","contentHeight","offset","initialRichText","styleAtEnd","handleDocumentMouseMove","syntheticEvent","handleDocumentPointerUp","targetNode","insideCanvas","hasActiveGesture","handleContextMenu","hit","isInMultiSelection","isCurrentSelection","elementText","useTextEditingHandlers","editPositionUpdateTimerRef","EDIT_MODE_BLUR_DEBOUNCE","onTextSelectionChange","handleTextEditComplete","latestRichText","editingChildId","handleTextEditBlur","isToolbarClick","classList","dataSlot","savedStart","savedEnd","handleTextKeyDown","textarea","rafId","lastUpdate","FRAME_INTERVAL","animate","timestamp","prevIsEditingRef","endPos","lastFormattingStyleRef","stylesAreEqual","newStyle","selectedEl","editingElement","tempElement","updateCursor","CanvasEditor","forwardRef","externalCanvasRef","canvasCutouts","pieceFocus","focusedPieceRectOverride","resolvedTheme","useTheme","internalCanvasRef","derivedFocusedPieceRect","React","match","selectionClipRect","TransformHandles","ArtboardRenderer","AlignmentSnapSystem","SpacingSystem","machine","InteractionStateMachine","elementsRef","selectedElementRef","ariaAnnouncement","setAriaAnnouncement","artboardManagerRef","elementsByArtboardRef","snapHook","resizeResult","allElements","_currentFormattingStyle","timeSinceMultiClick","lastRect","reposition","liveCanvas","artboardId","handleBlur","lastHandlesUpdateRef","elementToUpdate","elementKey","canvasClipPathValue","cssW","cssH","cutouts","jsxs","Fragment","textEl","textareaElement","newRichText","RichText","newCursorPos","oldText","updatedRichText","prefixLen","suffixLen","deleteStart","deleteEnd","insertText","insertStyle","canvasX","canvasY","createPortal","badgeVariants","cva","Badge","className","variant","size","dot","startContent","endContent","onClose","props","surfaceVariant","useSurface","isOnDefaultSurface","isOnSecondarySurface","effectiveVariant","surfaceOverrides","cn","Fieldset","disabled","Legend","RadioGroup","RadioGroupPrimitive","RadioGroupItem","surface","Radio","spinnerVariants","Spinner","IS_APPLE","MOD","Divider","CanvasContextMenu","menu","hasClipboardContent","deleteLayer","toggleLock","moveLayerForward","moveLayerBackward","bringToFront","sendToBack","groupLayers","ungroupLayer","findElementById","menuRef","scopeIds","isLocked","anyInScopeLocked","isGroup","isMulti","run","action","content","MenuItem","icons","USER_MIN_ZOOM","USER_MAX_ZOOM","MIN_ZOOM","MAX_ZOOM","ZOOM_SENSITIVITY","Canvas","propZoom","fitPadding","interactive","maxHeight","enableShortcuts","canvasEditorRef","contextZoom","setZoom","isPanning","setIsPanning","resetUserView","handleActiveChildChange","setHoveredElementId","useSelectionContext","setTextSelectionVersion","setExpandedPanelType","useToolStateContext","handleCropModeEnter","containerRef","calculatedZoom","setCalculatedZoom","setContainerWidth","isHeightConstrained","setIsHeightConstrained","lastSyncedZoomRef","hasFitOnceRef","isSpaceHeld","setIsSpaceHeld","panStartRef","contextMenu","setContextMenu","handleContextMenuRequest","request","closeContextMenu","handleWheel","container","cursorX","cursorY","delta","oldZoom","newZoom","zoomRatio","newPanX","newPanY","oldUserZoom","handlePanStart","isMiddleMouse","isSpacePan","handlePanMove","handlePanEnd","handleGlobalPointerUp","targetFocusedRect","focusedPiece","calculateFitZoom","fitWidth","fitHeight","fitZoom","constrainedByHeight","availableWidth","widthFitZoom","availableHeight","heightFitZoom","heightConstrainedZoom","cancelled","resizeObserver","effectiveZoom","lastFocusedPieceIdRef","subjectWidth","subjectHeight","canvasWidth","canvasHeight","fitCanvasWidth","currentContainerWidth","containerSizeRef","observer","entries","entry","containerW","containerH","minVisibleX","minVisibleY","minPanX","maxPanX","minPanY","maxPanY","clampedX","clampedY","containerStyle","innerWrapperStyle","ZoomPill","cx","cy","ZOOM_PRESETS","onSetZoom","pct","menuOpen","setMenuOpen","presetPct","active","open","sectionRegistry","registerSection","config","getSection","getSectionIds","getAllSections","unregisterSection","builtinsRegistered","registerBuiltinSections","registerPartBackedSections","EffectsPanel","ExportPanel","ImagePanel","GlyphPanel","ArtboardTabs","ContextualToolbars","ZoomControls","CropPanel","EffectsPanel$1","ExportPanel$1","GlyphPanel$1","ArtboardTabs$1","n","ZoomControls$1","EditorLayers","EditorMenu","EditorHistory","EditorAdd","registerBuiltinSectionsSync","components","StrokePanel","onChange","isExpanded","setIsExpanded","lineCap","lineJoin","handleEnabledChange","newEnabled","handleColorChange","handleWidthChange","handleOpacityChange","newOpacity","handleLineCapChange","newLineCap","handleLineJoinChange","newLineJoin","Disclosure","DisclosureTrigger","Label","Switch","DisclosureContent","ColorPickerDropdown","Slider","TooltipProvider","Tooltip","TooltipTrigger","TooltipContent","MaskItem","mask","isFirst","isLast","onUpdate","onRemove","onReorder","getMaskIcon","type","getMaskLabel","handleFeatherChange","handleInvertedToggle","handleMaskElementTypeChange","transformType","existingUrl","handleShapeTypeChange","shapeType","handleTextChange","textConfig","handleImageUrlChange","imgConfig","handleSizeChange","getMaskElementSize","Button","val","state","MasksPanel","masks","showAddMenu","setShowAddMenu","handleAddMask","newMask","createDefaultMaskElement","handleRemoveMask","maskId","handleUpdateMask","updates","m","handleReorderMask","newMasks","WORN_LIGHT","WORN_MEDIUM","WORN_HEAVY","CRACKED_LIGHT","CRACKED_MEDIUM","CRACKED_HEAVY","GRUNGE_LIGHT","GRUNGE_MEDIUM","GRUNGE_HEAVY","RETRO_LIGHT","RETRO_MEDIUM","RETRO_HEAVY","DISTRESS_PRESETS","getPreset","category","level","DistressPanel","distressEffect","ensureBuiltinTexturesReady","showPresets","setShowPresets","intensity","fadeAmount","grainAmount","scratchAmount","edgeWear","handlePresetApply","handleStyleChange","url","DISTRESS_TEXTURE_PRESETS","preloadTexture","handleTextureSelect","handleTextureOpacityChange","handleIntensityChange","handleFadeChange","handleGrainChange","handleScratchChange","handleEdgeWearChange","handleRandomize","hasCompositing","getCompositingParts","parts","describeCompositing","mode","scope","modeLabel","describeKnockout","CompositingPanel","knockoutParts","isCompositing","isKnockout","fillEnabled","strokeEnabled","handleBlendModeChange","newMode","handleFillToggle","handleStrokeToggle","handleScopeChange","newScope","GlyphPicker","isOpen","character","charIndex","fontWeight","currentGlyphOverride","onSelectGlyph","browseAll","alternates","setAlternates","loading","setLoading","setError","FontAnalyzer","alts","err","handleSelectGlyph","alternate","title","Drawer","DrawerContent","DrawerTitle","SparklesIcon","isSelected","isDefault","FEATURE_DEFINITIONS","OpenTypeFeaturesPanel","openTypeFeatures","glyphOverrides","onGlyphOverridesChange","availableFeatures","setAvailableFeatures","glyphPickerOpen","setGlyphPickerOpen","selectedCharIndex","setSelectedCharIndex","selectedCharacter","setSelectedCharacter","browseAllMode","setBrowseAllMode","alternateCounts","setAlternateCounts","_loadingCounts","setLoadingCounts","features","uniqueChars","counts","alternateCount","handleToggleFeature","tag","currentValue","newFeatures","key","handleCharacterClick","handleBrowseAllGlyphs","override","newOverrides","existingIndex","o","enabledCount","glyphOverridesCount","groupedFeatures","acc","feature","hasOverride","availableFeaturesInCategory","f","activeTab","setActiveTab","hasStroke","hasMasks","hasDistress","hasKnockout","isTextElement","hasTypography","Tabs","overrides","externalElement","externalOnElementUpdate","EffectsPanelCore","showContinuousExport","defaultScale","defaultFormat","onExportComplete","exportArtboard","exportAllArtboards","startContinuousExport","stopContinuousExport","isExporting","isContinuousExporting","supportsWorkerExport","workerStats","useExport","artboardsInfo","useArtboards","setScale","format","setFormat","interval","setInterval","handleExportActive","link","artboard","baseName","handleExportAll","exports","handleToggleContinuous","GlyphBrowserDrawer","onInsertGlyph","glyphs","setGlyphs","searchQuery","setSearchQuery","glyphList","glyph","charToInsert","codePoint","filteredGlyphs","Input","externalFontFamily","externalFontWeight","externalOnInsertGlyph","handleInsertGlyph","unicode","orientation","showAddButton","showCloseButtons","maxTabWidth","onArtboardCreate","onArtboardDelete","selectArtboard","createArtboard","deleteArtboard","renameArtboard","renamingId","setRenamingId","newName","setNewName","startRename","currentName","finishRename","cancelRename","handleAddArtboard","newArtboard","handleDeleteArtboard","tabStyle","isActive","isRenaming","position","showFitButton","showResetButton","zoomIn","zoomOut","zoomToFit","resetView","zoomPercentage","DEFAULT_AUTO_EXPORT_CONFIG","AutoExportManager","debounce","exportingRevision","intervals","callback","newConfig","oldConfig","CanvasSkeleton","LayersPanelSkeleton","itemCount","_","ToolbarSkeleton","validateKit","errors","warnings","panelSlots","knownPanelSections","section","panelType"],"mappings":"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"}