Minimalistic opinionated Mastodon web client.

🗣️ Pronunciation: /fænpi/ (FAN-pee) 🔊 Listen
This is an alternative web client for Mastodon.
production branchmain branch🐘 Follow @phanpy on Mastodon for updates ✨
Everything is designed and engineered following my taste and vision. This is a personal side project for me to learn about Mastodon and experiment with new UI/UX ideas.
They only appear in individual status page. This is to reduce clutter and distraction. It may result in lower engagement, but we're not chasing numbers here. - Boost is represented with the rocket icon.
The green double arrow icon (retweet for Twitter) doesn't look right for the term "boost". Green rocket looks weird, so I use purple.
- Short usernames (@username) are displayed in timelines, instead of the full account username (@username@instance).
Despite the guideline mentioned that "Decentralization must be transparent to the user", I don't think we should shove it to the face every single time. There are also some screen-reader-related accessibility concerns with the full username, though this web app is unfortunately not accessible yet. - No autoplay for video/GIF/whatever in timeline.
The timeline is already a huge mess with lots of people, brands, news and media trying to grab your attention. Let's not make it worse. (Current exception now would be animated emojis.) - Hash-based URLs.
This web app is not meant to be a full-fledged replacement to Mastodon's existing front-end. There's no SEO, database, serverless or any long-running servers. I could be wrong one day.
User name display

[NAME] @[username].@[username], always exclude the instance domain name.[NAME] looks the same as the @[username], then the @[username] is excluded as well.Boosts Carousel

Thread number badge (e.g. Thread 1/X)

inReplyToId from cache or additional API requests, until the root post is found.Thread without the number.Hashtag stuffing collapsing

... at the end.Filtered posts
Prerequisites: Node.js 22+
npm install - Install dependenciesnpm run dev - Start development server and messages:extract (clean + watch) in parallelnpm run build - Build for productionnpm run preview - Preview the production buildnpm run fetch-instances - Fetch instances list from joinmastodon.org/servers, save it to src/data/instances.jsonnpm run sourcemap - Run source-map-explorer on the production buildnpm run messages:extract - Extract messages from source files and update the locale message catalogsnpm run git:po-filter - Configure git to use po-filter for diffing .po filesSome of these may change in the future. The front-end world is ever-changing.
All translations are available as gettext .po files in the src/locales folder. The default language is English (en). CLDR Plural Rules are used for pluralization. RTL (right-to-left) languages are also supported with proper text direction, icon rendering and layout.
On page load, default language is detected via these methods, in order (first match is used):
lang e.g. /?lang=zh-HantlocalStorage key langnavigator.languageUsers can change the language in the settings, which sets the localStorage key lang.
*Inspired by Translate WordPress Handbook:
{account} (variable), <0>{name}</0> (tag with variable) and # (number placeholder).Intl.DateTimeFormat - e.g. "8 Aug", "08/08/2024"Intl.RelativeTimeFormat - e.g. "2 days ago", "in 2 days"Intl.NumberFormat - e.g. "1,000", "10K"Intl.DisplayNames - e.g. "English" (en) in Traditional Chinese (zh-Hant) is "英文"Intl.ListFormat - e.g. "A, B, and C"Intl.Locale (with polyfill for older browsers)Intl.Segmenter (with polyfill for older browsers)<input type="month"> will always follow the system's locale and not the user's set locale.U+200F, ‏) may need to be used for mixed RTL/LTR text, especially for <title> element (document.title).pseudo-LOCALE locale, used for pseudolocalization. It's for testing and won't show up on production.en) catalog messages are not bundled separatedly. Other locales are bundled as separate files and loaded on demand. This ensures that en is always available as fallback.Translations are managed on Crowdin. You can help by volunteering translations.
Read the intro documentation to get started.
This is a pure static web app. You can host it anywhere you want.
Two ways (choose one):
❌ NOT recommended.
Go to Releases and download the latest phanpy-dist.zip or phanpy-dist.tar.gz. It's pre-built so don't need to run any install/build commands. Extract it. Serve the folder of extracted files.
[!IMPORTANT] Text translations connect to an external service (
translang.phanpy.social).
✅ Recommended.
Requires Node.js.
Download or git clone this repository. Use production branch for stable releases, main for latest. Build it by running npm run build (after npm install). Serve the dist folder.
[!IMPORTANT] Text translations connect to an external service (
translang.phanpy.social). This can be configured with environment variables if you want to self-host your own instance.
Customization can be done by passing environment variables to the build command. Examples:
```bash PHANPY_CLI
$ claude mcp add phanpy \
-- python -m otcore.mcp_server <graph>