Files
wiki/raw/tag-reverse-proxy.html
T
2026-06-09 18:40:21 +02:00

1795 lines
79 KiB
HTML

<!DOCTYPE html>
<html lang="en" data-color-scheme="dark" class="scroll-smooth">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Self-Hosted Software and Apps</title>
<meta name="HandheldFriendly" content="True" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0f60d9">
<meta name="ghost-theme" content="Brief 1.6.0">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="e910ee56b4039bf3b4f57aac-|49"></script><link rel="preload stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'" crossorigin>
<style>body { --font-headings: var(--gh-font-heading, 'Inter'), sans-serif; }</style>
<style>body { --font-body: var(--gh-font-body, 'Inter'), sans-serif; }</style>
<script type="e910ee56b4039bf3b4f57aac-module" src="/assets/built/app.js?v=13fdcc73fe"></script>
<link rel="stylesheet" type="text/css" href="/assets/built/app.css?v=13fdcc73fe" />
<script type="e910ee56b4039bf3b4f57aac-text/javascript">
let preferredTheme = localStorage.getItem('PREFERRED_COLOR_SCHEME') || `dark`;
document.documentElement.setAttribute('data-color-scheme', preferredTheme);
// Global values needed
const THEME_CONFIG = {
SITE_URL: 'https://selfh.st',
CONTENT_API_URL: 'https://selfh.st/ghost/api/content/',
CONTENT_API_KEY: 'a4717b8b11f6e5ae98a6b78e16',
API_VERSION: 'v6.0',
PAGINATION: {
POSTS_PER_PAGE: parseInt('12'),
CURRENT_PAGE: parseInt(''),
NEXT_PAGE: parseInt(''),
NEXT_PAGE_LINK: '',
MAX_PAGES: parseInt(''),
LAST_PAGE: `` === `` ? true : false,
TOTAL: parseInt('')
}
}
function hexToRgb(hexColor) {
if (hexColor.slice(0, 1) === '#') { hexColor = hexColor.slice(1); }
if (hexColor.length === 3) { hexColor = hexColor.split('').map(function (hex) { return hex + hex;}).join(''); }
const r = parseInt(hexColor.substr(0,2),16);
const g = parseInt(hexColor.substr(2,2),16);
const b = parseInt(hexColor.substr(4,2),16);
return [r,g,b]
}
// Get contrast color;
function getColorContrast(hexColor) {
const rgb = hexToRgb(hexColor);
let yiq = ((rgb[0] * 299) + (rgb[1] * 587) + (rgb[2] * 114)) / 1000;
const colorContrast = (yiq >= 128) ? 'dark' : 'light';
// return
return colorContrast
};
function hexToHsl(hexColor) {
const rgb = hexToRgb(hexColor);
// Make r, g, and b fractions of 1
const r = rgb[0]/255;
const g = rgb[1]/255;
const b = rgb[2]/255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if ( max == min ) { h = s = 0; } else {
let d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch(max){
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [Math.round(h * 360),Math.round(s * 100),Math.round(l * 100)]
}
function setBrandHSL(hex) {
const colorHsl = hexToHsl(hex)
document.documentElement.style.setProperty('--color-brand-hsl', `${colorHsl[0]} ${colorHsl[1]}% ${colorHsl[2]}%`);
document.documentElement.style.setProperty('--color-brand-contrast', getColorContrast(hex) === 'dark' ? `hsl(${colorHsl[0]} ${colorHsl[1]}% 5%)` : `hsl(${colorHsl[0]} ${colorHsl[1]}% 98%)`);
}
setBrandHSL("#0f60d9");
</script>
<meta name="description" content="A directory of self-hosted software and applications for easy browsing and discovery">
<link rel="icon" href="https://selfh.st/content/images/size/w256h256/2023/09/favicon-1.png" type="image/png">
<link rel="canonical" href="https://selfh.st/apps/">
<meta name="referrer" content="no-referrer-when-downgrade">
<meta property="og:site_name" content="selfh.st">
<meta property="og:type" content="website">
<meta property="og:title" content="Self-Hosted Software and Apps">
<meta property="og:description" content="A directory of self-hosted software and applications for easy browsing and discovery">
<meta property="og:url" content="https://selfh.st/apps/">
<meta property="og:image" content="https://selfh.st/content/images/2024/08/2024-08-12-apps-featured-image-2.png">
<meta property="article:published_time" content="2024-07-09T09:49:21.000Z">
<meta property="article:modified_time" content="2026-05-24T08:48:37.000Z">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Self-Hosted Software and Apps">
<meta name="twitter:description" content="A directory of self-hosted software and applications for easy browsing and discovery">
<meta name="twitter:url" content="https://selfh.st/apps/">
<meta name="twitter:image" content="https://selfh.st/content/images/2024/08/2024-08-12-apps-featured-image-1.png">
<meta name="twitter:label1" content="Written by">
<meta name="twitter:data1" content="Ethan Sholly">
<meta property="og:image:width" content="1047">
<meta property="og:image:height" content="623">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"publisher": {
"@type": "Organization",
"name": "selfh.st",
"url": "https://selfh.st/",
"logo": {
"@type": "ImageObject",
"url": "https://selfh.st/content/images/2025/04/selfh-st-logo-dark-gray-1.svg"
}
},
"author": {
"@type": "Person",
"name": "Ethan Sholly",
"image": {
"@type": "ImageObject",
"url": "https://selfh.st/content/images/size/w1200/2025/08/Family-KSP-138---Copy.jpg",
"width": 1200,
"height": 1200
},
"url": "https://selfh.st/contributor/shollyethan/",
"sameAs": [
"https://ethansholly.com"
]
},
"headline": "Self-Hosted Software and Apps",
"url": "https://selfh.st/apps/",
"datePublished": "2024-07-09T09:49:21.000Z",
"dateModified": "2026-05-24T08:48:37.000Z",
"image": {
"@type": "ImageObject",
"url": "https://selfh.st/content/images/size/w1200/2025/10/selfh-st-apps-new--1-.png",
"width": 1200,
"height": 847
},
"description": "A directory of self-hosted software and applications for easy browsing and discovery",
"mainEntityOfPage": "https://selfh.st/apps/"
}
</script>
<meta name="generator" content="Ghost 6.44">
<link rel="alternate" type="application/rss+xml" title="selfh.st" href="https://selfh.st/rss/">
<script defer src="https://cdn.jsdelivr.net/ghost/portal@~2.68/umd/portal.min.js" data-i18n="true" data-ghost="https://selfh.st/" data-key="a4717b8b11f6e5ae98a6b78e16" data-api="https://selfh.st/ghost/api/content/" data-locale="en" crossorigin="anonymous" type="e910ee56b4039bf3b4f57aac-text/javascript"></script><style id="gh-members-styles">.gh-post-upgrade-cta-content,
.gh-post-upgrade-cta {
display: flex;
flex-direction: column;
align-items: center;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
text-align: center;
width: 100%;
color: #ffffff;
font-size: 16px;
}
.gh-post-upgrade-cta-content {
border-radius: 8px;
padding: 40px 4vw;
}
.gh-post-upgrade-cta h2 {
color: #ffffff;
font-size: 28px;
letter-spacing: -0.2px;
margin: 0;
padding: 0;
}
.gh-post-upgrade-cta p {
margin: 20px 0 0;
padding: 0;
}
.gh-post-upgrade-cta small {
font-size: 16px;
letter-spacing: -0.2px;
}
.gh-post-upgrade-cta a {
color: #ffffff;
cursor: pointer;
font-weight: 500;
box-shadow: none;
text-decoration: underline;
}
.gh-post-upgrade-cta a:hover {
color: #ffffff;
opacity: 0.8;
box-shadow: none;
text-decoration: underline;
}
.gh-post-upgrade-cta a.gh-btn {
display: block;
background: #ffffff;
text-decoration: none;
margin: 28px 0 0;
padding: 8px 18px;
border-radius: 4px;
font-size: 16px;
font-weight: 600;
}
.gh-post-upgrade-cta a.gh-btn:hover {
opacity: 0.92;
}</style><script async src="https://js.stripe.com/v3/" type="e910ee56b4039bf3b4f57aac-text/javascript"></script>
<script defer src="https://cdn.jsdelivr.net/ghost/sodo-search@~1.8/umd/sodo-search.min.js" data-key="a4717b8b11f6e5ae98a6b78e16" data-styles="https://cdn.jsdelivr.net/ghost/sodo-search@~1.8/umd/main.css" data-sodo-search="https://selfh.st/" data-locale="en" crossorigin="anonymous" type="e910ee56b4039bf3b4f57aac-text/javascript"></script>
<link href="https://selfh.st/webmentions/receive/" rel="webmention">
<script defer src="/public/cards.min.js?v=13fdcc73fe" type="e910ee56b4039bf3b4f57aac-text/javascript"></script>
<link rel="stylesheet" type="text/css" href="/public/cards.min.css?v=13fdcc73fe">
<script defer src="/public/comment-counts.min.js?v=13fdcc73fe" data-ghost-comments-counts-api="https://selfh.st/members/api/comments/counts/" type="e910ee56b4039bf3b4f57aac-text/javascript"></script>
<script defer src="/public/member-attribution.min.js?v=13fdcc73fe" type="e910ee56b4039bf3b4f57aac-text/javascript"></script><style>:root {--ghost-accent-color: #0f60d9;}</style>
<script src="https://selfh.st/assets/custom/js/events.js" data-endpoint="https://selfh.st/e" data-token="6d7685dba0874fb7cb8c497a50528878546ca861db897a365415198ce874502e" type="e910ee56b4039bf3b4f57aac-text/javascript"></script>
<script defer src="https://analytics.selfh.st/script.js" data-website-id="2fc73150-7d70-4b39-96e9-21cf22accd36" type="e910ee56b4039bf3b4f57aac-text/javascript"></script>
<a rel="me" aria-label="Mastodon" href="https://fosstodon.org/@shollyethan"></a>
<meta name="fediverse:creator" content="@selfhst@fosstodon.org">
<style>
h1 {
letter-spacing: -0.05em;
margin-bottom: 10px !important;
}
h2 {
letter-spacing: -0.05em;
margin-top: 25px !important;
margin-bottom: 10px !important;
}
h3, h4, h5, h6 {
letter-spacing: -0.05em;
}
.tracking-tight {
letter-spacing: -0.07em;
}
.ghost-content>*:not(h1,h2,h3,h4,h5,h6) {
font-size: 1.0rem;
line-height: 1.6;
}
.ghost-content a:not([class*=kg-],[class*=btn]) {
text-decoration: none !important;
color: #5a7de6;
}
.md\:text-lg {
font-size: 1.0rem !important;
letter-spacing: -0.03em;
line-height: 1.25;
}
.leading-none {
font-size: 0.8rem !important;
line-height: 1.00;
}
.gap-4 {
gap: 0.8rem;
}
.kg-code-card {
margin-top: 1.5em !important;
}
[data-hero-subheading] {
font-weight: 300;
}
[data-post-hero-content] {
gap: 0.5rem;
}
[data-hero-subheading] {
letter-spacing: -0.03em;
line-height: 1.25;
}
[data-post-card-title] {
letter-spacing: -0.03em;
}
[data-post-card-excerpt] {
letter-spacing: -0.03em;
line-height: 1.25;
font-weight: 300;
}
[data-post-card-info] {
letter-spacing: -0.03em;
line-height: 1.25;
}
[data-tag] {
letter-spacing: -0.03em;
line-height: 1.25;
}
[data-post-hero-authors] {
font-size: 14px;
}
</style>
<style>
#title-sponsor-link {
color: var(--color-typography-content) !important;
}
#newsletter-highlights-header {
margin-top: 10px !important;
}
#nts-header {
display: flex;
justify-content: center;
align-items: center;
margin-top: -25px !important;
margin-bottom: 13px !important;
}
html[data-color-scheme='dark'] #nts-header {
color: hsl(0 0% 90%);
}
#nts-header-text {
font-size: 13px;
font-weight: bold;
}
#nts-logo {
height: 30px !important;
width: 30px !important;
margin-top: 0px !important;
margin-bottom: 0px !important;
}
#nts-logo-text {
height: 20px !important;
margin-top: 0px;
margin-bottom: 0px;
}
#nts-logo-text-invert {
height: 20px !important;
margin-top: 0px;
margin-bottom: 0px;
}
html[data-color-scheme='dark'] #nts-logo-text-invert {
filter: contrast(0%) brightness(350%);
}
#nts-header-name {
font-size: 21px;
font-weight: bold;
}
#nts-body {
background: #f5f5f5;
font-color: #262626;
width: 95%;
font-size: 14.5px;
padding: 10px 15px;
border-radius: 8px;
margin-left: auto;
margin-right: auto;
margin-bottom: 26px;
line-height: 1.5;
}
html[data-color-scheme='dark'] #nts-body {
background: #242424;
color: hsl(0 0% 90%);
}
@media (max-width: 768px) {
#nts-body {
font-size: 14px;
}
}
#feedback-container {
border-radius: 10px !important;
position: relative;
height: 40dvh;
overflow: auto;
}
@media (max-width: 768px) {
#feedback-container {
height: 38dvh;
}
}
#feedback-embed {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border: 0;
}
</style>
<style>
.logo-directory {
height:55px;
margin-left:auto !important;
margin-right:auto !important;
margin-top:10px !important;
margin-bottom:13px !important;
}
.logo-directory-primary {
fill:#191919;
}
.logo-directory-secondary {
fill:#315dd4;
}
html[data-color-scheme='dark'] .logo-directory-primary {
fill: #ffffff;
}
@media (max-width: 768px) {
.logo-directory {
height:37px !important;
}
}
.title-directory {
letter-spacing: -0.05em !important;
font-size: 2.66667em !important;
font-weight: 700 !important;
line-height: 1.05 !important;
text-align:center !important;
justify-content:center !important;
margin-top:85px !important;
margin-bottom:5px !important;
color: rgb(245, 245, 245);
}
html[data-color-scheme='light'] .title-directory {
color: #2e2e2e;
}
.subtitle-directory {
margin: auto;
margin-top: 10px !important;
margin-bottom: 150px !important;
text-align: center;
line-height: 1.0 !important;
color: rgb(245, 245, 245);
}
html[data-color-scheme='light'] .subtitle-directory {
color: #2e2e2e;
}
@media (max-width: 768px) {
.title-directory {
text-align:center !important;
font-size: 31px !important;
display:flex !important;
margin-top:15px !important;
margin-bottom:14px !important;
line-height: 1.05 !important;
}
.subtitle-directory {
margin-bottom: 20px !important;
padding: 0px 20px !important;
}
}
</style>
<style>
.activity-author {
display: flex;
align-items: center;
font-size: 14px;
color: #8a8a8a;
}
.activity-favicon {
opacity: 1.0 !important;
width: 16px;
height: 16px;
margin-right: 6px;
vertical-align: middle;
}
</style>
<style>
.survey-canvas {
border-radius: 10px 10px 10px 10px;
margin: auto;
margin-top: 5px !important;
margin-bottom: 30px !important;
width: 98%;
padding: 0px 0px 0px 0px;
background-color: #363c4d;
--text-color: #f2f2f2;
--gridlines-color: #4d4d4d;
--chart-color-annual-2023: #6cc494;
--chart-color-annual-2024: #669ffa;
}
.survey-canvas-with-commentary {
display: grid;
border-radius: 10px 10px 10px 10px;
margin: auto;
margin-top: 5px !important;
margin-bottom: 15px !important;
width: 98%;
background-color: #383f49;
padding: 0px 0px 0px 0px
}
.survey-commentary {
display: inline-block;
width: 95% !important;
margin: 0 auto;
margin-bottom: 30px !important;
font-size: 16px !important;
font-weight: 300 !important;
letter-spacing: -0.03em !important;
line-height: 1.25 !important;
}
@media (max-width: 850px) {
.survey-commentary {
font-size: 14px !important;
}
}
.survey-table-header {
font-weight: 500;
font-size: 16px !important;
letter-spacing: -0.03em;
line-height: 2.00 !important;
margin-bottom: 15px !important;
margin-left: auto !important;
margin-right: auto !important;
}
.survey-table {
width: 100%;
font-size: 14px !important;
margin-top: 1px !important;
margin-bottom: 35px !important;
margin-left: auto !important;
margin-right: auto !important;
border-collapse: collapse;
font-family: var(--font-body);
}
#size-200 {
height: 200px;
}
#size-275 {
height: 275px;
}
#size-350 {
height: 350px;
}
#size-400 {
height: 400px;
}
#size-500 {
height: 500px;
}
</style>
<link rel="stylesheet" href="https://selfh.st/static/build/apps/v1/v1-1/details.css">
<link rel="stylesheet" href="https://selfh.st/static/build/apps/v1/v1-1/icons.css">
<link rel="stylesheet" href="https://selfh.st/static/build/apps/v1/v1-1/partners.css">
<link rel="stylesheet" href="https://selfh.st/static/build/apps/v1/v1-1/sidebar.css">
<link rel="stylesheet" href="https://selfh.st/static/build/apps/v1/v1-1/tags.css">
<link rel="stylesheet" href="https://selfh.st/static/build/apps/v1/v1-1/tiles.css">
<script type="e910ee56b4039bf3b4f57aac-text/javascript">window.ANALYTICS_TOKEN = "6d7685dba0874fb7cb8c497a50528878546ca861db897a365415198ce874502e";</script>
<style>
.ghost-content {
grid-template-columns: none !important;
display: block !important;
margin-left: auto;
margin-right: auto;
max-width: 90% !important;
}
.ghost-content .kg-card {
width: 60%;
margin-top: 55px !important;
margin-left: auto;
margin-right: auto;
}
html[data-color-scheme='dark'] .toggle-label {
color: #ededed;
}
.project-icon {
text-decoration: none !important;
}
.project-name {
text-decoration: none !important;
background-image: none !important;
}
.tag-link {
text-decoration: none !important;
background-image: none !important;
color: #262a30 !important;
}
img {
display: inline-block !important;
margin-top: 10px !important;
margin-bottom: 5px !important;
}
@media (max-width: 768px) {
.ghost-content {
max-width: 100% !important;
}
}
</style>
</head>
<body class="page-template page-apps subpixel-antialiased relative overflow-x-hidden bg-bgr text-typ"
x-data="{ menuOpen: false }" :class="menuOpen ? 'overflow-hidden' : 'overflow-y-auto'"
data-user="visitor"
@keydown.escape="menuOpen = false" @keydown.cmk.k="document.querySelector('[data-ghost-search]').click()"
x-init="navigator.platform.includes('Win') && document.body.classList.add('is-win')"
>
<a class="sr-only" href="#main">Skip to content</a>
<header class="px-5 sm:px-6 bg-bgr text-typ whitespace-nowrap text-sm" data-header>
<div class="max-w-container mx-auto py-4 md:py-6 flex items-center justify-center gap-2 relative">
<div class="flex-1 flex justify-start" data-header-brand>
<a class="" href="https://selfh.st" data-brand>
<picture data-logo='dark'>
<source
srcset="/content/images/size/w100/format/webp/2025/04/selfh-st-logo-white-1.svg 100w, /content/images/size/w320/format/webp/2025/04/selfh-st-logo-white-1.svg 320w, /content/images/size/w640/format/webp/2025/04/selfh-st-logo-white-1.svg 600w"
sizes="(max-width: 800px) 200px, 320px"
type="image/webp"
>
<img class="h-(--logo-h-mobile) w-(--logo-w-mobile) md:h-(--logo-h-desktop) md:w-(--logo-w-desktop)"
loading="eager"
srcset="/content/images/size/w100/format/webp/2025/04/selfh-st-logo-white-1.svg 100w, /content/images/size/w320/format/webp/2025/04/selfh-st-logo-white-1.svg 320w, /content/images/size/w640/format/webp/2025/04/selfh-st-logo-white-1.svg 640w"
sizes="(max-width: 800px) 200px, 320px"
src="/content/images/size/w30/2025/04/selfh-st-logo-white-1.svg"
alt="selfh.st"
/>
</picture>
<picture data-logo='default'>
<source
srcset="/content/images/size/w100/format/webp/2025/04/selfh-st-logo-dark-gray-1.svg 100w, /content/images/size/w320/format/webp/2025/04/selfh-st-logo-dark-gray-1.svg 320w, /content/images/size/w640/format/webp/2025/04/selfh-st-logo-dark-gray-1.svg 600w"
sizes="(max-width: 800px) 200px, 320px"
type="image/webp"
>
<img class="h-(--logo-h-mobile) w-(--logo-w-mobile) md:h-(--logo-h-desktop) md:w-(--logo-w-desktop)"
loading="eager"
srcset="/content/images/size/w100/format/webp/2025/04/selfh-st-logo-dark-gray-1.svg 100w, /content/images/size/w320/format/webp/2025/04/selfh-st-logo-dark-gray-1.svg 320w, /content/images/size/w640/format/webp/2025/04/selfh-st-logo-dark-gray-1.svg 640w"
sizes="(max-width: 800px) 200px, 320px"
src="/content/images/size/w30/2025/04/selfh-st-logo-dark-gray-1.svg"
alt="selfh.st"
/>
</picture>
</a>
</div>
<nav class="relative hidden md:flex justify-center" data-nav="header">
<ul class="flex gap-0.5">
<li class="nav-content flex items-center relative hover:bg-bgr-tone rounded-btn"
data-label="Content" data-slug="content" data-length="7">
<a class="w-full flex px-4 py-2 rounded-btn" href="https://selfh.st/"
>
<span>Content</span>
</a>
</li>
<li class="nav-newsletter flex items-center relative hover:bg-bgr-tone rounded-btn is-subitem"
data-label="- Newsletter" data-slug="newsletter" data-length="12">
<a class="w-full flex px-4 py-2 rounded-btn" href="https://selfh.st/tag/weekly/"
>
<span>- Newsletter</span>
</a>
</li>
<li class="nav-blog flex items-center relative hover:bg-bgr-tone rounded-btn is-subitem"
data-label="- Blog" data-slug="blog" data-length="6">
<a class="w-full flex px-4 py-2 rounded-btn" href="https://selfh.st/tag/post/"
>
<span>- Blog</span>
</a>
</li>
<li class="nav-surveys flex items-center relative hover:bg-bgr-tone rounded-btn is-subitem"
data-label="- Surveys" data-slug="surveys" data-length="9">
<a class="w-full flex px-4 py-2 rounded-btn" href="https://selfh.st/tag/survey/"
>
<span>- Surveys</span>
</a>
</li>
<li class="nav-podcast flex items-center relative hover:bg-bgr-tone rounded-btn is-subitem"
data-label="- Podcast" data-slug="podcast" data-length="9">
<a class="w-full flex px-4 py-2 rounded-btn" href="https://selfh.st/tag/cast/"
>
<span>- Podcast</span>
</a>
</li>
<li class="nav-apps nav-current flex items-center relative hover:bg-bgr-tone rounded-btn"
data-label="Apps" data-slug="apps" data-length="4">
<a class="w-full flex px-4 py-2 rounded-btn" href="https://selfh.st/apps/"
>
<span>Apps</span>
</a>
</li>
<li class="nav-icons flex items-center relative hover:bg-bgr-tone rounded-btn"
data-label="Icons" data-slug="icons" data-length="5">
<a class="w-full flex px-4 py-2 rounded-btn" href="https://selfh.st/icons/"
>
<span>Icons</span>
</a>
</li>
<li class="nav-submit-content flex items-center relative hover:bg-bgr-tone rounded-btn"
data-label="Submit Content" data-slug="submit-content" data-length="14">
<a class="w-full flex px-4 py-2 rounded-btn" href="https://selfh.st/submit/"
>
<span>Submit Content</span>
</a>
</li>
</ul>
</nav>
<div class="flex gap-1 md:gap-2 items-center" data-header-actions>
<button class="flex gap-0.5 p-0.5 border border-brd text-typ-tone rounded-btn hover:bg-bgr-tone" aria-label="Toggle color scheme"
data-color-scheme-toggle @click="toggleColorScheme(event)">
<span data-theme="light" class="px-1.5 py-[3px] opacity-50 rounded-btn">
<i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-sun size-4 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-sun-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M12 19a1 1 0 0 1 .993 .883l.007 .117v1a1 1 0 0 1 -1.993 .117l-.007 -.117v-1a1 1 0 0 1 1 -1z" stroke-width="0" fill="currentColor" />
<path d="M18.313 16.91l.094 .083l.7 .7a1 1 0 0 1 -1.32 1.497l-.094 -.083l-.7 -.7a1 1 0 0 1 1.218 -1.567l.102 .07z" stroke-width="0" fill="currentColor" />
<path d="M7.007 16.993a1 1 0 0 1 .083 1.32l-.083 .094l-.7 .7a1 1 0 0 1 -1.497 -1.32l.083 -.094l.7 -.7a1 1 0 0 1 1.414 0z" stroke-width="0" fill="currentColor" />
<path d="M4 11a1 1 0 0 1 .117 1.993l-.117 .007h-1a1 1 0 0 1 -.117 -1.993l.117 -.007h1z" stroke-width="0" fill="currentColor" />
<path d="M21 11a1 1 0 0 1 .117 1.993l-.117 .007h-1a1 1 0 0 1 -.117 -1.993l.117 -.007h1z" stroke-width="0" fill="currentColor" />
<path d="M6.213 4.81l.094 .083l.7 .7a1 1 0 0 1 -1.32 1.497l-.094 -.083l-.7 -.7a1 1 0 0 1 1.217 -1.567l.102 .07z" stroke-width="0" fill="currentColor" />
<path d="M19.107 4.893a1 1 0 0 1 .083 1.32l-.083 .094l-.7 .7a1 1 0 0 1 -1.497 -1.32l.083 -.094l.7 -.7a1 1 0 0 1 1.414 0z" stroke-width="0" fill="currentColor" />
<path d="M12 2a1 1 0 0 1 .993 .883l.007 .117v1a1 1 0 0 1 -1.993 .117l-.007 -.117v-1a1 1 0 0 1 1 -1z" stroke-width="0" fill="currentColor" />
<path d="M12 7a5 5 0 1 1 -4.995 5.217l-.005 -.217l.005 -.217a5 5 0 0 1 4.995 -4.783z" stroke-width="0" fill="currentColor" />
</svg>
</i> </span>
<span data-theme="dark" class="px-1.5 py-[3px] opacity-50 rounded-btn">
<i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-moon size-4 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-moon-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M12 1.992a10 10 0 1 0 9.236 13.838c.341 -.82 -.476 -1.644 -1.298 -1.31a6.5 6.5 0 0 1 -6.864 -10.787l.077 -.08c.551 -.63 .113 -1.653 -.758 -1.653h-.266l-.068 -.006l-.06 -.002z" stroke-width="0" fill="currentColor" />
</svg>
</i> </span>
</button>
<button class="flex items-center justify-center rounded-full size-8 md:size-9 hover:bg-bgr-tone cursor-pointer" data-ghost-search title="Search" aria-label="Search">
<i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-search w-5 h-5 stroke-2 fill-bgr-tone [&amp;&gt;svg]:fill-inherit stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-search" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<circle cx="10" cy="10" r="7" />
<line x1="21" y1="21" x2="15" y2="15" />
</svg>
</i> </button>
<button class="flex flex-col items-center gap-1 size-8 p-1 md:p-2 md:size-9 justify-center rounded-full group md:hidden transition-transform hover:bg-bgr-tone"
data-menu-toggle aria-label="Menu toggle" aria-expanded="false" aria-haspopup="true" aria-controls="menu"
@click="menuOpen = !menuOpen" :aria-expanded="menuOpen.toString()"
>
<span class="relative pointer-events-none h-0.5 rounded-xs bg-typ-tone w-4 transition-transform"
:class="menuOpen ? 'rotate-45 translate-y-[3px]' : ''"></span>
<span class="relative pointer-events-none h-0.5 rounded-xs bg-typ-tone w-4 transition-transform"
:class="menuOpen ? '-rotate-45 -translate-y-[3px]' : ''"></span>
</button>
<a href="/subscribe/" data-portal="subscribe" data-header-subscribe
class="hidden sm:flex items-center gap-1 bg-brand font-medium text-brand-contrast px-4 py-1.5 rounded-btn md:px-5 md:py-2 transition hover:ring-[3px] hover:ring-brand/20">
<span>Subscribe</span>
</a>
</div>
</div>
</header>
<div class="sticky shadow-xl z-100 top-[64px] md:top-[84px] w-full text-sm bg-bgr text-typ-tone py-4 border-t border-brd hidden md:hidden!"
:class="menuOpen ? 'block!' : 'hidden'" data-menu id="menu"
>
<div class="px-5 sm:px-6 flex flex-col gap-3 items-start">
<nav class="font-medium" data-nav="mobile">
<ul class="flex flex-col gap-1">
<li class="nav-content flex flex-wrap relative"
data-label="Content" data-slug="content" data-length="7">
<a class="flex py-0.5 rounded-theme hover:text-brand" href="https://selfh.st/"
>
<span>Content</span>
</a>
</li>
<li class="nav-newsletter flex flex-wrap relative is-subitem"
data-label="- Newsletter" data-slug="newsletter" data-length="12">
<a class="flex py-0.5 rounded-theme hover:text-brand" href="https://selfh.st/tag/weekly/"
>
<span>- Newsletter</span>
</a>
</li>
<li class="nav-blog flex flex-wrap relative is-subitem"
data-label="- Blog" data-slug="blog" data-length="6">
<a class="flex py-0.5 rounded-theme hover:text-brand" href="https://selfh.st/tag/post/"
>
<span>- Blog</span>
</a>
</li>
<li class="nav-surveys flex flex-wrap relative is-subitem"
data-label="- Surveys" data-slug="surveys" data-length="9">
<a class="flex py-0.5 rounded-theme hover:text-brand" href="https://selfh.st/tag/survey/"
>
<span>- Surveys</span>
</a>
</li>
<li class="nav-podcast flex flex-wrap relative is-subitem"
data-label="- Podcast" data-slug="podcast" data-length="9">
<a class="flex py-0.5 rounded-theme hover:text-brand" href="https://selfh.st/tag/cast/"
>
<span>- Podcast</span>
</a>
</li>
<li class="nav-apps nav-current flex flex-wrap relative"
data-label="Apps" data-slug="apps" data-length="4">
<a class="flex py-0.5 rounded-theme hover:text-brand" href="https://selfh.st/apps/"
>
<span>Apps</span>
</a>
</li>
<li class="nav-icons flex flex-wrap relative"
data-label="Icons" data-slug="icons" data-length="5">
<a class="flex py-0.5 rounded-theme hover:text-brand" href="https://selfh.st/icons/"
>
<span>Icons</span>
</a>
</li>
<li class="nav-submit-content flex flex-wrap relative"
data-label="Submit Content" data-slug="submit-content" data-length="14">
<a class="flex py-0.5 rounded-theme hover:text-brand" href="https://selfh.st/submit/"
>
<span>Submit Content</span>
</a>
</li>
</ul>
</nav>
<a href="/subscribe/" data-portal="subscribe"
class="flex sm:hidden items-center gap-1 bg-brand font-medium text-brand-contrast px-4 py-1.5 rounded-btn md:px-5 md:py-2 transition hover:ring-[3px] hover:ring-brand/20">
<span>Subscribe</span>
</a>
</div>
</div>
<main class="main" id="main" data-class="font-mono font-serif font-sans">
<article class="post ghost-content prose md:prose-lg prose-theme">
<!--kg-card-begin: html-->
<div class="title-directory">Self-Hosted Apps and Alternatives</div>
<!--kg-card-end: html-->
<!--kg-card-begin: html-->
<div class="subtitle-directory">Explore alternatives to popular software and services</div>
<!--kg-card-end: html-->
<!--kg-card-begin: html-->
<!-- No-JS fallback -->
<noscript>
<div style="padding:2rem;text-align:center;font-family:sans-serif;">
JavaScript is required to use the app directory.
</div>
</noscript>
<!-- Main Content Container -->
<div class="main-layout min-h-screen">
<!-- Directory Sidebar Toggle (Mobile Only) -->
<button id="directorySidebarToggle" class="directory-sidebar-toggle" aria-label="Toggle sidebar menu">
<svg aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</button>
<!-- Main Layout Inner Wrapper -->
<div class="main-layout-inner">
<!-- Left Sidebar for Filters -->
<aside class="sidebar">
<div class="sidebar-content">
<!-- Links Section -->
<div class="filter-section">
<h3 class="filter-title">Links</h3>
<div class="links-container">
<a href="https://selfh.st/apps-partners/" class="sidebar-link">
<span class="sidebar-link-text">Sponsors</span>
<svg aria-hidden="true" class="sidebar-link-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
</a>
<a href="https://selfh.st/submit" class="sidebar-link">
<span class="sidebar-link-text">Requests</span>
<svg aria-hidden="true" class="sidebar-link-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
</a>
<a href="https://selfh.st/apps-partners/#partners" class="sidebar-link">
<span class="sidebar-link-text">Partners</span>
<svg aria-hidden="true" class="sidebar-link-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
</a>
<a href="https://selfh.st/icons" class="sidebar-link">
<span class="sidebar-link-text">Icons</span>
<svg aria-hidden="true" class="sidebar-link-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
</a>
</div>
<div class="affiliate-disclaimer-tag" data-tooltip="Heads up: Some hosting partner links on this page are affiliate links. If you sign up through one, I earn a small commission at no cost to you.">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="12" y1="16" x2="12" y2="12"/>
<line x1="12" y1="8" x2="12.01" y2="8"/>
</svg>
<span>Affiliate Disclaimer</span>
</div>
</div>
<div class="sidebar-divider"></div>
<!-- Search Box -->
<div class="filter-section">
<h3 class="filter-title">Search</h3>
<div class="search-container">
<svg aria-hidden="true" class="search-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<input type="text" id="searchBox" placeholder="Search apps..." class="search-input">
</div>
</div>
<div class="sidebar-divider"></div>
<!-- Sort Controls -->
<div class="filter-section">
<label for="sortOptions" class="filter-title">Sort</label>
<select id="sortOptions" class="filter-select">
<option value="">Default</option>
<option value="stars">Stars</option>
<option value="activity">Activity</option>
<option value="alphabetical">Alphabetical</option>
<option value="recent">Recently Added</option>
<option value="age-asc">Age (Ascending)</option>
<option value="age-desc">Age (Descending)</option>
<option value="random">Random</option>
</select>
</div>
<div class="sidebar-divider"></div>
<!-- Category Selection -->
<div class="filter-section">
<h3 class="filter-title">Categories</h3>
<div class="space-y-1.5">
<div class="flex items-center">
<input id="category-software" type="radio" name="category" value="Software" checked class="category-radio">
<label for="category-software" class="category-label">Software</label>
</div>
<div class="flex items-center">
<input id="category-companion" type="radio" name="category" value="Companion" class="category-radio">
<label for="category-companion" class="category-label">Companions</label>
</div>
<!--<div class="flex items-center">
<input id="category-activitypub" type="radio" name="category" value="ActivityPub" class="category-radio">
<label for="category-activitypub" class="category-label">ActivityPub</label>
</div>-->
</div>
</div>
<div class="sidebar-divider"></div>
<!-- Bookmark Controls -->
<div class="filter-section">
<h3 class="filter-title">Bookmarks</h3>
<div class="bookmark-buttons-grid">
<button id="shareBookmarksButton" class="bookmark-button-with-text" title="Generate unique share URL">
<svg aria-hidden="true" fill="currentColor" viewBox="0 0 256 256">
<path d="M212.853,122.34277l-30.28418,30.28516a56.06576,56.06576,0,0,1-79.19629,0,12.0001,12.0001,0,0,1,16.9707-16.9707,32.03673,32.03673,0,0,0,45.25488,0l30.28418-30.28516a31.99969,31.99969,0,1,0-45.25488-45.25391L140.978,69.76562a11.99941,11.99941,0,1,1-16.96875-16.9707l9.64746-9.64746A55.99989,55.99989,0,1,1,212.853,122.34277Zm-97.834,63.89356-9.64648,9.64551a31.99969,31.99969,0,0,1-45.25488-45.25391l30.28418-30.28516a32.03673,32.03673,0,0,1,45.25488,0,12.0001,12.0001,0,0,0,16.9707-16.9707,56.06459,56.06459,0,0,0-79.19629,0L43.147,133.65723a55.99956,55.99956,0,0,0,79.19532,79.19531l9.64648-9.64551a11.99975,11.99975,0,1,0-16.96973-16.9707Z"/>
</svg>
<span class="bookmark-button-text">Share</span>
</button>
<button id="deleteBookmarksButton" class="bookmark-button-with-text" title="Delete all bookmarks">
<svg aria-hidden="true" fill="currentColor" viewBox="0 0 256 256">
<path d="M187.6,93.9H68.4c-5.2,0-9.5,4.3-8.6,9.5l13.8,113.2c0.9,8.6,8.6,14.7,17.3,14.7h73.4c8.6,0,16.4-6,17.3-14.7l13.8-113.2C196.2,98.2,192.8,93.9,187.6,93.9z"/>
<path d="M197.1,42.1h-43.2l0,0c0-9.5-7.8-17.3-17.3-17.3h-17.3c-9.5,0-17.3,7.8-17.3,17.3l0,0H58.9c-9.5,0-17.3,7.8-17.3,17.3V68c0,5.2,3.5,8.6,8.6,8.6h155.5c5.2,0,8.6-3.5,8.6-8.6v-8.6C214.4,49.8,206.6,42.1,197.1,42.1z"/>
</svg>
<span class="bookmark-button-text">Delete</span>
</button>
</div>
</div>
<div class="sidebar-divider"></div>
<!-- Filter Controls -->
<div class="filter-section">
<div class="filter-title-row">
<h3 class="filter-title">Filters</h3>
</div>
<div class="space-y-3">
<div class="space-y-2">
<div class="flex items-center space-x-3">
<input type="checkbox" id="toggleDetailsSwitch" class="filter-checkbox">
<label for="toggleDetailsSwitch" class="filter-checkbox-label">Show Details</label>
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="partnersFilterCheckbox" class="filter-checkbox">
<label for="partnersFilterCheckbox" class="filter-checkbox-label">Partners</label>
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="bookmarksFilterCheckbox" class="filter-checkbox">
<label for="bookmarksFilterCheckbox" class="filter-checkbox-label">Local Bookmarks</label>
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="iconsFilterCheckbox" class="filter-checkbox">
<label for="iconsFilterCheckbox" class="filter-checkbox-label">selfh.st/icons Integration</label>
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="toggleClosedSourceSwitch" checked class="filter-checkbox">
<label for="toggleClosedSourceSwitch" class="filter-checkbox-label">Include Closed Source</label>
</div>
</div>
<div>
<label class="filter-sublabel">Tags</label>
<div class="custom-multiselect-wrapper">
<button type="button" class="custom-multiselect-trigger" id="tagFilterTrigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="tagFilterOptions" aria-label="Filter by tag">
<span class="custom-multiselect-text">All</span>
<svg aria-hidden="true" class="custom-multiselect-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<div class="custom-multiselect-dropdown" id="tagFilterDropdown">
<div class="custom-multiselect-options" id="tagFilterOptions" role="listbox" aria-multiselectable="true"></div>
</div>
</div>
</div>
<div>
<label class="filter-sublabel" id="dynamicFilterLabel">Alternatives</label>
<div class="custom-multiselect-wrapper">
<button type="button" class="custom-multiselect-trigger" id="dynamicFilterTrigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="dynamicFilterOptions" aria-labelledby="dynamicFilterLabel">
<span class="custom-multiselect-text">All</span>
<svg aria-hidden="true" class="custom-multiselect-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<div class="custom-multiselect-dropdown" id="dynamicFilterDropdown">
<div class="custom-multiselect-options" id="dynamicFilterOptions" role="listbox" aria-multiselectable="true"></div>
</div>
</div>
</div>
<div>
<label class="filter-sublabel" for="licenseFilter">Licenses</label>
<div class="custom-multiselect-wrapper">
<button type="button" class="custom-multiselect-trigger" id="licenseFilterTrigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="licenseFilterOptions" aria-label="Filter by license">
<span class="custom-multiselect-text">All</span>
<svg aria-hidden="true" class="custom-multiselect-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<div class="custom-multiselect-dropdown" id="licenseFilterDropdown">
<div class="custom-multiselect-options" id="licenseFilterOptions" role="listbox" aria-multiselectable="true"></div>
</div>
</div>
</div>
<div>
<label class="filter-sublabel" for="languageFilter">Languages</label>
<div class="custom-multiselect-wrapper">
<button type="button" class="custom-multiselect-trigger" id="languageFilterTrigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="languageFilterOptions" aria-label="Filter by language">
<span class="custom-multiselect-text">All</span>
<svg aria-hidden="true" class="custom-multiselect-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<div class="custom-multiselect-dropdown" id="languageFilterDropdown">
<div class="custom-multiselect-options" id="languageFilterOptions" role="listbox" aria-multiselectable="true"></div>
</div>
</div>
</div>
<div class="slider-filter">
<label class="filter-sublabel slider-label">Stars</label>
<div class="range-slider-container">
<div class="single-dual-range" id="starSlider">
<div class="range-track"></div>
<div class="range-fill" id="starFill"></div>
<div class="range-thumb range-thumb-min" id="starThumbMin"></div>
<div class="range-thumb range-thumb-max" id="starThumbMax"></div>
</div>
<div class="range-display">
<span class="range-value"><span id="starMinValue">0</span> - <span id="starMaxValue">1000</span></span>
</div>
</div>
</div>
<div class="slider-filter">
<label class="filter-sublabel slider-label">Forks</label>
<div class="range-slider-container">
<div class="single-dual-range" id="forkSlider">
<div class="range-track"></div>
<div class="range-fill" id="forkFill"></div>
<div class="range-thumb range-thumb-min" id="forkThumbMin"></div>
<div class="range-thumb range-thumb-max" id="forkThumbMax"></div>
</div>
<div class="range-display">
<span class="range-value"><span id="forkMinValue">0</span> - <span id="forkMaxValue">500</span></span>
</div>
</div>
</div>
</div>
</div>
</div>
</aside>
<!-- Main Content Area -->
<div class="main-content-area">
<!-- Mobile Controls (visible only on mobile) -->
<div class="mobile-controls">
<div class="mobile-controls-row">
<div class="mobile-sort-container">
<label for="mobileSortOptions" class="mobile-control-label">Sort</label>
<select id="mobileSortOptions" class="mobile-sort-select">
<option value="default">Default</option>
<option value="stars">Stars</option>
<option value="activity">Activity</option>
<option value="alphabetical">Alphabetical</option>
<option value="recent">Recently Added</option>
<option value="age-asc">Age (Ascending)</option>
<option value="age-desc">Age (Descending)</option>
<option value="random">Random</option>
</select>
</div>
<div class="mobile-search-container">
<label for="mobileSearchBox" class="mobile-control-label">Search</label>
<div class="search-input-wrapper">
<svg aria-hidden="true" class="search-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<input type="text" id="mobileSearchBox" placeholder="Search apps..." class="search-input">
</div>
</div>
</div>
</div>
<!-- Main Content -->
<div class="content-wrapper">
<!-- Active Filter Chips -->
<div id="activeFiltersBar" class="active-filters-bar" aria-label="Active filters" aria-live="polite"></div>
<!-- Apps Grid -->
<div id="dataContainer" class="apps-grid">
<!-- App tiles will be generated here -->
</div>
<!-- No Results -->
<div id="noResultsContainer" class="no-results hidden">
<div class="no-results-icon">
<svg aria-hidden="true" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<h3 class="no-results-title">No apps found</h3>
<p class="no-results-description">
Try adjusting your filters or search query.
</p>
<button id="noResultsClearButton" class="no-results-button">
Clear Filters
</button>
</div>
</div>
</div>
</div>
</div> <!-- End main-layout-inner -->
<!-- Mobile Filter Button -->
<button id="mobileFilterBtn" class="mobile-filter-btn" aria-label="Open filters">
<svg aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"/>
</svg>
</button>
<!-- Mobile Filter Overlay -->
<div id="mobileFilterOverlay" class="mobile-filter-overlay" role="dialog" aria-modal="true" aria-labelledby="mobileFilterTitle" aria-hidden="true">
<div class="mobile-filter-content">
<div class="mobile-filter-header">
<h3 class="mobile-filter-title" id="mobileFilterTitle">Filters</h3>
<div class="mobile-filter-header-actions">
<div id="mobileClearFiltersContainer" class="hidden">
<button id="mobileClearFiltersButton" class="mobile-filter-clear-btn">Clear All</button>
</div>
<button id="mobileFilterClose" class="mobile-filter-apply-btn">Apply</button>
</div>
</div>
<div id="mobileFilterSections">
<!-- Categories -->
<div class="filter-section">
<h3 class="filter-title">Categories</h3>
<div class="space-y-1.5">
<div class="flex items-center">
<input id="m-category-software" type="radio" name="m-category" value="Software" class="category-radio">
<label for="m-category-software" class="category-label">Software</label>
</div>
<div class="flex items-center">
<input id="m-category-companion" type="radio" name="m-category" value="Companion" class="category-radio">
<label for="m-category-companion" class="category-label">Companions</label>
</div>
</div>
</div>
<!-- Options -->
<div class="filter-section">
<h3 class="filter-title">Options</h3>
<div class="space-y-2">
<div class="flex items-center space-x-3">
<input type="checkbox" id="m-toggleClosedSourceSwitch" class="filter-checkbox" checked>
<label for="m-toggleClosedSourceSwitch" class="filter-checkbox-label">Include Closed Source</label>
</div>
</div>
</div>
<!-- Filters -->
<div class="filter-section">
<h3 class="filter-title">Filters</h3>
<div class="space-y-3">
<div class="space-y-2">
<div class="flex items-center space-x-3">
<input type="checkbox" id="m-partnersFilterCheckbox" class="filter-checkbox">
<label for="m-partnersFilterCheckbox" class="filter-checkbox-label">Partners</label>
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="m-bookmarksFilterCheckbox" class="filter-checkbox">
<label for="m-bookmarksFilterCheckbox" class="filter-checkbox-label">Local Bookmarks</label>
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="m-iconsFilterCheckbox" class="filter-checkbox">
<label for="m-iconsFilterCheckbox" class="filter-checkbox-label">selfh.st/icons Integration</label>
</div>
</div>
<div>
<label class="filter-sublabel" for="m-tagFilter">Tags</label>
<select id="m-tagFilter" class="filter-select">
<option value="">All</option>
</select>
</div>
<div>
<label for="m-dynamicFilter" class="filter-sublabel" id="m-dynamicFilterLabel">Alternatives</label>
<select id="m-dynamicFilter" class="filter-select">
<option value="">All</option>
</select>
</div>
<div>
<label class="filter-sublabel" for="m-licenseFilter">Licenses</label>
<select id="m-licenseFilter" class="filter-select">
<option value="">All</option>
</select>
</div>
<div>
<label class="filter-sublabel" for="m-languageFilter">Languages</label>
<select id="m-languageFilter" class="filter-select">
<option value="">All</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<!--kg-card-end: html-->
<!--kg-card-begin: html-->
<script src="https://selfh.st/static/build/apps/v1/v1-1/apps.min.js" type="e910ee56b4039bf3b4f57aac-text/javascript"></script>
<!--kg-card-end: html-->
</article>
</main>
<footer class="px-5 sm:px-6 bg-bgr-tone mt-16 sm:bg-bgr " data-footer>
<div class="max-w-container mx-auto">
<div class="relative overflow-hidden flex flex-col items-center gap-6 bg-bgr-tone rounded-theme py-12 sm:px-8" data-footer-top>
<div class="flex flex-col items-center text-center gap-3 max-w-md" data-footer-brand>
<a href="https://selfh.st" data-brand>
<picture data-logo='dark'>
<source
srcset="/content/images/size/w100/format/webp/2025/04/selfh-st-logo-white-1.svg 100w, /content/images/size/w320/format/webp/2025/04/selfh-st-logo-white-1.svg 320w, /content/images/size/w640/format/webp/2025/04/selfh-st-logo-white-1.svg 600w"
sizes="(max-width: 800px) 200px, 320px"
type="image/webp"
>
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="e910ee56b4039bf3b4f57aac-|49"></script><img class="h-(--logo-h-mobile) w-(--logo-w-mobile) md:h-(--logo-h-desktop) md:w-(--logo-w-desktop) will-change-[filter] transition-[filter] blur-sm"
onload="this.classList.remove('blur-sm')"
loading="lazy"
srcset="/content/images/size/w100/format/webp/2025/04/selfh-st-logo-white-1.svg 100w, /content/images/size/w320/format/webp/2025/04/selfh-st-logo-white-1.svg 320w, /content/images/size/w640/format/webp/2025/04/selfh-st-logo-white-1.svg 640w"
sizes="(max-width: 800px) 200px, 320px"
src="/content/images/size/w30/2025/04/selfh-st-logo-white-1.svg"
alt="selfh.st"
/>
</picture>
<picture data-logo='default'>
<source
srcset="/content/images/size/w100/format/webp/2025/04/selfh-st-logo-dark-gray-1.svg 100w, /content/images/size/w320/format/webp/2025/04/selfh-st-logo-dark-gray-1.svg 320w, /content/images/size/w640/format/webp/2025/04/selfh-st-logo-dark-gray-1.svg 600w"
sizes="(max-width: 800px) 200px, 320px"
type="image/webp"
>
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="e910ee56b4039bf3b4f57aac-|49"></script><img class="h-(--logo-h-mobile) w-(--logo-w-mobile) md:h-(--logo-h-desktop) md:w-(--logo-w-desktop) will-change-[filter] transition-[filter] blur-sm"
onload="this.classList.remove('blur-sm')"
loading="lazy"
srcset="/content/images/size/w100/format/webp/2025/04/selfh-st-logo-dark-gray-1.svg 100w, /content/images/size/w320/format/webp/2025/04/selfh-st-logo-dark-gray-1.svg 320w, /content/images/size/w640/format/webp/2025/04/selfh-st-logo-dark-gray-1.svg 640w"
sizes="(max-width: 800px) 200px, 320px"
src="/content/images/size/w30/2025/04/selfh-st-logo-dark-gray-1.svg"
alt="selfh.st"
/>
</picture>
</a>
<p id="footer-label" class="text-typ-tone mb-2">Self-hosted news, content, updates, launches, events, and more</p>
<form class="group relative flex flex-col items-start w-full max-w-sm" data-members-form="subscribe" data-style="" >
<fieldset class="w-full bg-bgr border border-brd flex flex-wrap rounded-btn bg-bgr p-[5px] focus-within:border-brand focus-within:ring-4 focus-within:ring-brand/20 text-sm" data-form-subscribe>
<legend class="sr-only">Subscribe form</legend>
<input data-members-email class="text-sm bg-bgr text-typ flex-2 py-2 rounded-btn border-none focus:ring-0"
type="email" autocomplete="email" placeholder="Your email address"
aria-label="Your email address" required aria-required="true"/>
<button class="flex-1 px-4 py-2 bg-brand text-brand-contrast font-medium rounded-btn md:px-6 hover:ring-[3px] hover:ring-brand/20 cursor-pointer" type="submit" >
<span class="hidden group-[.loading]:flex items-center justify-center"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-loader size-5 [&amp;_svg]:animate-spin stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-loader-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 3a9 9 0 1 0 9 9" /></svg>
</i></span>
<span class="group-[.loading]:hidden">Subscribe</span>
</button>
</fieldset>
<div data-notification class="absolute z-1000 opacity-0 invisible group-[.success]:opacity-100 group-[.success]:visible group-[.success]:translate-y-0 group-[.error]:opacity-100 group-[.error]:visible group-[.error]:translate-y-0 left-0 -bottom-16 text-sm text-left font-medium leading-none flex items-center w-full max-w-md rounded-[6px] gap-2 bg-white text-black p-2 transition-all -translate-y-4 shadow-notification">
<div class="hidden group-[.success]:flex items-center gap-2 flex-1">
<i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-success size-6 text-success ml-1 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" class="icon icon-tabler icons-tabler-filled icon-tabler-circle-check"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M17 3.34a10 10 0 1 1 -14.995 8.984l-.005 -.324l.005 -.324a10 10 0 0 1 14.995 -8.336zm-1.293 5.953a1 1 0 0 0 -1.32 -.083l-.094 .083l-3.293 3.292l-1.293 -1.292l-.094 -.083a1 1 0 0 0 -1.403 1.403l.083 .094l2 2l.094 .083a1 1 0 0 0 1.226 0l.094 -.083l4 -4l.083 -.094a1 1 0 0 0 -.083 -1.32z" /></svg>
</i> <p>Great! Check your inbox and click the link.</p>
</div>
<div class="hidden group-[.error]:flex items-center gap-2 flex-1">
<i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-error size-6 text-error ml-1 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" class="icon icon-tabler icons-tabler-filled icon-tabler-exclamation-circle"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M17 3.34a10 10 0 1 1 -15 8.66l.005 -.324a10 10 0 0 1 14.995 -8.336m-5 11.66a1 1 0 0 0 -1 1v.01a1 1 0 0 0 2 0v-.01a1 1 0 0 0 -1 -1m0 -7a1 1 0 0 0 -1 1v4a1 1 0 0 0 2 0v-4a1 1 0 0 0 -1 -1" /></svg>
</i> <p>Sorry, something went wrong. Please try again.</p>
</div>
<button type="button" class="flex items-center justify-center size-8 opacity-25 hover:opacity-50 cursor-pointer"
@click="$el.parentNode.classList.add('opacity-0', 'invisible', '-translate-y-4');$el.parentNode.parentNode.classList.remove('success', 'error')">
<i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-ghost-close size-4 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="gh-portal-closeicon" alt="Close" data-testid="close-popup"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.2px !important;}</style></defs><path class="a" d="M.75 23.249l22.5-22.5M23.25 23.249L.75.749"></path></svg>
</i> </button>
</div>
</form>
</div>
<hr class="border-none h-[1px] w-full max-w-xs my-2 bg-linear-to-r from-transparent via-brd to-transparent"/>
<nav class="" data-footer-nav="secondary">
<ul class="relative flex flex-wrap items-center justify-center gap-3 text-typ text-sm z-10">
<li class="nav-about flex flex-row relative"
data-label="About" data-slug="about" data-length="5">
<a class="flex hover:text-typ-tone" href="https://selfh.st/about/"
>
<span>About</span>
</a>
</li>
<li class="nav-member-perks flex flex-row relative"
data-label="Member Perks" data-slug="member-perks" data-length="12">
<a class="flex hover:text-typ-tone" href="https://selfh.st/perks/"
>
<span>Member Perks</span>
</a>
</li>
<li class="nav-membership flex flex-row relative"
data-label="Membership" data-slug="membership" data-length="10">
<a class="flex hover:text-typ-tone" href="https://selfh.st/membership/"
>
<span>Membership</span>
</a>
</li>
<li class="nav-donate flex flex-row relative"
data-label="Donate" data-slug="donate" data-length="6">
<a class="flex hover:text-typ-tone" href="https://donate.stripe.com/8wMeX3c5M4jp6Ri7ss"
>
<span>Donate</span>
</a>
</li>
<li class="nav-contact flex flex-row relative"
data-label="Contact" data-slug="contact" data-length="7">
<a class="flex hover:text-typ-tone" href="https://selfh.st/contact/"
>
<span>Contact</span>
</a>
</li>
<li class="nav-sponsors flex flex-row relative"
data-label="Sponsors" data-slug="sponsors" data-length="8">
<a class="flex hover:text-typ-tone" href="https://selfh.st/sponsor/"
>
<span>Sponsors</span>
</a>
</li>
<li class="nav-subscribe flex flex-row relative"
data-label="Subscribe" data-slug="subscribe" data-length="9">
<a class="flex hover:text-typ-tone" href="https://selfh.st/#/portal"
>
<span>Subscribe</span>
</a>
</li>
<li class="nav-account flex flex-row relative"
data-label="Account" data-slug="account" data-length="7">
<a class="flex hover:text-typ-tone" href="https://selfh.st/signin/"
>
<span>Account</span>
</a>
</li>
<li class="nav-privacy-policy flex flex-row relative"
data-label="Privacy Policy" data-slug="privacy-policy" data-length="14">
<a class="flex hover:text-typ-tone" href="https://selfh.st/privacy-policy/"
>
<span>Privacy Policy</span>
</a>
</li>
</ul>
</nav>
<nav class="relative flex flex-wrap items-center gap-3 text-typ-tone text-sm z-10" data-socials="footer" x-data="socials">
<a :href="bluesky" :class="bluesky && 'block!'" class="hidden hover:text-typ" aria-label="Bluesky"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-brand-bluesky size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-brand-bluesky">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M6.335 5.144c-1.654 -1.199 -4.335 -2.127 -4.335 .826c0 .59 .35 4.953 .556 5.661c.713 2.463 3.13 2.75 5.444 2.369c-4.045 .665 -4.889 3.208 -2.667 5.41c1.03 1.018 1.913 1.59 2.667 1.59c2 0 3.134 -2.769 3.5 -3.5c.333 -.667 .5 -1.167 .5 -1.5c0 .333 .167 .833 .5 1.5c.366 .731 1.5 3.5 3.5 3.5c.754 0 1.637 -.571 2.667 -1.59c2.222 -2.203 1.378 -4.746 -2.667 -5.41c2.314 .38 4.73 .094 5.444 -2.369c.206 -.708 .556 -5.072 .556 -5.661c0 -2.953 -2.68 -2.025 -4.335 -.826c-2.293 1.662 -4.76 5.048 -5.665 6.856c-.905 -1.808 -3.372 -5.194 -5.665 -6.856z" />
</svg>
</i><span class="hidden">Bluesky</span></a>
<a :href="discord" :class="discord && 'block!'" class="hidden hover:text-typ" aria-label="Discord"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-brand-discord size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-brand-discord">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M8 12a1 1 0 1 0 2 0a1 1 0 0 0 -2 0" />
<path d="M14 12a1 1 0 1 0 2 0a1 1 0 0 0 -2 0" />
<path d="M15.5 17c0 1 1.5 3 2 3c1.5 0 2.833 -1.667 3.5 -3c.667 -1.667 .5 -5.833 -1.5 -11.5c-1.457 -1.015 -3 -1.34 -4.5 -1.5l-.972 1.923a11.913 11.913 0 0 0 -4.053 0l-.975 -1.923c-1.5 .16 -3.043 .485 -4.5 1.5c-2 5.667 -2.167 9.833 -1.5 11.5c.667 1.333 2 3 3.5 3c.5 0 2 -2 2 -3" />
<path d="M7 16.5c3.5 1 6.5 1 10 0" />
</svg>
</i><span class="hidden">Discord</span></a>
<a :href="github" :class="github && 'block!'" class="hidden hover:text-typ" aria-label="Github"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-brand-github size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-github" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<desc>Download more icon variants from https://tabler-icons.io/i/brand-github</desc>
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5" />
</svg>
</i><span class="hidden">Github</span></a>
<a :href="instagram" :class="instagram && 'block!'" class="hidden hover:text-typ" aria-label="Instagram"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-brand-instagram size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-instagram" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<desc>Download more icon variants from https://tabler-icons.io/i/brand-instagram</desc>
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<rect x="4" y="4" width="16" height="16" rx="4" />
<circle cx="12" cy="12" r="3" />
<line x1="16.5" y1="7.5" x2="16.5" y2="7.501" />
</svg>
</i><span class="hidden">Instagram</span></a>
<a :href="linkedin" :class="linkedin && 'block!'" class="hidden hover:text-typ" aria-label="Linkedin"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-brand-linkedin size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-linkedin" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path>
<rect x="2" y="9" width="4" height="12"></rect>
<circle cx="4" cy="4" r="2"></circle>
</svg>
</i><span class="hidden">Linkedin</span></a>
<a :href="mastodon" :class="mastodon && 'block!'" class="hidden hover:text-typ" aria-label="Mastodon"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-brand-mastodon size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-brand-mastodon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M18.648 15.254c-1.816 1.763 -6.648 1.626 -6.648 1.626a18.262 18.262 0 0 1 -3.288 -.256c1.127 1.985 4.12 2.81 8.982 2.475c-1.945 2.013 -13.598 5.257 -13.668 -7.636l-.026 -1.154c0 -3.036 .023 -4.115 1.352 -5.633c1.671 -1.91 6.648 -1.666 6.648 -1.666s4.977 -.243 6.648 1.667c1.329 1.518 1.352 2.597 1.352 5.633s-.456 4.074 -1.352 4.944z" /><path d="M12 11.204v-2.926c0 -1.258 -.895 -2.278 -2 -2.278s-2 1.02 -2 2.278v4.722m4 -4.722c0 -1.258 .895 -2.278 2 -2.278s2 1.02 2 2.278v4.722" /></svg>
</i><span class="hidden">Mastodon</span></a>
<a :href="pinterest" :class="pinterest && 'block!'" class="hidden hover:text-typ" aria-label="Pinterest"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-brand-pinterest size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-pinterest" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<line x1="8" y1="20" x2="12" y2="11" />
<path d="M10.7 14c.437 1.263 1.43 2 2.55 2c2.071 0 3.75 -1.554 3.75 -4a5 5 0 1 0 -9.7 1.7" />
<circle cx="12" cy="12" r="9" />
</svg>
</i><span class="hidden">Pinterest</span></a>
<a :href="reddit" :class="reddit && 'block!'" class="hidden hover:text-typ" aria-label="Reddit"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-brand-reddit size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-reddit" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M12 8c2.648 0 5.028 .826 6.675 2.14a2.5 2.5 0 0 1 2.326 4.36c0 3.59 -4.03 6.5 -9 6.5c-4.875 0 -8.845 -2.8 -9 -6.294l-1 -.206a2.5 2.5 0 0 1 2.326 -4.36c1.646 -1.313 4.026 -2.14 6.674 -2.14z" />
<path d="M12 8l1 -5l6 1" />
<circle cx="19" cy="4" r="1" />
<circle cx="9" cy="13" r=".5" fill="currentColor" />
<circle cx="15" cy="13" r=".5" fill="currentColor" />
<path d="M10 17c.667 .333 1.333 .5 2 .5s1.333 -.167 2 -.5" />
</svg>
</i><span class="hidden">Reddit</span></a>
<a :href="telegram" :class="telegram && 'block!'" class="hidden hover:text-typ" aria-label="Telegram"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-brand-telegram size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-telegram" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<desc>Download more icon variants from https://tabler-icons.io/i/brand-telegram</desc>
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M15 10l-4 4l6 6l4 -16l-18 7l4 2l2 6l3 -4" />
</svg>
</i><span class="hidden">Telegram</span></a>
<a :href="threads" :class="threads && 'block!'" class="hidden hover:text-typ" aria-label="Threads"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-brand-threads size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-brand-threads">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M19 7.5c-1.333 -3 -3.667 -4.5 -7 -4.5c-5 0 -8 2.5 -8 9s3.5 9 8 9s7 -3 7 -5s-1 -5 -7 -5c-2.5 0 -3 1.25 -3 2.5c0 1.5 1 2.5 2.5 2.5c2.5 0 3.5 -1.5 3.5 -5s-2 -4 -3 -4s-1.833 .333 -2.5 1" />
</svg>
</i><span class="hidden">Threads</span></a>
<a :href="tiktok" :class="tiktok && 'block!'" class="hidden hover:text-typ" aria-label="Tiktok"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-brand-tiktok size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-tiktok" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<desc>Download more icon variants from https://tabler-icons.io/i/brand-tiktok</desc>
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M9 12a4 4 0 1 0 4 4v-12a5 5 0 0 0 5 5" />
</svg>
</i><span class="hidden">Tiktok</span></a>
<a :href="whatsapp" :class="whatsapp && 'block!'" class="hidden hover:text-typ" aria-label="Whatsapp"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-brand-whatsapp size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-whatsapp" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<desc>Download more icon variants from https://tabler-icons.io/i/brand-whatsapp</desc>
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M3 21l1.65 -3.8a9 9 0 1 1 3.4 2.9l-5.05 .9" />
<path d="M9 10a0.5 .5 0 0 0 1 0v-1a0.5 .5 0 0 0 -1 0v1a5 5 0 0 0 5 5h1a0.5 .5 0 0 0 0 -1h-1a0.5 .5 0 0 0 0 1" />
</svg>
</i><span class="hidden">Whatsapp</span></a>
<a :href="youtube" :class="youtube && 'block!'" class="hidden hover:text-typ" aria-label="Youtube"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-brand-youtube size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-youtube" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<desc>Download more icon variants from https://tabler-icons.io/i/brand-youtube</desc>
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<rect x="3" y="5" width="18" height="14" rx="4" />
<path d="M10 9l5 3l-5 3z" />
</svg>
</i><span class="hidden">Youtube</span></a>
<a :href="rss" :class="rss && 'block!'" class="hidden hover:text-typ" aria-label="RSS"><i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-rss size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-rss" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<circle cx="5" cy="19" r="1" />
<path d="M4 4a16 16 0 0 1 16 16" />
<path d="M4 11a9 9 0 0 1 9 9" />
</svg>
</i><span class="hidden">RSS</span></a>
</nav>
<span data-footer-bg class="absolute -bottom-[0.14em] -left-[0.075em] text-center hidden font-semibold sm:block text-[clamp(5rem,4rem+5vw,8rem)] leading-none bg-linear-to-t from-brd to-bgr-tone text-transparent bg-clip-text z-0 opacity-75 whitespace-nowrap">selfh.st</span>
</div>
<div class="py-4 md:py-6 flex flex-wrap justify-center items-center text-center gap-2 relative border-t border-brd sm:border-none" data-footer-bottom>
<div class="text-sm text-typ-tone">
<span data-footer-date>&copy;2026&nbsp;<a class="hover:text-typ" href="https://selfh.st">selfh.st</a>.</span>
</div>
<button class="flex gap-0.5 p-0.5 border border-brd text-typ-tone rounded-btn hover:bg-bgr-tone" aria-label="Toggle color scheme"
data-color-scheme-toggle @click="toggleColorScheme(event)">
<span data-theme="light" class="px-1.5 py-[3px] opacity-50 rounded-btn">
<i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-sun size-4 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-sun-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M12 19a1 1 0 0 1 .993 .883l.007 .117v1a1 1 0 0 1 -1.993 .117l-.007 -.117v-1a1 1 0 0 1 1 -1z" stroke-width="0" fill="currentColor" />
<path d="M18.313 16.91l.094 .083l.7 .7a1 1 0 0 1 -1.32 1.497l-.094 -.083l-.7 -.7a1 1 0 0 1 1.218 -1.567l.102 .07z" stroke-width="0" fill="currentColor" />
<path d="M7.007 16.993a1 1 0 0 1 .083 1.32l-.083 .094l-.7 .7a1 1 0 0 1 -1.497 -1.32l.083 -.094l.7 -.7a1 1 0 0 1 1.414 0z" stroke-width="0" fill="currentColor" />
<path d="M4 11a1 1 0 0 1 .117 1.993l-.117 .007h-1a1 1 0 0 1 -.117 -1.993l.117 -.007h1z" stroke-width="0" fill="currentColor" />
<path d="M21 11a1 1 0 0 1 .117 1.993l-.117 .007h-1a1 1 0 0 1 -.117 -1.993l.117 -.007h1z" stroke-width="0" fill="currentColor" />
<path d="M6.213 4.81l.094 .083l.7 .7a1 1 0 0 1 -1.32 1.497l-.094 -.083l-.7 -.7a1 1 0 0 1 1.217 -1.567l.102 .07z" stroke-width="0" fill="currentColor" />
<path d="M19.107 4.893a1 1 0 0 1 .083 1.32l-.083 .094l-.7 .7a1 1 0 0 1 -1.497 -1.32l.083 -.094l.7 -.7a1 1 0 0 1 1.414 0z" stroke-width="0" fill="currentColor" />
<path d="M12 2a1 1 0 0 1 .993 .883l.007 .117v1a1 1 0 0 1 -1.993 .117l-.007 -.117v-1a1 1 0 0 1 1 -1z" stroke-width="0" fill="currentColor" />
<path d="M12 7a5 5 0 1 1 -4.995 5.217l-.005 -.217l.005 -.217a5 5 0 0 1 4.995 -4.783z" stroke-width="0" fill="currentColor" />
</svg>
</i> </span>
<span data-theme="dark" class="px-1.5 py-[3px] opacity-50 rounded-btn">
<i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-moon size-4 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-moon-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M12 1.992a10 10 0 1 0 9.236 13.838c.341 -.82 -.476 -1.644 -1.298 -1.31a6.5 6.5 0 0 1 -6.864 -10.787l.077 -.08c.551 -.63 .113 -1.653 -.758 -1.653h-.266l-.068 -.006l-.06 -.002z" stroke-width="0" fill="currentColor" />
</svg>
</i> </span>
</button> </div>
</div>
</footer>
<template data-toggle-template x-init="renderSubmenus()">
<button class="ml-1" data-class="rotate-180" title="Menu toggle" aria-label="Menu toggle"
aria-controls="" aria-expanded="false" @click="toggleSubmenu(event)"
>
<i class="icon [&_svg]:h-[inherit] [&_svg]:w-[inherit] [&_svg]:[stroke-width:inherit] icon-chevron-down size-4 stroke-2 stroke-2" role="presentation">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-down" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</i> </button>
</template>
<script type="e910ee56b4039bf3b4f57aac-text/javascript">
function renderSubmenus() {
const navItems = document.querySelectorAll('[data-nav] li')
const subItems = document.querySelectorAll('.is-subitem')
const mobileClass = "hidden flex flex-col basis-full items-start"
const desktopClass = "z-50 min-w-[calc(100%+24px)] absolute top-0 flex flex-col gap-0.5 shadow-2xl rounded-theme bg-bgr py-2 px-1.5 -ml-2 mt-11 opacity-0 invisible translate-y-1 transition-all"
// Remove '-' signs
subItems.forEach(item => {
const itemName = item.querySelector('a span')
itemName.innerText = itemName.innerText.slice(1)
});
// Add subitems in place
let subMenu, hasItems
navItems.forEach((item, index) => {
if (item.classList.contains('is-subitem') && !navItems[index - 1].classList.contains('is-subitem')) {
navItems[index - 1].classList.add('is-mainitem');
navItems[index - 1].setAttribute('x-on:click.outside', 'closeSubmenus(event)')
}
subMenu = item.classList.contains('is-subitem') ? subMenu : document.createElement('ul');
if (item.classList.contains('is-subitem')) {
subMenu.appendChild(item)
subMenu.setAttribute('data-submenu', '')
subMenu.getAttribute('id') === null ? subMenu.setAttribute('id', navItems[index - 1].getAttribute('data-slug')) : ''
const menuType = subMenu.closest('nav').getAttribute('data-nav')
subMenu.classList = menuType === 'header' ? desktopClass : mobileClass
} else {
item.appendChild(subMenu)
}
});
const dropdownMenus = document.querySelectorAll('[data-nav] .is-mainitem')
const toggle = document.querySelector('[data-toggle-template]');
dropdownMenus.forEach(menu => {
const toggleBtn = toggle.content.firstElementChild.cloneNode(true);
toggleBtn.setAttribute('aria-controls', menu.getAttribute('data-slug'))
menu.insertBefore(toggleBtn, menu.children[1]);
});
}
</script>
<script type="e910ee56b4039bf3b4f57aac-text/javascript">
window.socials = {
bluesky: '',
discord: '',
github: '',
instagram: '',
linkedin: '',
mastodon: '',
pinterest: '',
reddit: '',
telegram: '',
threads: '',
tiktok: '',
whatsapp: '',
youtube: '',
rss: '/rss'
}
document.addEventListener('alpine:init', () => {
Alpine.data('socials', () => (window.socials))
})
</script>
<script type="e910ee56b4039bf3b4f57aac-text/javascript">
</script>
<foreignObject><script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="e910ee56b4039bf3b4f57aac-|49" defer></script></foreignObject></body>
</html>