From a7ef2c9d7f459145fac5b8a78679aa71b292397c Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Tue, 26 May 2026 20:11:26 -0700 Subject: browser+resize: implement proper faux browser + resizable windows per design handoff Browser window (w-browser, Internet icon): - Full aqua browser chrome: toolbar with back/fwd/reload/home SVG nav buttons, URL bar (lock + scheme + host + path + blinking caret), bookmark chips with colored 12x12 favicon squares, scrollable page surface, status bar with animated progress bar - History stack (back/forward state, disable at ends) - Delegated click handler via data-go attributes for in-page navigation - Refresh button spins 700ms via CSS animation - Address bar + title bar update on every navigation - ARTICLES array with 4 entries (self-hosting, jazz, cooking, film) with drop-cap, Georgia body, IBM Plex Mono metadata, blockquotes, inline ilinks - Chrome theme: brushed mercury toolbar, dark navy article surface with iridescent radial hotspots, Audiowide titles with chromeShimmer drop-cap, Michroma UI labels, iridescent progress bar, custom scrollbar Resizable windows: - makeResizable() in aero.js: appends .rs-e (right edge), .rs-s (bottom edge), .rs-se (SE grip) handles; tracks mousedown/move/up; enforces minW/minH - .win.resized flex-column flip: body fills remaining height and scrolls - Aero grip (3-stripe diagonal, blue); Chrome grip (iridescent purple/cyan/pink) - Body cursor forced via body.rs-cursor-* classes during drag CSS in aero.css: chrome overrides for resize handle + full browser window theme CSS in index.html: resize handle rules, full browser/article reading styles Co-Authored-By: Claude Sonnet 4.6 --- aero.js | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) (limited to 'aero.js') diff --git a/aero.js b/aero.js index 9429ccf..8a950e9 100644 --- a/aero.js +++ b/aero.js @@ -83,6 +83,47 @@ function makeDraggable(el, handle) { window.addEventListener('mouseup', () => { dragging = false; }); } +function makeResizable(el, options = {}) { + const minW = options.minW || 280; + const minH = options.minH || 180; + const handles = ['e', 's', 'se']; + handles.forEach(mode => { + const h = document.createElement('div'); + h.className = 'rs rs-' + mode + ' no-drag'; + el.appendChild(h); + h.addEventListener('mousedown', (e) => startResize(e, mode)); + }); + let mode = null, sx = 0, sy = 0, sw = 0, sh = 0; + function startResize(e, m) { + e.preventDefault(); e.stopPropagation(); + mode = m; + sx = e.clientX; sy = e.clientY; + const r = el.getBoundingClientRect(); + sw = r.width; sh = r.height; + // pin current size as inline so first move doesn't snap + el.style.width = sw + 'px'; + el.style.height = sh + 'px'; + el.classList.add('resized'); + el.style.zIndex = (++window.__zTop || (window.__zTop = 100)); + document.body.classList.add('rs-cursor-' + m); + window.addEventListener('mousemove', onMove); + window.addEventListener('mouseup', onUp); + } + function onMove(e) { + if (!mode) return; + const dx = e.clientX - sx; + const dy = e.clientY - sy; + if (mode.includes('e')) el.style.width = Math.max(minW, sw + dx) + 'px'; + if (mode.includes('s')) el.style.height = Math.max(minH, sh + dy) + 'px'; + } + function onUp() { + mode = null; + document.body.classList.remove('rs-cursor-e', 'rs-cursor-s', 'rs-cursor-se'); + window.removeEventListener('mousemove', onMove); + window.removeEventListener('mouseup', onUp); + } +} + function counterHTML(val = 42137, label = "visitors") { const digits = String(val).padStart(7, '0').split('').map(d => `
${d}
`).join(''); return `
${digits}
${label}
`; @@ -228,7 +269,7 @@ async function fetchReelMouthFeed(limit = 6) { } window.Aero = { - spawnBubbles, makeClouds, sparkleCursor, makeDraggable, + spawnBubbles, makeClouds, sparkleCursor, makeDraggable, makeResizable, counterHTML, nowPlayingHTML, animateEq, musicToggleHTML, bindMusicToggle, // theme api getTheme, setTheme, mountThemeSwitcher, initTheme, THEMES, -- cgit v1.3-2-g0d8e