A minimalistic opinionated Mastodon web client
See the codeMinimalistic 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.
@username) are displayed in timelines, instead of the full account username (@username@instance).
[NAME] @[username].@[username], always exclude the instance domain name.[NAME] looks the same as the @[username], then the @[username] is excluded as well.

inReplyToId from cache or additional API requests, until the root post is found.Thread without the number.
... at the end.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:
PHANPY_CLIENT_NAME="Phanpy Dev" \
PHANPY_WEBSITE="https://dev.phanpy.social" \
npm run build
PHANPY_DEFAULT_INSTANCE=hachyderm.io \
PHANPY_DEFAULT_INSTANCE_REGISTRATION_URL=https://hachyderm.io/auth/sign_up \
PHANPY_PRIVACY_POLICY_URL=https://hachyderm.io/privacy-policy \
npm run build
It's also possible to set them in the .env file.
Available variables:
PHANPY_CLIENT_NAME (optional, default: Phanpy) affects:
PHANPY_WEBSITE (optional but recommended, no defaults) affects:
PHANPY_DEFAULT_INSTANCE (optional, no defaults):
https://PHANPY_DEFAULT_INSTANCE_REGISTRATION_URL (optional, no defaults):
https://mastodon.social/auth/sign_upPHANPY_PRIVACY_POLICY_URL (optional, default to official instance's privacy policy):
PHANPY_DEFAULT_LANG (optional):
en) if not specified.lang query parameter, lang key in localStorage and navigator.language)PHANPY_REFERRER_POLICY (optional, default: origin):
<meta> tag on the client-side.Referrer-Policy header configured on the server-side (not this variable).PHANPY_LINGVA_INSTANCES (DEPRECATED, optional, space-separated list, default: lingva.phanpy.social [...hard-coded list of fallback instances]):
/.envPHANPY_TRANSLANG_INSTANCES (optional, space-separated list, default: translang.phanpy.social):
/.envPHANPY_IMG_ALT_API_URL (optional, no defaults):
PHANPY_GIPHY_API_KEY (optional, no defaults):
PHANPY_DISALLOW_ROBOTS (optional, default: not set):
true, 1, etc) to override the robots.txt file and disallow all web crawlers from indexing the siteTry online search for "how to self-host static sites" as there are many ways to do it.
⚠️ DEPRECATED. See documentation for lingva-translate or lingva-api.
See documentation for translang-api.
Changelog available here: CHANGELOG.md
These are self-hosted by other wonderful folks.
Note: Add yours by creating a pull request.
There are also mobile app wrappers:
Costs involved in running and developing this web app:
Phanpy is a Ground-type Pokémon.
I am one of the earliest users of Twitter. Twitter was launched on 15 July 2006. I joined on December 2006 and my first tweet was posted on 18 December 2006.
I know how early Twitter looks like. It was fun.
Back then, I made a Twitter clone called "Twig" written in Python and Google App Engine. I almost made my own Twitter desktop client written in Appcelerator Titanium. I gave one of my best talks about the Twitter client in a mini-conference. I built this thing called "Twitter Columns", a web app that shows your list of followings, your followings' followings, your followers, your followers' followers and so on. In 2009, I wrote a blog post titled "How I got started with Twitter". I created two themes for DestroyTwitter (a desktop client made with Adobe Air by Jonnie Hallman) and one of them is called "Vimeo". In 2013, I wrote my own tweets backup site with a front-end to view my tweets and a CouchDB backend to store them.
It's been more than 15 years.
And here I am. Building a Mastodon web client.
Please, please copy the UI ideas and experiments from this app. I think some of them are pretty good and it would be great if more apps have them.
If you're not a developer, please tell your favourite social media client developers about this app and ask them to copy the UI ideas and experiments.
MIT.
(top 24 of 40)
40,401 followers · starred Aug 2023
1,314 followers · starred Jan 2023
13,166 followers · starred Mar 2024
582 followers · starred Sep 2023
A minimalistic opinionated Mastodon web client
See the codeMinimalistic 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.
@username) are displayed in timelines, instead of the full account username (@username@instance).
[NAME] @[username].@[username], always exclude the instance domain name.[NAME] looks the same as the @[username], then the @[username] is excluded as well.

inReplyToId from cache or additional API requests, until the root post is found.Thread without the number.
... at the end.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:
PHANPY_CLIENT_NAME="Phanpy Dev" \
PHANPY_WEBSITE="https://dev.phanpy.social" \
npm run build
PHANPY_DEFAULT_INSTANCE=hachyderm.io \
PHANPY_DEFAULT_INSTANCE_REGISTRATION_URL=https://hachyderm.io/auth/sign_up \
PHANPY_PRIVACY_POLICY_URL=https://hachyderm.io/privacy-policy \
npm run build
It's also possible to set them in the .env file.
Available variables:
PHANPY_CLIENT_NAME (optional, default: Phanpy) affects:
PHANPY_WEBSITE (optional but recommended, no defaults) affects:
PHANPY_DEFAULT_INSTANCE (optional, no defaults):
https://PHANPY_DEFAULT_INSTANCE_REGISTRATION_URL (optional, no defaults):
https://mastodon.social/auth/sign_upPHANPY_PRIVACY_POLICY_URL (optional, default to official instance's privacy policy):
PHANPY_DEFAULT_LANG (optional):
en) if not specified.lang query parameter, lang key in localStorage and navigator.language)PHANPY_REFERRER_POLICY (optional, default: origin):
<meta> tag on the client-side.Referrer-Policy header configured on the server-side (not this variable).PHANPY_LINGVA_INSTANCES (DEPRECATED, optional, space-separated list, default: lingva.phanpy.social [...hard-coded list of fallback instances]):
/.envPHANPY_TRANSLANG_INSTANCES (optional, space-separated list, default: translang.phanpy.social):
/.envPHANPY_IMG_ALT_API_URL (optional, no defaults):
PHANPY_GIPHY_API_KEY (optional, no defaults):
PHANPY_DISALLOW_ROBOTS (optional, default: not set):
true, 1, etc) to override the robots.txt file and disallow all web crawlers from indexing the siteTry online search for "how to self-host static sites" as there are many ways to do it.
⚠️ DEPRECATED. See documentation for lingva-translate or lingva-api.
See documentation for translang-api.
Changelog available here: CHANGELOG.md
These are self-hosted by other wonderful folks.
Note: Add yours by creating a pull request.
There are also mobile app wrappers:
Costs involved in running and developing this web app:
Phanpy is a Ground-type Pokémon.
I am one of the earliest users of Twitter. Twitter was launched on 15 July 2006. I joined on December 2006 and my first tweet was posted on 18 December 2006.
I know how early Twitter looks like. It was fun.
Back then, I made a Twitter clone called "Twig" written in Python and Google App Engine. I almost made my own Twitter desktop client written in Appcelerator Titanium. I gave one of my best talks about the Twitter client in a mini-conference. I built this thing called "Twitter Columns", a web app that shows your list of followings, your followings' followings, your followers, your followers' followers and so on. In 2009, I wrote a blog post titled "How I got started with Twitter". I created two themes for DestroyTwitter (a desktop client made with Adobe Air by Jonnie Hallman) and one of them is called "Vimeo". In 2013, I wrote my own tweets backup site with a front-end to view my tweets and a CouchDB backend to store them.
It's been more than 15 years.
And here I am. Building a Mastodon web client.
Please, please copy the UI ideas and experiments from this app. I think some of them are pretty good and it would be great if more apps have them.
If you're not a developer, please tell your favourite social media client developers about this app and ask them to copy the UI ideas and experiments.
MIT.
(top 24 of 40)
40,401 followers · starred Aug 2023
1,314 followers · starred Jan 2023
13,166 followers · starred Mar 2024
582 followers · starred Sep 2023