experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(pagestream): opaque cards, inline browsing, remove blue border, scroll fixes

- Replace translucent rgba backgrounds with opaque var(--base01/02/03) for cards, search, navbar
- Remove backdrop-filter blur from cards and inputs
- Remove scale transforms on active/inactive cards (no more width mismatch with navbar)
- Remove blue border/outline on active card by not setting selected/elevated attributes
- Active card now uses class-based highlighting with proper padding
- Enter key directly opens active card URL inline instead of calling click()
- Pages open in a full-viewport webview overlay instead of a new window
- Escape closes the browse overlay and returns to card stream
- Browse overlay includes its own peek-navbar with back/forward/reload and URL bar
- Scroll to bottom on load wrapped in requestAnimationFrame for reliable timing
- Keyboard navigation disabled during inline browse mode

+156 -54
+38 -19
extensions/pagestream/home.css
··· 33 33 peek-input.search-input { 34 34 display: block; 35 35 width: 100%; 36 - --peek-input-bg: rgba(var(--base01-rgb, 30, 30, 46), 0.8); 37 - --peek-input-border: rgba(var(--base02-rgb, 45, 45, 65), 0.6); 36 + --peek-input-bg: var(--base01); 37 + --peek-input-border: var(--base02); 38 38 --peek-input-height: 36px; 39 - backdrop-filter: blur(8px); 40 - -webkit-backdrop-filter: blur(8px); 41 39 border-radius: 8px; 42 40 } 43 41 ··· 59 57 60 58 /* Cards in the stream */ 61 59 .stream-container peek-card { 62 - --peek-card-bg: rgba(var(--base01-rgb, 30, 30, 46), 0.85); 63 - --peek-card-hover-bg: rgba(var(--base02-rgb, 45, 45, 65), 0.9); 60 + --peek-card-bg: var(--base01); 61 + --peek-card-hover-bg: var(--base02); 64 62 --peek-card-border: transparent; 65 63 --peek-card-radius: 8px; 66 64 --peek-card-padding: 10px; 67 65 --peek-card-gap: 4px; 68 66 flex-shrink: 0; 69 67 transition: transform 0.25s ease, box-shadow 0.25s ease, padding 0.25s ease, font-size 0.25s ease; 70 - backdrop-filter: blur(8px); 71 - -webkit-backdrop-filter: blur(8px); 72 68 } 73 69 74 70 .stream-container peek-card[selected] { 75 - --peek-card-bg: rgba(var(--base02-rgb, 45, 45, 65), 0.9); 71 + --peek-card-bg: var(--base02); 72 + --peek-card-border: transparent; 76 73 } 77 74 78 75 .stream-container peek-card[selected]:hover { 79 - --peek-card-bg: rgba(var(--base03-rgb, 60, 60, 80), 0.92); 76 + --peek-card-bg: var(--base03); 80 77 } 81 78 82 - /* Active/focused card is displayed at 2x size */ 79 + /* Active/focused card - highlighted but no blue border/outline */ 83 80 .stream-container peek-card.active-card { 84 - transform: scale(1.15); 85 - transform-origin: center; 86 - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35); 81 + --peek-card-bg: var(--base02); 82 + --peek-card-border: transparent; 87 83 --peek-card-padding: 16px; 88 84 --peek-card-gap: 8px; 85 + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); 89 86 z-index: 10; 90 87 } 91 88 ··· 107 104 font-size: 13px; 108 105 } 109 106 110 - /* Non-active cards are smaller */ 107 + /* Non-active cards are slightly dimmed */ 111 108 .stream-container peek-card:not(.active-card) { 112 - transform: scale(0.88); 113 109 opacity: 0.75; 114 110 } 115 111 ··· 197 193 } 198 194 199 195 peek-navbar { 200 - --peek-navbar-bg: rgba(var(--base01-rgb, 30, 30, 46), 0.85); 201 - backdrop-filter: blur(12px); 202 - -webkit-backdrop-filter: blur(12px); 196 + --peek-navbar-bg: var(--base01); 203 197 border-radius: 10px; 204 198 } 205 199 ··· 259 253 .filter-clear:hover { 260 254 color: var(--base05); 261 255 } 256 + 257 + /* Browse overlay - inline page viewer */ 258 + .browse-overlay { 259 + position: fixed; 260 + top: 0; 261 + left: 0; 262 + right: 0; 263 + bottom: 0; 264 + z-index: 100; 265 + display: flex; 266 + flex-direction: column; 267 + background: var(--base00, #1e1e2e); 268 + } 269 + 270 + .browse-overlay webview, 271 + .browse-overlay iframe { 272 + flex: 1; 273 + border: none; 274 + background: #fff; 275 + } 276 + 277 + .browse-overlay .navbar-container { 278 + padding: 8px 16px 12px 16px; 279 + flex-shrink: 0; 280 + }
+118 -35
extensions/pagestream/home.js
··· 97 97 filterTagId: null, 98 98 filterTagName: null, 99 99 isLoading: true, 100 - wasAtBottom: true // Track if user was scrolled to bottom 100 + wasAtBottom: true, // Track if user was scrolled to bottom 101 + browsing: false, // Whether we're in inline browse mode 102 + browseUrl: null // URL currently being browsed inline 101 103 }; 102 104 103 105 // Expose state for debugging in tests ··· 201 203 const updateSelection = () => { 202 204 const cards = getCards(); 203 205 cards.forEach((card, i) => { 204 - const isSelected = (i === state.selectedIndex); 205 - card.selected = isSelected; 206 - card.elevated = isSelected; 207 - if (isSelected) { 206 + const isActive = (i === state.selectedIndex); 207 + // Do not set selected/elevated - those trigger blue border in peek-card shadow DOM 208 + card.selected = false; 209 + card.elevated = false; 210 + if (isActive) { 208 211 card.classList.add('active-card'); 209 212 } else { 210 213 card.classList.remove('active-card'); ··· 374 377 } 375 378 }); 376 379 377 - // Click to open the URL 378 - let isOpening = false; 379 - card.addEventListener('card-click', async () => { 380 - if (isOpening) return; 381 - isOpening = true; 382 - try { 383 - state.selectedIndex = index; 384 - updateSelection(); 385 - debug && console.log('[pagestream] Opening:', url); 386 - await api.window.open(url, { 387 - role: 'content', 388 - key: url, 389 - width: 800, 390 - height: 600 391 - }); 392 - } finally { 393 - setTimeout(() => { isOpening = false; }, 500); 380 + // Click to open the URL inline 381 + card.addEventListener('card-click', () => { 382 + state.selectedIndex = index; 383 + updateSelection(); 384 + openInline(url); 385 + }); 386 + 387 + return card; 388 + }; 389 + 390 + // ===== Inline Browsing ===== 391 + 392 + /** 393 + * Open a URL inline in a browse overlay (iframe/webview) 394 + */ 395 + const openInline = (url) => { 396 + if (state.browsing) return; 397 + state.browsing = true; 398 + state.browseUrl = url; 399 + 400 + debug && console.log('[pagestream] Opening inline:', url); 401 + 402 + // Create the overlay 403 + const overlay = document.createElement('div'); 404 + overlay.className = 'browse-overlay'; 405 + overlay.id = 'browse-overlay'; 406 + 407 + // Create webview/iframe for the page 408 + const frame = document.createElement('webview'); 409 + frame.src = url; 410 + frame.id = 'browse-frame'; 411 + frame.setAttribute('allowpopups', ''); 412 + 413 + // Create a navbar for the browse session 414 + const navContainer = document.createElement('div'); 415 + navContainer.className = 'navbar-container'; 416 + const navbar = document.createElement('peek-navbar'); 417 + navbar.id = 'browse-navbar'; 418 + navbar.url = url; 419 + navbar.shortcuts = false; // host manages shortcuts 420 + navContainer.appendChild(navbar); 421 + 422 + overlay.appendChild(frame); 423 + overlay.appendChild(navContainer); 424 + document.body.appendChild(overlay); 425 + 426 + // Update navbar URL as webview navigates 427 + frame.addEventListener('did-navigate', (e) => { 428 + state.browseUrl = e.url || frame.src; 429 + navbar.setUrl(state.browseUrl); 430 + navbar.canGoBack = frame.canGoBack ? frame.canGoBack() : false; 431 + navbar.canGoForward = frame.canGoForward ? frame.canGoForward() : false; 432 + }); 433 + 434 + frame.addEventListener('did-navigate-in-page', (e) => { 435 + state.browseUrl = e.url || frame.src; 436 + navbar.setUrl(state.browseUrl); 437 + navbar.canGoBack = frame.canGoBack ? frame.canGoBack() : false; 438 + navbar.canGoForward = frame.canGoForward ? frame.canGoForward() : false; 439 + }); 440 + 441 + // Navbar navigation events 442 + navbar.addEventListener('navigate', (e) => { 443 + const navUrl = e.detail?.url; 444 + if (navUrl) { 445 + frame.src = navUrl; 446 + state.browseUrl = navUrl; 394 447 } 395 448 }); 396 449 397 - return card; 450 + navbar.addEventListener('go-back', () => { 451 + if (frame.canGoBack && frame.canGoBack()) frame.goBack(); 452 + }); 453 + 454 + navbar.addEventListener('go-forward', () => { 455 + if (frame.canGoForward && frame.canGoForward()) frame.goForward(); 456 + }); 457 + 458 + navbar.addEventListener('reload', () => { 459 + if (frame.reload) frame.reload(); 460 + }); 461 + }; 462 + 463 + /** 464 + * Close the inline browse overlay and return to card view 465 + */ 466 + const closeBrowse = () => { 467 + const overlay = document.getElementById('browse-overlay'); 468 + if (overlay) { 469 + overlay.remove(); 470 + } 471 + state.browsing = false; 472 + state.browseUrl = null; 398 473 }; 399 474 400 475 // ===== Filtering ===== ··· 453 528 * Returns { handled: true } if we handled it, { handled: false } to close window 454 529 */ 455 530 const handleEscape = () => { 456 - debug && console.log('[pagestream:esc] handleEscape called, search:', state.searchQuery, 'filter:', state.filterTagId); 531 + debug && console.log('[pagestream:esc] handleEscape called, browsing:', state.browsing, 'search:', state.searchQuery, 'filter:', state.filterTagId); 532 + 533 + // Close inline browse overlay first 534 + if (state.browsing) { 535 + closeBrowse(); 536 + return { handled: true }; 537 + } 457 538 458 539 // Clear tag filter first 459 540 if (state.filterTagId) { ··· 478 559 * Handle keyboard navigation 479 560 */ 480 561 const handleKeydown = (e) => { 562 + // Don't handle card navigation while in browse mode 563 + if (state.browsing) return; 564 + 481 565 const searchInput = document.querySelector('peek-input.search-input'); 482 566 const navbarEl = document.getElementById('navbar'); 483 567 ··· 536 620 case 'Enter': 537 621 if (!isInputFocused) { 538 622 e.preventDefault(); 539 - const selected = cards[state.selectedIndex]; 540 - if (selected) { 541 - selected.click(); 623 + // Open the active card's URL inline 624 + const filtered = getFilteredVisits(); 625 + const entry = filtered[state.selectedIndex]; 626 + if (entry) { 627 + openInline(entry.item.content); 542 628 } 543 629 } 544 630 break; ··· 583 669 const url = e.detail?.url; 584 670 if (url) { 585 671 debug && console.log('[pagestream] Navigating to:', url); 586 - api.window.open(url, { 587 - role: 'content', 588 - key: url, 589 - width: 800, 590 - height: 600 591 - }); 672 + openInline(url); 592 673 } 593 674 }); 594 675 ··· 612 693 await loadVisits(); 613 694 render(); 614 695 615 - // Scroll to bottom (newest) on initial load 616 - scrollToBottom(); 696 + // Scroll to bottom (newest) on initial load - use rAF to ensure layout is complete 697 + requestAnimationFrame(() => { 698 + scrollToBottom(); 699 + }); 617 700 618 701 // Auto-focus the navbar URL input on open 619 702 requestAnimationFrame(() => {