Social Annotations in the Atmosphere
15
fork

Configure Feed

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

feat(pwa)

+435 -290
+1
.gitignore
··· 15 15 .wxt 16 16 web-ext.config.ts 17 17 server/server 18 + server/tmp/ 18 19 server 19 20 20 21 # Editor directories and files
+2 -4
Caddyfile
··· 17 17 reverse_proxy 127.0.0.1:8081 18 18 } 19 19 20 - # Via landing page at root 20 + # Redirect root to seams.so 21 21 handle / { 22 - root * proxy/static 23 - rewrite * /via-landing.html 24 - file_server 22 + redir https://seams.so 25 23 } 26 24 27 25 # OAuth callback rewrite
assets/logo.png

This is a binary file and will not be displayed.

+11
deploy.sh
··· 1 1 #!/usr/bin/env bash 2 2 set -e 3 3 4 + echo "🏗️ Building landing page..." 5 + pnpm build:landing 6 + 4 7 echo "🏗️ Building Docker image with Nix..." 8 + 9 + # Add build artifacts to git index so Nix can see them (if they are ignored) 10 + echo "➕ Adding landing assets to git index..." 11 + git add -f landing/dist landing/assets landing/favicon.ico landing/manifest.json landing/sw.js 12 + 5 13 nix build .#docker 14 + 15 + echo "➖ Resetting git index..." 16 + git reset landing/dist landing/assets landing/favicon.ico landing/manifest.json landing/sw.js 6 17 7 18 echo "📦 Loading image into Docker..." 8 19 docker load < result
+17 -5
history/REFACTOR_PLAN.md
··· 63 63 - Updated `scripts/inject-oauth-plugin.ts` for extension localhost config. 64 64 - Added `PopupOAuthLauncher` for extension dev mode support. 65 65 66 - ## Phase 5: Optimization & Cleanup 66 + ## Phase 5: Search proxy and PWA 67 + - [ ] Add favicon and logo to seams.so 68 + - The logo is in assets/logo.png 69 + - We should resize the logo to be appropriate for the background page 70 + - We need to create the favicon 71 + - Logo should be only on the landing page, half cut on the right side of the left sidebar, it should be in the background and cover half the text 72 + - favicon should be available for the browser extension, via proxy, and landing page 73 + - [ ] **Add search from proxy HTML to landing.html** 74 + - Place the search box above the recent annotations on the right side 75 + - Cleanup unused static proxy HTML files. (`proxy/static/via-landing.html`) 76 + - [ ] Set redirect for sure.seams.so/ (without any proxy/ endpoint) to be seams.so 77 + - [ ] Ensure seams.so is a PWA that can handle share intent on mobile 78 + - Any URL shared to this PWA should redirect to the sure.seams.so/proxy/<url> page such that it can be annotated 79 + 80 + ## Phase 6: Optimization & Cleanup 67 81 **Goal:** Performance improvements and dead code removal. 68 82 - [ ] **Optimize Sync Algorithm** (`packages/core/src/background/worker.ts`) 69 83 - Use `Set.has()` for merging annotations. 70 - - [ ] **Add search from proxy HTML to landing.html 71 - - Place the search box above 72 - - Cleanup unused static proxy HTML files. (`proxy/static/via-landing.html`) 73 84 - [ ] Move logs to dev build only 74 85 - Logging on default production instances should be minimized 75 86 - [ ] Document messages types, and define them in @seams/core ··· 77 88 - [ ] Clean up an existing, **COMPLETED** AMPDO: tasks 78 89 - Report any incomplete AMPDO tasks 79 90 80 - ## Phase 6: Further Future Proofing 91 + ## Phase 7: Further Future Proofing 81 92 - [ ] Report why the content script is constantly running and checking and spitting out logs, our event driven arcitecture should stop this? I only want an explanation 82 93 - [ ] Change domain for go pkg imports to be `pkg.sealight.xyz` 94 + - [ ] Update Agents.md
+2 -1
landing/about.html
··· 11 11 <aside class="sidebar"> 12 12 <div class="sidebar-content"> 13 13 <header class="hero"> 14 - <div class="logo">Seams</div> 14 + <div class="logo">Seams<sup>alpha</sup></div> 15 15 <h1>About</h1> 16 16 <div class="cta-buttons"> 17 17 <a href="/" class="cta-secondary">← Back to Home</a> ··· 51 51 <ul> 52 52 <li>An AT Protocol account (most likely a Bluesky account)</li> 53 53 <li>The Seams browser extension (available for Chrome and Firefox)</li> 54 + <li>Or, simply paste any URL into the search bar on the <a href="/">home page</a> to annotate without installing anything using our proxy.</li> 54 55 </ul> 55 56 <p>Once installed, simply highlight text on any webpage and add your annotation. Your insights become part of the collective knowledge layer.</p> 56 57
landing/assets/logo.png

This is a binary file and will not be displayed.

landing/favicon.ico

This is a binary file and will not be displayed.

+28 -1
landing/index.html
··· 4 4 <meta charset="UTF-8"> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 6 <title>Seams - Wisdom is made Together</title> 7 + <link rel="icon" href="/favicon.ico"> 8 + <link rel="manifest" href="/manifest.json"> 9 + <meta name="theme-color" content="#2d5016"> 7 10 <link rel="stylesheet" href="landing.css"> 8 11 </head> 9 12 <body> 10 13 <div class="layout"> 11 14 <aside class="sidebar"> 15 + <img src="/assets/logo.png" class="sidebar-bg-logo" alt="" aria-hidden="true" /> 12 16 <div class="sidebar-content"> 13 17 <header class="hero"> 14 - <div class="logo">Seams</div> 18 + <div class="logo">Seams<sup>alpha</sup></div> 15 19 <h1><span style="white-space: nowrap;">Wisdom is Made</span> <br>Together</h1> 16 20 <p class="tagline">Annotations in the Atmosphere</p> 17 21 <div class="cta-buttons"> ··· 37 41 38 42 <main class="main-content"> 39 43 <div class="feed-section" id="feed"> 44 + <div class="search-container"> 45 + <form class="url-form" id="via-form"> 46 + <input 47 + type="url" 48 + id="url-input" 49 + placeholder="Paste a link to annotate" 50 + required 51 + autocomplete="url" 52 + /> 53 + <button type="submit">Annotate</button> 54 + </form> 55 + </div> 40 56 <div id="annotations-feed" class="annotations-feed"> 41 57 <div class="loading">Tending the garden...</div> 42 58 </div> ··· 61 77 </div> 62 78 63 79 <script> 80 + // Check if service worker is supported 81 + if ('serviceWorker' in navigator) { 82 + window.addEventListener('load', () => { 83 + navigator.serviceWorker.register('/sw.js').then(registration => { 84 + console.log('ServiceWorker registration successful with scope: ', registration.scope); 85 + }, err => { 86 + console.log('ServiceWorker registration failed: ', err); 87 + }); 88 + }); 89 + } 90 + 64 91 // Backend URL defaults to same origin (production) or can be overridden for development 65 92 // window.BACKEND_URL = 'http://localhost:8080'; 66 93
+106
landing/landing.css
··· 81 81 text-transform: uppercase; 82 82 color: #666; 83 83 margin-bottom: 24px; 84 + position: relative; 85 + display: inline-block; 86 + } 87 + 88 + .logo sup { 89 + font-size: 0.6em; 90 + text-transform: lowercase; 91 + color: var(--forest-green); 92 + font-weight: 500; 93 + letter-spacing: 0; 94 + vertical-align: super; 95 + margin-left: 2px; 84 96 } 85 97 86 98 .hero h1 { ··· 411 423 padding: 20px; 412 424 } 413 425 } 426 + 427 + /* Search Form */ 428 + .search-container { 429 + margin-bottom: 40px; 430 + } 431 + 432 + .url-form { 433 + display: flex; 434 + gap: 10px; 435 + width: 100%; 436 + } 437 + 438 + .url-form input[type="url"] { 439 + flex: 1; 440 + padding: 14px 18px; 441 + border: 2px solid #ddd; 442 + border-radius: 2px; 443 + font-size: 16px; 444 + transition: border-color 0.2s; 445 + font-family: inherit; 446 + background: #fff; 447 + } 448 + 449 + .url-form input[type="url"]:focus { 450 + outline: none; 451 + border-color: var(--forest-green); 452 + } 453 + 454 + .url-form button[type="submit"] { 455 + padding: 14px 32px; 456 + background: var(--forest-green); 457 + color: white; 458 + border: 1px dashed var(--forest-green-dark); 459 + border-radius: 2px; 460 + font-size: 16px; 461 + font-weight: 500; 462 + cursor: pointer; 463 + transition: all 0.2s; 464 + white-space: nowrap; 465 + font-family: inherit; 466 + } 467 + 468 + .url-form button[type="submit"]:hover { 469 + background: var(--forest-green-dark); 470 + transform: translateY(-1px); 471 + } 472 + 473 + @media (max-width: 640px) { 474 + .url-form { 475 + flex-direction: column; 476 + } 477 + .url-form button[type="submit"] { 478 + width: 100%; 479 + } 480 + } 481 + 482 + /* Sidebar Logo */ 483 + .sidebar { 484 + overflow-x: hidden; 485 + } 486 + 487 + .sidebar-bg-logo { 488 + position: absolute; 489 + right: -22%; 490 + top: 50%; 491 + transform: translateY(-50%); 492 + height: 70%; 493 + width: auto; 494 + opacity: 0.15; 495 + z-index: 0; 496 + pointer-events: none; 497 + /* Filter to make it monochrome if needed, or use opacity */ 498 + } 499 + 500 + .sidebar-content { 501 + position: relative; 502 + z-index: 1; 503 + } 504 + 505 + @media (max-width: 1024px) { 506 + .sidebar-bg-logo { 507 + right: 50%; 508 + transform: translate(50%, -50%); 509 + height: 100%; 510 + top: 50%; 511 + opacity: 0.15; 512 + } 513 + } 514 + 515 + @media (max-width: 640px) { 516 + .sidebar-bg-logo { 517 + height: 50vh; 518 + } 519 + }
+26
landing/landing.ts
··· 110 110 } 111 111 112 112 // Initialize 113 + 113 114 loadInitialAnnotations(); 115 + 116 + // Search Handler 117 + const viaForm = document.getElementById('via-form'); 118 + const urlInput = document.getElementById('url-input') as HTMLInputElement; 119 + 120 + if (viaForm && urlInput) { 121 + viaForm.addEventListener('submit', (e) => { 122 + e.preventDefault(); 123 + let url = urlInput.value.trim(); 124 + 125 + if (!url) return; 126 + 127 + // Add protocol if missing 128 + if (!url.match(/^https?:\/\//i)) { 129 + url = 'https://' + url; 130 + } 131 + 132 + // Determine proxy base URL 133 + const isLocal = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'; 134 + const proxyBase = isLocal ? 'http://localhost:8082' : 'https://sure.seams.so'; 135 + 136 + // Redirect to proxy route 137 + window.location.href = `${proxyBase}/proxy/${url}`; 138 + }); 139 + }
+31
landing/manifest.json
··· 1 + { 2 + "name": "Seams", 3 + "short_name": "Seams", 4 + "description": "Wisdom is made Together", 5 + "start_url": "/", 6 + "display": "standalone", 7 + "background_color": "#fafafa", 8 + "theme_color": "#2d5016", 9 + "icons": [ 10 + { 11 + "src": "/assets/logo.png", 12 + "sizes": "512x512", 13 + "type": "image/png" 14 + }, 15 + { 16 + "src": "/favicon.ico", 17 + "sizes": "64x64 32x32 16x16", 18 + "type": "image/x-icon" 19 + } 20 + ], 21 + "share_target": { 22 + "action": "/share-target/", 23 + "method": "POST", 24 + "enctype": "multipart/form-data", 25 + "params": { 26 + "title": "title", 27 + "text": "text", 28 + "url": "url" 29 + } 30 + } 31 + }
+74
landing/sw.js
··· 1 + // Simple service worker to support PWA installation and offline fallback 2 + const CACHE_NAME = 'seams-v1'; 3 + 4 + self.addEventListener('install', (event) => { 5 + event.waitUntil( 6 + caches.open(CACHE_NAME).then((cache) => { 7 + return cache.addAll([ 8 + '/', 9 + '/index.html', 10 + '/landing.css', 11 + '/fonts.css', 12 + '/favicon.ico', 13 + '/manifest.json' 14 + ]); 15 + }) 16 + ); 17 + }); 18 + 19 + self.addEventListener('fetch', (event) => { 20 + const url = new URL(event.request.url); 21 + 22 + // Handle share target POST requests 23 + if (event.request.method === 'POST' && url.pathname === '/share-target/') { 24 + event.respondWith((async () => { 25 + try { 26 + const formData = await event.request.formData(); 27 + const text = (formData.get('text') || '').toString(); 28 + const link = (formData.get('url') || '').toString(); 29 + 30 + // Helper to extract URL from text if needed 31 + const extractUrl = (str) => { 32 + const match = str.match(/(https?:\/\/[^\s]+)/); 33 + return match ? match[0] : null; 34 + }; 35 + 36 + let targetUrl = link || extractUrl(text); 37 + 38 + if (targetUrl) { 39 + const isLocal = self.location.hostname === 'localhost' || self.location.hostname === '127.0.0.1'; 40 + const proxyBase = isLocal ? 'http://localhost:8082' : 'https://sure.seams.so'; 41 + // Use a simple response that redirects via client-side JS/meta refresh 42 + // This avoids CORS/Service Worker restriction issues with cross-origin redirects 43 + const redirectUrl = `${proxyBase}/proxy/${targetUrl}`; 44 + return new Response( 45 + `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0;url=${redirectUrl}"></head><body>Redirecting to <a href="${redirectUrl}">${redirectUrl}</a>...<script>window.location.href="${redirectUrl}"</script></body></html>`, 46 + { 47 + headers: { 'Content-Type': 'text/html' } 48 + } 49 + ); 50 + } 51 + 52 + // Fallback if no URL found: redirect to home 53 + return Response.redirect('/', 303); 54 + } catch (e) { 55 + console.error('Share target error:', e); 56 + return Response.redirect('/', 303); 57 + } 58 + })()); 59 + return; 60 + } 61 + 62 + // For share target requests (which are often navigations), we must handle them. 63 + // Since we are using GET for share_target, the browser navigates to /?text=... 64 + // This falls under the navigation request. 65 + 66 + // Network first strategy for everything to ensure fresh content 67 + // but fall back to cache if offline 68 + event.respondWith( 69 + fetch(event.request) 70 + .catch(() => { 71 + return caches.match(event.request); 72 + }) 73 + ); 74 + });
proxy/static/favicon.ico

This is a binary file and will not be displayed.

+85
proxy/static/landing.css
··· 411 411 padding: 20px; 412 412 } 413 413 } 414 + 415 + /* Search Form */ 416 + .search-container { 417 + margin-bottom: 40px; 418 + } 419 + 420 + .url-form { 421 + display: flex; 422 + gap: 10px; 423 + width: 100%; 424 + } 425 + 426 + .url-form input[type="url"] { 427 + flex: 1; 428 + padding: 14px 18px; 429 + border: 2px solid #ddd; 430 + border-radius: 2px; 431 + font-size: 16px; 432 + transition: border-color 0.2s; 433 + font-family: inherit; 434 + background: #fff; 435 + } 436 + 437 + .url-form input[type="url"]:focus { 438 + outline: none; 439 + border-color: var(--forest-green); 440 + } 441 + 442 + .url-form button[type="submit"] { 443 + padding: 14px 32px; 444 + background: var(--forest-green); 445 + color: white; 446 + border: 1px dashed var(--forest-green-dark); 447 + border-radius: 2px; 448 + font-size: 16px; 449 + font-weight: 500; 450 + cursor: pointer; 451 + transition: all 0.2s; 452 + white-space: nowrap; 453 + font-family: inherit; 454 + } 455 + 456 + .url-form button[type="submit"]:hover { 457 + background: var(--forest-green-dark); 458 + transform: translateY(-1px); 459 + } 460 + 461 + @media (max-width: 640px) { 462 + .url-form { 463 + flex-direction: column; 464 + } 465 + .url-form button[type="submit"] { 466 + width: 100%; 467 + } 468 + } 469 + 470 + /* Sidebar Logo */ 471 + .sidebar { 472 + overflow-x: hidden; 473 + } 474 + 475 + .sidebar-bg-logo { 476 + position: absolute; 477 + right: -22%; 478 + top: 50%; 479 + transform: translateY(-50%); 480 + height: 70%; 481 + width: auto; 482 + opacity: 0.05; 483 + z-index: 0; 484 + pointer-events: none; 485 + /* Filter to make it monochrome if needed, or use opacity */ 486 + } 487 + 488 + .sidebar-content { 489 + position: relative; 490 + z-index: 1; 491 + } 492 + 493 + @media (max-width: 1024px) { 494 + .sidebar-bg-logo { 495 + right: -10%; 496 + height: 60%; 497 + } 498 + }
+49 -27
proxy/static/oauth-callback.html
··· 4 4 <meta charset="UTF-8"> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 6 <title>Seams OAuth Callback</title> 7 + <link rel="stylesheet" href="/landing.css"> 7 8 <style> 8 9 body { 9 - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 10 - display: flex; 11 - align-items: center; 12 - justify-content: center; 13 - height: 100vh; 14 - margin: 0; 15 - background: #f5f5f5; 10 + display: flex; 11 + align-items: center; 12 + justify-content: center; 13 + height: 100vh; 14 + margin: 0; 15 + overflow: hidden; 16 16 } 17 - .message { 18 - text-align: center; 19 - padding: 32px; 20 - background: white; 21 - border-radius: 8px; 22 - box-shadow: 0 2px 8px rgba(0,0,0,0.1); 17 + .callback-container { 18 + background: white; 19 + padding: 48px; 20 + border: 2px dashed #d0d0d0; 21 + border-radius: 2px; 22 + text-align: center; 23 + max-width: 400px; 24 + width: 90%; 25 + box-shadow: 0 4px 12px rgba(45, 80, 22, 0.05); 26 + } 27 + .logo { 28 + font-family: 'Spectral', serif; 29 + font-size: 16px; 30 + font-weight: 700; 31 + letter-spacing: 0.1em; 32 + text-transform: uppercase; 33 + color: #666; 34 + margin-bottom: 16px; 35 + } 36 + h2 { 37 + font-family: 'Fraunces', serif; 38 + font-size: 24px; 39 + color: #1a1a1a; 40 + margin-bottom: 16px; 23 41 } 24 42 .spinner { 25 - border: 3px solid #f3f3f3; 26 - border-top: 3px solid #0085ff; 27 - border-radius: 50%; 28 - width: 40px; 29 - height: 40px; 30 - animation: spin 1s linear infinite; 31 - margin: 0 auto 16px; 43 + border: 3px solid #f3f3f3; 44 + border-top: 3px solid var(--forest-green); 45 + border-radius: 50%; 46 + width: 32px; 47 + height: 32px; 48 + animation: spin 1s linear infinite; 49 + margin: 0 auto 24px; 32 50 } 33 51 @keyframes spin { 34 - 0% { transform: rotate(0deg); } 35 - 100% { transform: rotate(360deg); } 52 + 0% { transform: rotate(0deg); } 53 + 100% { transform: rotate(360deg); } 54 + } 55 + #status { 56 + color: #666; 57 + font-size: 14px; 36 58 } 37 59 </style> 38 60 <script type="module" crossorigin src="/static/seams-oauth-callback.js"></script> 39 - <link rel="modulepreload" crossorigin href="/static/assets/modulepreload-polyfill-B5Qt9EMX.js"> 61 + <link rel="modulepreload" crossorigin href="/static/assets/modulepreload-polyfill-Elc7_Ely.js"> 40 62 <link rel="modulepreload" crossorigin href="/static/assets/index-BKdQD0EM.js"> 41 - <link rel="modulepreload" crossorigin href="/static/assets/index-BNF_FRu3.js"> 42 63 </head> 43 64 <body> 44 - <div class="message"> 65 + <div class="callback-container"> 66 + <div class="logo">Seams</div> 45 67 <div class="spinner"></div> 46 - <h2>Completing login...</h2> 47 - <p id="status">Processing OAuth response</p> 68 + <h2>Connecting...</h2> 69 + <p id="status">Completing authentication</p> 48 70 </div> 49 71 </body> 50 72 </html>
-49
proxy/static/proxy/via-html/oauth-callback.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 - <title>Seams OAuth Callback</title> 7 - <style> 8 - body { 9 - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 10 - display: flex; 11 - align-items: center; 12 - justify-content: center; 13 - height: 100vh; 14 - margin: 0; 15 - background: #f5f5f5; 16 - } 17 - .message { 18 - text-align: center; 19 - padding: 32px; 20 - background: white; 21 - border-radius: 8px; 22 - box-shadow: 0 2px 8px rgba(0,0,0,0.1); 23 - } 24 - .spinner { 25 - border: 3px solid #f3f3f3; 26 - border-top: 3px solid #0085ff; 27 - border-radius: 50%; 28 - width: 40px; 29 - height: 40px; 30 - animation: spin 1s linear infinite; 31 - margin: 0 auto 16px; 32 - } 33 - @keyframes spin { 34 - 0% { transform: rotate(0deg); } 35 - 100% { transform: rotate(360deg); } 36 - } 37 - </style> 38 - <script type="module" crossorigin src="/static/seams-oauth-callback.js"></script> 39 - <link rel="modulepreload" crossorigin href="/static/assets/modulepreload-polyfill-Elc7_Ely.js"> 40 - <link rel="modulepreload" crossorigin href="/static/assets/index-BKdQD0EM.js"> 41 - </head> 42 - <body> 43 - <div class="message"> 44 - <div class="spinner"></div> 45 - <h2>Completing login...</h2> 46 - <p id="status">Processing OAuth response</p> 47 - </div> 48 - </body> 49 - </html>
-15
proxy/static/proxy/via-html/seams-sidebar.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 - <title>Seams Sidebar</title> 7 - <script type="module" crossorigin src="/static/seams-seams-sidebar.js"></script> 8 - <link rel="modulepreload" crossorigin href="/static/assets/modulepreload-polyfill-Elc7_Ely.js"> 9 - <link rel="modulepreload" crossorigin href="/static/assets/index-BKdQD0EM.js"> 10 - <link rel="stylesheet" crossorigin href="/static/assets/seams-sidebar-gO3IQ7ah.css"> 11 - </head> 12 - <body> 13 - <div id="app"></div> 14 - </body> 15 - </html>
+1 -3
proxy/static/seams-sidebar.html
··· 5 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 6 <title>Seams Sidebar</title> 7 7 <script type="module" crossorigin src="/static/seams-seams-sidebar.js"></script> 8 - <link rel="modulepreload" crossorigin href="/static/assets/modulepreload-polyfill-B5Qt9EMX.js"> 8 + <link rel="modulepreload" crossorigin href="/static/assets/modulepreload-polyfill-Elc7_Ely.js"> 9 9 <link rel="modulepreload" crossorigin href="/static/assets/index-BKdQD0EM.js"> 10 - <link rel="modulepreload" crossorigin href="/static/assets/index-BNF_FRu3.js"> 11 - <link rel="modulepreload" crossorigin href="/static/assets/index-kV7Brn54.js"> 12 10 <link rel="stylesheet" crossorigin href="/static/assets/seams-sidebar-gO3IQ7ah.css"> 13 11 </head> 14 12 <body>
-184
proxy/via-html/via-landing.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 - <title>Via - Seams Web Annotation Proxy</title> 7 - <link rel="stylesheet" href="landing.css"> 8 - <style> 9 - /* Extra styles for the proxy form in the main content area */ 10 - .proxy-container { 11 - padding: 60px 48px; 12 - max-width: 900px; 13 - } 14 - .url-form { 15 - display: flex; 16 - gap: 10px; 17 - margin-bottom: 30px; 18 - max-width: 600px; 19 - } 20 - 21 - input[type="url"] { 22 - flex: 1; 23 - padding: 14px 18px; 24 - border: 2px solid #ddd; 25 - border-radius: 2px; /* Match button radius */ 26 - font-size: 16px; 27 - transition: border-color 0.2s; 28 - font-family: inherit; 29 - } 30 - 31 - input[type="url"]:focus { 32 - outline: none; 33 - border-color: var(--forest-green); 34 - } 35 - 36 - button[type="submit"] { 37 - padding: 14px 32px; 38 - background: var(--forest-green); 39 - color: white; 40 - border: 1px dashed var(--forest-green-dark); 41 - border-radius: 2px; 42 - font-size: 16px; 43 - font-weight: 500; 44 - cursor: pointer; 45 - transition: all 0.2s; 46 - white-space: nowrap; 47 - font-family: inherit; 48 - } 49 - 50 - button[type="submit"]:hover { 51 - background: var(--forest-green-dark); 52 - transform: translateY(-1px); 53 - } 54 - 55 - .info-text { 56 - font-size: 16px; 57 - color: #333; 58 - line-height: 1.6; 59 - margin-bottom: 24px; 60 - max-width: 700px; 61 - } 62 - 63 - .example { 64 - margin-top: 24px; 65 - padding: 20px; 66 - background: #fff; 67 - border: 1px dashed #d0d0d0; 68 - border-radius: 2px; 69 - display: inline-block; 70 - } 71 - 72 - .example a { 73 - color: var(--forest-green); 74 - text-decoration: none; 75 - border-bottom: 1px dashed transparent; 76 - transition: border-color 0.2s; 77 - } 78 - .example a:hover { 79 - border-bottom-color: var(--forest-green); 80 - } 81 - 82 - /* Mobile tweaks */ 83 - @media (max-width: 640px) { 84 - .proxy-container { 85 - padding: 32px 24px; 86 - } 87 - .url-form { 88 - flex-direction: column; 89 - } 90 - button[type="submit"] { 91 - width: 100%; 92 - } 93 - } 94 - </style> 95 - </head> 96 - <body> 97 - <div class="layout"> 98 - <aside class="sidebar"> 99 - <div class="sidebar-content"> 100 - <header class="hero"> 101 - <div class="logo">Seams Via</div> 102 - <h1><span style="white-space: nowrap;">Wisdom is Made</span> <br>Accessible</h1> 103 - <p class="tagline">View and annotate any web page</p> 104 - <div class="cta-buttons"> 105 - <a href="https://seams.so" class="cta-primary">Go to Seams.so</a> 106 - </div> 107 - </header> 108 - 109 - <footer class="footer desktop-footer"> 110 - <div class="footer-icons"> 111 - <a href="https://tangled.org/@sealight.xyz/seams.so" target="_blank" aria-label="Tangled"> 112 - <img src="https://semble.so/_next/static/media/tangled-icon.b95d4d65.svg" alt="Tangled" /> 113 - </a> 114 - <a href="https://bsky.app" target="_blank" aria-label="Bluesky"> 115 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 320" style="width: 32px; height: 32px; opacity: 0.6;"> 116 - <path fill="currentColor" d="M180 142c-16.3-31.7-60.7-90.8-102-120C38.5-5.9 23.4-1 13.5 3.4 2.1 8.6 0 26.2 0 36.5c0 10.4 5.7 84.8 9.4 97.2 12.2 41 55.7 55 95.7 50.5-58.7 8.6-110.8 30-42.4 106.1 75.1 77.9 103-16.7 117.3-64.6 14.3 48 30.8 139 116 64.6 64-64.6 17.6-97.5-41.1-106.1 40 4.4 83.5-9.5 95.7-50.5 3.7-12.4 9.4-86.8 9.4-97.2 0-10.3-2-27.9-13.5-33C336.5-1 321.5-6 282 22c-41.3 29.2-85.7 88.3-102 120Z"/> 117 - </svg> 118 - </a> 119 - </div> 120 - </footer> 121 - </div> 122 - </aside> 123 - 124 - <main class="main-content"> 125 - <div class="proxy-container"> 126 - <div class="info-text"> 127 - <p>Via is a web annotation proxy that lets you view and create annotations on any web page using Seams.</p> 128 - <br> 129 - <p>Enter a URL below to view the page with all public annotations from the Seams community.</p> 130 - </div> 131 - 132 - <form class="url-form" id="via-form"> 133 - <input 134 - type="url" 135 - id="url-input" 136 - placeholder="Paste a link to annotate" 137 - required 138 - autocomplete="url" 139 - autofocus 140 - /> 141 - <button type="submit">Annotate</button> 142 - </form> 143 - 144 - <div class="example"> 145 - <strong>Try it:</strong> 146 - <a href="/proxy/https://newsletter.squishy.computer/p/places-to-intervene-in-a-system"> 147 - View example article with annotations 148 - </a> 149 - </div> 150 - </div> 151 - </main> 152 - 153 - <footer class="footer mobile-footer"> 154 - <div class="footer-icons"> 155 - <a href="https://tangled.org/@sealight.xyz/seams.so" target="_blank" aria-label="Tangled"> 156 - <img src="https://semble.so/_next/static/media/tangled-icon.b95d4d65.svg" alt="Tangled" /> 157 - </a> 158 - <a href="https://bsky.app" target="_blank" aria-label="Bluesky"> 159 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 320" style="width: 32px; height: 32px; opacity: 0.6;"> 160 - <path fill="currentColor" d="M180 142c-16.3-31.7-60.7-90.8-102-120C38.5-5.9 23.4-1 13.5 3.4 2.1 8.6 0 26.2 0 36.5c0 10.4 5.7 84.8 9.4 97.2 12.2 41 55.7 55 95.7 50.5-58.7 8.6-110.8 30-42.4 106.1 75.1 77.9 103-16.7 117.3-64.6 14.3 48 30.8 139 116 64.6 64-64.6 17.6-97.5-41.1-106.1 40 4.4 83.5-9.5 95.7-50.5 3.7-12.4 9.4-86.8 9.4-97.2 0-10.3-2-27.9-13.5-33C336.5-1 321.5-6 282 22c-41.3 29.2-85.7 88.3-102 120Z"/> 161 - </svg> 162 - </a> 163 - </div> 164 - </footer> 165 - </div> 166 - 167 - <script> 168 - document.getElementById('via-form').addEventListener('submit', (e) => { 169 - e.preventDefault(); 170 - let url = document.getElementById('url-input').value.trim(); 171 - 172 - if (!url) return; 173 - 174 - // Add protocol if missing 175 - if (!url.match(/^https?:\/\//i)) { 176 - url = 'https://' + url; 177 - } 178 - 179 - // Redirect to pywb proxy route 180 - window.location.href = `/proxy/${url}`; 181 - }); 182 - </script> 183 - </body> 184 - </html>
+1
scripts/postbuild-via.sh
··· 16 16 echo "🎨 Copying shared assets from landing..." 17 17 cp landing/landing.css proxy/static/ 18 18 cp landing/fonts.css proxy/static/ 19 + cp landing/favicon.ico proxy/static/ 19 20 # cp landing/landing.js proxy/static/ 20 21 mkdir -p proxy/static/fonts 21 22 cp landing/fonts/* proxy/static/fonts/
+1 -1
vite.landing.config.ts
··· 23 23 }, 24 24 }, 25 25 define: { 26 - 'import.meta.env.BACKEND_URL': JSON.stringify(process.env.BACKEND_URL || 'http://localhost:8080'), 26 + 'import.meta.env.BACKEND_URL': JSON.stringify(process.env.BACKEND_URL || ''), 27 27 }, 28 28 });