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

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: ["../.."],