mirror of
https://github.com/ancsemi/Haven.git
synced 2026-10-26 00:29:14 +00:00
693 lines
40 KiB
HTML
693 lines
40 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta property="og:title" content="Haven, a chat server you run yourself">
|
|
<meta property="og:description" content="Haven is a free, open source alternative to Discord that you host on your own computer. Channels, voice, screen sharing, threads and encrypted DMs. Friends join with a link, the desktop app or the Android app.">
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:url" content="https://haven-app.com/">
|
|
<meta property="og:image" content="https://haven-app.com/img/haven-4-6-default.jpg">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="Haven, a chat server you run yourself">
|
|
<meta name="twitter:description" content="Haven is a free, open source alternative to Discord that you host on your own computer. Channels, voice, screen sharing, threads and encrypted DMs. Friends join with a link, the desktop app or the Android app.">
|
|
<meta name="twitter:image" content="https://haven-app.com/img/haven-4-6-default.jpg">
|
|
<meta name="twitter:site" content="@ancsemi">
|
|
<link rel="canonical" href="https://haven-app.com/">
|
|
<meta name="theme-color" content="#F6F5F9">
|
|
<title>Haven, a chat server you run yourself</title>
|
|
<meta name="description" content="Haven is a free, open source alternative to Discord that you host on your own computer. Channels, voice, screen sharing, threads and encrypted DMs. Friends join with a link, the desktop app or the Android app.">
|
|
<link rel="icon" href="/favicon.ico" sizes="48x48">
|
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
<link rel="apple-touch-icon" href="/favicon-192.png">
|
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&family=IBM+Plex+Mono:wght@400;500&display=swap">
|
|
<style>
|
|
:root {
|
|
--paper: #F6F5F9;
|
|
--surface: #FFFFFF;
|
|
--ink: #1A1726;
|
|
--muted: #5E5A70;
|
|
--rule: #D9D5E3;
|
|
--rule-strong: #B9B3CB;
|
|
--accent: #5537C4;
|
|
--accent-ink: #FFFFFF;
|
|
--accent-soft: #ECE8FA;
|
|
--term-bg: #1B1826;
|
|
--term-ink: #E6E2F0;
|
|
--term-dim: #8F89A6;
|
|
--term-accent: #B7A9F8;
|
|
--accent-glow: rgba(85, 55, 196, 0.55);
|
|
--sans: "IBM Plex Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
|
|
--mono: "IBM Plex Mono", Consolas, "SFMono-Regular", Menlo, monospace;
|
|
--measure: 66ch;
|
|
--wide: 1080px;
|
|
--gutter: clamp(18px, 4vw, 40px);
|
|
--topbar: 64px;
|
|
color-scheme: light;
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
:root:not([data-theme="light"]) {
|
|
--paper: #14121C;
|
|
--surface: #1C1926;
|
|
--ink: #ECE9F3;
|
|
--muted: #A39FB3;
|
|
--rule: #2C2838;
|
|
--rule-strong: #443E57;
|
|
--accent: #A497F5;
|
|
--accent-ink: #14121C;
|
|
--accent-soft: #24203A;
|
|
--term-bg: #0F0D16;
|
|
--term-ink: #E6E2F0;
|
|
--term-dim: #7F7896;
|
|
--term-accent: #B7A9F8;
|
|
--accent-glow: rgba(164, 151, 245, 0.55);
|
|
color-scheme: dark;
|
|
}
|
|
}
|
|
:root[data-theme="dark"] {
|
|
--paper: #14121C;
|
|
--surface: #1C1926;
|
|
--ink: #ECE9F3;
|
|
--muted: #A39FB3;
|
|
--rule: #2C2838;
|
|
--rule-strong: #443E57;
|
|
--accent: #A497F5;
|
|
--accent-ink: #14121C;
|
|
--accent-soft: #24203A;
|
|
--term-bg: #0F0D16;
|
|
--term-ink: #E6E2F0;
|
|
--term-dim: #7F7896;
|
|
--term-accent: #B7A9F8;
|
|
--accent-glow: rgba(164, 151, 245, 0.55);
|
|
color-scheme: dark;
|
|
}
|
|
|
|
* { box-sizing: border-box; }
|
|
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
|
|
body {
|
|
margin: 0;
|
|
background: var(--paper);
|
|
color: var(--ink);
|
|
font-family: var(--sans);
|
|
font-size: 17px;
|
|
line-height: 1.6;
|
|
font-feature-settings: "kern" 1, "liga" 1;
|
|
}
|
|
a { color: var(--accent); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }
|
|
a:hover { text-decoration-thickness: 2px; }
|
|
a:focus-visible, button:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }
|
|
h1, h2, h3 { font-weight: 600; letter-spacing: -0.015em; line-height: 1.15; margin: 0; text-wrap: balance; }
|
|
h1 { font-size: clamp(34px, 5vw, 50px); font-weight: 700; letter-spacing: -0.025em; }
|
|
h2 { font-size: clamp(24px, 3vw, 30px); }
|
|
h3 { font-size: 19px; }
|
|
p { margin: 0; }
|
|
p + p { margin-top: 1em; }
|
|
code, kbd, .mono { font-family: var(--mono); font-size: 0.92em; }
|
|
code { background: var(--accent-soft); padding: 1px 5px; border-radius: 3px; }
|
|
ul, ol { margin: 0; padding-left: 1.25em; }
|
|
li + li { margin-top: 0.35em; }
|
|
img { max-width: 100%; height: auto; display: block; }
|
|
hr { border: 0; border-top: 1px solid var(--rule); margin: 0; }
|
|
|
|
.wrap { max-width: var(--wide); margin: 0 auto; padding: 0 var(--gutter); }
|
|
.prose { max-width: var(--measure); }
|
|
.muted { color: var(--muted); }
|
|
.small { font-size: 15px; }
|
|
.num { font-variant-numeric: tabular-nums; }
|
|
|
|
/* ── Top bar, pinned ─────────────────────────────── */
|
|
.top { position: sticky; top: 0; z-index: 20; border-bottom: 1px solid var(--rule); background: var(--paper); }
|
|
.top .wrap { display: flex; align-items: center; justify-content: space-between; gap: 24px; min-height: var(--topbar); flex-wrap: wrap; padding-top: 8px; padding-bottom: 8px; }
|
|
.brand { display: inline-flex; align-items: center; gap: 10px; color: var(--ink); text-decoration: none; font-weight: 600; font-size: 18px; letter-spacing: 0.01em; }
|
|
.brand svg { width: 26px; height: 26px; }
|
|
.nav { display: flex; gap: 22px; flex-wrap: wrap; }
|
|
.nav a { color: var(--muted); text-decoration: none; font-size: 15px; font-weight: 500; }
|
|
.nav a:hover { color: var(--ink); text-decoration: underline; }
|
|
.nav .btn-try { background: var(--accent); color: var(--accent-ink); padding: 8px 14px; border-radius: 4px; font-weight: 600; font-size: 14px; line-height: 1.2; }
|
|
.nav .btn-try:hover { color: var(--accent-ink); text-decoration: none; filter: brightness(1.08); }
|
|
@keyframes pulse-ring { 0% { box-shadow: 0 0 0 0 var(--accent-glow); } 70% { box-shadow: 0 0 0 16px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } }
|
|
.btn.pulse { border-color: var(--accent); background: var(--accent-soft); animation: pulse-ring 1.2s ease-out 3; }
|
|
|
|
/* ── Sections ────────────────────────────────────── */
|
|
section { padding: clamp(28px, 4vw, 48px) 0; border-top: 1px solid var(--rule); scroll-margin-top: var(--topbar); }
|
|
section:first-of-type { border-top: 0; }
|
|
.section-head { margin-bottom: 28px; }
|
|
.section-head p { margin-top: 12px; color: var(--muted); max-width: var(--measure); }
|
|
|
|
/* ── Hero ────────────────────────────────────────── */
|
|
.hero { padding-top: clamp(20px, 3vw, 36px); }
|
|
.hero-grid { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); gap: clamp(28px, 5vw, 64px); align-items: start; }
|
|
.hero-brand { display: inline-flex; align-items: center; gap: 0.32em; color: var(--ink); font-weight: 600; font-size: clamp(48px, 7vw, 76px); letter-spacing: 0.01em; line-height: 1; }
|
|
.hero-brand svg { width: 1.05em; height: 1.05em; flex: none; }
|
|
.hero-title { font-size: clamp(20px, 2.4vw, 25px); font-weight: 700; letter-spacing: -0.01em; margin-top: 30px; }
|
|
.hero .lead { font-size: 20px; line-height: 1.5; margin-top: 10px; max-width: 34em; }
|
|
.actions { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-top: 30px; }
|
|
.btn { display: inline-flex; align-items: center; gap: 8px; padding: 11px 18px; border-radius: 4px; font-weight: 600; font-size: 16px; text-decoration: none; border: 1px solid transparent; line-height: 1.2; }
|
|
.btn-primary { background: var(--accent); color: var(--accent-ink); }
|
|
.btn-primary:hover { filter: brightness(1.08); text-decoration: none; }
|
|
.btn-secondary { background: transparent; color: var(--ink); border-color: var(--rule-strong); }
|
|
.btn-secondary:hover { border-color: var(--ink); text-decoration: none; }
|
|
.btn .ver { font-family: var(--mono); font-weight: 500; font-size: 14px; opacity: 0.85; }
|
|
.actions .plain { font-size: 15px; }
|
|
|
|
.term { background: var(--term-bg); color: var(--term-ink); border-radius: 6px; padding: 18px 20px; font-family: var(--mono); font-size: 13.5px; line-height: 1.5; overflow-x: auto; border: 1px solid var(--rule-strong); }
|
|
.term pre { margin: 0; white-space: pre-wrap; }
|
|
.term .dim { color: var(--term-dim); }
|
|
.term .acc { color: var(--term-accent); }
|
|
.term-cap { margin-top: 12px; font-size: 14px; color: var(--muted); max-width: 46ch; }
|
|
|
|
.shot { margin-top: clamp(40px, 6vw, 64px); }
|
|
.shot img { border: 1px solid var(--rule-strong); border-radius: 6px; width: 100%; }
|
|
.shot figcaption, .fig figcaption { font-size: 14px; color: var(--muted); margin-top: 10px; }
|
|
figure { margin: 0; }
|
|
|
|
/* ── About ───────────────────────────────────────── */
|
|
.split { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: clamp(28px, 5vw, 72px); align-items: start; }
|
|
.split .fig img { border: 1px solid var(--rule-strong); border-radius: 18px; }
|
|
|
|
/* ── Feature lists ───────────────────────────────── */
|
|
.cols { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; }
|
|
.cols > div { padding: 0 24px; border-left: 1px solid var(--rule); }
|
|
.cols > div:first-child { padding-left: 0; border-left: 0; }
|
|
.cols > div:last-child { padding-right: 0; }
|
|
.cols h3 { margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--rule); }
|
|
.cols ul { list-style: disc; padding-left: 1.1em; }
|
|
.cols li { font-size: 16px; }
|
|
.cols li::marker { color: var(--accent); }
|
|
.cols li + li { margin-top: 0.5em; }
|
|
|
|
/* ── Steps ───────────────────────────────────────── */
|
|
.steps { counter-reset: step; list-style: none; padding: 0; max-width: var(--measure); }
|
|
.steps li { position: relative; padding-left: 52px; }
|
|
.steps li + li { margin-top: 20px; }
|
|
.steps li::before { counter-increment: step; content: counter(step); position: absolute; left: 0; top: 0; width: 34px; height: 34px; border: 1px solid var(--rule-strong); border-radius: 50%; display: grid; place-items: center; font-family: var(--mono); font-size: 14px; color: var(--ink); background: var(--surface); }
|
|
.steps strong { font-weight: 600; }
|
|
|
|
/* ── Table ───────────────────────────────────────── */
|
|
.tablewrap { overflow-x: auto; margin-top: 20px; }
|
|
table { border-collapse: collapse; width: 100%; font-size: 15px; }
|
|
th, td { text-align: left; vertical-align: top; padding: 11px 14px 11px 0; border-bottom: 1px solid var(--rule); }
|
|
th { font-weight: 600; font-size: 13px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }
|
|
td:first-child { font-family: var(--mono); font-size: 14px; max-width: 16em; }
|
|
tbody tr:last-child td { border-bottom: 1px solid var(--rule-strong); }
|
|
|
|
/* ── Compare ─────────────────────────────────────── */
|
|
.compare th:first-child, .compare td:first-child { font-family: var(--sans); font-size: 15px; max-width: none; }
|
|
.compare th:not(:first-child), .compare td:not(:first-child) { text-align: center; padding-left: 10px; padding-right: 10px; white-space: nowrap; }
|
|
.compare th.us, .compare td.us { background: var(--accent-soft); font-weight: 600; }
|
|
.compare .yes { color: var(--accent); font-weight: 700; }
|
|
.compare .no { color: var(--muted); }
|
|
.compare .note { font-size: 14px; color: var(--muted); }
|
|
.compare-cap { font-size: 14px; color: var(--muted); margin-top: 12px; }
|
|
|
|
/* ── Gallery ─────────────────────────────────────── */
|
|
.gallery { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 28px 24px; }
|
|
.gallery img { border: 1px solid var(--rule-strong); border-radius: 6px; width: 100%; }
|
|
.gallery figcaption { font-size: 14px; color: var(--muted); margin-top: 8px; }
|
|
@media (max-width: 700px) { .gallery { grid-template-columns: 1fr; } }
|
|
|
|
/* ── Lightbox ────────────────────────────────────── */
|
|
.zoomable { cursor: zoom-in; }
|
|
.lightbox { position: fixed; inset: 0; z-index: 50; background: rgba(10, 8, 16, 0.92); display: flex; align-items: center; justify-content: center; padding: 24px; cursor: zoom-out; }
|
|
.lightbox[hidden] { display: none; }
|
|
.lightbox img { max-width: 100%; max-height: 100%; border-radius: 6px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); }
|
|
.lb-nav { position: absolute; top: 50%; transform: translateY(-50%); width: 44px; height: 44px; border: 0; border-radius: 50%; background: rgba(255, 255, 255, 0.14); color: #fff; font-size: 30px; line-height: 1; cursor: pointer; display: grid; place-items: center; padding: 0 0 4px; }
|
|
.lb-nav:hover { background: rgba(255, 255, 255, 0.26); }
|
|
.lb-prev { left: 18px; }
|
|
.lb-next { right: 18px; }
|
|
@media (max-width: 700px) { .lb-nav { width: 36px; height: 36px; font-size: 24px; } .lb-prev { left: 8px; } .lb-next { right: 8px; } }
|
|
|
|
/* ── Facts ───────────────────────────────────────── */
|
|
.facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0; border-top: 1px solid var(--rule-strong); border-bottom: 1px solid var(--rule-strong); }
|
|
.facts div { padding: 18px 16px 18px 0; border-bottom: 1px solid var(--rule); }
|
|
.facts dt { font-size: 13px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }
|
|
.facts dd { margin: 4px 0 0; font-size: 22px; font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
|
|
/* ── Download ────────────────────────────────────── */
|
|
.dl { border: 1px solid var(--rule-strong); border-radius: 6px; background: var(--surface); }
|
|
.dl-row { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 20px; padding: 22px 24px; align-items: center; }
|
|
.dl-row + .dl-row { border-top: 1px solid var(--rule); }
|
|
.dl-row h3 { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
|
|
.dl-row h3 .ver { font-family: var(--mono); font-weight: 500; font-size: 15px; color: var(--muted); }
|
|
.dl-row p { color: var(--muted); font-size: 15px; margin-top: 4px; }
|
|
.dl-links { display: flex; flex-wrap: wrap; gap: 10px; justify-content: flex-end; }
|
|
.dl-links .btn { padding: 9px 14px; font-size: 15px; }
|
|
.try { margin-top: 22px; font-size: 16px; }
|
|
|
|
/* ── Footer ──────────────────────────────────────── */
|
|
footer { border-top: 1px solid var(--rule); padding: 36px 0 48px; color: var(--muted); font-size: 15px; }
|
|
footer .wrap { display: flex; flex-wrap: wrap; gap: 12px 24px; justify-content: space-between; align-items: center; }
|
|
footer nav { display: flex; flex-wrap: wrap; gap: 8px 20px; }
|
|
footer a { color: var(--muted); }
|
|
footer a:hover { color: var(--ink); }
|
|
|
|
@media (max-width: 860px) {
|
|
.hero-grid, .split, .dl-row { grid-template-columns: 1fr; }
|
|
.cols { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 28px; }
|
|
.cols > div:nth-child(odd) { border-left: 0; padding-left: 0; }
|
|
.cols > div:nth-child(even) { padding-right: 0; }
|
|
.split .fig { max-width: 260px; }
|
|
.dl-links { justify-content: flex-start; }
|
|
}
|
|
@media (max-width: 520px) {
|
|
body { font-size: 16px; }
|
|
.cols { grid-template-columns: 1fr; row-gap: 24px; }
|
|
.cols > div { border-left: 0; padding: 0; }
|
|
.cols > div + div { border-top: 1px solid var(--rule); padding-top: 20px; }
|
|
.nav { gap: 14px; }
|
|
:root { --topbar: 96px; }
|
|
}
|
|
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } * { transition: none !important; } .btn.pulse { animation: none; box-shadow: 0 0 0 3px var(--accent); } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header class="top">
|
|
<div class="wrap">
|
|
<a class="brand" href="#top" aria-label="Haven">
|
|
<svg viewBox="0 0 100 100" aria-hidden="true"><polygon points="50,3 93,28 93,72 50,97 7,72 7,28" fill="none" stroke="currentColor" stroke-width="8" stroke-linejoin="round"/></svg>
|
|
Haven
|
|
</a>
|
|
<nav class="nav" aria-label="Sections">
|
|
<a href="#about">About</a>
|
|
<a href="#features">Features</a>
|
|
<a href="#compare">Compare</a>
|
|
<a href="#setup">Setup</a>
|
|
<a href="#security">Security</a>
|
|
<a href="#screenshots">Gallery</a>
|
|
<a href="#download">Download</a>
|
|
<a href="https://github.com/ancsemi/Haven">GitHub</a>
|
|
<a class="btn-try" id="try-now" href="#top">Try it now</a>
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
|
|
<main id="top">
|
|
<section class="hero">
|
|
<div class="wrap">
|
|
<div class="hero-grid">
|
|
<div>
|
|
<div class="hero-brand" aria-hidden="true">
|
|
<svg viewBox="0 0 100 100"><polygon points="50,3 93,28 93,72 50,97 7,72 7,28" fill="none" stroke="currentColor" stroke-width="8" stroke-linejoin="round"/></svg>
|
|
Haven
|
|
</div>
|
|
<h1 class="hero-title">A chat server you run yourself.</h1>
|
|
<p class="lead">Haven is a free, open source alternative to Discord. It runs on your own computer, your friends join with a link, and there's no company in the middle.</p>
|
|
<div class="actions">
|
|
<a class="btn btn-primary" href="#download">Download Haven</a>
|
|
<a class="btn btn-secondary" id="try-server" href="https://haven.moviethingy.xyz/?invite=da0b9be7">Join the community server</a>
|
|
<a class="plain" href="https://github.com/ancsemi/Haven">Source on GitHub</a>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="term" role="img" aria-label="Haven's start-up message in a terminal: HAVEN is running, with the local and network addresses">
|
|
<pre><span class="dim">$</span> <span class="acc">Start Haven.bat</span>
|
|
|
|
<span class="acc">⬡ HAVEN is running</span>
|
|
------------------------------------
|
|
Name: Haven
|
|
Local: https://localhost:3000
|
|
Network: https://192.168.1.20:3000
|
|
Admin: you
|
|
------------------------------------
|
|
<span class="dim">Send your friends the link.</span></pre>
|
|
</div>
|
|
<p class="term-cap">What you see when you start it up.</p>
|
|
</div>
|
|
</div>
|
|
<figure class="shot">
|
|
<img class="zoomable" src="img/haven-4-6-default.jpg" width="1918" height="1010" alt="Haven's default theme: channels and DMs on the left, the member list on the right">
|
|
</figure>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="about">
|
|
<div class="wrap">
|
|
<div class="prose">
|
|
<div class="section-head">
|
|
<h2>About</h2>
|
|
</div>
|
|
<p>Haven works like a Discord server, but you host it yourself. Download the zip, run it, and it gives you a link your friends can use from a browser, the desktop app or the Android and iOS apps.</p>
|
|
<p>There's no company in the middle. No account with us, no email or phone number, and nothing to pay for. Your messages, files and voice calls stay on your own server.</p>
|
|
<p>Haven has been in development since February 2026. It's a spare-time project and it'll always be free.</p>
|
|
<p>Many helpful people with a shared passion for free, unfiltered and private chat have come together to improve it. Every change is public, with a <a href="https://github.com/ancsemi/Haven/blob/main/CHANGELOG.md">changelog</a> updated every release.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="features">
|
|
<div class="wrap">
|
|
<div class="section-head">
|
|
<h2>Features</h2>
|
|
<p>The short version. The <a href="https://github.com/ancsemi/Haven/blob/main/GUIDE.md">guide</a> covers everything.</p>
|
|
</div>
|
|
<div class="cols">
|
|
<div>
|
|
<h3>Chat</h3>
|
|
<ul>
|
|
<li>Text channels, threads and forums</li>
|
|
<li>Voice chat</li>
|
|
<li>Screen sharing, with more than one stream at a time</li>
|
|
<li>Watch videos and listen to music together</li>
|
|
<li>Encrypted direct messages</li>
|
|
<li>Reactions, stickers, polls and GIFs</li>
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<h3>Coming from Discord</h3>
|
|
<ul>
|
|
<li>Import your whole server, including threads, pins and attachments</li>
|
|
<li>Ferry bridges a Haven channel to a Discord channel, so friends who stay behind can still talk to you</li>
|
|
<li>Roles and permissions that work the way you're used to</li>
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<h3>Customization</h3>
|
|
<ul>
|
|
<li>26 themes with customizable effects, or write your own CSS</li>
|
|
<li>Bots and webhooks</li>
|
|
<li>Sign in with your own identity provider</li>
|
|
<li>Guest access, invite links and channel codes</li>
|
|
<li>Available in 11 languages: English, Français, Deutsch, Español, Polski, Русский, 中文, Português, فارسی, 日本語 and 한국어</li>
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<h3>Extras</h3>
|
|
<ul>
|
|
<li>Rich Presence: show what you're playing or listening to</li>
|
|
<li>Built-in games with leaderboards</li>
|
|
<li>Flash games, through Ruffle</li>
|
|
<li>Backups and updates from the settings page</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<figure class="shot">
|
|
<img class="zoomable" src="img/haven-features.jpg" width="1915" height="1005" alt="A community channel in the Braid theme with the member list open" loading="lazy">
|
|
</figure>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="compare">
|
|
<div class="wrap">
|
|
<div class="section-head">
|
|
<h2>Compared to the others</h2>
|
|
<p>How Haven stacks up against the usual suspects.</p>
|
|
</div>
|
|
<div class="tablewrap">
|
|
<table class="compare">
|
|
<thead>
|
|
<tr><th></th><th class="us">Haven</th><th>Discord</th><th>Matrix (Element)</th><th>Fluxer</th><th>Signal</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td>Self-hosted, you own the data</td><td class="us yes">✓</td><td class="no">✗</td><td class="yes">✓</td><td class="yes">✓</td><td class="no">✗</td></tr>
|
|
<tr><td>No account with a company</td><td class="us yes">✓</td><td class="no">✗</td><td class="note">If self-hosted</td><td class="yes">✓</td><td class="no">✗</td></tr>
|
|
<tr><td>No email or phone number needed</td><td class="us yes">✓</td><td class="no">✗</td><td class="note">Depends on server</td><td class="no">✗</td><td class="no">✗</td></tr>
|
|
<tr><td>End-to-end encrypted DMs</td><td class="us yes">✓</td><td class="no">✗</td><td class="yes">✓</td><td class="no">✗</td><td class="yes">✓</td></tr>
|
|
<tr><td>Voice chat</td><td class="us yes">✓</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td></tr>
|
|
<tr><td>Screen sharing</td><td class="us yes">✓</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td><td class="note">Desktop only</td></tr>
|
|
<tr><td>Open source</td><td class="us yes">✓</td><td class="no">✗</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td></tr>
|
|
<tr><td>Themes and custom CSS</td><td class="us note">26 themes + CSS</td><td class="note">Limited</td><td class="note">Depends on client</td><td class="note">CSS</td><td class="no">✗</td></tr>
|
|
<tr><td>Group channels</td><td class="us yes">✓</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td><td class="note">Groups only</td></tr>
|
|
<tr><td>Free, no paid tier</td><td class="us yes">✓</td><td class="note">Freemium</td><td class="yes">✓</td><td class="note">Freemium</td><td class="yes">✓</td></tr>
|
|
<tr><td>Join from a browser, nothing to install</td><td class="us yes">✓</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td><td class="no">✗</td></tr>
|
|
<tr><td>Works in a phone's browser</td><td class="us yes">✓</td><td class="note">Pushes you to the app</td><td class="yes">✓</td><td class="yes">✓</td><td class="no">✗</td></tr>
|
|
<tr><td>Import your history from Discord</td><td class="us yes">✓</td><td class="note">n/a</td><td class="note">Via a bridge</td><td class="no">✗</td><td class="no">✗</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<p class="compare-cap">Free tiers and default hosted setups, as of September 2026.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="setup">
|
|
<div class="wrap">
|
|
<div class="section-head">
|
|
<h2>Setup</h2>
|
|
<p>No terminal, no config files. There's a Docker image too.</p>
|
|
</div>
|
|
<ol class="steps">
|
|
<li><strong>Download the zip</strong> and unzip it anywhere.</li>
|
|
<li><strong>Double-click <code>Start Haven.bat</code></strong> (<code>start.sh</code> on Mac and Linux). It sets everything up and prints your link. If Node.js isn't installed yet, it offers to install it for you.</li>
|
|
<li><strong>Send the link to your friends.</strong> They can use a browser, the desktop app or the Android and iOS apps.</li>
|
|
</ol>
|
|
<p class="prose muted" style="margin-top: 26px;">To reach friends outside your home network you'll need to open a port or use a tunnel. The guide covers both, and there's a one-click tunnel in the admin settings.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="security">
|
|
<div class="wrap">
|
|
<div class="section-head">
|
|
<h2>Security and privacy</h2>
|
|
<p>How it actually works.</p>
|
|
</div>
|
|
<div class="split">
|
|
<div class="prose">
|
|
<ul>
|
|
<li><strong>Your data lives on the computer running the server,</strong> in one folder. Back it up, move it or delete it whenever you want.</li>
|
|
<li><strong>Direct messages are end-to-end encrypted.</strong> The server stores them but can't read them, and neither can the admin.</li>
|
|
<li><strong>Channel messages aren't end-to-end encrypted.</strong> Whoever runs the server can read them, just like a Discord admin can. Join servers run by people you trust, or run your own.</li>
|
|
<li><strong>Connections use HTTPS.</strong> Haven creates its own certificate the first time it starts, and you can swap in a real one.</li>
|
|
<li><strong>No telemetry.</strong> The server doesn't send usage data or crash reports anywhere.</li>
|
|
<li><strong>There's no central Haven server and no Haven account.</strong> Nothing to leak.</li>
|
|
</ul>
|
|
</div>
|
|
<figure class="fig">
|
|
<img class="zoomable" src="img/haven-phone-welcome.jpg" width="480" height="1040" alt="Haven's welcome screen in a phone browser" loading="lazy">
|
|
<figcaption>Joining from a phone browser. No app needed.</figcaption>
|
|
</figure>
|
|
</div>
|
|
<div class="prose">
|
|
<h3 style="margin-top: 36px;">What the server connects to</h3>
|
|
<p class="muted small" style="margin-top: 6px;">Every outside service a Haven server contacts, and when.</p>
|
|
</div>
|
|
<div class="tablewrap">
|
|
<table>
|
|
<thead>
|
|
<tr><th>Who</th><th>When</th><th>Why</th><th>Off switch</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td>api.github.com</td><td>When an admin clicks Check for updates</td><td>To see the latest version</td><td>Don't click it</td></tr>
|
|
<tr><td>Google Firebase, through the Haven push relay</td><td>When someone uses the Android app with push notifications on</td><td>To deliver the notification. The message title and text pass through it</td><td>Settings, Security</td></tr>
|
|
<tr><td>tenor.googleapis.com, api.giphy.com, api.klipy.com</td><td>When someone searches for a GIF</td><td>The search goes through your server, not the person's browser</td><td>Only when used</td></tr>
|
|
<tr><td>The site behind a posted link</td><td>When a message contains a link</td><td>Your server fetches the preview and the image, so your friends' browsers never touch that site</td><td>Per link</td></tr>
|
|
<tr><td>discord.com</td><td>Only if you set up Import or Ferry</td><td>To copy or bridge your Discord server</td><td>Off until you set it up</td></tr>
|
|
<tr><td>Spotify, Last.fm, Steam</td><td>Only for people who link an account</td><td>To show what they're playing or listening to</td><td>Per person</td></tr>
|
|
<tr><td>unicode.org</td><td>Monthly, only if you turn on emoji updates</td><td>New emoji</td><td>Off by default</td></tr>
|
|
<tr><td>ipify.org, portchecker.io</td><td>When you run the connectivity test or use dynamic DNS</td><td>To find your public address</td><td>Only when used</td></tr>
|
|
<tr><td>registry.npmjs.org</td><td>When an admin installs the voice relay in Large Server Setup</td><td>To download the relay</td><td>Only when used</td></tr>
|
|
<tr><td>stun.l.google.com, stun.cloudflare.com</td><td>When the voice relay starts without a public address filled in, or an admin clicks Detect</td><td>To find the address callers reach the relay on</td><td>Fill in the address yourself</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="numbers">
|
|
<div class="wrap">
|
|
<div class="section-head">
|
|
<h2>By the numbers</h2>
|
|
<p id="stats-caption">As of 29 September 2026.</p>
|
|
</div>
|
|
<dl class="facts">
|
|
<div><dt>Started</dt><dd data-stat="started">Feb 2026</dd></div>
|
|
<div><dt>Releases</dt><dd class="num" data-stat="releases">166</dd></div>
|
|
<div><dt>Commits</dt><dd class="num" data-stat="commits">1,479</dd></div>
|
|
<div><dt>Contributors</dt><dd class="num" data-stat="contributors">21</dd></div>
|
|
<div><dt>GitHub stars</dt><dd class="num" data-stat="stars">557</dd></div>
|
|
<div><dt>License</dt><dd>AGPL-3.0</dd></div>
|
|
</dl>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="download">
|
|
<div class="wrap">
|
|
<div class="section-head">
|
|
<h2>Download</h2>
|
|
<p>Most people only need the app. The server download is just for whoever hosts one.</p>
|
|
</div>
|
|
<div class="dl">
|
|
<div class="dl-row">
|
|
<div>
|
|
<h3>Haven Desktop <span class="ver">1.6.3</span></h3>
|
|
<p>The app for joining a server. Adds per-app audio sharing, mid-call device switching and a system tray. It can run a server too.</p>
|
|
<p class="muted small">The installer isn't code-signed yet, so Windows may say "Windows protected your PC" the first time. Click <b>More info</b>, then <b>Run anyway</b>.</p>
|
|
</div>
|
|
<div class="dl-links">
|
|
<a class="btn btn-primary" href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.6.3/Haven-Setup-1.6.3.exe">Windows installer</a>
|
|
<a class="btn btn-secondary" href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.6.3/Haven-1.6.3.AppImage">Linux AppImage</a>
|
|
<a class="btn btn-secondary" href="https://github.com/ancsemi/Haven-Desktop/releases/download/v1.6.3/haven-desktop_1.6.3_amd64.deb">Linux .deb</a>
|
|
</div>
|
|
</div>
|
|
<div class="dl-row">
|
|
<div>
|
|
<h3>Haven for Android</h3>
|
|
<p>A native app with chat, voice and push notifications.</p>
|
|
</div>
|
|
<div class="dl-links">
|
|
<a class="btn btn-secondary" href="https://play.google.com/store/apps/details?id=com.havenapp.mobile">Get it on Google Play</a>
|
|
</div>
|
|
</div>
|
|
<div class="dl-row">
|
|
<div>
|
|
<h3>Haven for iOS</h3>
|
|
<p>A native app, in testing through TestFlight. Until it is on the App Store, open your server in Safari and Add to Home Screen.</p>
|
|
</div>
|
|
<div class="dl-links">
|
|
<a class="btn btn-secondary" href="https://testflight.apple.com/join/ADeG5zRq">Join the TestFlight</a>
|
|
</div>
|
|
</div>
|
|
<div class="dl-row">
|
|
<div>
|
|
<h3>Haven Server <span class="ver">4.23.0</span></h3>
|
|
<p>Only for hosting your own server. Windows, macOS and Linux. Needs Node.js. About 5 MB.</p>
|
|
<p class="muted small">On Windows, the same "Windows protected your PC" message can show up for Start Haven.bat. More info, then Run anyway.</p>
|
|
</div>
|
|
<div class="dl-links">
|
|
<a class="btn btn-secondary" href="https://github.com/ancsemi/Haven/archive/refs/tags/v4.23.0.zip">Download .zip</a>
|
|
<a class="btn btn-secondary" href="https://github.com/ancsemi/Haven/blob/main/GUIDE.md">Setup guide</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="try">Want to try it first? Drop in to <a href="https://haven.moviethingy.xyz/?invite=da0b9be7">the community server</a> to say hi, or just to explore Haven in your browser. Throwaway accounts welcome! The channel code is <code>da0b9be7</code> if you need it.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="support">
|
|
<div class="wrap">
|
|
<div class="prose">
|
|
<div class="section-head">
|
|
<h2>Support</h2>
|
|
</div>
|
|
<p>Haven is free and always will be. Donations are always appreciated, but never needed. If you'd like to chip in, you can <a href="https://ko-fi.com/ancsemi">buy me a coffee on Ko-fi</a>.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="screenshots">
|
|
<div class="wrap">
|
|
<div class="section-head">
|
|
<h2>Gallery</h2>
|
|
</div>
|
|
<div class="gallery">
|
|
<figure><img class="zoomable" src="img/haven-screen-share.jpg" width="1200" height="591" alt="Screen sharing with voice chat and more than one stream" loading="lazy"></figure>
|
|
<figure><img class="zoomable" src="img/haven-theme-chat.jpg" width="1200" height="593" alt="The login screen with the theme picker" loading="lazy"></figure>
|
|
<figure><img class="zoomable" src="img/haven-theme-crt.jpg" width="1916" height="1006" alt="A DM window and profile card in the CRT theme" loading="lazy"></figure>
|
|
<figure><img class="zoomable" src="img/haven-theme-braid.jpg" width="1918" height="1009" alt="A chat channel in the Braid theme" loading="lazy"></figure>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<div class="lightbox" id="lightbox" role="dialog" aria-modal="true" aria-label="Enlarged screenshot" tabindex="-1" hidden>
|
|
<button class="lb-nav lb-prev" type="button" aria-label="Previous screenshot">‹</button>
|
|
<img alt="">
|
|
<button class="lb-nav lb-next" type="button" aria-label="Next screenshot">›</button>
|
|
</div>
|
|
|
|
<script>
|
|
// Screenshots open full size. The arrow keys, or the arrows on screen, step
|
|
// through every screenshot on the page; click anywhere else or press Escape
|
|
// to close.
|
|
(() => {
|
|
const box = document.getElementById('lightbox');
|
|
if (!box) return;
|
|
const big = box.querySelector('img');
|
|
const shots = Array.from(document.querySelectorAll('img.zoomable'));
|
|
let current = -1;
|
|
const show = (i) => {
|
|
current = (i + shots.length) % shots.length;
|
|
const img = shots[current];
|
|
big.src = img.currentSrc || img.src; big.alt = img.alt;
|
|
};
|
|
const open = (img) => { show(shots.indexOf(img)); box.hidden = false; document.body.style.overflow = 'hidden'; box.focus(); };
|
|
const close = () => { box.hidden = true; big.removeAttribute('src'); document.body.style.overflow = ''; };
|
|
shots.forEach((img) => {
|
|
img.tabIndex = 0; img.setAttribute('role', 'button'); img.setAttribute('aria-label', 'Enlarge screenshot');
|
|
img.addEventListener('click', () => open(img));
|
|
img.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); open(img); } });
|
|
});
|
|
box.querySelector('.lb-prev').addEventListener('click', (e) => { e.stopPropagation(); show(current - 1); });
|
|
box.querySelector('.lb-next').addEventListener('click', (e) => { e.stopPropagation(); show(current + 1); });
|
|
box.addEventListener('click', close);
|
|
document.addEventListener('keydown', (e) => {
|
|
if (box.hidden) return;
|
|
if (e.key === 'Escape') close();
|
|
else if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { e.preventDefault(); show(current + 1); }
|
|
else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { e.preventDefault(); show(current - 1); }
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
<script>
|
|
// "Try it now" in the bar: back to the top, then light up the community server button.
|
|
(() => {
|
|
const btn = document.getElementById('try-now');
|
|
const target = document.getElementById('try-server');
|
|
if (!btn || !target) return;
|
|
let timer = null;
|
|
btn.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
window.scrollTo({ top: 0, behavior: matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth' });
|
|
clearTimeout(timer);
|
|
target.classList.remove('pulse');
|
|
void target.offsetWidth;
|
|
target.classList.add('pulse');
|
|
target.focus({ preventScroll: true });
|
|
timer = setTimeout(() => target.classList.remove('pulse'), 3800);
|
|
});
|
|
})();
|
|
|
|
// "By the numbers" refreshes itself from GitHub's public API. If the request
|
|
// fails (offline, rate limited, blocked), the figures baked into the page stay.
|
|
(async () => {
|
|
const api = 'https://api.github.com/repos/ancsemi/Haven';
|
|
const headers = { Accept: 'application/vnd.github+json' };
|
|
const fmt = (n) => Number(n).toLocaleString('en-US');
|
|
const set = (k, v) => { const el = document.querySelector('[data-stat="' + k + '"]'); if (el && v != null && v !== '') el.textContent = v; };
|
|
const apply = (d) => {
|
|
set('stars', fmt(d.stars)); set('releases', fmt(d.releases)); set('commits', fmt(d.commits)); set('contributors', fmt(d.contributors));
|
|
if (d.started) set('started', new Date(d.started).toLocaleDateString('en-US', { month: 'short', year: 'numeric' }));
|
|
const cap = document.getElementById('stats-caption');
|
|
if (cap) cap.textContent = 'Live from GitHub.';
|
|
};
|
|
const cacheKey = 'haven_site_stats_v1';
|
|
try {
|
|
const c = JSON.parse(localStorage.getItem(cacheKey) || 'null');
|
|
if (c && Date.now() - c.t < 3600000) { apply(c.d); return; }
|
|
} catch { /* no cache */ }
|
|
// Counting endpoints: ask for one item and read the last page number.
|
|
const count = async (path) => {
|
|
const r = await fetch(api + '/' + path + '?per_page=1', { headers });
|
|
if (!r.ok) throw new Error(String(r.status));
|
|
const m = (r.headers.get('Link') || '').match(/[?&]page=(\d+)>;\s*rel="last"/);
|
|
return m ? Number(m[1]) : (await r.json()).length;
|
|
};
|
|
try {
|
|
const r = await fetch(api, { headers });
|
|
if (!r.ok) throw new Error(String(r.status));
|
|
const repo = await r.json();
|
|
const [releases, commits, contributors] = await Promise.all([count('releases'), count('commits'), count('contributors')]);
|
|
const d = { stars: repo.stargazers_count, releases, commits, contributors, started: repo.created_at };
|
|
try { localStorage.setItem(cacheKey, JSON.stringify({ t: Date.now(), d })); } catch { /* no storage */ }
|
|
apply(d);
|
|
} catch { /* keep the baked-in numbers */ }
|
|
})();
|
|
</script>
|
|
|
|
<footer>
|
|
<div class="wrap">
|
|
<nav aria-label="Project links">
|
|
<a href="https://github.com/ancsemi/Haven">Server on GitHub</a>
|
|
<a href="https://github.com/ancsemi/Haven-Desktop">Desktop on GitHub</a>
|
|
<a href="https://github.com/ancsemi/Haven/blob/main/GUIDE.md">Setup guide</a>
|
|
<a href="https://github.com/ancsemi/Haven/blob/main/CHANGELOG.md">Changelog</a>
|
|
<a href="https://haven-app.com/support.html">Help and support</a>
|
|
<a href="https://github.com/ancsemi/Haven/issues">Report a bug</a>
|
|
<a href="https://github.com/ancsemi/Haven/blob/main/LICENSE">AGPL-3.0</a>
|
|
</nav>
|
|
<span>Your server, your rules.</span>
|
|
</div>
|
|
</footer>
|
|
</body>
|
|
</html>
|