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: getting started

Owais Jamil (Jun 9, 2026, 3:12 PM -0500) b66acf27 c2cb023b

+575 -41
+5 -5
src/lib/components/AboutComputer.svelte
··· 16 16 <dl> 17 17 <div> 18 18 <dt>Operating system</dt> 19 - <dd>Intrepid Ibex Desktop 0.1</dd> 19 + <dd>Svelte 5</dd> 20 20 </div> 21 21 <div> 22 22 <dt>Desktop environment</dt> 23 - <dd>Classic GNOME-style shell</dd> 23 + <dd>GNOME 2</dd> 24 24 </div> 25 25 <div> 26 26 <dt>Protocol</dt> 27 - <dd>AT Protocol, public read-only mode</dd> 27 + <dd>at://</dd> 28 28 </div> 29 29 <div> 30 30 <dt>Account</dt> ··· 43 43 44 44 <p class="note"> 45 45 View the project on 46 - <a href="https://tangled.org/desertthunder.dev/ibex" target="_blank" rel="noreferrer">Tangled</a>. 47 - Atmosphere logos courtesy of 46 + <a href="https://tangled.org/desertthunder.dev/ibex" target="_blank" rel="noreferrer">Tangled</a>. Atmosphere logos 47 + courtesy of 48 48 <a href="https://tangled.org/cozylittle.house/atmologos" target="_blank" rel="noreferrer">atmologos</a>. 49 49 </p> 50 50 </section>
+13 -5
src/lib/components/GnomeMenuBar.svelte
··· 1 1 <script lang="ts"> 2 + import { goto } from '$app/navigation'; 2 3 import { accountSetup } from '$lib/atproto/setup.svelte'; 3 4 import { repoBrowser } from '$lib/atproto/repo.svelte'; 4 5 import { desktopSession } from '$lib/desktop-session.svelte'; ··· 9 10 windowManager.close('gedit'); 10 11 repoBrowser.reset(); 11 12 accountSetup.reset(); 13 + void goto('/browse'); 14 + } 15 + 16 + function openCollection(collectionName: string) { 17 + if (!accountSetup.identity) return; 18 + 19 + void goto('/browse'); 20 + repoBrowser.selectCollection(accountSetup.identity, collectionName); 12 21 } 13 22 </script> 14 23 15 24 <nav class="panel-menus" aria-label="System menus"> 16 - <button class="applications-menu" type="button"> 25 + <button class="applications-menu" type="button" onclick={() => goto('/')}> 17 26 <img src="/icons/humanity/apps/system-file-manager.svg" alt="" width="20" height="20" /> 18 27 <span>Applications</span> 19 28 </button> ··· 26 35 {#each repoBrowser.collections.slice(0, 10) as collection (collection.name)} 27 36 <button 28 37 type="button" 29 - onclick={() => 30 - accountSetup.identity && repoBrowser.selectCollection(accountSetup.identity, collection.name)}> 38 + onclick={() => openCollection(collection.name)}> 31 39 <img src={collection.icon} alt="" width="16" height="16" /> 32 40 <span>{collection.name}</span> 33 41 </button> ··· 42 50 <summary>System</summary> 43 51 <div class="menu-popover system-popover"> 44 52 {#if accountSetup.identity} 45 - <p class="menu-heading">Signed in as @{accountSetup.identity.handle}</p> 53 + <p class="menu-heading">Browsing @{accountSetup.identity.handle}</p> 46 54 {/if} 47 55 <button type="button" onclick={() => desktopSession.lock()}> 48 56 <img src="/icons/humanity/status/network-wireless-encrypted.svg" alt="" width="16" height="16" /> ··· 50 58 </button> 51 59 <button type="button" onclick={changeAccount}> 52 60 <img src="/icons/humanity/places/user-home.svg" alt="" width="16" height="16" /> 53 - <span>Change Account…</span> 61 + <span>Change Repo…</span> 54 62 </button> 55 63 </div> 56 64 </details>
+2
src/lib/components/SetupDialog.svelte
··· 1 1 <script lang="ts"> 2 + import { goto } from '$app/navigation'; 2 3 import { resolveAccount } from '$lib/atproto/identity'; 3 4 import type { AccountIdentity } from '$lib/atproto/types'; 4 5 import { accountSetup, setupDefaults } from '$lib/atproto/setup.svelte'; ··· 28 29 function useAccount() { 29 30 if (resolvedIdentity) { 30 31 accountSetup.save(resolvedIdentity); 32 + void goto('/browse'); 31 33 } 32 34 } 33 35 </script>
+316
src/lib/components/WelcomeScreen.svelte
··· 1 + <script lang="ts"> 2 + import { goto } from '$app/navigation'; 3 + import { accountSetup } from '$lib/atproto/setup.svelte'; 4 + import AboutComputer from '$lib/components/AboutComputer.svelte'; 5 + 6 + const quickLinks = [ 7 + { 8 + label: 'Browse public repo', 9 + description: 'Open Nautilus and inspect public ATProto collections.', 10 + icon: '/icons/humanity/apps/system-file-manager.svg', 11 + href: '/browse' 12 + }, 13 + { 14 + label: 'Getting started', 15 + description: 'Read the short guide in Document Viewer.', 16 + icon: '/icons/humanity/mimes/gnome-mime-application-pdf.svg', 17 + href: '/docs/getting-started' 18 + }, 19 + { 20 + label: 'Project source', 21 + description: 'Visit the Tangled repo in a new tab.', 22 + icon: '/icons/humanity/apps/web-browser.svg', 23 + href: 'https://tangled.org/desertthunder.dev/ibex', 24 + external: true 25 + } 26 + ]; 27 + 28 + function openLink(link: (typeof quickLinks)[number]) { 29 + if (link.external) { 30 + window.open(link.href, '_blank', 'noopener,noreferrer'); 31 + return; 32 + } 33 + 34 + void goto(link.href); 35 + } 36 + </script> 37 + 38 + <section class="welcome-screen" aria-labelledby="welcome-title"> 39 + <div class="welcome-banner"> 40 + <div class="badge-stack" aria-hidden="true"> 41 + <img src="/icons/humanity/devices/computer.svg" alt="" width="72" height="72" /> 42 + <img src="/icons/humanity/apps/internet-feed-reader.svg" alt="" width="38" height="38" /> 43 + </div> 44 + <div> 45 + <p class="eyebrow">Browse the ATmosphere</p> 46 + <h2 id="welcome-title">Welcome to Intrepid Ibex</h2> 47 + <p>Browse public AT Protocol repositories like it's 2008.</p> 48 + </div> 49 + </div> 50 + 51 + <div class="welcome-grid"> 52 + <section class="start-panel" aria-labelledby="start-title"> 53 + <div class="panel-titlebar"> 54 + <img src="/icons/humanity/mimes/text-x-generic.svg" alt="" width="18" height="18" /> 55 + <h3 id="start-title">What would you like to do?</h3> 56 + </div> 57 + 58 + <div class="repo-strip"> 59 + <span>Browsing public repo</span> 60 + <strong>{accountSetup.identity ? `@${accountSetup.identity.handle}` : 'not configured'}</strong> 61 + </div> 62 + 63 + <div class="quick-links"> 64 + {#each quickLinks as link (link.label)} 65 + <button type="button" class="quick-link" onclick={() => openLink(link)}> 66 + <img src={link.icon} alt="" width="36" height="36" /> 67 + <span> 68 + <strong>{link.label}</strong> 69 + <small>{link.description}</small> 70 + </span> 71 + </button> 72 + {/each} 73 + </div> 74 + </section> 75 + 76 + <section class="notes-panel" aria-labelledby="notes-title"> 77 + <div class="panel-titlebar"> 78 + <img src="/icons/humanity/apps/accessories-text-editor.svg" alt="" width="18" height="18" /> 79 + <h3 id="notes-title">Getting started</h3> 80 + </div> 81 + <ol> 82 + <li>Choose a public ATProto repo by handle.</li> 83 + <li>Open Nautilus to browse collections and records.</li> 84 + <li>Open records in gedit, then share stable DID-based URLs.</li> 85 + </ol> 86 + <p> 87 + Direct links look like 88 + <code>/records/:did/:collection/:rkey</code> and reopen the desktop around that record. 89 + </p> 90 + </section> 91 + </div> 92 + 93 + <div class="about-embed"> 94 + <AboutComputer /> 95 + </div> 96 + </section> 97 + 98 + <style> 99 + .welcome-screen { 100 + display: grid; 101 + grid-template-rows: auto auto auto; 102 + gap: var(--space-4); 103 + height: 100%; 104 + overflow: auto; 105 + padding: var(--space-5); 106 + color: #24170f; 107 + background: 108 + radial-gradient(circle at 12% 0%, rgb(233 84 32 / 0.16), transparent 15rem), 109 + linear-gradient(135deg, #fff6e4 0%, #e2cba8 48%, #b08a5c 100%); 110 + } 111 + 112 + .welcome-banner, 113 + .start-panel, 114 + .notes-panel, 115 + .about-embed { 116 + border: 1px solid #8e6d43; 117 + border-radius: var(--radius-3); 118 + box-shadow: 119 + 0 1px 0 rgb(255 255 255 / 0.75) inset, 120 + 0 0.65rem 1.4rem rgb(72 42 18 / 0.18); 121 + } 122 + 123 + .welcome-banner { 124 + display: grid; 125 + grid-template-columns: 5.5rem minmax(0, 1fr); 126 + gap: var(--space-4); 127 + align-items: center; 128 + padding: var(--space-5); 129 + background: 130 + linear-gradient(90deg, rgb(255 255 255 / 0.68), rgb(255 255 255 / 0.18)), linear-gradient(#fffaf0, #ead5b4); 131 + } 132 + 133 + .badge-stack { 134 + position: relative; 135 + display: grid; 136 + place-items: center; 137 + width: 5rem; 138 + height: 5rem; 139 + background: linear-gradient(#fdf2d5, #c7955d); 140 + border: 1px solid #7b5632; 141 + border-radius: 0.55rem; 142 + box-shadow: var(--shadow-sunken); 143 + } 144 + 145 + .badge-stack img:first-child { 146 + filter: drop-shadow(0 2px 2px rgb(0 0 0 / 0.25)); 147 + } 148 + 149 + .badge-stack img:last-child { 150 + position: absolute; 151 + right: -0.45rem; 152 + bottom: -0.35rem; 153 + padding: 0.25rem; 154 + background: #fff6df; 155 + border: 1px solid #8e6d43; 156 + border-radius: var(--radius-2); 157 + box-shadow: var(--shadow-raised); 158 + } 159 + 160 + .eyebrow { 161 + color: var(--orange-700); 162 + font-size: var(--text-0); 163 + font-weight: 700; 164 + letter-spacing: 0.04em; 165 + text-transform: uppercase; 166 + } 167 + 168 + h2 { 169 + margin-top: var(--space-1); 170 + font-size: clamp(1.6rem, 4vw, 2.5rem); 171 + line-height: var(--leading-tight); 172 + } 173 + 174 + .welcome-banner p:last-child { 175 + max-width: 48rem; 176 + margin-top: var(--space-2); 177 + color: var(--text-muted); 178 + font-size: var(--text-2); 179 + } 180 + 181 + .welcome-grid { 182 + display: grid; 183 + grid-template-columns: minmax(18rem, 1.2fr) minmax(16rem, 0.8fr); 184 + gap: var(--space-4); 185 + } 186 + 187 + .start-panel, 188 + .notes-panel { 189 + overflow: hidden; 190 + background: #fff8ed; 191 + } 192 + 193 + .panel-titlebar { 194 + display: flex; 195 + align-items: center; 196 + gap: var(--space-2); 197 + min-height: 2rem; 198 + padding: var(--space-2) var(--space-3); 199 + color: white; 200 + background: linear-gradient(#6e4a2e, #3f2617); 201 + text-shadow: 0 1px 0 rgb(0 0 0 / 0.65); 202 + } 203 + 204 + .panel-titlebar h3 { 205 + font-size: var(--text-1); 206 + } 207 + 208 + .repo-strip { 209 + display: flex; 210 + align-items: center; 211 + justify-content: space-between; 212 + gap: var(--space-3); 213 + padding: var(--space-2) var(--space-3); 214 + background: linear-gradient(#f5e4c7, #dcc19a); 215 + border-bottom: 1px solid #b29165; 216 + font-size: var(--text-1); 217 + } 218 + 219 + .repo-strip span { 220 + color: var(--text-muted); 221 + font-weight: 700; 222 + } 223 + 224 + .repo-strip strong { 225 + overflow: hidden; 226 + text-overflow: ellipsis; 227 + white-space: nowrap; 228 + } 229 + 230 + .quick-links { 231 + display: grid; 232 + gap: var(--space-2); 233 + padding: var(--space-3); 234 + } 235 + 236 + .quick-link { 237 + display: grid; 238 + grid-template-columns: 2.25rem minmax(0, 1fr); 239 + gap: var(--space-3); 240 + align-items: center; 241 + width: 100%; 242 + padding: var(--space-3); 243 + text-align: left; 244 + background: linear-gradient(#fffdf7, #ead7b8); 245 + border: 1px solid #b29165; 246 + border-radius: var(--radius-2); 247 + box-shadow: var(--shadow-raised); 248 + cursor: default; 249 + } 250 + 251 + .quick-link:hover { 252 + background: linear-gradient(#fff8e8, #dfc294); 253 + border-color: #835b32; 254 + } 255 + 256 + .quick-link:active { 257 + box-shadow: var(--shadow-sunken); 258 + transform: translateY(1px); 259 + } 260 + 261 + .quick-link span { 262 + display: grid; 263 + gap: 0.1rem; 264 + } 265 + 266 + .quick-link strong { 267 + font-size: var(--text-2); 268 + } 269 + 270 + .quick-link small, 271 + .notes-panel p, 272 + .notes-panel li { 273 + color: var(--text-muted); 274 + font-size: var(--text-1); 275 + line-height: var(--leading-roomy); 276 + } 277 + 278 + .notes-panel ol { 279 + display: grid; 280 + gap: var(--space-2); 281 + padding: var(--space-4) var(--space-5) var(--space-2); 282 + list-style: decimal; 283 + } 284 + 285 + .notes-panel p { 286 + padding: 0 var(--space-4) var(--space-4); 287 + } 288 + 289 + .notes-panel code { 290 + padding: 0.05rem 0.25rem; 291 + font-family: var(--font-mono); 292 + background: #ead7b8; 293 + border: 1px solid #d2b991; 294 + border-radius: var(--radius-1); 295 + } 296 + 297 + .about-embed { 298 + overflow: visible; 299 + } 300 + 301 + .about-embed :global(.about-computer) { 302 + height: auto; 303 + min-height: 0; 304 + } 305 + 306 + @media (max-width: 820px) { 307 + .welcome-screen { 308 + padding: var(--space-3); 309 + } 310 + 311 + .welcome-banner, 312 + .welcome-grid { 313 + grid-template-columns: 1fr; 314 + } 315 + } 316 + </style>
+1 -1
src/lib/window-manager.svelte.ts
··· 41 41 }, 42 42 { 43 43 id: 'document-viewer', 44 - title: 'CHANGELOG.md - Document Viewer', 44 + title: 'Getting Started - Document Viewer', 45 45 icon: '/icons/humanity/mimes/gnome-mime-application-pdf.svg', 46 46 isOpen: false, 47 47 isMinimized: false,
+59 -27
src/routes/+layout.svelte
··· 1 1 <script lang="ts"> 2 2 import { onMount } from 'svelte'; 3 3 import { dev } from '$app/environment'; 4 + import { goto } from '$app/navigation'; 4 5 import { page } from '$app/state'; 5 6 import { repoBrowser } from '$lib/atproto/repo.svelte'; 6 7 import { accountSetup } from '$lib/atproto/setup.svelte'; ··· 29 30 let cacheDisabled = $state(false); 30 31 let handledRecordRoute = $state<string | null>(null); 31 32 32 - const windowTitle = $derived( 33 - accountSetup.isConfigured ? 'AT Protocol Collections - Intrepid Ibex' : 'AT Protocol Account Setup' 34 - ); 35 - const windowIcon = $derived( 36 - accountSetup.isConfigured ? '/icons/humanity/apps/internet-feed-reader.svg' : '/icons/humanity/places/user-home.svg' 37 - ); 33 + const routeRequiresSetup = $derived(page.route.id === '/browse' && !accountSetup.isConfigured); 34 + const routeUsesNativeWindow = $derived(page.route.id === '/' || page.route.id?.startsWith('/docs')); 35 + const windowTitle = $derived.by(() => { 36 + if (routeRequiresSetup) return 'AT Protocol Account Setup'; 37 + if (page.route.id === '/') return 'Welcome to Intrepid Ibex'; 38 + if (page.route.id?.startsWith('/docs')) return 'Getting Started - Document Viewer'; 39 + return 'AT Protocol Collections - Intrepid Ibex'; 40 + }); 41 + const windowIcon = $derived.by(() => { 42 + if (routeRequiresSetup) return '/icons/humanity/places/user-home.svg'; 43 + if (page.route.id === '/') return '/icons/humanity/devices/computer.svg'; 44 + if (page.route.id?.startsWith('/docs')) return '/icons/humanity/mimes/gnome-mime-application-pdf.svg'; 45 + return '/icons/humanity/apps/internet-feed-reader.svg'; 46 + }); 38 47 const mainWindow = $derived(windowManager.getWindow('main')); 39 48 const aboutWindow = $derived(windowManager.getWindow('about-computer')); 40 49 const geditWindow = $derived(windowManager.getWindow('gedit')); ··· 44 53 { 45 54 label: 'ibex Home', 46 55 icon: '/icons/humanity/places/user-home.svg', 47 - selected: false, 48 - onactivate: () => windowManager.restore('main') 56 + selected: page.route.id === '/', 57 + onactivate: () => { 58 + windowManager.restore('main'); 59 + void goto('/'); 60 + } 49 61 }, 50 62 { 51 63 label: 'Collections', 52 64 icon: '/icons/humanity/places/folder.svg', 53 - onactivate: () => windowManager.restore('main') 65 + selected: page.route.id === '/browse' || page.route.id?.startsWith('/records'), 66 + onactivate: () => { 67 + windowManager.restore('main'); 68 + void goto('/browse'); 69 + } 54 70 }, 55 71 { 56 72 label: 'Computer', ··· 64 80 { 65 81 label: 'Document Viewer', 66 82 icon: '/icons/humanity/mimes/gnome-mime-application-pdf.svg', 67 - selected: documentViewerWindow?.isOpen && !documentViewerWindow.isMinimized, 68 - onactivate: () => windowManager.open('document-viewer') 83 + selected: page.route.id?.startsWith('/docs') || (documentViewerWindow?.isOpen && !documentViewerWindow.isMinimized), 84 + onactivate: () => { 85 + windowManager.restore('main'); 86 + void goto('/docs'); 87 + } 69 88 }, 70 89 { 71 90 label: 'Trash', ··· 216 235 217 236 {#if mainWindow?.isOpen && !mainWindow.isMinimized} 218 237 <div class="primary-window" class:maximized={mainWindow.isMaximized} style:z-index={mainWindow.zIndex}> 219 - <AppWindow 220 - windowId="main" 221 - title={windowTitle} 222 - icon={windowIcon} 223 - maximized={mainWindow.isMaximized} 224 - onfocus={() => windowManager.focus('main')} 225 - onminimize={() => windowManager.minimize('main')} 226 - onmaximize={() => windowManager.toggleMaximize('main')}> 227 - {#if accountSetup.isConfigured} 238 + {#if routeUsesNativeWindow} 239 + <NativeWindow 240 + windowId="main" 241 + title={windowTitle} 242 + icon={windowIcon} 243 + maximized={mainWindow.isMaximized} 244 + onfocus={() => windowManager.focus('main')} 245 + onminimize={() => windowManager.minimize('main')} 246 + onmaximize={() => windowManager.toggleMaximize('main')}> 228 247 {@render children()} 229 - {:else} 230 - <SetupDialog /> 231 - {/if} 232 - </AppWindow> 248 + </NativeWindow> 249 + {:else} 250 + <AppWindow 251 + windowId="main" 252 + title={windowTitle} 253 + icon={windowIcon} 254 + maximized={mainWindow.isMaximized} 255 + onfocus={() => windowManager.focus('main')} 256 + onminimize={() => windowManager.minimize('main')} 257 + onmaximize={() => windowManager.toggleMaximize('main')}> 258 + {#if routeRequiresSetup} 259 + <SetupDialog /> 260 + {:else} 261 + {@render children()} 262 + {/if} 263 + </AppWindow> 264 + {/if} 233 265 </div> 234 266 {/if} 235 267 ··· 258 290 style:z-index={documentViewerWindow.zIndex}> 259 291 <AppWindow 260 292 windowId="document-viewer" 261 - title="CHANGELOG.md - Document Viewer" 293 + title="Getting Started - Document Viewer" 262 294 icon="/icons/humanity/mimes/gnome-mime-application-pdf.svg" 263 - address="/docs/changelog" 295 + address="/docs/getting-started" 264 296 maximized={documentViewerWindow.isMaximized} 265 297 onfocus={() => windowManager.focus('document-viewer')} 266 298 onminimize={() => windowManager.minimize('document-viewer')} 267 299 onmaximize={() => windowManager.toggleMaximize('document-viewer')} 268 300 onclose={() => windowManager.close('document-viewer')}> 269 - <DocumentViewer slug="changelog" /> 301 + <DocumentViewer slug="getting-started" /> 270 302 </AppWindow> 271 303 </div> 272 304 {/if}
+2 -2
src/routes/+page.svelte
··· 1 1 <script lang="ts"> 2 - import CollectionBrowser from '$lib/components/CollectionBrowser.svelte'; 2 + import WelcomeScreen from '$lib/components/WelcomeScreen.svelte'; 3 3 </script> 4 4 5 - <CollectionBrowser /> 5 + <WelcomeScreen />
+5
src/routes/browse/+page.svelte
··· 1 + <script lang="ts"> 2 + import CollectionBrowser from '$lib/components/CollectionBrowser.svelte'; 3 + </script> 4 + 5 + <CollectionBrowser />
+142
src/routes/docs/+page.svelte
··· 1 + <script lang="ts"> 2 + import { goto } from '$app/navigation'; 3 + 4 + const documents = [ 5 + { 6 + title: 'Getting Started', 7 + slug: 'getting-started', 8 + description: 'A short guide to browsing public ATProto repos and sharing records.', 9 + icon: '/icons/humanity/mimes/gnome-mime-application-pdf.svg' 10 + }, 11 + { 12 + title: 'Changelog', 13 + slug: 'changelog', 14 + description: 'Release notes and recent project changes.', 15 + icon: '/icons/humanity/mimes/text-x-generic.svg' 16 + } 17 + ]; 18 + </script> 19 + 20 + <svelte:head> 21 + <title>Documents - Intrepid Ibex</title> 22 + </svelte:head> 23 + 24 + <section class="docs-index" aria-labelledby="docs-title"> 25 + <header class="index-header"> 26 + <img src="/icons/humanity/mimes/gnome-mime-application-pdf.svg" alt="" width="56" height="56" /> 27 + <div> 28 + <p class="eyebrow">Document Viewer</p> 29 + <h2 id="docs-title">Documents</h2> 30 + <p>Open local project notes and help files.</p> 31 + </div> 32 + </header> 33 + 34 + <div class="document-list" aria-label="Available documents"> 35 + {#each documents as document (document.slug)} 36 + <button type="button" class="document-row" onclick={() => goto(`/docs/${document.slug}`)}> 37 + <img src={document.icon} alt="" width="40" height="40" /> 38 + <span> 39 + <strong>{document.title}</strong> 40 + <small>{document.description}</small> 41 + </span> 42 + </button> 43 + {/each} 44 + </div> 45 + </section> 46 + 47 + <style> 48 + .docs-index { 49 + display: grid; 50 + align-content: start; 51 + gap: var(--space-4); 52 + height: 100%; 53 + min-height: 0; 54 + overflow: auto; 55 + padding: var(--space-5); 56 + background: 57 + radial-gradient(circle at 18% 0%, rgb(233 84 32 / 0.12), transparent 15rem), linear-gradient(#d8c2a0, #9b7a51); 58 + } 59 + 60 + .index-header, 61 + .document-list { 62 + width: min(48rem, 100%); 63 + margin: 0 auto; 64 + background: #fff8ed; 65 + border: 1px solid #8e6d43; 66 + border-radius: var(--radius-2); 67 + box-shadow: 68 + 0 1px 0 rgb(255 255 255 / 0.8) inset, 69 + 0 1.25rem 2.5rem rgb(0 0 0 / 0.18); 70 + } 71 + 72 + .index-header { 73 + display: grid; 74 + grid-template-columns: 56px minmax(0, 1fr); 75 + gap: var(--space-4); 76 + align-items: center; 77 + padding: var(--space-5); 78 + } 79 + 80 + .eyebrow { 81 + color: var(--orange-700); 82 + font-size: var(--text-0); 83 + font-weight: 700; 84 + letter-spacing: 0.04em; 85 + text-transform: uppercase; 86 + } 87 + 88 + h2 { 89 + margin-top: var(--space-1); 90 + font-size: var(--text-6); 91 + line-height: var(--leading-tight); 92 + } 93 + 94 + .index-header p:last-child { 95 + margin-top: var(--space-2); 96 + color: var(--text-muted); 97 + font-size: var(--text-2); 98 + } 99 + 100 + .document-list { 101 + display: grid; 102 + gap: 1px; 103 + overflow: hidden; 104 + padding: var(--space-2); 105 + } 106 + 107 + .document-row { 108 + display: grid; 109 + grid-template-columns: 40px minmax(0, 1fr); 110 + gap: var(--space-3); 111 + align-items: center; 112 + width: 100%; 113 + padding: var(--space-3); 114 + text-align: left; 115 + border: 1px solid transparent; 116 + border-radius: var(--radius-2); 117 + cursor: default; 118 + } 119 + 120 + .document-row:hover { 121 + color: white; 122 + background: var(--selection); 123 + } 124 + 125 + .document-row span { 126 + display: grid; 127 + gap: 0.15rem; 128 + } 129 + 130 + .document-row strong { 131 + font-size: var(--text-2); 132 + } 133 + 134 + .document-row small { 135 + color: var(--text-muted); 136 + font-size: var(--text-1); 137 + } 138 + 139 + .document-row:hover small { 140 + color: rgb(255 255 255 / 0.82); 141 + } 142 + </style>
+2 -1
src/routes/docs/[slug]/+page.svelte
··· 23 23 24 24 <style> 25 25 .docs-route { 26 - height: 100vh; 26 + height: 100%; 27 + min-height: 0; 27 28 } 28 29 </style>
+28
src/routes/docs/getting-started.md
··· 1 + # Getting Started 2 + 3 + Intrepid Ibex is a public AT Protocol browser dressed like an Ubuntu 8.10 desktop. 4 + It stores everything in your browser. 5 + 6 + ## Browse a repo 7 + 8 + Start with a public handle such as `desertthunder.dev`. The setup dialog resolves the 9 + handle to a DID and finds the repo's public PDS when it is advertised. 10 + 11 + Open **Browse public repo** to view collections in Nautilus. Pick a collection to load 12 + records, then open any record in gedit for a formatted JSON view. 13 + 14 + ## Share a record 15 + 16 + Record windows use stable DID-based URLs: 17 + 18 + ```txt 19 + /records/:did/:collection/:rkey 20 + ``` 21 + 22 + Opening one of those links boots the desktop, selects the collection in Nautilus, and 23 + opens the record in gedit. 24 + 25 + ## Local cache 26 + 27 + Records you fetch are cached in your browser with IndexedDB. Search works against that 28 + local cache, and cache failures should stay recoverable from the boot screen.