This is a simple floating window class made by Gemini. ## Prompts ```plaintext In HTML and Javascript, some attractive looking openable, draggable, possibly resizeable, closeable windows. Overflow options can be hidden, scroll. Also a resize to contents button. With an X in the corner to close. As a Javascript class that we subclass to define the contents and configuration for. And necessary CSS for styling. yes please. and an example html file with clickable elements that open various windows. Snapping to the siees works. But could the windows remember their unsnapped size to return to when unsnapping from an an edge. I don't like the snapping animation. ``` ## CSS ```css :root { --win-bg: #ffffff; --win-border: #bdc3c7; --win-header: #ecf0f1; --win-shadow: 0 10px 30px rgba(0,0,0,0.3); --taskbar-bg: #2c3e50; --accent: #3498db; } body { margin: 0; height: 100vh; overflow: hidden; /* Prevent body scroll while dragging */ } /* The Window Container */ .win-instance { position: absolute; min-width: 200px; min-height: 150px; background: var(--win-bg); border: 1px solid var(--win-border); box-shadow: var(--win-shadow); display: flex; flex-direction: column; border-radius: 8px; overflow: hidden; z-index: 10; xxtransition: width 0.15s ease-out, height 0.15s ease-out; /* Smooth snapping */ } /* Header/Title Bar */ .win-header { padding: 10px 15px; background: var(--win-header); cursor: move; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--win-border); user-select: none; } .win-title { font-weight: bold; font-size: 14px; color: #333; } /* Window Buttons */ .win-controls button { margin-left: 4px; cursor: pointer; border: none; background: #d5dbdb; border-radius: 4px; padding: 4px 10px; font-size: 12px; transition: background 0.2s; } .win-controls button:hover { background: #bdc3c7; } .win-close:hover { background: #e74c3c !important; color: white; } /* Content Area */ .win-content { flex-grow: 1; padding: 15px; position: relative; background: white; } /* Resize Handle */ .resizer { width: 16px; height: 16px; position: absolute; right: 0; bottom: 0; cursor: nwse-resize; background: linear-gradient(135deg, transparent 50%, #bdc3c7 50%); } /* Taskbar */ #taskbar { position: fixed; bottom: 0; left: 0; right: 0; height: 45px; background: var(--taskbar-bg); display: flex; padding: 0 10px; gap: 8px; align-items: center; z-index: 10000; } .task-item { background: #34495e; color: white; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 13px; border-bottom: 2px solid transparent; } .task-item:hover { background: #456789; } .task-item.active { border-bottom: 2px solid var(--accent); } /* States */ .win-minimized { display: none !important; } ``` ## Javascript ```js class FloatingWindow { constructor(title, options = {}) { this.title = title; this.width = options.width || '400px'; this.height = options.height || '300px'; this.overflow = options.overflow || 'auto'; this.id = 'win-' + Math.random().toString(36).substr(2, 9); // State Tracking this.isSnapped = false; this.previousState = { width: this.width, height: this.height, top: '100px', left: '100px' }; this.init(); } init() { this.el = document.createElement('div'); this.el.className = 'win-instance'; this.el.id = this.id; this.el.style.width = this.width; this.el.style.height = this.height; this.el.style.left = '100px'; this.el.style.top = '100px'; this.el.innerHTML = `
${this.title}
`; this.contentArea = this.el.querySelector('.win-content'); document.body.appendChild(this.el); this.createTaskbarItem(); this.setupEvents(); this.bringToFront(); this.render(); // Subclass content } createTaskbarItem() { const bar = document.getElementById('taskbar'); if (!bar) return; const btn = document.createElement('div'); btn.className = 'task-item'; btn.id = 'task-' + this.id; btn.innerText = this.title; btn.onclick = () => this.toggleMinimize(); bar.appendChild(btn); } bringToFront() { document.querySelectorAll('.win-instance').forEach(w => w.style.zIndex = 10); document.querySelectorAll('.task-item').forEach(t => t.classList.remove('active')); this.el.style.zIndex = 100; const tItem = document.getElementById('task-' + this.id); if (tItem) tItem.classList.add('active'); } toggleMinimize() { if (this.el.classList.contains('win-minimized')) { this.el.classList.remove('win-minimized'); this.bringToFront(); } else { this.el.classList.add('win-minimized'); } } setupEvents() { const header = this.el.querySelector('.win-header'); // Header Buttons this.el.querySelector('.win-close').onclick = () => { this.el.remove(); const tItem = document.getElementById('task-' + this.id); if (tItem) tItem.remove(); }; this.el.querySelector('.win-min').onclick = () => this.toggleMinimize(); this.el.querySelector('.win-fit').onclick = () => { this.isSnapped = false; this.el.style.width = 'auto'; this.el.style.height = 'auto'; }; // Mouse Interaction this.el.onmousedown = () => this.bringToFront(); header.onmousedown = (e) => this.startDrag(e); this.el.querySelector('.resizer').onmousedown = (e) => this.startResize(e); } startDrag(e) { let pos1 = 0, pos2 = 0, pos3 = e.clientX, pos4 = e.clientY; document.onmousemove = (moveEvent) => { // 1. If snapped, restore before moving further if (this.isSnapped) { this.restoreFromSnap(moveEvent.clientX); // Recalculate positions after restore to avoid jumping pos3 = moveEvent.clientX; pos4 = moveEvent.clientY; } pos1 = pos3 - moveEvent.clientX; pos2 = pos4 - moveEvent.clientY; pos3 = moveEvent.clientX; pos4 = moveEvent.clientY; let newTop = this.el.offsetTop - pos2; let newLeft = this.el.offsetLeft - pos1; // 2. Check for Snap Zones if (moveEvent.clientX < 10) { this.applySnap(0, 0, '50%', 'calc(100% - 45px)'); } else if (moveEvent.clientX > window.innerWidth - 10) { this.applySnap('50%', 0, '50%', 'calc(100% - 45px)'); } else if (moveEvent.clientY < 10) { this.applySnap(0, 0, '100%', 'calc(100% - 45px)'); } else { this.el.style.top = newTop + "px"; this.el.style.left = newLeft + "px"; } }; document.onmouseup = () => document.onmousemove = null; } applySnap(left, top, w, h) { if (!this.isSnapped) { this.previousState = { width: this.el.style.width, height: this.el.style.height, top: this.el.style.top, left: this.el.style.left }; this.isSnapped = true; } this.el.style.left = left; this.el.style.top = top; this.el.style.width = w; this.el.style.height = h; } restoreFromSnap(mouseX) { this.isSnapped = false; this.el.style.width = this.previousState.width; this.el.style.height = this.previousState.height; // Center window under cursor horizontally during pull-away const w = parseInt(this.el.style.width) || 400; this.el.style.left = (mouseX - (w / 2)) + 'px'; } startResize(e) { e.preventDefault(); e.stopPropagation(); this.isSnapped = false; // Resizing breaks snap document.onmousemove = (ev) => { this.el.style.width = (ev.clientX - this.el.offsetLeft) + 'px'; this.el.style.height = (ev.clientY - this.el.offsetTop) + 'px'; }; document.onmouseup = () => document.onmousemove = null; } render() {} // To be overridden } ``` ## HTML Example Usage Assuming CSS is in `win1.css` and Javascript in `win1.js`. See the example [here](win1.html). ```html JS Window System

Dashboard

```