browse the protocol like its 2008 ibex.desertthunder.dev
ubuntu atproto svelte
7

Configure Feed

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

feat: add a not-impl modal and atmosphere info

Owais Jamil (Jun 9, 2026, 3:24 PM -0500) 64f7c386 1c8eee98

+251 -14
+2
README.md
··· 34 34 - Ubuntu 11 ([duh](https://archive.org/details/ubuntu-11.10-desktop-i386-20110812)) 35 35 - Atmosphere app logos are adapted from 36 36 [atmologos](https://tangled.org/cozylittle.house/atmologos). 37 + - Atmosphere [info](/src/routes/docs/atmosphere.md) adapted from 38 + [Atmosphere Account](https://atmosphereaccount.com/).
+15 -10
src/lib/components/AppWindow.svelte
··· 1 1 <script lang="ts"> 2 2 import { browser } from '$app/environment'; 3 3 import { onMount } from 'svelte'; 4 + import { notImplemented } from '$lib/not-implemented.svelte'; 4 5 import type { Snippet } from 'svelte'; 5 6 6 7 type Props = { ··· 191 192 aria-label={maximized ? 'Restore' : 'Maximize'} 192 193 tabindex={onmaximize ? 0 : -1} 193 194 onclick={onmaximize}></button> 194 - <button class="close-control" type="button" aria-label="Close" tabindex={onclose ? 0 : -1} onclick={onclose} 195 - ></button> 195 + <button 196 + class="close-control" 197 + type="button" 198 + aria-label="Close" 199 + tabindex={onclose ? 0 : -1} 200 + onclick={onclose ?? (() => notImplemented.show())}></button> 196 201 </div> 197 202 </header> 198 203 199 204 {#if showMenubar} 200 205 <nav class="menubar" aria-label="Application menu"> 201 - <button type="button">Collection</button> 202 - <button type="button">Navigate</button> 203 - <button type="button">View</button> 204 - <button type="button">Bookmarks</button> 205 - <button type="button">Help</button> 206 + <button type="button" onclick={() => notImplemented.show()}>Collection</button> 207 + <button type="button" onclick={() => notImplemented.show()}>Navigate</button> 208 + <button type="button" onclick={() => notImplemented.show()}>View</button> 209 + <button type="button" onclick={() => notImplemented.show()}>Bookmarks</button> 210 + <button type="button" onclick={() => notImplemented.show()}>Help</button> 206 211 </nav> 207 212 {/if} 208 213 209 214 {#if showToolbar} 210 215 <div class="toolbar" aria-label="Application toolbar"> 211 - <button type="button"><span aria-hidden="true">◀</span> Back</button> 212 - <button type="button"><span aria-hidden="true">▶</span> Forward</button> 213 - <button type="button" 216 + <button type="button" onclick={() => notImplemented.show()}><span aria-hidden="true">◀</span> Back</button> 217 + <button type="button" onclick={() => notImplemented.show()}><span aria-hidden="true">▶</span> Forward</button> 218 + <button type="button" onclick={() => notImplemented.show()} 214 219 ><img src="/icons/humanity/actions/mail-send-receive.svg" alt="" width="18" height="18" /> Sync</button> 215 220 <label> 216 221 <span class="sr-only">Address</span>
+16
src/lib/components/DocumentViewer.svelte
··· 129 129 list-style: decimal; 130 130 } 131 131 132 + .document-page :global(a) { 133 + color: #164a8b; 134 + font-weight: 700; 135 + text-decoration-line: underline; 136 + text-decoration-thickness: 2px; 137 + text-underline-offset: 0.18em; 138 + background: linear-gradient(transparent 58%, rgb(114 159 207 / 0.28) 58%); 139 + border-radius: var(--radius-1); 140 + } 141 + 142 + .document-page :global(a:hover) { 143 + color: white; 144 + background: var(--selection); 145 + text-decoration-color: rgb(255 255 255 / 0.8); 146 + } 147 + 132 148 .document-page :global(code) { 133 149 padding: 0.08rem 0.3rem; 134 150 font-family: var(--font-mono);
+1 -1
src/lib/components/GnomeMenuBar.svelte
··· 22 22 </script> 23 23 24 24 <nav class="panel-menus" aria-label="System menus"> 25 - <button class="applications-menu" type="button" onclick={() => goto('/')}> 25 + <button class="applications-menu" type="button" onclick={() => goto('/docs/atmosphere')}> 26 26 <img src="/icons/humanity/apps/system-file-manager.svg" alt="" width="20" height="20" /> 27 27 <span>Applications</span> 28 28 </button>
+2 -1
src/lib/components/GnomePanel.svelte
··· 1 1 <script lang="ts"> 2 + import { notImplemented } from '$lib/not-implemented.svelte'; 2 3 import { windowManager } from '$lib/window-manager.svelte'; 3 4 import GnomeMenuBar from '$lib/components/GnomeMenuBar.svelte'; 4 5 ··· 14 15 15 16 <div class="panel-launchers" aria-label="Launchers"> 16 17 {#each launchers as launcher (launcher.label)} 17 - <button type="button" title={launcher.label}> 18 + <button type="button" title={launcher.label} onclick={() => notImplemented.show()}> 18 19 <img src={launcher.icon} alt="" width="22" height="22" /> 19 20 </button> 20 21 {/each}
+136
src/lib/components/NotImplementedDialog.svelte
··· 1 + <script lang="ts"> 2 + import { notImplemented } from '$lib/not-implemented.svelte'; 3 + 4 + function closeFromBackdrop(event: MouseEvent) { 5 + if (event.target === event.currentTarget) { 6 + notImplemented.close(); 7 + } 8 + } 9 + </script> 10 + 11 + {#if notImplemented.message} 12 + <div class="dialog-backdrop" role="presentation" onclick={closeFromBackdrop}> 13 + <div 14 + class="not-implemented-dialog" 15 + role="dialog" 16 + aria-modal="true" 17 + aria-labelledby="not-implemented-title" 18 + tabindex="-1"> 19 + <header class="dialog-titlebar"> 20 + <div> 21 + <img src="/icons/humanity/mimes/text-x-generic.svg" alt="" width="18" height="18" /> 22 + <h2 id="not-implemented-title">Not Implemented</h2> 23 + </div> 24 + <button type="button" aria-label="Close" onclick={() => notImplemented.close()}></button> 25 + </header> 26 + 27 + <div class="dialog-body"> 28 + <img src="/icons/humanity/devices/computer.svg" alt="" width="48" height="48" /> 29 + <p>{notImplemented.message}</p> 30 + </div> 31 + 32 + <footer class="dialog-actions"> 33 + <button type="button" onclick={() => notImplemented.close()}>OK</button> 34 + </footer> 35 + </div> 36 + </div> 37 + {/if} 38 + 39 + <style> 40 + .dialog-backdrop { 41 + position: fixed; 42 + inset: 1.75rem 0 0; 43 + display: grid; 44 + place-items: center; 45 + padding: var(--space-4); 46 + background: rgb(0 0 0 / 0.18); 47 + z-index: 10000; 48 + } 49 + 50 + .not-implemented-dialog { 51 + width: min(22rem, 100%); 52 + overflow: hidden; 53 + color: var(--text); 54 + background: #f4e6cc; 55 + border: 1px solid #2a170d; 56 + border-radius: var(--radius-3) var(--radius-3) var(--radius-2) var(--radius-2); 57 + box-shadow: var(--shadow-window); 58 + } 59 + 60 + .dialog-titlebar { 61 + display: flex; 62 + align-items: center; 63 + justify-content: space-between; 64 + min-height: 1.85rem; 65 + padding: 3px var(--space-2) 3px var(--space-3); 66 + color: #fff1d7; 67 + background: 68 + linear-gradient(rgb(255 255 255 / 0.14), transparent 45%), 69 + linear-gradient(var(--window-title-start), var(--window-title-end)); 70 + border-bottom: 1px solid #1b0f09; 71 + text-shadow: 0 1px 1px rgb(0 0 0 / 0.9); 72 + } 73 + 74 + .dialog-titlebar div { 75 + display: flex; 76 + align-items: center; 77 + gap: var(--space-2); 78 + min-width: 0; 79 + } 80 + 81 + .dialog-titlebar h2 { 82 + font-size: var(--text-2); 83 + } 84 + 85 + .dialog-titlebar button { 86 + width: 14px; 87 + height: 14px; 88 + border: 1px solid #1e120a; 89 + border-radius: var(--radius-round); 90 + background: radial-gradient(circle at 35% 30%, #fff4d0, #cf7b1f 45%, #6d3412 78%); 91 + box-shadow: 0 1px 0 rgb(255 255 255 / 0.24) inset; 92 + cursor: default; 93 + } 94 + 95 + .dialog-titlebar button:hover { 96 + background: radial-gradient(circle at 35% 30%, #ffd6c9, #ef2929 42%, #660000 78%); 97 + } 98 + 99 + .dialog-body { 100 + display: grid; 101 + grid-template-columns: 48px minmax(0, 1fr); 102 + gap: var(--space-4); 103 + align-items: center; 104 + padding: var(--space-5); 105 + background: linear-gradient(#fff8ed, #e4cfad); 106 + } 107 + 108 + .dialog-body p { 109 + font-size: var(--text-3); 110 + font-weight: 700; 111 + } 112 + 113 + .dialog-actions { 114 + display: flex; 115 + justify-content: end; 116 + padding: var(--space-3); 117 + background: #d6bf99; 118 + border-top: 1px solid #a5875d; 119 + } 120 + 121 + .dialog-actions button { 122 + min-width: 5.5rem; 123 + height: 1.8rem; 124 + padding: 0 var(--space-4); 125 + background: linear-gradient(#fff9ec, #ceb48a); 126 + border: 1px solid #9b7d55; 127 + border-radius: var(--radius-2); 128 + box-shadow: var(--shadow-raised); 129 + font-weight: 700; 130 + cursor: default; 131 + } 132 + 133 + .dialog-actions button:active { 134 + box-shadow: var(--shadow-sunken); 135 + } 136 + </style>
+13
src/lib/not-implemented.svelte.ts
··· 1 + class NotImplementedState { 2 + message = $state<string | null>(null); 3 + 4 + show(message = 'Coming soon :)') { 5 + this.message = message; 6 + } 7 + 8 + close() { 9 + this.message = null; 10 + } 11 + } 12 + 13 + export const notImplemented = new NotImplementedState();
+7 -2
src/routes/+layout.svelte
··· 18 18 import GnomePanel from '$lib/components/GnomePanel.svelte'; 19 19 import LockScreen from '$lib/components/LockScreen.svelte'; 20 20 import NativeWindow from '$lib/components/NativeWindow.svelte'; 21 + import NotImplementedDialog from '$lib/components/NotImplementedDialog.svelte'; 21 22 import SetupDialog from '$lib/components/SetupDialog.svelte'; 22 23 import '$lib/styles/style.css'; 23 24 ··· 35 36 const windowTitle = $derived.by(() => { 36 37 if (routeRequiresSetup) return 'AT Protocol Account Setup'; 37 38 if (page.route.id === '/') return 'Welcome to Intrepid Ibex'; 38 - if (page.route.id?.startsWith('/docs')) return 'Getting Started - Document Viewer'; 39 + // TODO: this should be slug aware 40 + if (page.route.id?.startsWith('/docs')) return 'Document Viewer'; 39 41 return 'AT Protocol Collections - Intrepid Ibex'; 40 42 }); 41 43 const windowIcon = $derived.by(() => { ··· 80 82 { 81 83 label: 'Document Viewer', 82 84 icon: '/icons/humanity/mimes/gnome-mime-application-pdf.svg', 83 - selected: page.route.id?.startsWith('/docs') || (documentViewerWindow?.isOpen && !documentViewerWindow.isMinimized), 85 + selected: 86 + page.route.id?.startsWith('/docs') || (documentViewerWindow?.isOpen && !documentViewerWindow.isMinimized), 84 87 onactivate: () => { 85 88 windowManager.restore('main'); 86 89 void goto('/docs'); ··· 328 331 </aside> 329 332 {/if} 330 333 </main> 334 + 335 + <NotImplementedDialog /> 331 336 332 337 {#if desktopSession.isLocked} 333 338 <LockScreen identity={accountSetup.identity} onunlock={() => desktopSession.unlock()} />
+6
src/routes/docs/+page.svelte
··· 9 9 icon: '/icons/humanity/mimes/gnome-mime-application-pdf.svg' 10 10 }, 11 11 { 12 + title: 'Atmosphere', 13 + slug: 'atmosphere', 14 + description: 'A short note about portable accounts and the open social web.', 15 + icon: '/icons/humanity/apps/web-browser.svg' 16 + }, 17 + { 12 18 title: 'Changelog', 13 19 slug: 'changelog', 14 20 description: 'Release notes and recent project changes.',
+53
src/routes/docs/atmosphere.md
··· 1 + # Atmosphere 2 + 3 + The Atmosphere is an open social web made of apps and services that work together. 4 + Instead of creating a fresh account for every app, you can use one portable identity 5 + across the network. 6 + 7 + An Atmosphere account is like a passport: one handle, one home for your data, and the 8 + freedom to choose the apps you use. 9 + 10 + ## One account, many apps 11 + 12 + Posts, likes, follows, comments, lists, photos, videos, blogs, and new kinds of records 13 + can all live with the same account. Apps can present that data in different ways, but 14 + the identity stays yours. 15 + 16 + ## Built differently 17 + 18 + - **Universal identity** — the same `@handle` can identify you across Atmosphere apps. 19 + - **Account ownership** — your profile and records are not trapped inside one app. 20 + - **Anyone can build** — developers can make new apps that plug into the same network. 21 + - **Personal domains** — your handle can be your own domain, so your identity follows 22 + your name or project. 23 + 24 + ## Choose your provider 25 + 26 + Your account lives with a provider: a service that stores your personal data and keeps 27 + it available to apps. A provider might be an app you already use, an independent host, 28 + or infrastructure you run yourself. 29 + 30 + You can move providers without starting over. Your account, records, and relationships 31 + are designed to remain portable. 32 + 33 + ## Post once, show everywhere 34 + 35 + A blog post might appear in a reader. A photo might appear in a gallery. Music might 36 + show up in a player. The same account can produce records that many apps understand, 37 + and the ecosystem can grow without a fixed list of app types. 38 + 39 + ## Your account, your choice 40 + 41 + No single company has to decide what you see or where you go. Moderation, feeds, and 42 + apps can be chosen in layers: 43 + 44 + - Subscribe to community moderation and block lists. 45 + - Switch feeds like playlists. 46 + - Move between apps and providers while keeping your audience. 47 + 48 + The promise is an internet that is open by default and owned by the people using 49 + it. 50 + 51 + ## Credit 52 + 53 + Adapted from [Atmosphere Account](https://atmosphereaccount.com/).