// ---- Context menu (right-click) for icon removal ---- function onIconContextMenu(e, icon) e.preventDefault(); removeContextMenu(); // remove existing const menu = document.createElement('div'); menu.className = 'context-menu'; // position near cursor menu.style.left = `$e.clientXpx`; menu.style.top = `$e.clientYpx`; const removeOption = document.createElement('div'); removeOption.className = 'context-menu-item'; removeOption.innerHTML = '🗑️ Remove icon'; removeOption.addEventListener('click', (ev) => ev.stopPropagation(); deleteIconById(icon.id); removeContextMenu(); ); const infoOption = document.createElement('div'); infoOption.className = 'context-menu-item'; infoOption.innerHTML = 'ℹ️ About'; infoOption.addEventListener('click', (ev) => ev.stopPropagation(); showToast(`$icon.name — small desktop icon ); const divider = document.createElement('div'); divider.className = 'context-menu-divider'; const resetOption = document.createElement('div'); resetOption.className = 'context-menu-item'; resetOption.innerHTML = '🔄 Reset all positions'; resetOption.addEventListener('click', (ev) => ev.stopPropagation(); resetToDefaultGrid(); removeContextMenu(); ); menu.appendChild(removeOption); menu.appendChild(infoOption); menu.appendChild(divider); menu.appendChild(resetOption); document.body.appendChild(menu); activeContextMenu = menu; // click outside to close const closeHandler = (clickEvent) => if (!menu.contains(clickEvent.target)) removeContextMenu(); document.removeEventListener('click', closeHandler); ; setTimeout(() => document.addEventListener('click', closeHandler), 10); // ---- Render all icons from iconsState ---- function renderAllIcons() if (!desktopEl) return; desktopEl.innerHTML = ''; // clear all iconsState.forEach(icon => const iconDiv = document.createElement('div'); iconDiv.className = 'desktop-icon'; iconDiv.setAttribute('data-id', icon.id); iconDiv.style.left = `$icon.xpx`; iconDiv.style.top = `$icon.ypx`; iconDiv.style.zIndex = 10; // graphic container const graphicDiv = document.createElement('div'); graphicDiv.className = 'icon-graphic'; // use emoji or custom svg text const emojiSpan = document.createElement('span'); emojiSpan.style.fontSize = '32px'; emojiSpan.style.filter = `drop-shadow(0 2px 4px rgba(0,0,0,0.4))`; emojiSpan.textContent = icon.emoji; graphicDiv.appendChild(emojiSpan); const labelSpan = document.createElement('span'); labelSpan.className = 'icon-label'; labelSpan.textContent = icon.name; iconDiv.appendChild(graphicDiv); iconDiv.appendChild(labelSpan); // attach event listeners iconDiv.addEventListener('mousedown', (e) => onMouseDown(e, icon)); iconDiv.addEventListener('dblclick', (e) => e.stopPropagation(); onDoubleClickIcon(icon); ); iconDiv.addEventListener('contextmenu', (e) => onIconContextMenu(e, icon)); desktopEl.appendChild(iconDiv); ); // ---- initialize icons: build state, load stored positions or default grid ---- function initIcons() // build iconsState from DB iconsState = ICON_DB.map(db => ( id: db.id, name: db.name, emoji: db.emoji, colorTint: db.colorTint, x: 0, y: 0 )); // get container dimensions const rect = desktopEl.getBoundingClientRect(); const defaultMarginX = 40; const defaultMarginY = 40; const colStep = 110; const rowStep = 120; const cols = 3; iconsState.forEach((icon, idx) => const col = idx % cols; const row = Math.floor(idx / cols); icon.x = defaultMarginX + col * colStep; icon.y = defaultMarginY + row * rowStep; ); // try load stored positions after creating default grid const hasStored = loadStoredPositions(); // if stored positions are loaded, they overwrite defaults // but also ensure they are in bounds after loading clampIconPositions(); // will adjust if needed and re-render renderAllIcons(); if (!hasStored) // initially persist default grid persistPositions(); // ---- global desktop right click: reset menu or add? ---- function onDesktopContextMenu(e) e.preventDefault(); removeContextMenu(); const menu = document.createElement('div'); menu.className = 'context-menu'; menu.style.left = `$e.clientXpx`; menu.style.top = `$e.clientYpx`; const resetPos = document.createElement('div'); resetPos.className = 'context-menu-item'; resetPos.innerHTML = '📌 Reset icon positions'; resetPos.addEventListener('click', () => resetToDefaultGrid(); removeContextMenu(); ); const refreshHint = document.createElement('div'); refreshHint.className = 'context-menu-item'; refreshHint.innerHTML = '🖱️ Double-click any icon'; refreshHint.addEventListener('click', () => showToast('Double-click icons to open apps! Right-click to remove.', 1500); removeContextMenu(); ); menu.appendChild(resetPos); menu.appendChild(refreshHint); document.body.appendChild(menu); activeContextMenu = menu; const closeHandler = (ce) => if (!menu.contains(ce.target)) removeContextMenu(); document.removeEventListener('click', closeHandler); ; setTimeout(() => document.addEventListener('click', closeHandler), 10); // ---- resize handler to keep icons visible ---- function handleResize() if (!desktopEl) return; clampIconPositions(); // re-render to refresh positions from updated state renderAllIcons(); // ---- set up event listeners, prevent default context menu on desktop ---- function bindGlobalEvents() desktopEl.addEventListener('contextmenu', onDesktopContextMenu); window.addEventListener('resize', () => handleResize(); ); // clicking anywhere outside context menu: handled but also any click on desktop removes menu window.addEventListener('click', (e) => if (activeContextMenu && !activeContextMenu.contains(e.target)) removeContextMenu(); ); // optional: disable default drag behaviour on images window.addEventListener('dragstart', (e) => e.preventDefault()); // ready to launch function launchDesktop() initIcons(); bindGlobalEvents(); // extra: initial clamp after fonts loaded setTimeout(() => handleResize(), 50); launchDesktop(); </script> </body> </html>
// double-click handler: open action (simulate app launch) function onDoubleClickIcon(icon) // add bounce animation class const iconElement = document.querySelector(`.desktop-icon[data-id='$icon.id']`); if (iconElement) iconElement.classList.add('icon-bounce'); setTimeout(() => iconElement.classList.remove('icon-bounce'), 220); // custom action based on icon id let message = `📂 Opened "$icon.name"`; if (icon.id === 'app_music') message = `🎵 Now playing: lo-fi desktop waves · ♪`; else if (icon.id === 'app_terminal') message = `💻 $> welcome ~ Desktop ready.`; else if (icon.id === 'app_settings') message = `⚙️ Display preferences: icon drag enabled.`; else if (icon.id === 'trash_bin') message = `🗑️ Trash is empty · double-click to delete? (right-click to remove any icon)`; else if (icon.id === 'folder_docs') message = `📄 Documents · nothing here yet.`; else if (icon.id === 'folder_pics') message = `🌄 Gallery preview: beautiful canvas desktop.`; showToast(message, 1600); console.log(`[Launch] $icon.name double-clicked`); small icons on desktop
/* icon text label */ .icon-label font-size: 12px; font-weight: 500; color: #fef7e0; text-shadow: 0 1px 3px rgba(0,0,0,0.6); background: rgba(0, 0, 0, 0.45); backdrop-filter: blur(4px); padding: 4px 8px; border-radius: 20px; max-width: 92px; white-space: nowrap; overflow-x: hidden; text-overflow: ellipsis; letter-spacing: 0.3px; transition: background 0.1s; // ---- Context menu (right-click) for icon removal
// save icons positions to localStorage function persistPositions() const positions = iconsState.map(icon => ( id: icon.id, x: icon.x, y: icon.y )); localStorage.setItem('desktopIconsLayout', JSON.stringify(positions)); (right-click to remove any icon)`; else if (icon