From 653b5c5f1bd491fed5e53ef6cdc4712d64e7162f Mon Sep 17 00:00:00 2001 From: Ewan Croft Date: Sun, 8 Mar 2026 02:08:14 +0000 Subject: [PATCH] feat(pds-landing): 2.0.0 --- packages/pds-landing/README.md | 139 ++++++++++---- packages/pds-landing/package.json | 26 ++- .../src/lib/components/ContactSection.svelte | 59 ++++++ .../src/lib/components/Divider.svelte | 9 + .../src/lib/components/KVGrid.svelte | 87 +++++++++ .../src/lib/components/LinkList.svelte | 60 ++++++ .../src/lib/components/PDSFooter.svelte | 43 +++++ .../src/lib/components/PDSPage.svelte | 172 ++++++++++++++++++ .../src/lib/components/PromptLine.svelte | 48 +++++ .../src/lib/components/SectionLabel.svelte | 25 +++ .../src/lib/components/StatusGrid.svelte | 92 ++++++++++ .../src/lib/components/Tagline.svelte | 24 +++ .../src/lib/components/TerminalCard.svelte | 77 ++++++++ packages/pds-landing/src/lib/index.ts | 25 ++- .../src/lib/utils/fetchPDSStatus.ts | 99 ++++++++++ packages/pds-landing/src/routes/+layout.ts | 1 + packages/pds-landing/src/routes/+page.svelte | 8 +- packages/pds-landing/src/routes/layout.css | 29 ++- pnpm-lock.yaml | 4 + 19 files changed, 980 insertions(+), 47 deletions(-) create mode 100644 packages/pds-landing/src/lib/components/ContactSection.svelte create mode 100644 packages/pds-landing/src/lib/components/Divider.svelte create mode 100644 packages/pds-landing/src/lib/components/KVGrid.svelte create mode 100644 packages/pds-landing/src/lib/components/LinkList.svelte create mode 100644 packages/pds-landing/src/lib/components/PDSFooter.svelte create mode 100644 packages/pds-landing/src/lib/components/PDSPage.svelte create mode 100644 packages/pds-landing/src/lib/components/PromptLine.svelte create mode 100644 packages/pds-landing/src/lib/components/SectionLabel.svelte create mode 100644 packages/pds-landing/src/lib/components/StatusGrid.svelte create mode 100644 packages/pds-landing/src/lib/components/Tagline.svelte create mode 100644 packages/pds-landing/src/lib/components/TerminalCard.svelte create mode 100644 packages/pds-landing/src/lib/utils/fetchPDSStatus.ts create mode 100644 packages/pds-landing/src/routes/+layout.ts diff --git a/packages/pds-landing/README.md b/packages/pds-landing/README.md index 1a9e8ad..4e6a84c 100644 --- a/packages/pds-landing/README.md +++ b/packages/pds-landing/README.md @@ -1,65 +1,132 @@ -# Svelte library +# @ewanc26/pds-landing -Everything you need to build a Svelte library, powered by [`sv`](https://npmjs.com/package/sv). +Composable Svelte 5 components for an ATProto PDS landing page — terminal aesthetic, live status fetching, zero config to drop in. -Read more about creating a library [in the docs](https://svelte.dev/docs/kit/packaging). +## Install -## Creating a project +```bash +pnpm add @ewanc26/pds-landing @ewanc26/ui +``` -If you're seeing this, you've probably already done this step. Congrats! +## Quick start — full page + +```svelte + + + + +``` -```sh -# create a new project in the current directory -npx sv create +Import the PDS design tokens and base styles once in your layout: -# create a new project in my-app -npx sv create my-app +```css +/* app.css / layout.css */ +@import '@ewanc26/ui/styles/pds-tokens.css'; ``` -To recreate this project with the same configuration: +--- -```sh -# recreate this project -pnpm dlx sv@0.12.5 create --template library --types ts --add prettier tailwindcss="plugins:typography" sveltekit-adapter="adapter:static" --install pnpm ./packages/pds-landing -``` +## Mix-and-match — primitives + +All primitives are exported individually so you can compose custom layouts. + +```svelte + -## Developing + + + -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + + -```sh -npm run dev + -# or start the server and open the app in a new browser tab -npm run dev -- --open + + + + + + + + + + ``` -Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app. +### Use raw KV data -## Building +```svelte + -```sh -npm pack + ``` -To create a production version of your showcase app: +### Fetch status yourself -```sh -npm run build +```ts +import { fetchPDSStatus } from '@ewanc26/pds-landing'; + +const { health, description, accountCount } = await fetchPDSStatus('https://pds.example.com'); ``` -You can preview the production build with `npm run preview`. +--- -> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. +## Components -## Publishing +| Component | Description | +|---|---| +| `PDSPage` | Full assembled landing page (convenience) | +| `TerminalCard` | Terminal window shell with traffic-light dots titlebar | +| `PromptLine` | `user@host:path $` bash prompt header | +| `Tagline` | Dimmed subtitle beneath the prompt | +| `SectionLabel` | Uppercase section heading | +| `Divider` | Thin green-tinted `
` | +| `KVGrid` | Key-value grid with ok/warn/err/loading states | +| `StatusGrid` | Live-fetching PDS status grid (wraps `KVGrid`) | +| `LinkList` | `→ link` list | +| `ContactSection` | Bluesky mention + optional email | +| `PDSFooter` | Footer with nixpkgs / atproto links | -Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)). +## Design tokens -To publish your library to [npm](https://www.npmjs.com): +All components consume CSS custom properties from `@ewanc26/ui/styles/pds-tokens.css`: -```sh -npm publish +``` +--pds-font-mono +--pds-color-crust / mantle / base / surface-0 / surface-1 / overlay-0 +--pds-color-text / subtext-0 +--pds-color-green / red / yellow / shadow ``` diff --git a/packages/pds-landing/package.json b/packages/pds-landing/package.json index 6341ecd..629c381 100644 --- a/packages/pds-landing/package.json +++ b/packages/pds-landing/package.json @@ -1,6 +1,7 @@ { - "name": "pds-landing", - "version": "0.0.1", + "name": "@ewanc26/pds-landing", + "version": "2.0.0", + "description": "Composable Svelte components for an ATProto PDS landing page — terminal-aesthetic UI with live status fetching.", "scripts": { "dev": "vite dev", "build": "vite build && npm run prepack", @@ -26,11 +27,17 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "svelte": "./dist/index.js" + "svelte": "./dist/index.js", + "default": "./dist/index.js" } }, "peerDependencies": { - "svelte": "^5.0.0" + "@sveltejs/kit": ">=2.0.0", + "svelte": "^5.0.0", + "tailwindcss": ">=4.0.0" + }, + "dependencies": { + "@ewanc26/ui": "workspace:*" }, "devDependencies": { "@sveltejs/adapter-static": "^3.0.10", @@ -50,6 +57,13 @@ "vite": "^7.3.1" }, "keywords": [ - "svelte" - ] + "svelte", + "atproto", + "bluesky", + "pds", + "landing-page" + ], + "publishConfig": { + "access": "public" + } } diff --git a/packages/pds-landing/src/lib/components/ContactSection.svelte b/packages/pds-landing/src/lib/components/ContactSection.svelte new file mode 100644 index 0000000..1c426a0 --- /dev/null +++ b/packages/pds-landing/src/lib/components/ContactSection.svelte @@ -0,0 +1,59 @@ + + +{#if blueskyHandle && profileUrl} +

+ Send a mention on Bluesky to + @{blueskyHandle} +

+{/if} + +{#if email} +

+ Email: {email} +

+{/if} + + diff --git a/packages/pds-landing/src/lib/components/Divider.svelte b/packages/pds-landing/src/lib/components/Divider.svelte new file mode 100644 index 0000000..9278010 --- /dev/null +++ b/packages/pds-landing/src/lib/components/Divider.svelte @@ -0,0 +1,9 @@ +
+ + diff --git a/packages/pds-landing/src/lib/components/KVGrid.svelte b/packages/pds-landing/src/lib/components/KVGrid.svelte new file mode 100644 index 0000000..b35c8d4 --- /dev/null +++ b/packages/pds-landing/src/lib/components/KVGrid.svelte @@ -0,0 +1,87 @@ + + +
+ {#each items as item (item.key)} + {item.key} + {item.value} + {/each} +
+ + diff --git a/packages/pds-landing/src/lib/components/LinkList.svelte b/packages/pds-landing/src/lib/components/LinkList.svelte new file mode 100644 index 0000000..6bf7290 --- /dev/null +++ b/packages/pds-landing/src/lib/components/LinkList.svelte @@ -0,0 +1,60 @@ + + + + + diff --git a/packages/pds-landing/src/lib/components/PDSFooter.svelte b/packages/pds-landing/src/lib/components/PDSFooter.svelte new file mode 100644 index 0000000..bc5bd89 --- /dev/null +++ b/packages/pds-landing/src/lib/components/PDSFooter.svelte @@ -0,0 +1,43 @@ + + +
+ {#if showNixpkg} + powered by + nixpkgs#bluesky-pds + {/if} + {#if showNixpkg && showAtproto} +  ·  + {/if} + {#if showAtproto} + atproto.com + {/if} +
+ + diff --git a/packages/pds-landing/src/lib/components/PDSPage.svelte b/packages/pds-landing/src/lib/components/PDSPage.svelte new file mode 100644 index 0000000..5d8130e --- /dev/null +++ b/packages/pds-landing/src/lib/components/PDSPage.svelte @@ -0,0 +1,172 @@ + + +
+ + + + + {#if showStatus} + + + {/if} + + {#if showEndpoints} + + +

+ Most API routes are under /xrpc/ +

+ + {/if} + + {#if showLinks} + + + + {/if} + + {#if showContact} + + + + {/if} +
+ + {#if showFooter} + + {/if} +
+ + diff --git a/packages/pds-landing/src/lib/components/PromptLine.svelte b/packages/pds-landing/src/lib/components/PromptLine.svelte new file mode 100644 index 0000000..8abf194 --- /dev/null +++ b/packages/pds-landing/src/lib/components/PromptLine.svelte @@ -0,0 +1,48 @@ + + +
+ {user}@{host}:{path} +
+ + diff --git a/packages/pds-landing/src/lib/components/SectionLabel.svelte b/packages/pds-landing/src/lib/components/SectionLabel.svelte new file mode 100644 index 0000000..03bdc8a --- /dev/null +++ b/packages/pds-landing/src/lib/components/SectionLabel.svelte @@ -0,0 +1,25 @@ + + + + + diff --git a/packages/pds-landing/src/lib/components/StatusGrid.svelte b/packages/pds-landing/src/lib/components/StatusGrid.svelte new file mode 100644 index 0000000..14f7714 --- /dev/null +++ b/packages/pds-landing/src/lib/components/StatusGrid.svelte @@ -0,0 +1,92 @@ + + + diff --git a/packages/pds-landing/src/lib/components/Tagline.svelte b/packages/pds-landing/src/lib/components/Tagline.svelte new file mode 100644 index 0000000..75fbe00 --- /dev/null +++ b/packages/pds-landing/src/lib/components/Tagline.svelte @@ -0,0 +1,24 @@ + + +

+ {#if children}{@render children()}{:else}{text ?? ''}{/if} +

+ + diff --git a/packages/pds-landing/src/lib/components/TerminalCard.svelte b/packages/pds-landing/src/lib/components/TerminalCard.svelte new file mode 100644 index 0000000..fe70bdc --- /dev/null +++ b/packages/pds-landing/src/lib/components/TerminalCard.svelte @@ -0,0 +1,77 @@ + + +
+
+ + + + {title} +
+
+ {#if children}{@render children()}{/if} +
+
+ + diff --git a/packages/pds-landing/src/lib/index.ts b/packages/pds-landing/src/lib/index.ts index 47d3c46..ac381c3 100644 --- a/packages/pds-landing/src/lib/index.ts +++ b/packages/pds-landing/src/lib/index.ts @@ -1 +1,24 @@ -// Reexport your entry components here +// ─── Utilities ──────────────────────────────────────────────────────────────── +export { fetchPDSStatus } from './utils/fetchPDSStatus.js'; +export type { PDSHealth, PDSDescription, PDSStatusResult } from './utils/fetchPDSStatus.js'; + +// ─── Primitive components ───────────────────────────────────────────────────── +export { default as TerminalCard } from './components/TerminalCard.svelte'; +export { default as PromptLine } from './components/PromptLine.svelte'; +export { default as Tagline } from './components/Tagline.svelte'; +export { default as SectionLabel } from './components/SectionLabel.svelte'; +export { default as Divider } from './components/Divider.svelte'; +export { default as KVGrid } from './components/KVGrid.svelte'; +export type { KVItem } from './components/KVGrid.svelte'; +export { default as LinkList } from './components/LinkList.svelte'; +export type { LinkItem } from './components/LinkList.svelte'; + +// ─── Smart / data-fetching components ──────────────────────────────────────── +export { default as StatusGrid } from './components/StatusGrid.svelte'; + +// ─── Compound / section components ─────────────────────────────────────────── +export { default as ContactSection } from './components/ContactSection.svelte'; +export { default as PDSFooter } from './components/PDSFooter.svelte'; + +// ─── Full-page convenience component ───────────────────────────────────────── +export { default as PDSPage } from './components/PDSPage.svelte'; diff --git a/packages/pds-landing/src/lib/utils/fetchPDSStatus.ts b/packages/pds-landing/src/lib/utils/fetchPDSStatus.ts new file mode 100644 index 0000000..715b138 --- /dev/null +++ b/packages/pds-landing/src/lib/utils/fetchPDSStatus.ts @@ -0,0 +1,99 @@ +/** + * Types and utilities for fetching live status from an ATProto PDS. + */ + +export interface PDSHealth { + reachable: boolean; + /** Reported software version, or null if unreachable / not exposed. */ + version: string | null; +} + +export interface PDSDescription { + did: string | null; + inviteCodeRequired: boolean | null; + phoneVerificationRequired: boolean | null; + availableUserDomains: string[]; + links: { + privacyPolicy?: string; + termsOfService?: string; + } | null; + contact: { email?: string } | null; +} + +export interface PDSStatusResult { + health: PDSHealth; + description: PDSDescription; + /** Total repo count, or -1 if the fetch failed. */ + accountCount: number; +} + +async function fetchJSON(url: string): Promise { + const r = await fetch(url); + if (!r.ok) throw new Error(`HTTP ${r.status}`); + return r.json(); +} + +/** + * Fetch live status from a PDS. + * + * @param baseUrl - Origin to prepend to `/xrpc/…` paths. + * Defaults to `''` (same origin, works in-browser). + */ +export async function fetchPDSStatus(baseUrl = ''): Promise { + // ── health ──────────────────────────────────────────────────────────────── + let health: PDSHealth = { reachable: false, version: null }; + try { + const h = (await fetchJSON(`${baseUrl}/xrpc/_health`)) as { version?: string }; + health = { reachable: true, version: h.version ?? null }; + } catch { + // leave defaults + } + + // ── description ─────────────────────────────────────────────────────────── + let description: PDSDescription = { + did: null, + inviteCodeRequired: null, + phoneVerificationRequired: null, + availableUserDomains: [], + links: null, + contact: null + }; + try { + const d = (await fetchJSON(`${baseUrl}/xrpc/com.atproto.server.describeServer`)) as { + did?: string; + inviteCodeRequired?: boolean; + phoneVerificationRequired?: boolean; + availableUserDomains?: string[]; + links?: { privacyPolicy?: string; termsOfService?: string }; + contact?: { email?: string }; + }; + description = { + did: d.did ?? null, + inviteCodeRequired: d.inviteCodeRequired ?? null, + phoneVerificationRequired: d.phoneVerificationRequired ?? null, + availableUserDomains: d.availableUserDomains ?? [], + links: d.links ?? null, + contact: d.contact ?? null + }; + } catch { + // leave defaults + } + + // ── account count (paginated) ───────────────────────────────────────────── + let accountCount = 0; + try { + let cursor: string | undefined; + do { + const url = + `${baseUrl}/xrpc/com.atproto.sync.listRepos?limit=1000` + + (cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''); + const r = (await fetchJSON(url)) as { repos?: unknown[]; cursor?: string }; + accountCount += (r.repos ?? []).length; + cursor = r.cursor; + } while (cursor); + } catch { + accountCount = -1; + } + + return { health, description, accountCount }; +} diff --git a/packages/pds-landing/src/routes/+layout.ts b/packages/pds-landing/src/routes/+layout.ts new file mode 100644 index 0000000..189f71e --- /dev/null +++ b/packages/pds-landing/src/routes/+layout.ts @@ -0,0 +1 @@ +export const prerender = true; diff --git a/packages/pds-landing/src/routes/+page.svelte b/packages/pds-landing/src/routes/+page.svelte index 9c42926..a5ab9e3 100644 --- a/packages/pds-landing/src/routes/+page.svelte +++ b/packages/pds-landing/src/routes/+page.svelte @@ -1,3 +1,5 @@ -

Welcome to your library project

-

Create your package using @sveltejs/package and preview/showcase your work with SvelteKit

-

Visit svelte.dev/docs/kit to read the documentation

+ + + diff --git a/packages/pds-landing/src/routes/layout.css b/packages/pds-landing/src/routes/layout.css index 1c4d2a8..dac1868 100644 --- a/packages/pds-landing/src/routes/layout.css +++ b/packages/pds-landing/src/routes/layout.css @@ -1,2 +1,29 @@ @import 'tailwindcss'; -@plugin '@tailwindcss/typography'; +@import '@ewanc26/ui/styles/pds-tokens.css'; + +/* ─── Global resets & base ───────────────────────────────────────────────── */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, +body { + min-height: 100dvh; + background-color: var(--pds-color-crust); + color: var(--pds-color-text); + font-family: var(--pds-font-mono); + font-size: clamp(13px, 3.5vw, 15px); + line-height: 1.6; +} + +body { + display: flex; + flex-direction: column; + align-items: center; + padding: clamp(1.5rem, 6vw, 4rem) clamp(1rem, 4vw, 1.5rem) clamp(1.5rem, 6vw, 3rem); + gap: 2rem; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1704afd..5f0b805 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,10 @@ importers: version: 5.9.3 packages/pds-landing: + dependencies: + '@ewanc26/ui': + specifier: workspace:* + version: link:../ui devDependencies: '@sveltejs/adapter-static': specifier: ^3.0.10 -- 2.51.2