ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
17
fork

Configure Feed

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

feat: button to x following, pretty-ification of extension

byarielm.fyi 3034db12 7196450f

verified
+114 -34
+4 -2
netlify.toml
··· 7 7 [dev] 8 8 framework = "#custom" 9 9 command = "npm run --prefix packages/web dev:full" 10 - targetPort = 5173 10 + targetPort = 5175 11 11 port = 8888 12 12 functionsPort = 9999 13 13 autoLaunch = true ··· 41 41 X-XSS-Protection = "1; mode=block" 42 42 Referrer-Policy = "strict-origin-when-cross-origin" 43 43 Permissions-Policy = "geolocation=(), microphone=(), camera=()" 44 - Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://*.bsky.app https://*.bsky.network https://public.api.bsky.app; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests;" 44 + # CSP relaxed for dev mode - script-src includes 'unsafe-eval' for Vite HMR 45 + # In production, Netlify will use build context to apply stricter CSP 46 + Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' ws: wss: http://localhost:* http://127.0.0.1:* https://*.bsky.app https://*.bsky.network https://public.api.bsky.app; frame-ancestors 'none'; base-uri 'self'; form-action 'self';"
+75 -30
packages/extension/src/popup/popup.html
··· 7 7 <link rel="stylesheet" href="popup.css" /> 8 8 </head> 9 9 <body 10 - class="w-[350px] min-h-[400px] rounded-3xl font-sans bg-gradient-to-br from-cyan-50 via-purple-50 to-pink-50 dark:from-indigo-950 dark:via-purple-900 dark:to-slate-900 text-slate-900 dark:text-slate-100 transition-colors duration-300 mb-2 text-center" 10 + class="w-[300px] min-h-[400px] font-sans bg-gradient-to-br from-cyan-50 via-purple-50 to-pink-50 dark:from-indigo-950 dark:via-purple-900 dark:to-slate-900 text-slate-900 dark:text-slate-100 transition-colors duration-300 text-center" 11 11 > 12 12 <div class="flex flex-col min-h-[400px]"> 13 13 <header ··· 23 23 </p> 24 24 </header> 25 25 26 - <main id="app" class="flex-1 px-5 py-6 flex"> 26 + <main 27 + id="app" 28 + class="flex-1 px-5 py-6 flex flex-col justify-center" 29 + > 27 30 <!-- Idle state --> 28 - <div id="state-idle" class="w-full text-center hidden"> 31 + <div 32 + id="state-idle" 33 + class="w-full text-center flex flex-col justify-center" 34 + > 29 35 <div class="text-5xl mb-4">🔍</div> 30 36 <p class="text-base mb-3 text-purple-950 dark:text-cyan-50"> 31 37 Go to your Twitter/X Following page 32 38 </p> 33 - <p class="text-sm text-slate-500 dark:text-cyan-100 mt-2"> 34 - Visit x.com/your-username/following 35 - </p> 39 + <a 40 + href="https://x.com/following" 41 + target="_blank" 42 + class="w-full px-6 py-2 bg-orange-600 hover:bg-orange-500 text-white font-bold rounded-lg shadow-md hover:shadow-lg transition-all mt-4 inline-block no-underline" 43 + > 44 + Open X Following 45 + </a> 36 46 </div> 37 47 38 48 <!-- Ready state --> 39 - <div id="state-ready" class="w-full text-center hidden"> 49 + <div 50 + id="state-ready" 51 + class="w-full text-center flex flex-col justify-center" 52 + > 40 53 <div class="text-5xl mb-4">✅</div> 41 54 <p class="text-base mb-3 text-purple-950 dark:text-cyan-50"> 42 55 Ready to scan <span id="platform-name"></span> 43 56 </p> 44 57 <button 45 58 id="btn-start" 46 - class="w-full bg-orange-600 hover:bg-orange-700 text-white font-semibold py-3 px-6 rounded-lg mt-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-orange-600/30 active:translate-y-0" 59 + class="w-full px-6 py-2 bg-orange-600 hover:bg-orange-500 text-white font-bold rounded-lg shadow-md hover:shadow-lg transition-all mt-4" 47 60 > 48 61 Start Scan 49 62 </button> 50 63 </div> 51 64 52 65 <!-- Scraping state --> 53 - <div id="state-scraping" class="w-full text-center hidden"> 66 + <div 67 + id="state-scraping" 68 + class="w-full text-center flex flex-col justify-center" 69 + > 54 70 <div class="text-5xl mb-4 spinner">⏳</div> 55 71 <p class="text-base mb-3 text-purple-950 dark:text-cyan-50"> 56 72 Scanning... ··· 65 81 ></div> 66 82 </div> 67 83 <p 68 - class="text-base font-semibold text-slate-700 dark:text-cyan-50" 84 + class="text-base font-semibold text-purple-900 dark:text-cyan-100" 69 85 > 70 86 Found <span id="count">0</span> users 71 87 </p> ··· 73 89 </div> 74 90 75 91 <!-- Complete state --> 76 - <div id="state-complete" class="w-full text-center hidden"> 92 + <div 93 + id="state-complete" 94 + class="w-full text-center flex flex-col justify-center" 95 + > 77 96 <div class="text-5xl mb-4">🎉</div> 78 97 <p class="text-base mb-3 text-purple-950 dark:text-cyan-50"> 79 98 Scan complete! 80 99 </p> 81 - <p class="text-sm text-slate-500 dark:text-slate-400 mt-2"> 100 + <p class="text-base text-purple-950 dark:text-cyan-50 mt-2"> 82 101 Found 83 102 <strong 84 103 id="final-count" ··· 89 108 </p> 90 109 <button 91 110 id="btn-upload" 92 - class="w-full bg-orange-600 hover:bg-orange-700 text-white font-semibold py-3 px-6 rounded-lg mt-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-orange-600/30 active:translate-y-0" 111 + class="w-full px-6 py-2 bg-orange-600 hover:bg-orange-500 text-white font-bold rounded-lg shadow-md hover:shadow-lg transition-all mt-4" 93 112 > 94 113 Open in ATlast 95 114 </button> 96 115 </div> 97 116 98 117 <!-- Uploading state --> 99 - <div id="state-uploading" class="w-full text-center hidden"> 118 + <div 119 + id="state-uploading" 120 + class="w-full text-center flex flex-col justify-center" 121 + > 100 122 <div class="text-5xl mb-4 spinner">📤</div> 101 123 <p class="text-base mb-3 text-purple-950 dark:text-cyan-50"> 102 124 Uploading to ATlast... ··· 104 126 </div> 105 127 106 128 <!-- Error state --> 107 - <div id="state-error" class="w-full text-center hidden"> 129 + <div 130 + id="state-error" 131 + class="w-full text-center flex flex-col justify-center" 132 + > 108 133 <div class="text-5xl mb-4">⚠️</div> 109 134 <p class="text-base mb-3 text-purple-950 dark:text-cyan-50"> 110 135 Error 111 136 </p> 112 137 <p 113 138 id="error-message" 114 - class="text-[13px] text-red-600 dark:text-red-400 mt-2 p-3 bg-red-50 dark:bg-red-950/50 rounded border-l-[3px] border-red-600" 139 + class="text-sm text-purple-950 dark:text-cyan-50 mt-2 p-3 bg-white-50 dark:bg-slate-950/50 rounded border-l-[3px] border-red-600" 115 140 ></p> 116 141 <button 117 142 id="btn-retry" 118 - class="w-full bg-white dark:bg-purple-950 text-purple-700 dark:text-cyan-400 border-2 border-purple-700 dark:border-cyan-400 font-semibold py-2.5 px-6 rounded-lg mt-4 transition-all duration-200 hover:bg-purple-50 dark:hover:bg-purple-900" 143 + class="w-full px-6 py-2 bg-orange-600 hover:bg-orange-500 text-white rounded-lg font-bold shadow-md hover:shadow-lg transition-all mt-4" 119 144 > 120 145 Try Again 121 146 </button> 122 147 </div> 123 148 124 149 <!-- Server offline state --> 125 - <div id="state-offline" class="w-full hidden"> 126 - <div class="text-5xl text-center mb-4">🔌</div> 150 + <div 151 + id="state-offline" 152 + class="w-full flex flex-col justify-center text-center" 153 + > 154 + <div class="text-5xl mb-4">🔌</div> 127 155 <p class="text-base mb-3 text-purple-950 dark:text-cyan-50"> 128 156 Server not available 129 157 </p> ··· 139 167 </p> 140 168 <button 141 169 id="btn-check-server" 142 - class="w-full bg-orange-600 hover:bg-orange-500 text-white font-medium py-3 px-6 rounded-lg mt-4 shadow-md hover:shadow-lg transition-all duration-200" 170 + class="w-full px-6 py-2 bg-orange-600 hover:bg-orange-500 text-white rounded-lg font-medium shadow-md hover:shadow-lg transition-all mt-4" 143 171 > 144 172 Check Again 145 173 </button> 146 174 </div> 147 175 148 176 <!-- Not logged in state --> 149 - <div id="state-not-logged-in" class="w-full text-center hidden"> 177 + <div 178 + id="state-not-logged-in" 179 + class="w-full text-center flex flex-col justify-center" 180 + > 150 181 <div class="text-5xl mb-4">🔐</div> 151 182 <p class="text-base mb-3 text-purple-950 dark:text-cyan-50"> 152 183 Not logged in to ATlast 153 184 </p> 154 185 <p 155 - class="text-[13px] text-red-600 dark:text-red-400 mt-2 p-3 bg-red-50 dark:bg-red-950/50 rounded border-l-[3px] border-red-600" 186 + class="text-[13px] text-red-600 dark:text-red-400 mt-2 p-3 bg-white-50 dark:bg-slate-950/50 rounded border-l-2 border-red-600" 156 187 > 157 188 Please log in to ATlast first, then return here to scan. 158 189 </p> 159 190 <button 160 191 id="btn-open-atlast" 161 - class="w-full bg-orange-600 hover:bg-orange-700 text-white font-semibold py-3 px-6 rounded-lg mt-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-orange-600/30 active:translate-y-0" 192 + class="w-full px-6 py-2 bg-orange-600 hover:bg-orange-500 text-white rounded-lg font-bold shadow-md hover:shadow-lg transition-all mt-4" 162 193 > 163 194 Open ATlast 164 195 </button> 165 196 <button 166 197 id="btn-retry-login" 167 - class="w-full bg-white dark:bg-purple-950 text-purple-700 dark:text-cyan-400 border-2 border-purple-700 dark:border-cyan-400 font-semibold py-2.5 px-6 rounded-lg mt-4 transition-all duration-200 hover:bg-purple-50 dark:hover:bg-purple-900" 198 + class="w-full px-4 py-2 text-purple-750 dark:text-cyan-250 hover:text-purple-950 dark:hover:text-cyan-50 transition-colors mt-2" 168 199 > 169 200 Check Again 170 201 </button> 171 202 </div> 172 203 </main> 173 204 174 - <footer class="text-center mb-6 rounded-3xl"> 175 - <a 176 - href="https://atlast.byarielm.fyi" 177 - target="_blank" 178 - class="text-purple-750 dark:text-cyan-250 no-underline text-l font-medium hover:underline" 179 - >atlast.byarielm.fyi</a 205 + <footer class="text-left"> 206 + <div 207 + class="mt-4 px-10 py-2 -indent-6 border-orange-650/50 dark:border-amber-400/50 bg-white/50 dark:bg-slate-900/50" 180 208 > 209 + <p class="text-sm text-purple-900 dark:text-cyan-100"> 210 + 💡 <strong>Need help?</strong> Contact @byarielm.fyi on 211 + <a 212 + href="https://bsky.app/profile/atlast.byarielm.fyi" 213 + target="_blank" 214 + class="text-orange-600 dark:text-amber-400 hover:underline" 215 + >Bluesky</a 216 + > 217 + or 218 + <a 219 + href="https://tangled.org/byarielm.fyi/atlast" 220 + target="_blank" 221 + class="text-orange-600 dark:text-amber-400 hover:underline" 222 + >Tangled</a 223 + > 224 + </p> 225 + </div> 181 226 </footer> 182 227 </div> 183 228
+33 -2
packages/extension/src/popup/popup.ts
··· 213 213 error: "Failed to scrape page", 214 214 }), 215 215 ); 216 - toolbar.appendChild(createButton("Offline", { status: "idle" })); 217 - toolbar.appendChild(createButton("Not Logged In", { status: "idle" })); 216 + 217 + // Special buttons that directly show state (not part of ExtensionState status) 218 + const btnOffline = document.createElement("button"); 219 + btnOffline.textContent = "Offline"; 220 + btnOffline.style.cssText = ` 221 + background: #f97316; 222 + color: white; 223 + border: none; 224 + padding: 4px 8px; 225 + border-radius: 4px; 226 + cursor: pointer; 227 + font-size: 11px; 228 + `; 229 + btnOffline.onclick = () => { 230 + showState("offline"); 231 + }; 232 + toolbar.appendChild(btnOffline); 233 + 234 + const btnNotLoggedIn = document.createElement("button"); 235 + btnNotLoggedIn.textContent = "Not Logged In"; 236 + btnNotLoggedIn.style.cssText = ` 237 + background: #f97316; 238 + color: white; 239 + border: none; 240 + padding: 4px 8px; 241 + border-radius: 4px; 242 + cursor: pointer; 243 + font-size: 11px; 244 + `; 245 + btnNotLoggedIn.onclick = () => { 246 + showState("notLoggedIn"); 247 + }; 248 + toolbar.appendChild(btnNotLoggedIn); 218 249 219 250 document.body.appendChild(toolbar); 220 251 }
+2
packages/web/vite.config.ts
··· 19 19 ], 20 20 }, 21 21 server: { 22 + port: 5175, 23 + strictPort: true, // Don't auto-increment port if in use 22 24 fs: { 23 25 // Allow serving files from the monorepo root 24 26 allow: ["../.."],