diff --git a/CHANGELOG.md b/CHANGELOG.md
index f45d7a2..379af8a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,34 @@
# Changelog
+## Changes 10/23/2025 (v1.6.1)
+
+feat(ui): unified zone toggle + polished interactions for sidebar/top cards
+
+- Add floating toggle button styling (hover lift, press, focus ring, ripple)
+ for #zonesToggleFloating and #sidebarToggleFloating (CSS).
+- Ensure icons are visible and centered; enforce consistent sizing/color.
+- Introduce unified “zones collapsed” state persisted via `localStorage.zonesCollapsed`.
+- Update dragAndDrop.js to:
+ - manage a single floating toggle for both Sidebar and Top Zone
+ - keep toggle visible when cards are in Top Zone; hide only when both cards are in Header
+ - rotate icon 90° when both cards are in Top Zone and panels are open
+ - respect collapsed state during DnD flows and on load
+ - preserve original DnD behaviors and saved orders (sidebar/header)
+- Minor layout/visibility fixes during drag (clear temp heights; honor collapsed).
+
+Notes:
+
+- No breaking API changes; existing `sidebarOrder` / `headerOrder` continue to work.
+- New key: `zonesCollapsed` (string '0'/'1') controls visibility of Sidebar + Top Zone.
+
+UX:
+
+- Floating toggle feels more “material”: subtle hover elevation, press feedback,
+ focus ring, and click ripple to restore the prior interactive feel.
+- Icons remain legible on white (explicit color set), centered in the circular button.
+
+---
+
## Changes 10/22/2025 (v1.6.0)
feat(acl): granular per-folder permissions + stricter gates; WebDAV & UI aligned
diff --git a/public/css/styles.css b/public/css/styles.css
index 1d3ce30..5c37e39 100644
--- a/public/css/styles.css
+++ b/public/css/styles.css
@@ -2308,4 +2308,69 @@ body.dark-mode .user-dropdown .user-menu .item:hover {
}
:root { --perm-caret: #444; } /* light */
-body.dark-mode { --perm-caret: #ccc; } /* dark */
\ No newline at end of file
+body.dark-mode { --perm-caret: #ccc; } /* dark */
+
+#zonesToggleFloating,
+#sidebarToggleFloating {
+ transition:
+ transform 160ms cubic-bezier(.2,.0,.2,1),
+ box-shadow 160ms cubic-bezier(.2,.0,.2,1),
+ border-color 160ms cubic-bezier(.2,.0,.2,1),
+ background-color 160ms cubic-bezier(.2,.0,.2,1);
+}
+
+#zonesToggleFloating .material-icons,
+#zonesToggleFloating .material-icons-outlined,
+#sidebarToggleFloating .material-icons,
+#sidebarToggleFloating .material-icons-outlined {
+ color: #333 !important;
+ font-size: 22px;
+ line-height: 1;
+ display: block;
+}
+
+#zonesToggleFloating:hover,
+#sidebarToggleFloating:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 6px 16px rgba(0,0,0,.14);
+ border-color: #cfcfcf;
+}
+
+#zonesToggleFloating:active,
+#sidebarToggleFloating:active {
+ transform: translateY(0) scale(.96);
+ box-shadow: 0 3px 8px rgba(0,0,0,.12);
+}
+
+#zonesToggleFloating:focus-visible,
+#sidebarToggleFloating:focus-visible {
+ outline: none;
+ box-shadow:
+ 0 6px 16px rgba(0,0,0,.14),
+ 0 0 0 3px rgba(25,118,210,.25); /* soft brandy ring */
+}
+
+#zonesToggleFloating::after,
+#sidebarToggleFloating::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+ background: radial-gradient(circle, rgba(0,0,0,.12) 0%, rgba(0,0,0,0) 60%);
+ transform: scale(0);
+ opacity: 0;
+ transition: transform 300ms ease, opacity 450ms ease;
+ pointer-events: none;
+}
+
+#zonesToggleFloating:active::after,
+#sidebarToggleFloating:active::after {
+ transform: scale(1.4);
+ opacity: 1;
+}
+
+#zonesToggleFloating.is-collapsed,
+#sidebarToggleFloating.is-collapsed {
+ background: #fafafa;
+ border-color: #e2e2e2;
+}
\ No newline at end of file
diff --git a/public/js/adminPanel.js b/public/js/adminPanel.js
index f6d6547..b9ffee7 100644
--- a/public/js/adminPanel.js
+++ b/public/js/adminPanel.js
@@ -4,7 +4,7 @@ import { loadAdminConfigFunc } from './auth.js';
import { showToast, toggleVisibility, attachEnterKeyListener } from './domUtils.js';
import { sendRequest } from './networkUtils.js';
-const version = "v1.6.0";
+const version = "v1.6.1";
const adminTitle = `${t("admin_panel")} ${version}`;
diff --git a/public/js/dragAndDrop.js b/public/js/dragAndDrop.js
index 8260186..89a3a16 100644
--- a/public/js/dragAndDrop.js
+++ b/public/js/dragAndDrop.js
@@ -1,29 +1,234 @@
// dragAndDrop.js
-// This file handles drag-and-drop functionality for cards in the sidebar, header and top drop zones.
-// It also manages the visibility of the sidebar and header drop zones based on the current state of the application.
-// It includes functions to save and load the order of cards in the sidebar and header from localStorage.
-// It also includes functions to handle the drag-and-drop events, including mouse movements and drop zones.
-// It uses CSS classes to manage the appearance of the sidebar and header drop zones during drag-and-drop operations.
+// Enhances the dashboard with drag-and-drop functionality for cards:
+// - injects a tiny floating toggle btn
+// - remembers collapsed state in localStorage
+// - keeps the original card DnD + order logic intact
// ---- responsive defaults ----
const MEDIUM_MIN = 1205; // matches your small-screen cutoff
const MEDIUM_MAX = 1600; // tweak as you like
+const TOGGLE_TOP_PX = 10;
+const TOGGLE_LEFT_PX = 100;
+
+const TOGGLE_ICON_OPEN = 'view_sidebar';
+const TOGGLE_ICON_CLOSED = 'menu';
+
+function updateSidebarToggleUI() {
+ const btn = document.getElementById('sidebarToggleFloating');
+ const sidebar = getSidebar();
+ if (!btn || !sidebar) return;
+
+ if (!hasSidebarCards()) { btn.remove(); return; }
+
+ const collapsed = isSidebarCollapsed();
+ btn.innerHTML = `${
+ collapsed ? TOGGLE_ICON_CLOSED : TOGGLE_ICON_OPEN
+ }`;
+ btn.title = collapsed ? 'Show sidebar' : 'Hide sidebar';
+ btn.style.display = 'block';
+ btn.classList.toggle('toggle-ping', collapsed);
+}
+
+
+function hasSidebarCards() {
+ const sb = getSidebar();
+ return !!sb && sb.querySelectorAll('#uploadCard, #folderManagementCard').length > 0;
+}
+
+function hasTopZoneCards() {
+ const tz = getTopZone();
+ return !!tz && tz.querySelectorAll('#uploadCard, #folderManagementCard').length > 0;
+}
+
+// Both cards are in the top zone (upload + folder)
+function allCardsInTopZone() {
+ const tz = getTopZone();
+ if (!tz) return false;
+ const hasUpload = !!tz.querySelector('#uploadCard');
+ const hasFolder = !!tz.querySelector('#folderManagementCard');
+ return hasUpload && hasFolder;
+ }
+
+function isZonesCollapsed() {
+ return localStorage.getItem('zonesCollapsed') === '1';
+}
+function setZonesCollapsed(collapsed) {
+ localStorage.setItem('zonesCollapsed', collapsed ? '1' : '0');
+ applyZonesCollapsed();
+ updateZonesToggleUI();
+}
+function applyZonesCollapsed() {
+ const collapsed = isZonesCollapsed();
+ const sidebar = getSidebar();
+ const topZone = getTopZone();
+
+ if (sidebar) sidebar.style.display = collapsed ? 'none' : (hasSidebarCards() ? 'block' : 'none');
+ if (topZone) topZone.style.display = collapsed ? 'none' : (hasTopZoneCards() ? '' : '');
+}
+
function isMediumScreen() {
const w = window.innerWidth;
return w >= MEDIUM_MIN && w < MEDIUM_MAX;
}
+// ----- Sidebar collapse state helpers -----
+function getSidebar() {
+ return document.getElementById('sidebarDropArea');
+}
+function getTopZone() {
+ return document.getElementById('uploadFolderRow');
+}
+
+function isSidebarCollapsed() {
+ return localStorage.getItem('sidebarCollapsed') === '1';
+}
+
+function setSidebarCollapsed(collapsed) {
+ localStorage.setItem('sidebarCollapsed', collapsed ? '1' : '0');
+ applySidebarCollapsed();
+ updateSidebarToggleUI();
+}
+
+function applySidebarCollapsed() {
+ const sidebar = getSidebar();
+ if (!sidebar) return;
+
+ // We avoid hard-coding layout assumptions: simply hide/show the sidebar area.
+ // If you want a sliding effect, add CSS later; JS will just toggle display.
+ const collapsed = isSidebarCollapsed();
+ sidebar.style.display = collapsed ? 'none' : 'block';
+}
+
+function ensureZonesToggle() {
+ // show only if at least one zone *can* show a card
+ const shouldShow = hasSidebarCards() || hasTopZoneCards();
+
+ let btn = document.getElementById('sidebarToggleFloating');
+ if (!shouldShow) {
+ if (btn) btn.remove();
+ return;
+ }
+ if (!btn) {
+ btn = document.createElement('button');
+ btn.id = 'sidebarToggleFloating';
+ btn.type = 'button';
+ btn.setAttribute('aria-label', 'Toggle panels');
+ Object.assign(btn.style, {
+ position: 'fixed',
+ left: `${TOGGLE_LEFT_PX}px`,
+ top: `${TOGGLE_TOP_PX}px`,
+ zIndex: '1000',
+ width: '38px',
+ height: '38px',
+ borderRadius: '19px',
+ border: '1px solid #ccc',
+ background: '#fff',
+ cursor: 'pointer',
+ boxShadow: '0 2px 6px rgba(0,0,0,.15)',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: '0',
+ lineHeight: '0',
+ });
+ btn.addEventListener('click', () => {
+ setZonesCollapsed(!isZonesCollapsed());
+ });
+ document.body.appendChild(btn);
+ }
+ updateZonesToggleUI();
+}
+function updateZonesToggleUI() {
+ const btn = document.getElementById('sidebarToggleFloating');
+ if (!btn) return;
+
+ // if neither zone has cards, remove the toggle
+ if (!hasSidebarCards() && !hasTopZoneCards()) {
+ btn.remove();
+ return;
+ }
+
+ const collapsed = isZonesCollapsed();
+ const iconName = collapsed ? TOGGLE_ICON_CLOSED : TOGGLE_ICON_OPEN;
+ btn.innerHTML = `${iconName}`;
+ btn.title = collapsed ? 'Show panels' : 'Hide panels';
+ btn.style.display = 'block';
+
+ // Rotate the icon 90° when BOTH cards are in the top zone and panels are open
+ const iconEl = btn.querySelector('.toggle-icon');
+ if (iconEl) {
+ iconEl.style.transition = 'transform 0.2s ease';
+ iconEl.style.display = 'inline-flex';
+ iconEl.style.alignItems = 'center';
+ if (!collapsed && allCardsInTopZone()) {
+ iconEl.style.transform = 'rotate(90deg)';
+ } else {
+ iconEl.style.transform = 'rotate(0deg)';
+ }
+ }
+}
+
+// create a small floating toggle button (no HTML edits needed)
+function ensureSidebarToggle() {
+ const sidebar = getSidebar();
+ if (!sidebar) return;
+
+ // Only show if there are cards
+ if (!hasSidebarCards()) {
+ const existing = document.getElementById('sidebarToggleFloating');
+ if (existing) existing.remove();
+ return;
+ }
+
+ let btn = document.getElementById('sidebarToggleFloating');
+ if (!btn) {
+ btn = document.createElement('button');
+ btn.id = 'sidebarToggleFloating';
+ btn.type = 'button';
+ btn.setAttribute('aria-label', 'Toggle sidebar');
+
+ Object.assign(btn.style, {
+ position: 'fixed',
+ left: `${TOGGLE_LEFT_PX}px`,
+ top: `${TOGGLE_TOP_PX}px`,
+ zIndex: '10010',
+ width: '38px',
+ height: '38px',
+ borderRadius: '19px',
+ border: '1px solid #ccc',
+ background: '#fff',
+ cursor: 'pointer',
+ boxShadow: '0 2px 6px rgba(0,0,0,.15)',
+ display: 'block',
+ });
+
+ btn.addEventListener('click', () => {
+ setSidebarCollapsed(!isSidebarCollapsed());
+ // icon/title/animation update after state change
+ updateSidebarToggleUI();
+ });
+
+ document.body.appendChild(btn);
+ }
+
+ // set correct icon/title right away
+ //updateSidebarToggleUI();
+ //applySidebarCollapsed();
+ updateZonesToggleUI();
+ applyZonesCollapsed();
+}
+
// Moves cards into the sidebar based on the saved order in localStorage.
export function loadSidebarOrder() {
- const sidebar = document.getElementById('sidebarDropArea');
+ const sidebar = getSidebar();
if (!sidebar) return;
const orderStr = localStorage.getItem('sidebarOrder');
const headerOrderStr = localStorage.getItem('headerOrder');
- const defaultAppliedKey = 'layoutDefaultApplied_v1'; // bump the suffix if you ever change logic
+ const defaultAppliedKey = 'layoutDefaultApplied_v1'; // bump if logic changes
- // If we have a saved order (sidebar or header), just honor it as before
+ // If we have a saved order (sidebar), honor it as before
if (orderStr) {
const order = JSON.parse(orderStr || '[]');
if (Array.isArray(order) && order.length > 0) {
@@ -37,6 +242,11 @@ export function loadSidebarOrder() {
}
});
updateSidebarVisibility();
+ //applySidebarCollapsed(); // NEW: honor collapsed state
+ //ensureSidebarToggle(); // NEW: inject toggle
+ applyZonesCollapsed();
+ ensureZonesToggle();
+
return;
}
}
@@ -45,6 +255,10 @@ export function loadSidebarOrder() {
const headerOrder = JSON.parse(headerOrderStr || '[]');
if (Array.isArray(headerOrder) && headerOrder.length > 0) {
updateSidebarVisibility();
+ //applySidebarCollapsed();
+ //ensureSidebarToggle();
+ applyZonesCollapsed();
+ ensureZonesToggle();
return;
}
@@ -72,73 +286,85 @@ export function loadSidebarOrder() {
}
updateSidebarVisibility();
+ //applySidebarCollapsed();
+ //ensureSidebarToggle();
+ applyZonesCollapsed();
+ ensureZonesToggle();
}
-
- export function loadHeaderOrder() {
- const headerDropArea = document.getElementById('headerDropArea');
- if (!headerDropArea) return;
-
- // 1) Clear out any icons that might already be in the drop area
- headerDropArea.innerHTML = '';
-
- // 2) Read the saved array (or empty array if invalid/missing)
- let stored;
- try {
- stored = JSON.parse(localStorage.getItem('headerOrder') || '[]');
- } catch {
- stored = [];
- }
-
- // 3) Deduplicate IDs
- const uniqueIds = Array.from(new Set(stored));
-
- // 4) Re-insert exactly one icon per saved card ID
- uniqueIds.forEach(id => {
- const card = document.getElementById(id);
- if (card) insertCardInHeader(card, null);
- });
-
- // 5) Persist the cleaned, deduped list back to storage
- localStorage.setItem('headerOrder', JSON.stringify(uniqueIds));
+export function loadHeaderOrder() {
+ const headerDropArea = document.getElementById('headerDropArea');
+ if (!headerDropArea) return;
+
+ // 1) Clear out any icons that might already be in the drop area
+ headerDropArea.innerHTML = '';
+
+ // 2) Read the saved array (or empty array if invalid/missing)
+ let stored;
+ try {
+ stored = JSON.parse(localStorage.getItem('headerOrder') || '[]');
+ } catch {
+ stored = [];
}
-
- // Internal helper: update sidebar visibility based on its content.
- function updateSidebarVisibility() {
- const sidebar = document.getElementById('sidebarDropArea');
- if (sidebar) {
- const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard');
- if (cards.length > 0) {
+
+ // 3) Deduplicate IDs
+ const uniqueIds = Array.from(new Set(stored));
+
+ // 4) Re-insert exactly one icon per saved card ID
+ uniqueIds.forEach(id => {
+ const card = document.getElementById(id);
+ if (card) insertCardInHeader(card, null);
+ });
+
+ // 5) Persist the cleaned, deduped list back to storage
+ localStorage.setItem('headerOrder', JSON.stringify(uniqueIds));
+}
+
+// Internal helper: update sidebar visibility based on its content.
+// NOTE: do NOT auto-hide if user manually collapsed; that is separate.
+function updateSidebarVisibility() {
+ const sidebar = getSidebar();
+ if (!sidebar) return;
+
+ const anyCards = hasSidebarCards();
+
+ // clear any leftover drag height
+ sidebar.style.height = '';
+
+ if (anyCards) {
sidebar.classList.add('active');
- sidebar.style.display = 'block';
+ // respect the unified zones-collapsed switch
+ sidebar.style.display = isZonesCollapsed() ? 'none' : 'block';
} else {
sidebar.classList.remove('active');
sidebar.style.display = 'none';
}
- // Save the current order in localStorage.
- saveSidebarOrder();
- }
+
+ // Save order and update toggle visibility
+ saveSidebarOrder();
+ ensureZonesToggle(); // will hide/remove the button if no cards
+}
+
+// NEW: Save header order to localStorage.
+function saveHeaderOrder() {
+ const headerDropArea = document.getElementById('headerDropArea');
+ if (headerDropArea) {
+ const icons = Array.from(headerDropArea.children);
+ // Each header icon stores its associated card in the property cardElement.
+ const order = icons.map(icon => icon.cardElement.id);
+ localStorage.setItem('headerOrder', JSON.stringify(order));
}
-
- // NEW: Save header order to localStorage.
- function saveHeaderOrder() {
- const headerDropArea = document.getElementById('headerDropArea');
- if (headerDropArea) {
- const icons = Array.from(headerDropArea.children);
- // Each header icon stores its associated card in the property cardElement.
- const order = icons.map(icon => icon.cardElement.id);
- localStorage.setItem('headerOrder', JSON.stringify(order));
- }
- }
-
- // Internal helper: update top zone layout (center a card if one column is empty).
- function updateTopZoneLayout() {
- const leftCol = document.getElementById('leftCol');
- const rightCol = document.getElementById('rightCol');
-
- const leftIsEmpty = !leftCol.querySelector('#uploadCard');
- const rightIsEmpty = !rightCol.querySelector('#folderManagementCard');
-
+}
+
+// Internal helper: update top zone layout (center a card if one column is empty).
+function updateTopZoneLayout() {
+ const leftCol = document.getElementById('leftCol');
+ const rightCol = document.getElementById('rightCol');
+
+ const leftIsEmpty = !leftCol?.querySelector('#uploadCard');
+ const rightIsEmpty = !rightCol?.querySelector('#folderManagementCard');
+
+ if (leftCol && rightCol) {
if (leftIsEmpty && !rightIsEmpty) {
leftCol.style.display = 'none';
rightCol.style.margin = '0 auto';
@@ -152,385 +378,316 @@ export function loadSidebarOrder() {
rightCol.style.margin = '';
}
}
-
- // When a card is being dragged, if the top drop zone is empty, set its min-height.
- function addTopZoneHighlight() {
- const topZone = document.getElementById('uploadFolderRow');
- if (topZone) {
- topZone.classList.add('highlight');
- if (topZone.querySelectorAll('#uploadCard, #folderManagementCard').length === 0) {
- topZone.style.minHeight = '375px';
- }
- }
- }
-
- // When the drag ends, remove the extra min-height.
- function removeTopZoneHighlight() {
- const topZone = document.getElementById('uploadFolderRow');
- if (topZone) {
- topZone.classList.remove('highlight');
- topZone.style.minHeight = '';
- }
- }
-
- // Vertical slide/fade animation helper.
- function animateVerticalSlide(card) {
- card.style.transform = 'translateY(30px)';
- card.style.opacity = '0';
- // Force reflow.
- card.offsetWidth;
- requestAnimationFrame(() => {
- card.style.transition = 'transform 0.3s ease, opacity 0.3s ease';
- card.style.transform = 'translateY(0)';
- card.style.opacity = '1';
- });
- setTimeout(() => {
- card.style.transition = '';
- card.style.transform = '';
- card.style.opacity = '';
- }, 310);
- }
-
- // Internal helper: insert card into sidebar at a proper position based on event.clientY.
- function insertCardInSidebar(card, event) {
- const sidebar = document.getElementById('sidebarDropArea');
- if (!sidebar) return;
- const existingCards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
- let inserted = false;
- for (const currentCard of existingCards) {
- const rect = currentCard.getBoundingClientRect();
- const midY = rect.top + rect.height / 2;
- if (event.clientY < midY) {
- sidebar.insertBefore(card, currentCard);
- inserted = true;
- break;
- }
- }
- if (!inserted) {
- sidebar.appendChild(card);
- }
- // Ensure card fills the sidebar.
- card.style.width = '100%';
- animateVerticalSlide(card);
- }
-
- // Internal helper: save the current sidebar card order to localStorage.
- function saveSidebarOrder() {
- const sidebar = document.getElementById('sidebarDropArea');
- if (sidebar) {
- const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard');
- const order = Array.from(cards).map(card => card.id);
- localStorage.setItem('sidebarOrder', JSON.stringify(order));
- }
- }
-
- // Helper: move cards from sidebar back to the top drop area when on small screens.
- function moveSidebarCardsToTop() {
- if (window.innerWidth < 1205) {
- const sidebar = document.getElementById('sidebarDropArea');
- if (!sidebar) return;
- const cards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
- cards.forEach(card => {
- const orig = document.getElementById(card.dataset.originalContainerId);
- if (orig) {
- orig.appendChild(card);
- animateVerticalSlide(card);
- }
- });
- updateSidebarVisibility();
- updateTopZoneLayout();
- }
- }
-
- // Listen for window resize to automatically move sidebar cards back to top on small screens.
- window.addEventListener('resize', function () {
- if (window.innerWidth < 1205) {
- moveSidebarCardsToTop();
- }
- });
-
- // This function ensures the top drop zone (#uploadFolderRow) has a stable width when empty.
- function ensureTopZonePlaceholder() {
- const topZone = document.getElementById('uploadFolderRow');
- if (!topZone) return;
+}
+
+// When a card is being dragged, if the top drop zone is empty, set its min-height.
+function addTopZoneHighlight() {
+ const topZone = document.getElementById('uploadFolderRow');
+ if (topZone) {
+ topZone.classList.add('highlight');
if (topZone.querySelectorAll('#uploadCard, #folderManagementCard').length === 0) {
- let placeholder = topZone.querySelector('.placeholder');
- if (!placeholder) {
- placeholder = document.createElement('div');
- placeholder.className = 'placeholder';
- placeholder.style.visibility = 'hidden';
- placeholder.style.display = 'block';
- placeholder.style.width = '100%';
- placeholder.style.height = '375px';
- topZone.appendChild(placeholder);
- }
- } else {
- const placeholder = topZone.querySelector('.placeholder');
- if (placeholder) placeholder.remove();
+ topZone.style.minHeight = '375px';
}
}
-
- // --- NEW HELPER FUNCTIONS FOR HEADER DROP ZONE ---
-
- // Show header drop zone and add a "drag-active" class so that the pseudo-element appears.
- function showHeaderDropZone() {
- const headerDropArea = document.getElementById('headerDropArea');
- if (headerDropArea) {
- headerDropArea.style.display = 'inline-flex';
- headerDropArea.classList.add('drag-active');
+}
+
+// When the drag ends, remove the extra min-height.
+function removeTopZoneHighlight() {
+ const topZone = document.getElementById('uploadFolderRow');
+ if (topZone) {
+ topZone.classList.remove('highlight');
+ topZone.style.minHeight = '';
+ }
+}
+
+// Vertical slide/fade animation helper.
+function animateVerticalSlide(card) {
+ card.style.transform = 'translateY(30px)';
+ card.style.opacity = '0';
+ // Force reflow.
+ card.offsetWidth;
+ requestAnimationFrame(() => {
+ card.style.transition = 'transform 0.3s ease, opacity 0.3s ease';
+ card.style.transform = 'translateY(0)';
+ card.style.opacity = '1';
+ });
+ setTimeout(() => {
+ card.style.transition = '';
+ card.style.transform = '';
+ card.style.opacity = '';
+ }, 310);
+}
+
+// Internal helper: insert card into sidebar at a proper position based on event.clientY.
+function insertCardInSidebar(card, event) {
+ const sidebar = getSidebar();
+ if (!sidebar) return;
+ const existingCards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
+ let inserted = false;
+ for (const currentCard of existingCards) {
+ const rect = currentCard.getBoundingClientRect();
+ const midY = rect.top + rect.height / 2;
+ if (event.clientY < midY) {
+ sidebar.insertBefore(card, currentCard);
+ inserted = true;
+ break;
}
}
-
- // Hide header drop zone by removing the "drag-active" class.
- // If a header icon is present (i.e. a card was dropped), the drop zone remains visible without the dashed border.
- function hideHeaderDropZone() {
- const headerDropArea = document.getElementById('headerDropArea');
- if (headerDropArea) {
- headerDropArea.classList.remove('drag-active');
- if (headerDropArea.children.length === 0) {
- headerDropArea.style.display = 'none';
+ if (!inserted) {
+ sidebar.appendChild(card);
+ }
+ // Ensure card fills the sidebar.
+ card.style.width = '100%';
+ animateVerticalSlide(card);
+ // if user dropped into sidebar, auto-un-collapse if currently collapsed
+ if (isZonesCollapsed()) setZonesCollapsed(false);
+}
+
+// Internal helper: save the current sidebar card order to localStorage.
+function saveSidebarOrder() {
+ const sidebar = getSidebar();
+ if (sidebar) {
+ const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard');
+ const order = Array.from(cards).map(card => card.id);
+ localStorage.setItem('sidebarOrder', JSON.stringify(order));
+ }
+}
+
+// Helper: move cards from sidebar back to the top drop area when on small screens.
+function moveSidebarCardsToTop() {
+ if (window.innerWidth < 1205) {
+ const sidebar = getSidebar();
+ if (!sidebar) return;
+ const cards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
+ cards.forEach(card => {
+ const orig = document.getElementById(card.dataset.originalContainerId);
+ if (orig) {
+ orig.appendChild(card);
+ animateVerticalSlide(card);
}
+ });
+ updateSidebarVisibility();
+ updateTopZoneLayout();
+ }
+}
+
+// Listen for window resize to automatically move sidebar cards back to top on small screens.
+window.addEventListener('resize', function () {
+ if (window.innerWidth < 1205) {
+ moveSidebarCardsToTop();
+ }
+});
+
+// This function ensures the top drop zone (#uploadFolderRow) has a stable width when empty.
+function ensureTopZonePlaceholder() {
+ const topZone = document.getElementById('uploadFolderRow');
+ if (!topZone) return;
+ if (topZone.querySelectorAll('#uploadCard, #folderManagementCard').length === 0) {
+ let placeholder = topZone.querySelector('.placeholder');
+ if (!placeholder) {
+ placeholder = document.createElement('div');
+ placeholder.className = 'placeholder';
+ placeholder.style.visibility = 'hidden';
+ placeholder.style.display = 'block';
+ placeholder.style.width = '100%';
+ placeholder.style.height = '375px';
+ topZone.appendChild(placeholder);
+ }
+ } else {
+ const placeholder = topZone.querySelector('.placeholder');
+ if (placeholder) placeholder.remove();
+ }
+}
+
+// --- Header drop zone helpers ---
+
+function showHeaderDropZone() {
+ const headerDropArea = document.getElementById('headerDropArea');
+ if (headerDropArea) {
+ headerDropArea.style.display = 'inline-flex';
+ headerDropArea.classList.add('drag-active');
+ }
+}
+
+function hideHeaderDropZone() {
+ const headerDropArea = document.getElementById('headerDropArea');
+ if (headerDropArea) {
+ headerDropArea.classList.remove('drag-active');
+ if (headerDropArea.children.length === 0) {
+ headerDropArea.style.display = 'none';
}
}
-
- // === NEW FUNCTION: Insert card into header drop zone as a material icon ===
- function insertCardInHeader(card, event) {
- const headerDropArea = document.getElementById('headerDropArea');
- if (!headerDropArea) return;
-
- // For folder management and upload cards, preserve the original by moving it to a hidden container.
- if (card.id === 'folderManagementCard' || card.id === 'uploadCard') {
- let hiddenContainer = document.getElementById('hiddenCardsContainer');
- if (!hiddenContainer) {
- hiddenContainer = document.createElement('div');
- hiddenContainer.id = 'hiddenCardsContainer';
- hiddenContainer.style.display = 'none';
- document.body.appendChild(hiddenContainer);
- }
- // Move the original card to the hidden container if it's not already there.
- if (card.parentNode.id !== 'hiddenCardsContainer') {
- hiddenContainer.appendChild(card);
- }
- } else {
- // For other cards, simply remove from current container.
- if (card.parentNode) {
- card.parentNode.removeChild(card);
- }
- }
-
- // Create the header icon button.
- const iconButton = document.createElement('button');
- iconButton.className = 'header-card-icon';
- // Remove default button styling.
- iconButton.style.border = 'none';
- iconButton.style.background = 'none';
- iconButton.style.outline = 'none';
- iconButton.style.cursor = 'pointer';
-
- // Choose an icon based on the card type with 24px size.
- if (card.id === 'uploadCard') {
- iconButton.innerHTML = 'cloud_upload';
- } else if (card.id === 'folderManagementCard') {
- iconButton.innerHTML = 'folder';
- } else {
- iconButton.innerHTML = 'insert_drive_file';
- }
-
- // Save a reference to the card in the icon button.
- iconButton.cardElement = card;
- // Associate this icon with the card for future removal.
- card.headerIconButton = iconButton;
-
- let modal = null;
- let isLocked = false;
- let hoverActive = false;
-
- // showModal: When triggered, ensure the card is attached to the modal.
- function showModal() {
- if (!modal) {
- modal = document.createElement('div');
- modal.className = 'header-card-modal';
- modal.style.position = 'fixed';
- modal.style.top = '55px';
- modal.style.right = '80px';
- modal.style.zIndex = '11000';
- // Render the modal but initially keep it hidden.
- modal.style.display = 'block';
- modal.style.visibility = 'hidden';
- modal.style.opacity = '0';
- modal.style.background = 'none';
- modal.style.border = 'none';
- modal.style.padding = '0';
- modal.style.boxShadow = 'none';
- document.body.appendChild(modal);
- // Attach modal hover events.
- modal.addEventListener('mouseover', handleMouseOver);
- modal.addEventListener('mouseout', handleMouseOut);
- iconButton.modalInstance = modal;
- }
- // If the card isn't already in the modal, remove it from the hidden container and attach it.
- if (!modal.contains(card)) {
- const hiddenContainer = document.getElementById('hiddenCardsContainer');
- if (hiddenContainer && hiddenContainer.contains(card)) {
- hiddenContainer.removeChild(card);
- }
- modal.appendChild(card);
- }
- // Reveal the modal.
- modal.style.visibility = 'visible';
- modal.style.opacity = '1';
- }
-
- // hideModal: Hide the modal and return the card to the hidden container.
- function hideModal() {
- if (modal && !isLocked && !hoverActive) {
- modal.style.visibility = 'hidden';
- modal.style.opacity = '0';
- // Return the card to the hidden container.
- const hiddenContainer = document.getElementById('hiddenCardsContainer');
- if (hiddenContainer && modal.contains(card)) {
- hiddenContainer.appendChild(card);
- }
- }
- }
-
- function handleMouseOver() {
- hoverActive = true;
- showModal();
- }
-
- function handleMouseOut() {
- hoverActive = false;
- setTimeout(() => {
- if (!hoverActive && !isLocked) {
- hideModal();
- }
- }, 300);
- }
-
- // Attach hover events to the icon.
- iconButton.addEventListener('mouseover', handleMouseOver);
- iconButton.addEventListener('mouseout', handleMouseOut);
-
- // Toggle the locked state on click so the modal stays open.
- iconButton.addEventListener('click', (e) => {
- isLocked = !isLocked;
- if (isLocked) {
- showModal();
- } else {
- hideModal();
- }
- e.stopPropagation();
+}
+
+// Insert card into header drop zone as a material icon
+function insertCardInHeader(card, event) {
+ const headerDropArea = document.getElementById('headerDropArea');
+ if (!headerDropArea) return;
+
+ // Preserve the original by moving it to a hidden container.
+ if (card.id === 'folderManagementCard' || card.id === 'uploadCard') {
+ let hiddenContainer = document.getElementById('hiddenCardsContainer');
+ if (!hiddenContainer) {
+ hiddenContainer = document.createElement('div');
+ hiddenContainer.id = 'hiddenCardsContainer';
+ hiddenContainer.style.display = 'none';
+ document.body.appendChild(hiddenContainer);
+ }
+ if (card.parentNode?.id !== 'hiddenCardsContainer') {
+ hiddenContainer.appendChild(card);
+ }
+ } else if (card.parentNode) {
+ card.parentNode.removeChild(card);
+ }
+
+ const iconButton = document.createElement('button');
+ iconButton.className = 'header-card-icon';
+ iconButton.style.border = 'none';
+ iconButton.style.background = 'none';
+ iconButton.style.outline = 'none';
+ iconButton.style.cursor = 'pointer';
+
+ if (card.id === 'uploadCard') {
+ iconButton.innerHTML = 'cloud_upload';
+ } else if (card.id === 'folderManagementCard') {
+ iconButton.innerHTML = 'folder';
+ } else {
+ iconButton.innerHTML = 'insert_drive_file';
+ }
+
+ iconButton.cardElement = card;
+ card.headerIconButton = iconButton;
+
+ let modal = null;
+ let isLocked = false;
+ let hoverActive = false;
+
+ function showModal() {
+ if (!modal) {
+ modal = document.createElement('div');
+ modal.className = 'header-card-modal';
+ Object.assign(modal.style, {
+ position: 'fixed',
+ top: '55px',
+ right: '80px',
+ zIndex: '11000',
+ display: 'block',
+ visibility: 'hidden',
+ opacity: '0',
+ background: 'none',
+ border: 'none',
+ padding: '0',
+ boxShadow: 'none',
});
-
- // Append the header icon button into the header drop zone.
- headerDropArea.appendChild(iconButton);
- // Save the updated header order.
- saveHeaderOrder();
+ document.body.appendChild(modal);
+ modal.addEventListener('mouseover', handleMouseOver);
+ modal.addEventListener('mouseout', handleMouseOut);
+ iconButton.modalInstance = modal;
}
-
- // === Main Drag and Drop Initialization ===
- export function initDragAndDrop() {
- function run() {
- const draggableCards = document.querySelectorAll('#uploadCard, #folderManagementCard');
- draggableCards.forEach(card => {
- if (!card.dataset.originalContainerId) {
- card.dataset.originalContainerId = card.parentNode.id;
- }
- const header = card.querySelector('.card-header');
- if (header) {
- header.classList.add('drag-header');
- }
-
- let isDragging = false;
- let dragTimer = null;
- let offsetX = 0, offsetY = 0;
- let initialLeft, initialTop;
-
- if (header) {
- header.addEventListener('mousedown', function (e) {
- e.preventDefault();
- const card = this.closest('.card');
- // Capture the card's initial bounding rectangle.
- const initialRect = card.getBoundingClientRect();
- const originX = ((e.clientX - initialRect.left) / initialRect.width) * 100;
- const originY = ((e.clientY - initialRect.top) / initialRect.height) * 100;
- card.style.transformOrigin = `${originX}% ${originY}%`;
-
- // Store the initial rect so we use it later.
- dragTimer = setTimeout(() => {
- isDragging = true;
- card.classList.add('dragging');
- card.style.pointerEvents = 'none';
- addTopZoneHighlight();
-
- const sidebar = document.getElementById('sidebarDropArea');
- if (sidebar) {
- sidebar.classList.add('active');
- sidebar.style.display = 'block';
- sidebar.classList.add('highlight');
- sidebar.style.height = '800px';
- }
-
- // Show header drop zone while dragging.
- showHeaderDropZone();
-
- // Use the stored initialRect.
- initialLeft = initialRect.left + window.pageXOffset;
- initialTop = initialRect.top + window.pageYOffset;
- offsetX = e.pageX - initialLeft;
- offsetY = e.pageY - initialTop;
-
- // Remove any associated header icon if present.
- if (card.headerIconButton) {
- if (card.headerIconButton.parentNode) {
- card.headerIconButton.parentNode.removeChild(card.headerIconButton);
- }
- if (card.headerIconButton.modalInstance && card.headerIconButton.modalInstance.parentNode) {
- card.headerIconButton.modalInstance.parentNode.removeChild(card.headerIconButton.modalInstance);
- }
- card.headerIconButton = null;
- saveHeaderOrder();
- }
-
- // Append card to body and fix its dimensions.
- document.body.appendChild(card);
- card.style.position = 'absolute';
- card.style.left = initialLeft + 'px';
- card.style.top = initialTop + 'px';
- card.style.width = initialRect.width + 'px';
- card.style.height = initialRect.height + 'px';
- card.style.minWidth = initialRect.width + 'px';
- card.style.flexShrink = '0';
- card.style.zIndex = '10000';
- }, 500);
- });
- header.addEventListener('mouseup', function () {
- clearTimeout(dragTimer);
- });
- }
-
- document.addEventListener('mousemove', function (e) {
- if (isDragging) {
- card.style.left = (e.pageX - offsetX) + 'px';
- card.style.top = (e.pageY - offsetY) + 'px';
- }
- });
-
- document.addEventListener('mouseup', function (e) {
- if (isDragging) {
- isDragging = false;
- card.style.pointerEvents = '';
- card.classList.remove('dragging');
- removeTopZoneHighlight();
-
- const sidebar = document.getElementById('sidebarDropArea');
+ if (!modal.contains(card)) {
+ const hiddenContainer = document.getElementById('hiddenCardsContainer');
+ if (hiddenContainer && hiddenContainer.contains(card)) {
+ hiddenContainer.removeChild(card);
+ }
+ modal.appendChild(card);
+ }
+ modal.style.visibility = 'visible';
+ modal.style.opacity = '1';
+ }
+
+ function hideModal() {
+ if (modal && !isLocked && !hoverActive) {
+ modal.style.visibility = 'hidden';
+ modal.style.opacity = '0';
+ const hiddenContainer = document.getElementById('hiddenCardsContainer');
+ if (hiddenContainer && modal.contains(card)) {
+ hiddenContainer.appendChild(card);
+ }
+ }
+ }
+
+ function handleMouseOver() {
+ hoverActive = true;
+ showModal();
+ }
+
+ function handleMouseOut() {
+ hoverActive = false;
+ setTimeout(() => {
+ if (!hoverActive && !isLocked) {
+ hideModal();
+ }
+ }, 300);
+ }
+
+ iconButton.addEventListener('mouseover', handleMouseOver);
+ iconButton.addEventListener('mouseout', handleMouseOut);
+
+ iconButton.addEventListener('click', (e) => {
+ isLocked = !isLocked;
+ if (isLocked) showModal();
+ else hideModal();
+ e.stopPropagation();
+ });
+
+ headerDropArea.appendChild(iconButton);
+ saveHeaderOrder();
+}
+
+// === Main Drag and Drop Initialization ===
+export function initDragAndDrop() {
+ function run() {
+ // make sure toggle exists even if user hasn't dragged yet
+ // ensureSidebarToggle();
+ //applySidebarCollapsed();
+ applyZonesCollapsed();
+ ensureZonesToggle();
+
+ const draggableCards = document.querySelectorAll('#uploadCard, #folderManagementCard');
+ draggableCards.forEach(card => {
+ if (!card.dataset.originalContainerId && card.parentNode) {
+ card.dataset.originalContainerId = card.parentNode.id;
+ }
+ const header = card.querySelector('.card-header');
+ if (header) {
+ header.classList.add('drag-header');
+ }
+
+ let isDragging = false;
+ let dragTimer = null;
+ let offsetX = 0, offsetY = 0;
+ let initialLeft, initialTop;
+
+ if (header) {
+ header.addEventListener('mousedown', function (e) {
+ e.preventDefault();
+ const card = this.closest('.card');
+ const initialRect = card.getBoundingClientRect();
+ const originX = ((e.clientX - initialRect.left) / initialRect.width) * 100;
+ const originY = ((e.clientY - initialRect.top) / initialRect.height) * 100;
+ card.style.transformOrigin = `${originX}% ${originY}%`;
+
+ dragTimer = setTimeout(() => {
+ isDragging = true;
+ card.classList.add('dragging');
+ card.style.pointerEvents = 'none';
+ addTopZoneHighlight();
+
+ const sidebar = getSidebar();
if (sidebar) {
- sidebar.classList.remove('highlight');
- sidebar.style.height = '';
+ sidebar.classList.add('active');
+ sidebar.style.display = isSidebarCollapsed() ? 'none' : 'block';
+ sidebar.classList.add('highlight');
+ sidebar.style.height = '800px';
}
-
- // Remove any existing header icon if present.
+
+ showHeaderDropZone();
+
+ initialLeft = initialRect.left + window.pageXOffset;
+ initialTop = initialRect.top + window.pageYOffset;
+ offsetX = e.pageX - initialLeft;
+ offsetY = e.pageY - initialTop;
+
if (card.headerIconButton) {
if (card.headerIconButton.parentNode) {
card.headerIconButton.parentNode.removeChild(card.headerIconButton);
@@ -541,111 +698,159 @@ export function loadSidebarOrder() {
card.headerIconButton = null;
saveHeaderOrder();
}
-
- let droppedInSidebar = false;
- let droppedInTop = false;
- let droppedInHeader = false;
-
- // Check if dropped in sidebar drop zone.
- const sidebarElem = document.getElementById('sidebarDropArea');
- if (sidebarElem) {
- const rect = sidebarElem.getBoundingClientRect();
- const dropZoneBottom = rect.top + 800; // Virtual drop zone height.
- if (
- e.clientX >= rect.left &&
- e.clientX <= rect.right &&
- e.clientY >= rect.top &&
- e.clientY <= dropZoneBottom
- ) {
- insertCardInSidebar(card, e);
- droppedInSidebar = true;
- }
- }
- // Check the top drop zone.
- const topRow = document.getElementById('uploadFolderRow');
- if (!droppedInSidebar && topRow) {
- const rect = topRow.getBoundingClientRect();
- if (
- e.clientX >= rect.left &&
- e.clientX <= rect.right &&
- e.clientY >= rect.top &&
- e.clientY <= rect.bottom
- ) {
- let container;
- if (card.id === 'uploadCard') {
- container = document.getElementById('leftCol');
- } else if (card.id === 'folderManagementCard') {
- container = document.getElementById('rightCol');
- }
- if (container) {
- ensureTopZonePlaceholder();
- updateTopZoneLayout();
- container.appendChild(card);
- droppedInTop = true;
- // Set a fixed width during animation.
- card.style.width = "363px";
- animateVerticalSlide(card);
- setTimeout(() => {
- card.style.removeProperty('width');
- }, 210);
- }
- }
- }
- // Check the header drop zone.
- const headerDropArea = document.getElementById('headerDropArea');
- if (!droppedInSidebar && !droppedInTop && headerDropArea) {
- const rect = headerDropArea.getBoundingClientRect();
- if (
- e.clientX >= rect.left &&
- e.clientX <= rect.right &&
- e.clientY >= rect.top &&
- e.clientY <= rect.bottom
- ) {
- insertCardInHeader(card, e);
- droppedInHeader = true;
- }
- }
- // If card was not dropped in any zone, return it to its original container.
- if (!droppedInSidebar && !droppedInTop && !droppedInHeader) {
- const orig = document.getElementById(card.dataset.originalContainerId);
- if (orig) {
- orig.appendChild(card);
- card.style.removeProperty('width');
- }
- }
-
- // Clear inline drag-related styles.
- [
- 'position',
- 'left',
- 'top',
- 'z-index',
- 'height',
- 'min-width',
- 'flex-shrink',
- 'transition',
- 'transform',
- 'opacity'
- ].forEach(prop => card.style.removeProperty(prop));
-
- // For sidebar drops, force width to 100%.
- if (droppedInSidebar) {
- card.style.width = '100%';
- }
-
- updateTopZoneLayout();
- updateSidebarVisibility();
-
- // Hide header drop zone if no icon is present.
- hideHeaderDropZone();
- }
+
+ document.body.appendChild(card);
+ card.style.position = 'absolute';
+ card.style.left = initialLeft + 'px';
+ card.style.top = initialTop + 'px';
+ card.style.width = initialRect.width + 'px';
+ card.style.height = initialRect.height + 'px';
+ card.style.minWidth = initialRect.width + 'px';
+ card.style.flexShrink = '0';
+ card.style.zIndex = '10000';
+ }, 500);
});
+
+ header.addEventListener('mouseup', function () {
+ clearTimeout(dragTimer);
+ });
+ }
+
+ document.addEventListener('mousemove', function (e) {
+ if (isDragging) {
+ card.style.left = (e.pageX - offsetX) + 'px';
+ card.style.top = (e.pageY - offsetY) + 'px';
+ }
});
- }
-
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', run);
- } else {
- run();
- }
- }
\ No newline at end of file
+
+ document.addEventListener('mouseup', function (e) {
+ if (isDragging) {
+ isDragging = false;
+ card.style.pointerEvents = '';
+ card.classList.remove('dragging');
+ removeTopZoneHighlight();
+
+ const sidebar = getSidebar();
+ if (sidebar) {
+ sidebar.classList.remove('highlight');
+ sidebar.style.height = '';
+ }
+
+ if (card.headerIconButton) {
+ if (card.headerIconButton.parentNode) {
+ card.headerIconButton.parentNode.removeChild(card.headerIconButton);
+ }
+ if (card.headerIconButton.modalInstance && card.headerIconButton.modalInstance.parentNode) {
+ card.headerIconButton.modalInstance.parentNode.removeChild(card.headerIconButton.modalInstance);
+ }
+ card.headerIconButton = null;
+ saveHeaderOrder();
+ }
+
+ let droppedInSidebar = false;
+ let droppedInTop = false;
+ let droppedInHeader = false;
+
+ // Check if dropped in sidebar drop zone.
+ const sidebarElem = getSidebar();
+ if (sidebarElem) {
+ const rect = sidebarElem.getBoundingClientRect();
+ const dropZoneBottom = rect.top + 800; // Virtual drop zone height.
+ if (
+ e.clientX >= rect.left &&
+ e.clientX <= rect.right &&
+ e.clientY >= rect.top &&
+ e.clientY <= dropZoneBottom
+ ) {
+ insertCardInSidebar(card, e);
+ droppedInSidebar = true;
+ }
+ }
+
+ // Check the top drop zone.
+ const topRow = document.getElementById('uploadFolderRow');
+ if (!droppedInSidebar && topRow) {
+ const rect = topRow.getBoundingClientRect();
+ if (
+ e.clientX >= rect.left &&
+ e.clientX <= rect.right &&
+ e.clientY >= rect.top &&
+ e.clientY <= rect.bottom
+ ) {
+ let container;
+ if (card.id === 'uploadCard') {
+ container = document.getElementById('leftCol');
+ } else if (card.id === 'folderManagementCard') {
+ container = document.getElementById('rightCol');
+ }
+ if (container) {
+ ensureTopZonePlaceholder();
+ updateTopZoneLayout();
+ container.appendChild(card);
+ droppedInTop = true;
+ card.style.width = "363px";
+ animateVerticalSlide(card);
+ setTimeout(() => {
+ card.style.removeProperty('width');
+ }, 210);
+ }
+ }
+ }
+
+ // Check the header drop zone.
+ const headerDropArea = document.getElementById('headerDropArea');
+ if (!droppedInSidebar && !droppedInTop && headerDropArea) {
+ const rect = headerDropArea.getBoundingClientRect();
+ if (
+ e.clientX >= rect.left &&
+ e.clientX <= rect.right &&
+ e.clientY >= rect.top &&
+ e.clientY <= rect.bottom
+ ) {
+ insertCardInHeader(card, e);
+ droppedInHeader = true;
+ }
+ }
+
+ // If card was not dropped in any zone, return it to its original container.
+ if (!droppedInSidebar && !droppedInTop && !droppedInHeader) {
+ const orig = document.getElementById(card.dataset.originalContainerId);
+ if (orig) {
+ orig.appendChild(card);
+ card.style.removeProperty('width');
+ }
+ }
+
+ // Clear inline drag-related styles.
+ [
+ 'position',
+ 'left',
+ 'top',
+ 'z-index',
+ 'height',
+ 'min-width',
+ 'flex-shrink',
+ 'transition',
+ 'transform',
+ 'opacity'
+ ].forEach(prop => card.style.removeProperty(prop));
+
+ // For sidebar drops, force width to 100%.
+ if (droppedInSidebar) {
+ card.style.width = '100%';
+ }
+
+ updateTopZoneLayout();
+ updateSidebarVisibility();
+ hideHeaderDropZone();
+ }
+ });
+ });
+ }
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', run);
+ } else {
+ run();
+ }
+}
\ No newline at end of file