audio streaming app plyr.fm
38
fork

Configure Feed

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

polish login page copy and UX for create account flow (#818)

- sign-in: link "internet handle" to internethandle.org
- sign-in: show Bluesky/Blacksky with logos and names in hint
- sign-in: consolidate FAQ into "new to the atmosphere?" toggle
- create account: "choose a home for your data" with visual explainer link
- create account: move PDS description inside card with link to info page
- create account: add Bluesky/Blacksky note with logos
- backend: add info_url to PdsOption model and config

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

authored by

nate nowack
Claude Opus 4.5
and committed by
GitHub
27c42301 a8b3546a

+63 -70
+2
backend/src/backend/api/auth.py
··· 94 94 url: str 95 95 recommended: bool = False 96 96 description: str | None = None 97 + info_url: str | None = None 97 98 98 99 99 100 class PdsOptionsResponse(BaseModel): ··· 119 120 url=str(pds.get("url", "")), 120 121 recommended=bool(pds.get("recommended", False)), 121 122 description=str(pds["description"]) if pds.get("description") else None, 123 + info_url=str(pds["info_url"]) if pds.get("info_url") else None, 122 124 ) 123 125 for pds in settings.account_creation.recommended_pds 124 126 ],
+2
backend/src/backend/config.py
··· 775 775 { 776 776 "name": "selfhosted.social", 777 777 "url": "https://selfhosted.social", 778 + "description": "free, community-run PDS", 779 + "info_url": "https://selfhosted.social/info", 778 780 }, 779 781 ], 780 782 description="List of recommended PDS options for account creation",
+57 -68
frontend/src/routes/login/+page.svelte
··· 11 11 url: string; 12 12 recommended: boolean; 13 13 description?: string; 14 + info_url?: string; 14 15 } 15 16 16 17 let mode = $state<Mode>('signin'); ··· 113 114 <form onsubmit={startOAuth}> 114 115 {#if mode === 'signin'} 115 116 <div class="input-group"> 116 - <label for="handle">internet handle</label> 117 + <label for="handle"> 118 + <a href="https://internethandle.org" target="_blank" rel="noopener" class="handle-label-link">internet handle</a> 119 + </label> 117 120 <HandleAutocomplete 118 121 bind:value={handle} 119 122 onSelect={handleSelect} 120 123 placeholder="you.bsky.social" 121 124 disabled={loading} 122 125 /> 126 + <div class="handle-hint"> 127 + <span>your @handle from</span> 128 + <a href="https://bsky.app" target="_blank" rel="noopener" class="service-inline bluesky"><svg viewBox="0 0 600 530" width="12" height="11"><path d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z" fill="currentColor"/></svg> Bluesky</a> 129 + <span>or</span> 130 + <a href="https://blackskyweb.xyz" target="_blank" rel="noopener" class="service-inline blacksky"><svg viewBox="0 0 88 75" width="11" height="10" fill="currentColor"><path d="M41.9565 74.9643L24.0161 74.9653L41.9565 74.9643ZM63.8511 74.9653H45.9097L63.8501 74.9643V57.3286H63.8511V74.9653ZM45.9097 44.5893C45.9099 49.2737 49.7077 53.0707 54.3921 53.0707H63.8501V57.3286H54.3921C49.7077 57.3286 45.9099 61.1257 45.9097 65.81V74.9643H41.9565V65.81C41.9563 61.1258 38.1593 57.3287 33.4751 57.3286H24.0161V53.0707H33.4741C38.1587 53.0707 41.9565 49.2729 41.9565 44.5883V35.1303H45.9097V44.5893ZM63.8511 53.0707H63.8501V35.1303H63.8511V53.0707Z"/><path d="M52.7272 9.83198C49.4148 13.1445 49.4148 18.5151 52.7272 21.8275L59.4155 28.5158L56.4051 31.5262L49.7169 24.8379C46.4044 21.5254 41.0338 21.5254 37.7213 24.8379L31.2482 31.3111L28.4527 28.5156L34.9259 22.0424C38.2383 18.7299 38.2383 13.3594 34.9259 10.0469L28.2378 3.35883L31.2482 0.348442L37.9365 7.03672C41.2489 10.3492 46.6195 10.3492 49.932 7.03672L56.6203 0.348442L59.4155 3.14371L52.7272 9.83198Z"/><path d="M24.3831 23.2335C23.1706 27.7584 25.8559 32.4095 30.3808 33.6219L39.5172 36.07L38.4154 40.182L29.2793 37.734C24.7544 36.5215 20.1033 39.2068 18.8909 43.7317L16.5215 52.5745L12.7028 51.5513L15.0721 42.7088C16.2846 38.1839 13.5993 33.5328 9.07434 32.3204L-0.0620117 29.8723L1.03987 25.76L10.1762 28.2081C14.7011 29.4206 19.3522 26.7352 20.5647 22.2103L23.0127 13.074L26.8311 14.0971L24.3831 23.2335Z"/><path d="M67.3676 22.0297C68.5801 26.5546 73.2311 29.2399 77.756 28.0275L86.8923 25.5794L87.9941 29.6914L78.8578 32.1394C74.3329 33.3519 71.6476 38.003 72.86 42.5279L75.2294 51.3707L71.411 52.3938L69.0417 43.5513C67.8293 39.0264 63.1782 36.3411 58.6533 37.5535L49.5169 40.0016L48.415 35.8894L57.5514 33.4413C62.0763 32.2288 64.7616 27.5778 63.5492 23.0528L61.1011 13.9165L64.9195 12.8934L67.3676 22.0297Z"/></svg> Blacksky</a> 131 + </div> 123 132 </div> 124 133 125 134 <button type="submit" class="primary" disabled={loading || !handle.trim()}> ··· 127 136 </button> 128 137 {:else} 129 138 <div class="input-group"> 130 - <span class="input-label">pick a home on <a href="https://atproto.com" target="_blank" rel="noopener" class="atmosphere-link">the atmosphere</a></span> 139 + <span class="input-label">choose a <a href="https://at-me.zzstoatzz.io/view/?handle=zzstoatzz.io" target="_blank" rel="noopener" class="home-link">home</a> for your data</span> 131 140 <div class="pds-options"> 132 141 {#each pdsOptions as pds (pds.url)} 133 142 <label class="pds-option" class:selected={selectedPds === pds.url}> ··· 146 155 {/if} 147 156 </div> 148 157 {#if pds.description} 149 - <span class="pds-description">{pds.description}</span> 158 + {#if pds.info_url} 159 + <a href={pds.info_url} target="_blank" rel="noopener" class="pds-description-link" onclick={(e) => e.stopPropagation()}>{pds.description}</a> 160 + {:else} 161 + <span class="pds-description">{pds.description}</span> 162 + {/if} 150 163 {/if} 151 164 </div> 152 165 </label> 153 166 {/each} 154 167 </div> 155 168 <p class="pds-note"> 156 - or sign up directly with 157 - <a href="https://bsky.app" target="_blank" rel="noopener">Bluesky</a> 158 - or 159 - <a href="https://blackskyweb.xyz" target="_blank" rel="noopener">Blacksky</a> 169 + <a href="https://bsky.app" target="_blank" rel="noopener" class="service-inline"><svg viewBox="0 0 600 530" width="12" height="11" fill="currentColor"><path d="m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z"/></svg> Bluesky</a> and 170 + <a href="https://blackskyweb.xyz" target="_blank" rel="noopener" class="service-inline"><svg viewBox="0 0 88 75" width="11" height="10" fill="currentColor"><path d="M41.9565 74.9643L24.0161 74.9653L41.9565 74.9643ZM63.8511 74.9653H45.9097L63.8501 74.9643V57.3286H63.8511V74.9653ZM45.9097 44.5893C45.9099 49.2737 49.7077 53.0707 54.3921 53.0707H63.8501V57.3286H54.3921C49.7077 57.3286 45.9099 61.1257 45.9097 65.81V74.9643H41.9565V65.81C41.9563 61.1258 38.1593 57.3287 33.4751 57.3286H24.0161V53.0707H33.4741C38.1587 53.0707 41.9565 49.2729 41.9565 44.5883V35.1303H45.9097V44.5893ZM63.8511 53.0707H63.8501V35.1303H63.8511V53.0707Z"/><path d="M52.7272 9.83198C49.4148 13.1445 49.4148 18.5151 52.7272 21.8275L59.4155 28.5158L56.4051 31.5262L49.7169 24.8379C46.4044 21.5254 41.0338 21.5254 37.7213 24.8379L31.2482 31.3111L28.4527 28.5156L34.9259 22.0424C38.2383 18.7299 38.2383 13.3594 34.9259 10.0469L28.2378 3.35883L31.2482 0.348442L37.9365 7.03672C41.2489 10.3492 46.6195 10.3492 49.932 7.03672L56.6203 0.348442L59.4155 3.14371L52.7272 9.83198Z"/><path d="M24.3831 23.2335C23.1706 27.7584 25.8559 32.4095 30.3808 33.6219L39.5172 36.07L38.4154 40.182L29.2793 37.734C24.7544 36.5215 20.1033 39.2068 18.8909 43.7317L16.5215 52.5745L12.7028 51.5513L15.0721 42.7088C16.2846 38.1839 13.5993 33.5328 9.07434 32.3204L-0.0620117 29.8723L1.03987 25.76L10.1762 28.2081C14.7011 29.4206 19.3522 26.7352 20.5647 22.2103L23.0127 13.074L26.8311 14.0971L24.3831 23.2335Z"/><path d="M67.3676 22.0297C68.5801 26.5546 73.2311 29.2399 77.756 28.0275L86.8923 25.5794L87.9941 29.6914L78.8578 32.1394C74.3329 33.3519 71.6476 38.003 72.86 42.5279L75.2294 51.3707L71.411 52.3938L69.0417 43.5513C67.8293 39.0264 63.1782 36.3411 58.6533 37.5535L49.5169 40.0016L48.415 35.8894L57.5514 33.4413C62.0763 32.2288 64.7616 27.5778 63.5492 23.0528L61.1011 13.9165L64.9195 12.8934L67.3676 22.0297Z"/></svg> Blacksky</a> also host accounts. 160 171 </p> 161 172 </div> 162 173 ··· 173 184 onclick={() => (showHandleInfo = !showHandleInfo)} 174 185 aria-expanded={showHandleInfo} 175 186 > 176 - <span>what is an internet handle?</span> 177 - <svg 178 - class="chevron" 179 - class:open={showHandleInfo} 180 - width="16" 181 - height="16" 182 - viewBox="0 0 24 24" 183 - fill="none" 184 - stroke="currentColor" 185 - stroke-width="2" 186 - > 187 + <span>new to <a href="https://atproto.com" target="_blank" rel="noopener" class="toggle-link" onclick={(e) => e.stopPropagation()}>the atmosphere</a>?</span> 188 + <svg class="chevron" class:open={showHandleInfo} width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> 187 189 <polyline points="6 9 12 15 18 9"></polyline> 188 190 </svg> 189 191 </button> 190 192 {#if showHandleInfo} 191 193 <div class="faq-content"> 192 - <p> 193 - your internet handle is a domain that identifies you across apps built on 194 - <a href="https://atproto.com" target="_blank" rel="noopener">AT Protocol</a>. 195 - if you signed up for Bluesky or another ATProto service, you already have one 196 - (like <code>yourname.bsky.social</code>). 197 - </p> 194 + <p class="welcome">hey, welcome in! you'll need an account.</p> 198 195 <p> 199 - read more at <a href="https://internethandle.org" target="_blank" rel="noopener">internethandle.org</a>. 200 - </p> 201 - </div> 202 - {/if} 203 - 204 - <button 205 - class="faq-toggle" 206 - onclick={() => (showPdsInfo = !showPdsInfo)} 207 - aria-expanded={showPdsInfo} 208 - > 209 - <span>don't have one?</span> 210 - <svg 211 - class="chevron" 212 - class:open={showPdsInfo} 213 - width="16" 214 - height="16" 215 - viewBox="0 0 24 24" 216 - fill="none" 217 - stroke="currentColor" 218 - stroke-width="2" 219 - > 220 - <polyline points="6 9 12 15 18 9"></polyline> 221 - </svg> 222 - </button> 223 - {#if showPdsInfo} 224 - <div class="faq-content"> 225 - <p> 226 - pick a home on the atmosphere &mdash; 227 196 {#if creationEnabled} 228 - <button class="link-button" onclick={switchToCreate}>create an account</button> 229 - {:else} 230 - sign up for <a href="https://bsky.app" target="_blank" rel="noopener">Bluesky</a> 197 + <button class="link-button" onclick={switchToCreate}>create one here</button>, or 231 198 {/if} 232 - to get started. 199 + sign up with <a href="https://bsky.app" target="_blank" rel="noopener">Bluesky</a> or <a href="https://blackskyweb.xyz" target="_blank" rel="noopener">Blacksky</a> and come back. 233 200 </p> 234 201 </div> 235 202 {/if} ··· 351 318 font-size: var(--text-base); 352 319 & .atmosphere-link { color: var(--accent); text-decoration: none; } 353 320 & .atmosphere-link:hover { text-decoration: underline; } 321 + & .handle-label-link { color: var(--text-secondary); text-decoration: none; border-bottom: 1px dashed var(--border-default); } 322 + & .handle-label-link:hover { color: var(--text-primary); border-bottom-color: var(--text-tertiary); } 323 + & .home-link { color: var(--accent); text-decoration: none; border-bottom: 1px solid var(--accent); } 324 + & .home-link:hover { border-bottom-style: dashed; } 354 325 } 326 + 327 + .handle-hint { 328 + display: flex; 329 + align-items: center; 330 + gap: 0.375rem; 331 + font-size: var(--text-sm); 332 + color: var(--text-tertiary); 333 + } 334 + 355 335 356 336 button.primary { 357 337 width: 100%; ··· 397 377 text-align: left; 398 378 } 399 379 400 - .faq-toggle:hover { 401 - color: var(--text-primary); 402 - } 380 + .faq-toggle:hover { color: var(--text-primary); } 403 381 404 - .chevron { 405 - transition: transform 0.2s; 406 - flex-shrink: 0; 407 - } 382 + .toggle-link { color: inherit; text-decoration: none; border-bottom: 1px dashed var(--border-default); } 383 + .toggle-link:hover { color: var(--accent); border-bottom-color: var(--accent); } 408 384 409 - .chevron.open { 410 - transform: rotate(180deg); 411 - } 385 + .chevron { transition: transform 0.2s; flex-shrink: 0; } 386 + .chevron.open { transform: rotate(180deg); } 412 387 413 388 .faq-content { 414 389 padding: 0 0 1rem 0; ··· 417 392 line-height: 1.6; 418 393 & p { margin: 0 0 0.75rem 0; text-align: left; } 419 394 & p:last-child { margin-bottom: 0; } 395 + & p.welcome { color: var(--text-primary); font-weight: 500; } 420 396 & a { color: var(--accent); text-decoration: none; } 421 397 & a:hover { text-decoration: underline; } 422 398 & code { background: var(--bg-secondary); padding: 0.15rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.85em; } ··· 496 472 border-radius: var(--radius-sm); 497 473 } 498 474 499 - .pds-description { 475 + .pds-description, .pds-description-link { 500 476 font-size: var(--text-sm); 501 477 color: var(--text-tertiary); 502 478 } 479 + 480 + .pds-description-link { text-decoration: none; } 481 + .pds-description-link:hover { color: var(--accent); } 503 482 504 483 .pds-note { 505 484 margin: 0.75rem 0 0; 506 - font-size: var(--text-sm); 485 + font-size: var(--text-xs); 507 486 color: var(--text-tertiary); 508 487 text-align: center; 509 488 & a { color: var(--accent); text-decoration: none; } 510 489 & a:hover { text-decoration: underline; } 490 + } 491 + 492 + 493 + .service-inline { 494 + display: inline-flex; 495 + align-items: center; 496 + gap: 0.25rem; 497 + & svg { vertical-align: middle; } 498 + &.bluesky:hover { color: #0085ff; } 499 + &.blacksky:hover { color: #fff; } 511 500 } 512 501 513 502 .back-link {
+2 -2
loq.toml
··· 35 35 36 36 [[rules]] 37 37 path = "backend/src/backend/api/auth.py" 38 - max_lines = 784 38 + max_lines = 790 39 39 40 40 [[rules]] 41 41 path = "backend/src/backend/api/lists.py" ··· 51 51 52 52 [[rules]] 53 53 path = "backend/src/backend/config.py" 54 - max_lines = 843 54 + max_lines = 850 55 55 56 56 [[rules]] 57 57 path = "backend/src/backend/storage/r2.py"