# plspace *It's always Pleroma.* A static, client-side frontend for Pleroma that looks and behaves like MySpace circa 2005. This is a 100% Claude and ChatGPT vibe-coded application. Point it at your server. It talks to that server's REST API directly from the browser — there is no plspace backend, and `dist/` is a folder of static files you can host anywhere. ## Repository The canonical repository is hosted on Radicle: `rad://z2gAKC6ESt5ZBV419uVPf2vFtEHCT`. ## What it looks like | MySpace | plspace | | --- | --- | | Profile page: photo, headline, vitals, Contacting box, Interests table | `#/@user@server` | | Latest Blog Entries with "(view more)" | Account statuses, headline list | | Blurbs: "About me", "Who I'd like to meet" | The bio, split on a heading if you wrote one | | Friend Space grid | Followers | | Mail Center, Friend Request Manager | Notifications, follow requests | | Bulletin Space | The local timeline | | Kudos | Favourites | | Custom profile layouts | CSS in a profile field named `css` | ## Running it ```bash npm install && npm run dev ``` ```bash npm run build ``` `npm run build` type-checks and then bundles to `dist/`. Because routing is hash-based, the output works from any path — a subdirectory, an S3 bucket, GitHub Pages — with no server rewrite rules. ## Signing in plspace registers itself as an OAuth app on your server the first time you sign in there, then redirects you to that server's own consent screen. Your password is never entered into plspace; it only ever receives OAuth tokens, which are stored in `localStorage` and used directly from your browser. Short-lived access tokens are renewed with a server-issued refresh token when available. PKCE is used where the server supports it, with an automatic fallback for servers that don't. You can also browse without signing in — click **Just look around**. Note that many servers set `restrict_unauthenticated` and refuse timeline reads from anonymous callers; the suggested servers on the sign-in page are ones verified to allow it. ## Making it yours Settings has a CSS editor, five starter layouts, and a full class reference. Everything about the appearance is overridable — see [`src/styles/README.md`](src/styles/README.md) for the contract. In short: no CSS framework, no Svelte scoped styles, no hashed class names, every colour and metric is a custom property, and user CSS is always last in the cascade so plain single-class selectors win without `!important`. There is no built-in dark mode. A dark theme is just token overrides, so it ships as a preset (**Midnight**, **Terminal**) you can apply, edit or replace, rather than as a toggle you can't. To publish a layout other people see on your profile, put CSS in a profile field named `css` on your server. It is scoped to your profile's subtree and filtered before it is applied. Your profile fields also drive the profile page: name one `Music`, `Movies`, `Television`, `Books` or `Heroes` and it fills the Interests table; name one `Mood`, `Location`, `Gender` or `Headline` and it fills the block beside your photo. ## Layout ``` src/ lib/ api/ client (fetch, Link-header pagination), OAuth, endpoints, entity types stores/ session, Feed (cursor pagination), theme (user + profile CSS) util/ HTML sanitizing, 2005-flavoured date formats, account -> profile mapping router.svelte.ts themes.ts starter layouts, written only in terms of tokens components/ chrome/ header, nav, footer common/ Module, RichText, Avatar, Pager, TabBar profile/ identity, contacting box, interests, details, friend space blog/ entry, attachments, poll, preview card, composer people/ person row and list routes/ Home, Profile, Timeline, StatusPage, Mail, Browse, Search, Compose, Login, Settings styles/ tokens, base, layout, chrome, module, profile, blog, forms ``` ## Safety notes All HTML from the API — post content, bios, profile field values — is sanitized with DOMPurify before it reaches `{@html}`, in exactly one component (`RichText.svelte`) plus the profile-field helper. Mention and hashtag links are rewritten to in-app routes; every other link gets `target="_blank"` with `rel="noopener noreferrer"`. ## Compatibility Written against the standard `/api/v1` REST API and the local Egregoros implementation, as well as live servers. Anything implementations differ on degrades rather than fails: `/api/v2/instance` falls back to v1, `/api/v1/accounts/lookup` falls back to search, pagination falls back to the last item's id when a server drops the `Link` header, and per-account privacy flags are read in both their spellings. Egregoros' hour-long access tokens and rotating refresh tokens are supported; notification folders are filtered defensively because its current API ignores `types[]`. Profile controls are capability-aware, so unsupported Egregoros fields are not shown as though they could be saved. Other software speaking the same API therefore works, but Pleroma is what this targets.