diff --git a/src/lib/components/dnd/DraggableItem.svelte b/src/lib/components/dnd/DraggableItem.svelte
new file mode 100644
index 00000000..243efa4c
--- /dev/null
+++ b/src/lib/components/dnd/DraggableItem.svelte
@@ -0,0 +1,199 @@
+
+
+
+
+
+ {@render children?.()}
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/dnd/DropZone.svelte b/src/lib/components/dnd/DropZone.svelte
new file mode 100644
index 00000000..6056f225
--- /dev/null
+++ b/src/lib/components/dnd/DropZone.svelte
@@ -0,0 +1,179 @@
+
+
+
+
+
+ {@render children?.()}
+
+
+
\ No newline at end of file
diff --git a/src/lib/composables/drag-drop.svelte.ts b/src/lib/composables/drag-drop.svelte.ts
new file mode 100644
index 00000000..10ec8b62
--- /dev/null
+++ b/src/lib/composables/drag-drop.svelte.ts
@@ -0,0 +1,356 @@
+import type { GridCharacter, GridWeapon, GridSummon } from '$lib/types/api/party'
+
+export type GridItemType = 'character' | 'weapon' | 'summon'
+export type GridItem = GridCharacter | GridWeapon | GridSummon
+
+export interface Position {
+ container: string
+ position: number
+}
+
+export interface DragSource extends Position {
+ type: GridItemType
+}
+
+export interface DraggedItem {
+ type: GridItemType
+ data: GridItem
+ source: DragSource
+}
+
+export interface DropTarget extends Position {
+ type: GridItemType
+}
+
+export interface DragOperation {
+ id: string
+ type: 'move' | 'swap' | 'reorder'
+ timestamp: number
+ source: {
+ container: string
+ position: number
+ itemId: string
+ }
+ target: {
+ container: string
+ position: number
+ itemId?: string
+ }
+ status: 'pending' | 'synced' | 'failed'
+ retryCount: number
+}
+
+export interface TouchState {
+ touchStartPos: { x: number; y: number } | null
+ touchStartTime: number
+ longPressTimer: number | null
+ touchThreshold: number
+ longPressDuration: number
+ currentTouch: Touch | null
+}
+
+export interface DragDropState {
+ isDragging: boolean
+ draggedItem: DraggedItem | null
+ hoveredOver: DropTarget | null
+ validDrop: boolean
+ dragPreview: HTMLElement | null
+ operationQueue: DragOperation[]
+ lastError: Error | null
+ touchState: TouchState
+}
+
+export interface DragDropHandlers {
+ onDrop?: (from: DragSource, to: DropTarget, item: GridItem) => void
+ onSwap?: (from: DragSource, to: DropTarget, fromItem: GridItem, toItem: GridItem) => void
+ onValidate?: (from: DragSource, to: DropTarget) => boolean
+ onLocalUpdate?: (operation: DragOperation) => void
+}
+
+export function createDragDropContext(handlers: DragDropHandlers = {}) {
+ let state = $state({
+ isDragging: false,
+ draggedItem: null,
+ hoveredOver: null,
+ validDrop: false,
+ dragPreview: null,
+ operationQueue: [],
+ lastError: null,
+ touchState: {
+ touchStartPos: null,
+ touchStartTime: 0,
+ longPressTimer: null,
+ touchThreshold: 10,
+ longPressDuration: 500,
+ currentTouch: null
+ }
+ })
+
+ function detectItemType(item: GridItem): GridItemType {
+ if ('character' in item) return 'character'
+ if ('weapon' in item) return 'weapon'
+ if ('summon' in item) return 'summon'
+ throw new Error('Unknown item type')
+ }
+
+ function handlePointerDown(e: PointerEvent, item: GridItem, source: Position, type: GridItemType) {
+ if (e.pointerType === 'touch') {
+ initiateTouchDrag(e, item, source, type)
+ } else {
+ startDrag(item, { ...source, type })
+ }
+ }
+
+ function initiateTouchDrag(e: PointerEvent, item: GridItem, source: Position, type: GridItemType) {
+ state.touchState.touchStartPos = { x: e.clientX, y: e.clientY }
+ state.touchState.touchStartTime = Date.now()
+
+ state.touchState.longPressTimer = window.setTimeout(() => {
+ startDrag(item, { ...source, type })
+ if ('vibrate' in navigator) {
+ navigator.vibrate(50)
+ }
+ }, state.touchState.longPressDuration)
+ }
+
+ function handlePointerMove(e: PointerEvent) {
+ if (!state.touchState.touchStartPos) return
+
+ const distance = Math.sqrt(
+ Math.pow(e.clientX - state.touchState.touchStartPos.x, 2) +
+ Math.pow(e.clientY - state.touchState.touchStartPos.y, 2)
+ )
+
+ if (distance > state.touchState.touchThreshold && state.touchState.longPressTimer) {
+ clearTimeout(state.touchState.longPressTimer)
+ state.touchState.longPressTimer = null
+ }
+ }
+
+ function handlePointerUp() {
+ if (state.touchState.longPressTimer) {
+ clearTimeout(state.touchState.longPressTimer)
+ state.touchState.longPressTimer = null
+ }
+ state.touchState.touchStartPos = null
+ }
+
+ function startDrag(item: GridItem, source: DragSource) {
+ try {
+ console.group('🚀 Drag Start')
+ console.log('Item:', item)
+ console.log('Source:', source)
+ console.groupEnd()
+
+ state.isDragging = true
+ state.draggedItem = {
+ type: source.type,
+ data: item,
+ source
+ }
+ createDragPreview(item)
+ } catch (error) {
+ handleDragError(error as Error)
+ }
+ }
+
+ function createDragPreview(item: GridItem) {
+ const preview = document.createElement('div')
+ preview.className = 'drag-preview'
+ preview.style.position = 'fixed'
+ preview.style.pointerEvents = 'none'
+ preview.style.zIndex = '10000'
+ preview.style.opacity = '0.8'
+
+ const itemName = 'character' in item ? item.character.name :
+ 'weapon' in item ? item.weapon.name :
+ 'summon' in item ? item.summon.name : 'Unknown'
+
+ preview.innerHTML = `
+
+ ${itemName || 'Item'}
+
+ `
+ document.body.appendChild(preview)
+ state.dragPreview = preview
+ }
+
+ function updateHover(target: DropTarget | null) {
+ state.hoveredOver = target
+
+ if (target && state.draggedItem) {
+ const isValid = validateDrop(state.draggedItem.source, target)
+ state.validDrop = isValid
+ } else {
+ state.validDrop = false
+ }
+ }
+
+ function determineOperationType(source: Position, target: Position, targetHasItem: boolean): 'move' | 'swap' | 'reorder' {
+ if (source.position === target.position && source.container === target.container) return 'reorder'
+ if (targetHasItem) return 'swap'
+ return 'move'
+ }
+
+ function endDrag(targetHasItem: boolean = false) {
+ try {
+ console.group('🏁 Drag End')
+ console.log('Final state:', { ...state })
+
+ if (state.validDrop && state.draggedItem && state.hoveredOver) {
+ const operation: DragOperation = {
+ id: crypto.randomUUID(),
+ type: determineOperationType(state.draggedItem.source, state.hoveredOver, targetHasItem),
+ timestamp: Date.now(),
+ source: {
+ container: state.draggedItem.source.container,
+ position: state.draggedItem.source.position,
+ itemId: state.draggedItem.data.id
+ },
+ target: {
+ container: state.hoveredOver.container,
+ position: state.hoveredOver.position,
+ itemId: targetHasItem ? 'has-item' : undefined
+ },
+ status: 'pending',
+ retryCount: 0
+ }
+
+ state.operationQueue.push(operation)
+ console.log('📝 Operation queued:', operation)
+
+ handlers.onLocalUpdate?.(operation)
+ }
+
+ console.groupEnd()
+ } catch (error) {
+ handleDragError(error as Error)
+ } finally {
+ cleanupDragState()
+ }
+ }
+
+ function handleDragError(error: Error) {
+ console.error('🔥 Drag operation failed:', error)
+ state.lastError = error
+ cleanupDragState()
+ }
+
+ function cleanupDragState() {
+ state.isDragging = false
+ state.draggedItem = null
+ state.hoveredOver = null
+ state.validDrop = false
+
+ if (state.dragPreview) {
+ state.dragPreview.remove()
+ state.dragPreview = null
+ }
+
+ if (state.touchState.longPressTimer) {
+ clearTimeout(state.touchState.longPressTimer)
+ }
+ state.touchState = {
+ ...state.touchState,
+ touchStartPos: null,
+ longPressTimer: null,
+ currentTouch: null
+ }
+ }
+
+ function validateDrop(source: DragSource, target: DropTarget): boolean {
+ console.group('🎯 Drop Validation')
+ console.log('Source:', source)
+ console.log('Target:', target)
+
+ // Can't drop on self
+ if (source.container === target.container && source.position === target.position) {
+ console.log('❌ Cannot drop on self')
+ console.groupEnd()
+ return false
+ }
+
+ // Type mismatch check
+ if (source.type !== target.type) {
+ console.log('❌ Type mismatch:', source.type, 'vs', target.type)
+ console.groupEnd()
+ return false
+ }
+
+ // Custom validation
+ if (handlers.onValidate) {
+ const customValid = handlers.onValidate(source, target)
+ console.log(customValid ? '✅ Custom validation passed' : '❌ Custom validation failed')
+ console.groupEnd()
+ return customValid
+ }
+
+ console.log('✅ Drop allowed')
+ console.groupEnd()
+ return true
+ }
+
+ function handleDrop(target: DropTarget, targetItem?: GridItem) {
+ if (!state.draggedItem || !state.validDrop) {
+ console.log('❌ Invalid drop attempt')
+ return false
+ }
+
+ const source = state.draggedItem.source
+ const item = state.draggedItem.data
+
+ console.group('💧 Handle Drop')
+ console.log('From:', source)
+ console.log('To:', target)
+ console.log('Item:', item)
+ console.log('Target Item:', targetItem)
+
+ if (targetItem) {
+ // Swap items
+ console.log('🔄 Swapping items')
+ handlers.onSwap?.(source, target, item, targetItem)
+ } else {
+ // Move to empty slot
+ console.log('📦 Moving to empty slot')
+ handlers.onDrop?.(source, target, item)
+ }
+
+ console.groupEnd()
+ endDrag(!!targetItem)
+ return true
+ }
+
+ function updateDragPreviewPosition(x: number, y: number) {
+ if (state.dragPreview) {
+ state.dragPreview.style.left = `${x + 10}px`
+ state.dragPreview.style.top = `${y - 20}px`
+ }
+ }
+
+ function getQueuedOperations() {
+ return state.operationQueue.filter(op => op.status === 'pending')
+ }
+
+ function clearQueue() {
+ state.operationQueue = []
+ }
+
+ return {
+ get state() {
+ return state
+ },
+ startDrag,
+ updateHover,
+ endDrag,
+ validateDrop,
+ handleDrop,
+ handlePointerDown,
+ handlePointerMove,
+ handlePointerUp,
+ updateDragPreviewPosition,
+ getQueuedOperations,
+ clearQueue
+ }
+}
+
+export type DragDropContext = ReturnType
\ No newline at end of file