diff --git a/src/scripts/components/ServicesManager.js b/src/scripts/components/ServicesManager.js index 2f02951..777efd7 100644 --- a/src/scripts/components/ServicesManager.js +++ b/src/scripts/components/ServicesManager.js @@ -30,6 +30,9 @@ export class ServicesManager { draggedElement: null, draggedIndex: -1, placeholder: null, + initialMouseY: 0, + initialElementY: 0, + mouseOffset: 0, }; this.elements = { @@ -363,26 +366,42 @@ export class ServicesManager { this.dragState.draggedElement = serviceItem; this.dragState.draggedIndex = parseInt(serviceItem.dataset.index); - // Create placeholder + // Store initial positions for mouse following + const rect = serviceItem.getBoundingClientRect(); + this.dragState.initialMouseY = event.clientY; + this.dragState.initialElementY = rect.top; + this.dragState.mouseOffset = event.clientY - rect.top; + + // Create placeholder with smooth appearance this.dragState.placeholder = document.createElement("div"); this.dragState.placeholder.className = "selected-service-item drag-placeholder"; this.dragState.placeholder.innerHTML = `
- Drop here +
`; - // Add dragging class + // Add dragging classes with smooth animation serviceItem.classList.add("dragging"); document.body.classList.add("dragging"); - // Insert placeholder + // Insert placeholder with animation + this.dragState.placeholder.style.height = "0px"; + this.dragState.placeholder.style.opacity = "0"; + this.dragState.placeholder.style.marginBottom = "0px"; serviceItem.parentNode.insertBefore( this.dragState.placeholder, serviceItem.nextSibling ); + + // Animate placeholder appearance + requestAnimationFrame(() => { + this.dragState.placeholder.style.height = serviceItem.offsetHeight + "px"; + this.dragState.placeholder.style.opacity = "1"; + this.dragState.placeholder.style.marginBottom = "0.75rem"; + }); } /** @@ -392,6 +411,15 @@ export class ServicesManager { handleDragMove(event) { if (!this.dragState.isDragging) return; + // Make dragged element follow mouse vertically + const draggedElement = this.dragState.draggedElement; + if (draggedElement) { + const deltaY = event.clientY - this.dragState.initialMouseY; + draggedElement.style.transform = `translateY(${deltaY}px) scale(1.05)`; + draggedElement.style.zIndex = "1000"; + draggedElement.style.position = "relative"; + } + const serviceItems = Array.from( this.elements.selectedServices.querySelectorAll( ".selected-service-item:not(.dragging):not(.drag-placeholder)" @@ -399,11 +427,20 @@ export class ServicesManager { ); let targetIndex = serviceItems.length; // Default to end + let closestItem = null; + let closestDistance = Infinity; + // Find the closest item for smoother animation for (let i = 0; i < serviceItems.length; i++) { const item = serviceItems[i]; const rect = item.getBoundingClientRect(); const itemCenter = rect.top + rect.height / 2; + const distance = Math.abs(event.clientY - itemCenter); + + if (distance < closestDistance) { + closestDistance = distance; + closestItem = item; + } if (event.clientY < itemCenter) { targetIndex = i; @@ -411,7 +448,21 @@ export class ServicesManager { } } - // Move placeholder to new position + // Add smooth animation classes to other items + serviceItems.forEach((item, index) => { + const rect = item.getBoundingClientRect(); + const itemCenter = rect.top + rect.height / 2; + + if (event.clientY < itemCenter && index >= targetIndex) { + item.style.transform = "translateY(8px)"; + } else if (event.clientY > itemCenter && index < targetIndex) { + item.style.transform = "translateY(-8px)"; + } else { + item.style.transform = "translateY(0)"; + } + }); + + // Move placeholder to new position with smooth transition const targetItem = serviceItems[targetIndex]; if (targetItem) { targetItem.parentNode.insertBefore( @@ -431,6 +482,18 @@ export class ServicesManager { handleDragEnd(event) { if (!this.dragState.isDragging) return; + // Reset transforms on all items with smooth animation + const allServiceItems = Array.from( + this.elements.selectedServices.querySelectorAll( + ".selected-service-item:not(.dragging):not(.drag-placeholder)" + ) + ); + + allServiceItems.forEach((item) => { + item.style.transform = ""; + item.style.transition = "transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)"; + }); + // Calculate new index based on placeholder position const allItems = Array.from( this.elements.selectedServices.querySelectorAll( @@ -446,26 +509,47 @@ export class ServicesManager { // Update services order this.reorderServices(this.dragState.draggedIndex, newIndex); - // Clean up - this.dragState.draggedElement.classList.remove("dragging"); - document.body.classList.remove("dragging"); + // Animate placeholder disappearance if (this.dragState.placeholder && this.dragState.placeholder.parentNode) { - this.dragState.placeholder.parentNode.removeChild( - this.dragState.placeholder - ); + this.dragState.placeholder.style.height = "0px"; + this.dragState.placeholder.style.opacity = "0"; + this.dragState.placeholder.style.marginBottom = "0px"; + + setTimeout(() => { + if ( + this.dragState.placeholder && + this.dragState.placeholder.parentNode + ) { + this.dragState.placeholder.parentNode.removeChild( + this.dragState.placeholder + ); + } + }, 300); } + // Clean up with smooth animation + this.dragState.draggedElement.classList.remove("dragging"); + this.dragState.draggedElement.style.transform = ""; + this.dragState.draggedElement.style.zIndex = ""; + this.dragState.draggedElement.style.position = ""; + document.body.classList.remove("dragging"); + // Reset drag state this.dragState = { isDragging: false, draggedElement: null, draggedIndex: -1, placeholder: null, + initialMouseY: 0, + initialElementY: 0, + mouseOffset: 0, }; - // Update UI and trigger change event - this.renderSelectedServices(); - this.triggerSelectionChange(); + // Update UI and trigger change event with smooth animation + setTimeout(() => { + this.renderSelectedServices(); + this.triggerSelectionChange(); + }, 50); } /** diff --git a/src/styles/main.css b/src/styles/main.css index 9e14bba..08fe6a5 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -996,7 +996,7 @@ body::before { -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.15); border-radius: 12px; - transition: all 0.3s ease; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); position: relative; overflow: hidden; } @@ -1578,18 +1578,37 @@ body::before { ========================================================================== */ .dragging .selected-service-item { z-index: 1000; - box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); + transform: scale(1.02); + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .drag-placeholder { - border: 2px dashed var(--primary-color) !important; - background: rgba(37, 99, 235, 0.1) !important; - opacity: 0.7; + border: 2px solid var(--primary-color) !important; + background: linear-gradient( + 135deg, + rgba(37, 99, 235, 0.08), + rgba(37, 99, 235, 0.15) + ) !important; + opacity: 1; + animation: pulse-glow 1.5s ease-in-out infinite alternate; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +@keyframes pulse-glow { + 0% { + box-shadow: 0 0 5px rgba(37, 99, 235, 0.3); + transform: scale(1); + } + 100% { + box-shadow: 0 0 20px rgba(37, 99, 235, 0.6); + transform: scale(1.02); + } } .drag-placeholder .service-name { color: var(--primary-color) !important; - font-style: italic; + opacity: 0.8; } body.dragging { @@ -1604,26 +1623,48 @@ body.dragging * { .service-drag-handle { cursor: grab; opacity: 0.6; - transition: opacity 0.2s ease; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .service-drag-handle:hover { opacity: 1; color: var(--primary-color); + transform: scale(1.1); } .service-drag-handle:active { cursor: grabbing; + transform: scale(0.95); } .selected-service-item.dragging { - transition: none; + transition: none !important; z-index: 1000; position: relative; + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.4); + pointer-events: none; +} + +/* Smooth animations for reordering */ +.selected-service-item:not(.dragging):not(.drag-placeholder) { + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Enhanced animations for drag interactions */ +.selected-service-item.drag-over-top { + transform: translateY(-8px); +} + +.selected-service-item.drag-over-bottom { + transform: translateY(8px); } /* Dark theme drag styles */ [data-theme="dark"] .drag-placeholder { border-color: var(--primary-color) !important; - background: rgba(37, 99, 235, 0.15) !important; + background: linear-gradient( + 135deg, + rgba(37, 99, 235, 0.12), + rgba(37, 99, 235, 0.2) + ) !important; }