// Dark Mode Toggle function toggleDarkMode() { const body = document.documentElement; const isDark = body.getAttribute('data-theme') === 'dark'; const toggle = document.getElementById('darkModeToggle'); if (isDark) { body.removeAttribute('data-theme'); localStorage.setItem('theme', 'light'); toggle.innerHTML = ''; } else { body.setAttribute('data-theme', 'dark'); localStorage.setItem('theme', 'dark'); toggle.innerHTML = ''; } } // Load saved theme preference function loadTheme() { const savedTheme = localStorage.getItem('theme'); const toggle = document.getElementById('darkModeToggle'); if (savedTheme === 'dark') { document.documentElement.setAttribute('data-theme', 'dark'); if (toggle) { toggle.innerHTML = ''; } } } // Sidebar toggle for mobile function toggleSidebar() { const sidebar = document.getElementById('sidebar'); sidebar.classList.toggle('open'); } // Close sidebar when clicking outside on mobile document.addEventListener('click', function(event) { const sidebar = document.getElementById('sidebar'); const toggle = document.getElementById('sidebarToggle'); if (window.innerWidth <= 768) { if (!sidebar.contains(event.target) && !toggle.contains(event.target)) { sidebar.classList.remove('open'); } } }); // Confirm delete actions document.querySelectorAll('[data-confirm]').forEach(function(el) { el.addEventListener('click', function(e) { if (!confirm(this.getAttribute('data-confirm'))) { e.preventDefault(); } }); }); // Draggable Blocks Functionality function initDraggableBlocks() { const grids = document.querySelectorAll('.dashboard-grid'); grids.forEach(function(grid) { const cards = grid.querySelectorAll('.card'); // Add drag handles to card headers cards.forEach(function(card, index) { const header = card.querySelector('.card-header'); if (header) { // Add unique ID to card if not present if (!card.id) { card.id = 'block-' + index; } // Create drag handle const dragHandle = document.createElement('div'); dragHandle.className = 'drag-handle'; dragHandle.innerHTML = ''; dragHandle.title = 'Drag to reorder'; // Add draggable class to header header.classList.add('draggable'); header.prepend(dragHandle); // Make header draggable makeHeaderDraggable(header, card, grid); } }); // Load saved layout loadLayout(grid); }); } function makeHeaderDraggable(header, card, grid) { let draggedElement = null; let placeholder = null; let ghost = null; let animationFrame = null; header.addEventListener('mousedown', function(e) { // Only start drag from drag handle if (!e.target.closest('.drag-handle')) return; e.preventDefault(); draggedElement = card; // Get card dimensions const rect = card.getBoundingClientRect(); const offsetX = e.clientX - rect.left; const offsetY = e.clientY - rect.top; // Store original data-position to restore later const originalDataPosition = card.getAttribute('data-position'); // Remove data-position during drag to allow free placement if (originalDataPosition) { card.removeAttribute('data-position'); } // Create ghost element (floating preview) ghost = document.createElement('div'); ghost.className = 'sortable-ghost'; ghost.style.position = 'fixed'; ghost.style.pointerEvents = 'none'; ghost.style.zIndex = '99999'; ghost.style.width = rect.width + 'px'; ghost.style.height = rect.height + 'px'; ghost.style.backgroundColor = 'var(--card-bg, white)'; ghost.style.border = '2px solid var(--primary)'; ghost.style.borderRadius = 'var(--radius)'; ghost.style.opacity = '0.9'; ghost.style.boxShadow = 'var(--shadow-lg)'; ghost.style.cursor = 'grabbing'; document.body.appendChild(ghost); // Create placeholder element (shows drop position) placeholder = document.createElement('div'); placeholder.className = 'sortable-placeholder'; placeholder.style.minHeight = rect.height + 'px'; card.parentNode.insertBefore(placeholder, card); // Keep original card visible but add dragging style card.classList.add('dragging'); card.style.opacity = '0.5'; card.style.transform = 'scale(0.98)'; // Initial position ghost.style.top = (e.clientY - offsetY) + 'px'; ghost.style.left = (e.clientX - offsetX) + 'px'; function updateGhostPosition(clientY, clientX) { // Position ghost directly at cursor position ghost.style.top = (clientY - offsetY) + 'px'; ghost.style.left = (clientX - offsetX) + 'px'; } // Get grid layout info for 2D position tracking const gridRect = grid.getBoundingClientRect(); const gridStyle = window.getComputedStyle(grid); const gridGap = parseInt(gridStyle.gap) || 20; // Calculate column positions for 2D grid function getColumnFromX(x) { // Calculate which column the x position falls into const relativeX = x - gridRect.left; const colWidth = (gridRect.width + gridGap) / Math.max(1, Math.floor(gridRect.width / 300)); // Estimate columns based on min 300px width return Math.floor(relativeX / (colWidth + gridGap)); } function onMouseMove(e) { e.preventDefault(); // Cancel any pending animation frame if (animationFrame) { cancelAnimationFrame(animationFrame); } // Use requestAnimationFrame for smooth updates animationFrame = requestAnimationFrame(function() { updateGhostPosition(e.clientY, e.clientX); // Use a point slightly offset from cursor to avoid ghost interference // This ensures we detect the card under the cursor, not the ghost const checkX = e.clientX; const checkY = e.clientY + 10; // 10px below cursor for better detection // Find the element under the offset point const elementUnderCursor = document.elementFromPoint(checkX, checkY); // Check if we're directly over the placeholder (skip processing to avoid flicker) if (elementUnderCursor && elementUnderCursor.closest('.sortable-placeholder')) { return; // Already over placeholder, don't change position } // Find the card that contains or is the element under cursor let targetCard = null; if (elementUnderCursor) { targetCard = elementUnderCursor.closest('.card'); // Make sure it's not the dragged card if (targetCard && targetCard.classList.contains('dragging')) { targetCard = null; } } // If we found a valid target card, determine insert position if (targetCard) { const targetRect = targetCard.getBoundingClientRect(); const targetMiddleY = targetRect.top + targetRect.height / 2; // If cursor is above the card's middle, insert before it // If cursor is below, insert after it // Use checkY for consistent comparison with detection point if (checkY < targetMiddleY) { // Check if placeholder is already in the correct position if (targetCard.nextElementSibling !== placeholder) { grid.insertBefore(placeholder, targetCard); } } else { // Insert after the target card const nextCard = targetCard.nextElementSibling; // If next sibling is the placeholder or the dragged element, it's already in the right position if (nextCard === placeholder || nextCard === draggedElement) { // Already in correct position, don't move } else if (nextCard && nextCard.classList.contains('card')) { grid.insertBefore(placeholder, nextCard); } else { grid.appendChild(placeholder); } } } else { // No target card found - check if we should move to end // This handles the case where we're dragging over empty space or the grid background // If cursor is within the grid bounds, move to end if (e.clientX >= gridRect.left && e.clientX <= gridRect.right && e.clientY >= gridRect.top && e.clientY <= gridRect.bottom) { // Check if we're over the dragged element (which is hidden but still in DOM) const isOverDraggedElement = elementUnderCursor && (elementUnderCursor.closest('.card.dragging') || elementUnderCursor.closest('.sortable-placeholder')); if (!isOverDraggedElement) { // Move placeholder to end if not already there const lastCard = grid.querySelector('.card:last-of-type'); if (lastCard && lastCard.nextElementSibling !== placeholder) { grid.appendChild(placeholder); } } } } }); } function onMouseUp(e) { if (animationFrame) { cancelAnimationFrame(animationFrame); } if (draggedElement) { draggedElement.classList.remove('dragging'); // Restore original card styles draggedElement.style.opacity = ''; draggedElement.style.transform = ''; // Ensure the card is placed in the correct position // The placeholder shows where the card should go if (placeholder && placeholder.parentNode) { // Move the dragged element to the placeholder position placeholder.parentNode.replaceChild(draggedElement, placeholder); } else { // If no placeholder exists, append to the end grid.appendChild(draggedElement); } // Save layout saveLayout(grid); } if (ghost && ghost.parentNode) { ghost.parentNode.removeChild(ghost); } if (placeholder && placeholder.parentNode) { placeholder.parentNode.removeChild(placeholder); } document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); } function saveLayout(grid) { const pageKey = getPageKey(); const cards = grid.querySelectorAll('.card'); const order = Array.from(cards).map(card => card.id); localStorage.setItem('blockLayout_' + pageKey, JSON.stringify(order)); } function loadLayout(grid) { const pageKey = getPageKey(); const saved = localStorage.getItem('blockLayout_' + pageKey); if (saved) { try { const order = JSON.parse(saved); const cards = Array.from(grid.querySelectorAll('.card')); // Sort cards according to saved order order.forEach(function(id) { const card = cards.find(c => c.id === id); if (card) { grid.appendChild(card); } }); } catch (e) { console.error('Failed to load layout:', e); } } } function getPageKey() { const path = window.location.pathname; if (path.includes('/tryouts/')) { return 'tryout'; } if (path.includes('/profile')) { return 'profile'; } return 'dashboard'; } // Initialize draggable blocks on DOM ready document.addEventListener('DOMContentLoaded', function() { // Load theme preference loadTheme(); // Initialize draggable blocks initDraggableBlocks(); const alerts = document.querySelectorAll('.alert-dismissible'); alerts.forEach(function(alert) { setTimeout(function() { if (alert.parentElement) { alert.style.opacity = '0'; alert.style.transition = 'opacity 0.3s ease'; setTimeout(function() { if (alert.parentElement) { alert.remove(); } }, 300); } }, 5000); }); });