aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-25 16:31:52 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-25 16:31:52 +0200
commit8bb94a39609f57be2c486f579849eebb04606808 (patch)
tree46d5553f514619d8f1f33af6eb32a6de314a03d0 /packages
parent10c58792c7591accdb84c92201f77843ec39ee7b (diff)
downloadmeshbay-8bb94a39609f57be2c486f579849eebb04606808.tar.gz
feat(hub): friendlier welcome page, link previews, robots.txt and favicon
Welcome page: privacy said once, a three-step "how it works", a documentation box, download (green) and legal links under the sign-in form, on a dark gradient backdrop covering the whole page. Link previews: Open Graph tags in the app shell, rendered for identity.id, with the square icon as image. robots.txt, favicon and touch icon served at the origin root. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/webapp.py43
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/app.py4
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/apple-touch-icon.pngbin0 -> 34590 bytes
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/auth-page.js86
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/favicon.icobin0 -> 7291 bytes
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/de.js30
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/en.js30
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/es.js30
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js30
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/it.js30
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js30
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js30
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js30
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js30
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js30
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/og-image.jpgbin0 -> 33661 bytes
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/robots.txt9
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/style.css205
-rw-r--r--packages/meshbay-hub/tests/test_site_basics.py81
-rw-r--r--packages/meshbay-hub/tests/test_welcome_layout_measured.py48
20 files changed, 599 insertions, 177 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/webapp.py b/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
index 626c639..c9cb8d5 100644
--- a/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
+++ b/packages/meshbay-hub/src/meshbay_hub/api/webapp.py
@@ -12,6 +12,7 @@ The root route (/) returns the SPA HTML shell.
"""
import hashlib
+import html
from pathlib import Path
from fastapi import APIRouter
@@ -142,17 +143,49 @@ CSP = "; ".join([
@router.get("/app", response_class=HTMLResponse)
async def app_root():
- return HTMLResponse(_HTML, headers=_NO_STORE)
+ return HTMLResponse(_shell, headers=_NO_STORE)
@router.get("/app/{path:path}", response_class=HTMLResponse)
async def app_catchall(path: str):
- return HTMLResponse(_HTML, headers=_NO_STORE)
+ return HTMLResponse(_shell, headers=_NO_STORE)
@router.get("/", response_class=HTMLResponse)
async def index():
- return HTMLResponse(_HTML, headers=_NO_STORE)
+ return HTMLResponse(_shell, headers=_NO_STORE)
+
+
+# What a messenger draws for a link to this hub: an invitation, or the address
+# itself. Signal and WhatsApp read these tags and nothing else, and resolve only
+# an absolute og:image, so the shell is rendered for the hub's public name —
+# `identity.id`, the name mail links already use. Short on purpose: a preview
+# shows a line or two of the description and cuts the rest. The image is the
+# square icon, which is the shape a thumbnail is cut to anyway.
+PREVIEW_DESCRIPTION = "Your files stay at home. Reach them from anywhere."
+
+
+def _preview_tags(hub_id: str) -> str:
+ origin = html.escape(f"https://{hub_id}", quote=True)
+ return f"""\
+ <meta name="description" content="{PREVIEW_DESCRIPTION}">
+ <meta property="og:type" content="website">
+ <meta property="og:site_name" content="MeshBay">
+ <meta property="og:title" content="MeshBay">
+ <meta property="og:description" content="{PREVIEW_DESCRIPTION}">
+ <meta property="og:url" content="{origin}/">
+ <meta property="og:image" content="{origin}/og-image.jpg">
+ <meta property="og:image:type" content="image/jpeg">
+ <meta property="og:image:width" content="600">
+ <meta property="og:image:height" content="600">
+ <meta property="og:image:alt" content="The MeshBay logo">
+"""
+
+
+def configure(hub_id: str) -> None:
+ """Render the shell for this hub's public name (`identity.id`)."""
+ global _shell
+ _shell = _HTML.replace("{preview}", _preview_tags(hub_id))
_HTML = """\
@@ -171,6 +204,8 @@ _HTML = """\
<meta name="viewport"
content="width=device-width, initial-scale=1, interactive-widget=resizes-content">
<title>MeshBay</title>
+{preview} <link rel="icon" href="/a/{v}/favicon.ico" sizes="16x16 32x32 48x48">
+ <link rel="apple-touch-icon" href="/a/{v}/apple-touch-icon.png">
<link rel="stylesheet" href="/a/{v}/style.css">
</head>
<body>
@@ -205,3 +240,5 @@ _HTML = """\
</body>
</html>
""".replace("{v}", ASSET_V)
+
+_shell = _HTML.replace("{preview}", _preview_tags("meshbay.org"))
diff --git a/packages/meshbay-hub/src/meshbay_hub/app.py b/packages/meshbay-hub/src/meshbay_hub/app.py
index b1d9626..7ccf598 100644
--- a/packages/meshbay-hub/src/meshbay_hub/app.py
+++ b/packages/meshbay-hub/src/meshbay_hub/app.py
@@ -37,6 +37,7 @@ from meshbay_hub.api.signaling import router as signaling_router
from meshbay_hub.api.users import router as users_router
from meshbay_hub.api.users import set_config as users_set_config
from meshbay_hub.api.webapp import ASSET_V, CSP, STATIC_DIR
+from meshbay_hub.api.webapp import configure as webapp_configure
from meshbay_hub.api.webapp import router as webapp_router
from meshbay_hub.auth import generate_hub_keypair, load_hub_keypair
from meshbay_hub.config import HubConfig
@@ -97,6 +98,9 @@ def create_app(cfg: HubConfig | None = None) -> FastAPI:
from meshbay_hub.config import load_config
if cfg is None:
cfg = load_config()
+ # Here rather than in the lifespan: the shell is served by routes that exist
+ # as soon as the app does, and a test client need not start it to read them.
+ webapp_configure(cfg.identity.id)
@asynccontextmanager
async def lifespan(app: FastAPI):
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/apple-touch-icon.png b/packages/meshbay-hub/src/meshbay_hub/static/apple-touch-icon.png
new file mode 100644
index 0000000..aacd0fa
--- /dev/null
+++ b/packages/meshbay-hub/src/meshbay_hub/static/apple-touch-icon.png
Binary files differ
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js b/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js
index 70c48f2..2164eae 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js
@@ -207,7 +207,9 @@ export function LoginPage({ onLogin }) {
return html`
<div class="page-center">
+ <div class="welcome-backdrop" aria-hidden="true"></div>
<div class="welcome">
+ <div class="welcome-side">
<div class="card login-card">
<h2>${t('login.title')}</h2>
<form onSubmit=${onSubmit}>
@@ -234,6 +236,8 @@ export function LoginPage({ onLogin }) {
<a href="#/reset">${t('login.forgot')}</a>
</div>
</div>
+ ${!platform.isNative && html`<${WelcomeLinks} />`}
+ </div>
${!platform.isNative && html`<${WelcomePitch} />`}
</div>
</div>
@@ -244,20 +248,49 @@ export function LoginPage({ onLogin }) {
// application the reader has already downloaded it, and the links point at the
// project's own site rather than at whichever hub the application is set to.
//
-// Every sentence here is held to MESHBAY_DESIGN.md §2.3. "Data never transits
-// the hub" is a claim the design makes; "the hub cannot read anything" is one it
-// forbids (T3), which is why the list below says what never *reaches* the hub
-// and stops there. "End-to-end" means device to node, as §2.3 defines it.
+// Written for somebody who is not in IT: what it does for them first, how it
+// works in three steps, and privacy said once, plainly. Every sentence is held
+// to MESHBAY_DESIGN.md §2.3 — "your content never passes through meshbay.org"
+// is a claim the design makes; "meshbay.org cannot read anything" is one it
+// forbids (T3). "Encrypted all the way" means device to node, as §2.3 defines.
const WELCOME_APPS = [
['chat', 'welcome.app_chat'], ['image', 'welcome.app_photos'],
['video', 'welcome.app_media'], ['play', 'welcome.app_video'],
['music', 'welcome.app_music'],
];
+const WELCOME_STEPS = [
+ ['home', 'welcome.step_home'], ['globe', 'welcome.step_anywhere'],
+ ['user', 'welcome.step_share'],
+];
const WELCOME_USES = [
['chat', 'welcome.use_chat'], ['image', 'welcome.use_photos'],
['cast', 'welcome.use_media'], ['pencil', 'welcome.use_apps'],
];
-const WELCOME_NEVER = ['welcome.never_transit', 'welcome.never_stored', 'welcome.never_e2e'];
+const WELCOME_BADGES = ['welcome.badge_free', 'welcome.badge_open',
+ 'welcome.badge_no_ads', 'welcome.badge_no_tracking'];
+
+const REPO = 'https://git.meshbay.org/meshbay.git/about/';
+const WELCOME_DOCS = [
+ ['folder', 'welcome.docs_source', [['welcome.docs_repo', REPO]]],
+ ['user', 'welcome.docs_user', [
+ ['welcome.docs_quickstart', `${REPO}docs/QUICKSTART.md`],
+ ['welcome.docs_userguide', `${REPO}docs/USERGUIDE.md`]]],
+ ['gear', 'welcome.docs_devel', [
+ ['welcome.docs_design', `${REPO}docs/MESHBAY_DESIGN.md`],
+ ['welcome.docs_protocol', `${REPO}docs/MESHBAY_NODE_PROTOCOL.md`]]],
+];
+
+// Under the sign-in form rather than at the foot of the text: on a desktop the
+// text column runs far below the form, and these were the last thing on it.
+function WelcomeLinks() {
+ return html`
+ <div class="welcome-links">
+ <a class="welcome-cta" href="https://meshbay.org/downloads/">
+ <${Icon} name="download" />${t('welcome.download')}</a>
+ <a class="welcome-legal" href="https://meshbay.org/legal/">${t('welcome.legal')}</a>
+ </div>
+ `;
+}
function WelcomePitch() {
return html`
@@ -268,8 +301,13 @@ function WelcomePitch() {
${WELCOME_APPS.map(([icon, key]) => html`
<li key=${key}><${Icon} name=${icon} />${t(key)}</li>`)}
</ul>
- <p>${t('welcome.groups')}</p>
- <p class="welcome-e2e"><${Icon} name="lock" /><span>${t('welcome.e2e')}</span></p>
+
+ <h2 class="welcome-h">${t('welcome.how_title')}</h2>
+ <ol class="welcome-steps">
+ ${WELCOME_STEPS.map(([icon, key]) => html`
+ <li key=${key}><span class="welcome-step-icon"><${Icon} name=${icon} /></span>
+ <span>${t(key)}</span></li>`)}
+ </ol>
<h2 class="welcome-h">${t('welcome.uses_title')}</h2>
<ul class="welcome-uses">
@@ -278,22 +316,30 @@ function WelcomePitch() {
<span>${t(key)}</span></li>`)}
</ul>
- <div class="welcome-hub">
- <h2 class="welcome-h">${t('welcome.hub_title')}</h2>
- <p>${t('welcome.hub_body')}</p>
- <p class="welcome-hub-never">${t('welcome.hub_never')}</p>
- <ul class="welcome-never">
- ${WELCOME_NEVER.map(key => html`
- <li key=${key}><${Icon} name="check" /><span>${t(key)}</span></li>`)}
- </ul>
- <p class="welcome-hub-free">${t('welcome.hub_free')}</p>
+ <div class="welcome-private">
+ <span class="welcome-private-icon"><${Icon} name="shield" /></span>
+ <div>
+ <h2 class="welcome-private-title">${t('welcome.private_title')}</h2>
+ <p>${t('welcome.private_body')}</p>
+ <ul class="welcome-badges">
+ ${WELCOME_BADGES.map(key => html`
+ <li key=${key}><${Icon} name="check" />${t(key)}</li>`)}
+ </ul>
+ </div>
</div>
- <div class="welcome-links">
- <a class="welcome-cta" href="https://meshbay.org/downloads/">
- <${Icon} name="download" />${t('welcome.download')}</a>
- <a class="welcome-legal" href="https://meshbay.org/legal/">${t('welcome.legal')}</a>
+ <div class="welcome-docs">
+ <h2 class="welcome-h">${t('welcome.docs_title')}</h2>
+ <ul>
+ ${WELCOME_DOCS.map(([icon, label, links]) => html`
+ <li key=${label}><${Icon} name=${icon} />
+ <span class="welcome-docs-label">${t(label)}</span>
+ <span class="welcome-docs-links">${links.map(([key, href], i) => html`
+ ${i > 0 && ' · '}<a key=${key} href=${href} target="_blank"
+ rel="noopener noreferrer">${t(key)}</a>`)}</span></li>`)}
+ </ul>
</div>
+
</section>
`;
}
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/favicon.ico b/packages/meshbay-hub/src/meshbay_hub/static/favicon.ico
new file mode 100644
index 0000000..c24a117
--- /dev/null
+++ b/packages/meshbay-hub/src/meshbay_hub/static/favicon.ico
Binary files differ
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
index 7008f79..215f596 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
@@ -93,26 +93,36 @@ export default {
'login.locked': "Zu viele falsche Passphrasen für dieses Konto. Versuchen Sie es in {minutes} Min. erneut oder setzen Sie Ihre Passphrase zurück.",
// Sign-in page: what MeshBay is (browser only)
'welcome.title': 'Das Internet, wie es gedacht war.',
- 'welcome.lead': 'MeshBay ist Open-Source-Software, mit der Sie aus der Ferne auf Ihre persönlichen Dateien zugreifen und Anwendungen direkt auf dem Speicher Ihres eigenen Computers betreiben:',
+ 'welcome.lead': 'Ihre Fotos, Musik, Videos und Unterhaltungen bleiben zu Hause, auf Ihrem eigenen Computer. Mit MeshBay genießen Sie sie von überall und teilen sie mit den Menschen Ihrer Wahl.',
'welcome.app_chat': 'Chat',
'welcome.app_photos': 'Fotos',
'welcome.app_media': 'Mediacenter',
'welcome.app_video': 'Videoplayer',
'welcome.app_music': 'Musikplayer',
- 'welcome.groups': 'Erstellen Sie Gruppen, um Ihren Liebsten Zugriff auf einige dieser Anwendungen zu geben.',
- 'welcome.e2e': 'Ihre Daten sind Ende-zu-Ende-verschlüsselt und gehen Peer-to-Peer direkt von jedem Gerät zu dem Computer, auf dem sie liegen (Ihrem Node). Sie laufen niemals über den Hub meshbay.org.',
+ 'welcome.how_title': 'So funktioniert es',
+ 'welcome.step_home': 'Installieren Sie MeshBay auf einem Computer zu Hause. Ihre Dateien bleiben, wo sie sind.',
+ 'welcome.step_anywhere': 'Melden Sie sich von Ihrem Handy, Laptop oder jedem Browser aus an, wo immer Sie sind.',
+ 'welcome.step_share': 'Erstellen Sie Gruppen und laden Sie Familie und Freunde ein.',
'welcome.uses_title': 'Was Sie damit tun können',
'welcome.use_chat': 'Sofortnachrichten innerhalb einer Gruppe',
'welcome.use_photos': 'Fotoalben mit Familie und Freunden teilen',
'welcome.use_media': 'Von überall auf Ihr Mediacenter zu Hause zugreifen, für Sie und Ihren Haushalt (mit Streaming und Chromecast über die Desktop-App)',
'welcome.use_apps': 'Eigene Anwendungen auf dem Node entwickeln, den Sie betreiben',
- 'welcome.hub_title': 'Was meshbay.org tut',
- 'welcome.hub_body': 'meshbay.org ist lediglich ein Signalisierungsdienst: Er meldet Sie an und verbindet die Beteiligten miteinander (Plug-and-Play, über Heimnetzwerke hinweg).',
- 'welcome.hub_never': 'Ihre Inhalte erreichen ihn nie:',
- 'welcome.never_transit': 'Nichts läuft über ihn: keine Inhalte, keine Indexierung',
- 'welcome.never_stored': 'Keine Ihrer Dateien oder Nachrichten wird dort gespeichert',
- 'welcome.never_e2e': 'Durchgehende Verschlüsselung, von jedem Gerät bis zu Ihrem Node',
- 'welcome.hub_free': 'Der Dienst ist kostenlos. Es gibt bewusst kein Geschäftsmodell, kein Tracking und keine Werbung. Viel Spaß!',
+ 'welcome.private_title': 'Privat von Grund auf',
+ 'welcome.private_body': 'Ihre Dateien und Nachrichten reisen verschlüsselt, direkt von Ihren Geräten zu Ihrem eigenen Computer. meshbay.org hilft Ihren Geräten nur, sich zu finden: Ihre Inhalte laufen nie darüber.',
+ 'welcome.badge_free': 'Kostenlos',
+ 'welcome.badge_open': 'Open Source',
+ 'welcome.badge_no_ads': 'Keine Werbung',
+ 'welcome.badge_no_tracking': 'Kein Tracking',
+ 'welcome.docs_title': 'Dokumentation',
+ 'welcome.docs_source': 'Quellcode',
+ 'welcome.docs_repo': 'git.meshbay.org',
+ 'welcome.docs_user': 'Benutzerdoku',
+ 'welcome.docs_quickstart': 'Schnellstart',
+ 'welcome.docs_userguide': 'Benutzerhandbuch',
+ 'welcome.docs_devel': 'Entwicklerdoku',
+ 'welcome.docs_design': 'Architektur',
+ 'welcome.docs_protocol': 'Protokoll',
'welcome.download': 'Herunterladen (Beta)',
'welcome.legal': 'Rechtliche Hinweise',
'register.err_mismatch': 'Die Passwörter stimmen nicht überein',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
index 1ea6eda..4c4cf38 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
@@ -96,26 +96,36 @@ export default {
'login.locked': "Too many wrong passphrases for this account. Try again in {minutes} min, or reset your passphrase.",
// Sign-in page: what MeshBay is (browser only)
'welcome.title': 'The Internet as it was meant to be.',
- 'welcome.lead': 'MeshBay is open-source software that gives you remote access to your personal files, and runs applications on top of the storage on your own computer:',
+ 'welcome.lead': 'Your photos, music, videos and conversations stay at home, on your own computer. MeshBay lets you enjoy them from anywhere, and share them with the people you choose.',
'welcome.app_chat': 'Chat',
'welcome.app_photos': 'Photos',
'welcome.app_media': 'Media center',
'welcome.app_video': 'Video player',
'welcome.app_music': 'Music player',
- 'welcome.groups': 'Create groups to give the people close to you access to some of these applications.',
- 'welcome.e2e': 'Your data is end-to-end encrypted and travels peer to peer, from each device to the computer that hosts it (your node). It never passes through the meshbay.org hub.',
+ 'welcome.how_title': 'How it works',
+ 'welcome.step_home': 'Install MeshBay on a computer at home. Your files stay right where they are.',
+ 'welcome.step_anywhere': 'Sign in from your phone, your laptop or any browser, wherever you are.',
+ 'welcome.step_share': 'Create groups and invite your family and friends to join them.',
'welcome.uses_title': 'What you can do with it',
'welcome.use_chat': 'Instant messaging within a group',
'welcome.use_photos': 'Share photo albums with your family and friends',
'welcome.use_media': 'Reach your home media center from anywhere, for the whole household (streaming, plus Chromecast from the desktop app)',
'welcome.use_apps': 'Build your own applications on the node you host',
- 'welcome.hub_title': 'What meshbay.org does',
- 'welcome.hub_body': 'meshbay.org is only a signaling service: it signs you in and connects the parties to one another, plug-and-play, across home networks.',
- 'welcome.hub_never': 'Your content never reaches it:',
- 'welcome.never_transit': 'Nothing passes through it: no content, no indexing',
- 'welcome.never_stored': 'None of your files or messages are stored on it',
- 'welcome.never_e2e': 'Encryption runs end to end, from each device to your node',
- 'welcome.hub_free': 'The service is free. There is no business model by design, no tracking and no ads. Enjoy!',
+ 'welcome.private_title': 'Private by design',
+ 'welcome.private_body': 'Your files and messages travel encrypted, straight from your devices to your own computer. meshbay.org simply helps your devices find each other: your content never passes through it.',
+ 'welcome.badge_free': 'Free',
+ 'welcome.badge_open': 'Open source',
+ 'welcome.badge_no_ads': 'No ads',
+ 'welcome.badge_no_tracking': 'No tracking',
+ 'welcome.docs_title': 'Documentation',
+ 'welcome.docs_source': 'Source code',
+ 'welcome.docs_repo': 'git.meshbay.org',
+ 'welcome.docs_user': 'User docs',
+ 'welcome.docs_quickstart': 'Quick start',
+ 'welcome.docs_userguide': 'User guide',
+ 'welcome.docs_devel': 'Developer docs',
+ 'welcome.docs_design': 'Design',
+ 'welcome.docs_protocol': 'Protocol',
'welcome.download': 'Download (beta)',
'welcome.legal': 'Legal information',
'register.err_mismatch': 'Passwords do not match',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
index df65367..9f5158f 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
@@ -92,26 +92,36 @@ export default {
'login.locked': "Demasiadas frases de contraseña incorrectas para esta cuenta. Vuelva a intentarlo en {minutes} min o restablezca su frase de contraseña.",
// Sign-in page: what MeshBay is (browser only)
'welcome.title': 'Internet como debió ser.',
- 'welcome.lead': 'MeshBay es un software de código abierto que le da acceso remoto a sus archivos personales y ejecuta aplicaciones sobre el almacenamiento de su propio ordenador:',
+ 'welcome.lead': 'Sus fotos, su música, sus vídeos y sus conversaciones se quedan en casa, en su propio ordenador. MeshBay le permite disfrutarlos desde cualquier lugar y compartirlos con las personas que usted elija.',
'welcome.app_chat': 'Chat',
'welcome.app_photos': 'Fotos',
'welcome.app_media': 'Centro multimedia',
'welcome.app_video': 'Reproductor de vídeo',
'welcome.app_music': 'Reproductor de música',
- 'welcome.groups': 'Cree grupos para dar a sus allegados acceso a algunas de estas aplicaciones.',
- 'welcome.e2e': 'Sus datos están cifrados de extremo a extremo y viajan entre pares, directamente de cada dispositivo al ordenador que los aloja: su nodo. Nunca pasan por el hub meshbay.org.',
+ 'welcome.how_title': 'Cómo funciona',
+ 'welcome.step_home': 'Instale MeshBay en un ordenador de casa. Sus archivos se quedan donde están.',
+ 'welcome.step_anywhere': 'Inicie sesión desde su móvil, su portátil o cualquier navegador, esté donde esté.',
+ 'welcome.step_share': 'Cree grupos e invite a su familia y a sus amigos a unirse.',
'welcome.uses_title': 'Qué puede hacer con él',
'welcome.use_chat': 'Mensajería instantánea dentro de un grupo',
'welcome.use_photos': 'Compartir álbumes de fotos con su familia y amigos',
'welcome.use_media': 'Acceder desde cualquier lugar a su centro multimedia, para usted y su hogar, con streaming y Chromecast desde la aplicación de escritorio',
'welcome.use_apps': 'Crear sus propias aplicaciones en el nodo que aloja',
- 'welcome.hub_title': 'Qué hace meshbay.org',
- 'welcome.hub_body': 'meshbay.org es solo un servicio de señalización: le autentica y conecta a las partes entre sí, sin configuración, a través de redes domésticas.',
- 'welcome.hub_never': 'Su contenido nunca llega a él:',
- 'welcome.never_transit': 'Nada pasa por él: ni contenido ni indexación',
- 'welcome.never_stored': 'Ninguno de sus archivos o mensajes se almacena en él',
- 'welcome.never_e2e': 'Cifrado de extremo a extremo, de cada dispositivo a su nodo',
- 'welcome.hub_free': 'El servicio es gratuito. Deliberadamente no hay modelo de negocio, ni rastreo, ni publicidad. ¡Disfrútelo!',
+ 'welcome.private_title': 'Privado desde el diseño',
+ 'welcome.private_body': 'Sus archivos y mensajes viajan cifrados, directamente de sus dispositivos a su propio ordenador. meshbay.org solo ayuda a que sus dispositivos se encuentren: su contenido nunca pasa por él.',
+ 'welcome.badge_free': 'Gratis',
+ 'welcome.badge_open': 'Código abierto',
+ 'welcome.badge_no_ads': 'Sin publicidad',
+ 'welcome.badge_no_tracking': 'Sin rastreo',
+ 'welcome.docs_title': 'Documentación',
+ 'welcome.docs_source': 'Código fuente',
+ 'welcome.docs_repo': 'git.meshbay.org',
+ 'welcome.docs_user': 'Docs de usuario',
+ 'welcome.docs_quickstart': 'Inicio rápido',
+ 'welcome.docs_userguide': 'Guía de usuario',
+ 'welcome.docs_devel': 'Docs de desarrollo',
+ 'welcome.docs_design': 'Diseño',
+ 'welcome.docs_protocol': 'Protocolo',
'welcome.download': 'Descargar (beta)',
'welcome.legal': 'Información legal',
'register.err_mismatch': 'Las contraseñas no coinciden',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
index e5d542a..c072d5b 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
@@ -92,26 +92,36 @@ export default {
'login.locked': "Trop de phrases secrètes erronées pour ce compte. Réessayez dans {minutes} min, ou réinitialisez votre phrase secrète.",
// Sign-in page: what MeshBay is (browser only)
'welcome.title': 'L’Internet tel qu’il aurait dû être.',
- 'welcome.lead': 'MeshBay est un logiciel open source qui vous donne accès à distance à vos fichiers personnels et fait tourner des applications sur le stockage de votre ordinateur :',
+ 'welcome.lead': 'Vos photos, votre musique, vos vidéos et vos conversations restent chez vous, sur votre propre ordinateur. MeshBay vous permet d’en profiter de partout, et de les partager avec les personnes de votre choix.',
'welcome.app_chat': 'Messagerie',
'welcome.app_photos': 'Photos',
'welcome.app_media': 'Médiathèque',
'welcome.app_video': 'Lecteur vidéo',
'welcome.app_music': 'Lecteur de musique',
- 'welcome.groups': 'Créez des groupes pour donner accès à certaines de ces applications à vos proches.',
- 'welcome.e2e': 'Vos données sont chiffrées de bout en bout et circulent en pair à pair, de chaque appareil jusqu’à l’ordinateur qui les héberge (votre nœud). Elles ne passent jamais par le hub meshbay.org.',
+ 'welcome.how_title': 'Comment ça marche',
+ 'welcome.step_home': 'Installez MeshBay sur un ordinateur à la maison. Vos fichiers restent là où ils sont.',
+ 'welcome.step_anywhere': 'Connectez-vous depuis votre téléphone, votre portable ou n’importe quel navigateur, où que vous soyez.',
+ 'welcome.step_share': 'Créez des groupes et invitez-y votre famille et vos amis.',
'welcome.uses_title': 'Ce que vous pouvez en faire',
'welcome.use_chat': 'Messagerie instantanée au sein d’un groupe',
'welcome.use_photos': 'Partager des albums photo avec votre famille et vos amis',
'welcome.use_media': 'Accéder à distance à votre médiathèque personnelle, pour vous et votre foyer (en streaming, et avec Chromecast depuis l’application de bureau)',
'welcome.use_apps': 'Créer vos propres applications sur le nœud que vous hébergez',
- 'welcome.hub_title': 'Le rôle de meshbay.org',
- 'welcome.hub_body': 'meshbay.org est un simple service de signalisation : il vous authentifie et met les participants en relation, sans configuration, à travers les réseaux domestiques.',
- 'welcome.hub_never': 'Vos contenus ne l’atteignent jamais :',
- 'welcome.never_transit': 'Rien ne transite par lui : aucun contenu, aucune indexation',
- 'welcome.never_stored': 'Aucun de vos fichiers ou messages n’y est stocké',
- 'welcome.never_e2e': 'Chiffrement de bout en bout, de chaque appareil jusqu’à votre nœud',
- 'welcome.hub_free': 'Le service est gratuit : aucun modèle économique (c’est voulu), aucun traçage, aucune publicité. Profitez-en !',
+ 'welcome.private_title': 'Privé par conception',
+ 'welcome.private_body': 'Vos fichiers et vos messages circulent chiffrés, directement de vos appareils jusqu’à votre propre ordinateur. meshbay.org aide simplement vos appareils à se trouver : vos contenus ne passent jamais par lui.',
+ 'welcome.badge_free': 'Gratuit',
+ 'welcome.badge_open': 'Open source',
+ 'welcome.badge_no_ads': 'Sans publicité',
+ 'welcome.badge_no_tracking': 'Sans traçage',
+ 'welcome.docs_title': 'Documentation',
+ 'welcome.docs_source': 'Code source',
+ 'welcome.docs_repo': 'git.meshbay.org',
+ 'welcome.docs_user': 'Docs utilisateur',
+ 'welcome.docs_quickstart': 'Démarrage rapide',
+ 'welcome.docs_userguide': 'Guide utilisateur',
+ 'welcome.docs_devel': 'Docs développeur',
+ 'welcome.docs_design': 'Conception',
+ 'welcome.docs_protocol': 'Protocole',
'welcome.download': 'Télécharger (bêta)',
'welcome.legal': 'Informations légales',
'register.err_mismatch': 'Les mots de passe ne correspondent pas',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
index e376550..28c6a3d 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
@@ -93,26 +93,36 @@ export default {
'login.locked': "Troppe passphrase errate per questo account. Riprovi tra {minutes} min oppure reimposti la passphrase.",
// Sign-in page: what MeshBay is (browser only)
'welcome.title': 'Internet come doveva essere.',
- 'welcome.lead': 'MeshBay è un software open source che le permette di accedere da remoto ai suoi file personali e di usare applicazioni basate sullo spazio di archiviazione del suo computer:',
+ 'welcome.lead': 'Le sue foto, la sua musica, i suoi video e le sue conversazioni restano a casa, sul suo computer. MeshBay le permette di goderseli ovunque e di condividerli con le persone che sceglie.',
'welcome.app_chat': 'Chat',
'welcome.app_photos': 'Foto',
'welcome.app_media': 'Media center',
'welcome.app_video': 'Lettore video',
'welcome.app_music': 'Lettore musicale',
- 'welcome.groups': 'Crei gruppi per dare alle persone a lei care l’accesso ad alcune di queste applicazioni.',
- 'welcome.e2e': 'I suoi dati sono cifrati end-to-end e viaggiano peer-to-peer, direttamente da ogni dispositivo al computer che li ospita: il suo nodo. Non passano mai dall’hub meshbay.org.',
+ 'welcome.how_title': 'Come funziona',
+ 'welcome.step_home': 'Installi MeshBay su un computer di casa. I suoi file restano dove sono.',
+ 'welcome.step_anywhere': 'Acceda dal telefono, dal portatile o da qualsiasi browser, ovunque si trovi.',
+ 'welcome.step_share': 'Crei dei gruppi e inviti la sua famiglia e i suoi amici a unirsi.',
'welcome.uses_title': 'Cosa può farci',
'welcome.use_chat': 'Messaggistica istantanea all’interno di un gruppo',
'welcome.use_photos': 'Condividere album fotografici con famiglia e amici',
'welcome.use_media': 'Accedere ovunque al media center di casa, per lei e la sua famiglia, con streaming e Chromecast dall’app desktop',
'welcome.use_apps': 'Creare le proprie applicazioni sul nodo che ospita',
- 'welcome.hub_title': 'Cosa fa meshbay.org',
- 'welcome.hub_body': 'meshbay.org è solo un servizio di segnalazione: la autentica e mette in contatto le parti, plug-and-play, attraverso le reti domestiche.',
- 'welcome.hub_never': 'I suoi contenuti non lo raggiungono mai:',
- 'welcome.never_transit': 'Nulla passa da lì: nessun contenuto, nessuna indicizzazione',
- 'welcome.never_stored': 'Nessun suo file o messaggio vi è archiviato',
- 'welcome.never_e2e': 'Crittografia end-to-end, da ogni dispositivo al suo nodo',
- 'welcome.hub_free': 'Il servizio è gratuito. Nessun modello di business (per scelta), nessun tracciamento, nessuna pubblicità. Buon divertimento!',
+ 'welcome.private_title': 'Privato per natura',
+ 'welcome.private_body': 'I suoi file e i suoi messaggi viaggiano cifrati, direttamente dai suoi dispositivi al suo computer. meshbay.org aiuta solo i dispositivi a trovarsi: i suoi contenuti non vi passano mai.',
+ 'welcome.badge_free': 'Gratuito',
+ 'welcome.badge_open': 'Open source',
+ 'welcome.badge_no_ads': 'Niente pubblicità',
+ 'welcome.badge_no_tracking': 'Nessun tracciamento',
+ 'welcome.docs_title': 'Documentazione',
+ 'welcome.docs_source': 'Codice sorgente',
+ 'welcome.docs_repo': 'git.meshbay.org',
+ 'welcome.docs_user': 'Documentazione utente',
+ 'welcome.docs_quickstart': 'Avvio rapido',
+ 'welcome.docs_userguide': 'Guida utente',
+ 'welcome.docs_devel': 'Documentazione tecnica',
+ 'welcome.docs_design': 'Architettura',
+ 'welcome.docs_protocol': 'Protocollo',
'welcome.download': 'Scarica (beta)',
'welcome.legal': 'Note legali',
'register.err_mismatch': 'Le password non coincidono',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
index 622fa5a..f357881 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
@@ -93,26 +93,36 @@ export default {
'login.locked': "このアカウントでパスフレーズの誤りが多すぎます。{minutes} 分後にもう一度お試しいただくか、パスフレーズをリセットしてください。",
// Sign-in page: what MeshBay is (browser only)
'welcome.title': '本来あるべき姿のインターネット。',
- 'welcome.lead': 'MeshBay は、個人のファイルにどこからでもアクセスでき、自分のパソコンのストレージ上でアプリを動かせるオープンソースソフトウェアです。',
+ 'welcome.lead': '写真、音楽、動画、会話は、ご自宅の自分のパソコンに置いたまま。MeshBay なら、どこからでも楽しめて、選んだ相手と共有できます。',
'welcome.app_chat': 'チャット',
'welcome.app_photos': '写真',
'welcome.app_media': 'メディアセンター',
'welcome.app_video': '動画プレーヤー',
'welcome.app_music': '音楽プレーヤー',
- 'welcome.groups': 'グループを作成して、家族や友人に一部のアプリへのアクセスを許可できます。',
- 'welcome.e2e': 'データはエンドツーエンドで暗号化され、各デバイスからデータを保管するパソコン(あなたのノード)へピアツーピアで直接届きます。meshbay.org のハブを経由することは一切ありません。',
+ 'welcome.how_title': 'しくみ',
+ 'welcome.step_home': '自宅のパソコンに MeshBay をインストール。ファイルはその場所に置いたままです。',
+ 'welcome.step_anywhere': 'スマートフォン、ノートパソコン、どのブラウザからでも、どこにいてもログインできます。',
+ 'welcome.step_share': 'グループを作って、家族や友人を招待しましょう。',
'welcome.uses_title': 'できること',
'welcome.use_chat': 'グループ内でのインスタントメッセージ',
'welcome.use_photos': '家族や友人とのフォトアルバム共有',
'welcome.use_media': '自宅のメディアセンターに外出先からアクセス。ご家族みんなで使え、ストリーミングに対応し、デスクトップアプリからは Chromecast も利用できます',
'welcome.use_apps': 'ホストしているノード上で独自のアプリを開発',
- 'welcome.hub_title': 'meshbay.org の役割',
- 'welcome.hub_body': 'meshbay.org はシグナリング専用のサービスです。ログインを処理し、家庭のネットワーク越しに参加者同士を設定不要でつなぎます。',
- 'welcome.hub_never': 'あなたのコンテンツが届くことはありません:',
- 'welcome.never_transit': '何も経由しません(コンテンツもインデックスもなし)',
- 'welcome.never_stored': 'ファイルやメッセージは一切保存されません',
- 'welcome.never_e2e': '各デバイスからノードまでエンドツーエンドで暗号化',
- 'welcome.hub_free': 'サービスは無料です。ビジネスモデルは意図的に持たず、トラッキングも広告もありません。どうぞお楽しみください!',
+ 'welcome.private_title': '最初からプライベート',
+ 'welcome.private_body': 'ファイルやメッセージは暗号化され、あなたの端末から自分のパソコンへ直接届きます。meshbay.org は端末どうしをつなぐ手助けをするだけで、あなたのコンテンツが経由することはありません。',
+ 'welcome.badge_free': '無料',
+ 'welcome.badge_open': 'オープンソース',
+ 'welcome.badge_no_ads': '広告なし',
+ 'welcome.badge_no_tracking': 'トラッキングなし',
+ 'welcome.docs_title': 'ドキュメント',
+ 'welcome.docs_source': 'ソースコード',
+ 'welcome.docs_repo': 'git.meshbay.org',
+ 'welcome.docs_user': 'ユーザー向け',
+ 'welcome.docs_quickstart': 'クイックスタート',
+ 'welcome.docs_userguide': 'ユーザーガイド',
+ 'welcome.docs_devel': '開発者向け',
+ 'welcome.docs_design': '設計',
+ 'welcome.docs_protocol': 'プロトコル',
'welcome.download': 'ダウンロード(ベータ版)',
'welcome.legal': '法的情報',
'register.err_mismatch': 'パスワードが一致しません',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
index be0b43b..64e1f00 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
@@ -93,26 +93,36 @@ export default {
'login.locked': "Te veel onjuiste wachtwoordzinnen voor dit account. Probeer het over {minutes} min opnieuw of stel uw wachtwoordzin opnieuw in.",
// Sign-in page: what MeshBay is (browser only)
'welcome.title': 'Het internet zoals het bedoeld was.',
- 'welcome.lead': 'MeshBay is opensourcesoftware waarmee u op afstand bij uw persoonlijke bestanden kunt, en die toepassingen draait bovenop de opslag van uw eigen computer:',
+ 'welcome.lead': 'Uw foto’s, muziek, video’s en gesprekken blijven thuis, op uw eigen computer. Met MeshBay geniet u er overal van en deelt u ze met wie u maar wilt.',
'welcome.app_chat': 'Chat',
'welcome.app_photos': 'Foto’s',
'welcome.app_media': 'Mediacenter',
'welcome.app_video': 'Videospeler',
'welcome.app_music': 'Muziekspeler',
- 'welcome.groups': 'Maak groepen aan om uw naasten toegang te geven tot een aantal van deze toepassingen.',
- 'welcome.e2e': 'Uw gegevens zijn end-to-end versleuteld en gaan peer-to-peer, rechtstreeks van elk apparaat naar de computer waarop ze staan: uw node. Ze gaan nooit via de hub meshbay.org.',
+ 'welcome.how_title': 'Zo werkt het',
+ 'welcome.step_home': 'Installeer MeshBay op een computer thuis. Uw bestanden blijven waar ze zijn.',
+ 'welcome.step_anywhere': 'Meld u aan vanaf uw telefoon, laptop of elke browser, waar u ook bent.',
+ 'welcome.step_share': 'Maak groepen en nodig familie en vrienden uit om mee te doen.',
'welcome.uses_title': 'Wat u ermee kunt doen',
'welcome.use_chat': 'Chatten binnen een groep',
'welcome.use_photos': 'Fotoalbums delen met familie en vrienden',
'welcome.use_media': 'Overal bij uw mediacenter thuis, voor u en uw huishouden, met streaming en Chromecast via de desktop-app',
'welcome.use_apps': 'Uw eigen toepassingen bouwen op de node die u host',
- 'welcome.hub_title': 'Wat meshbay.org doet',
- 'welcome.hub_body': 'meshbay.org is alleen een signaleringsdienst: het meldt u aan en brengt de partijen met elkaar in contact, plug-and-play, over thuisnetwerken heen.',
- 'welcome.hub_never': 'Uw inhoud komt er nooit terecht:',
- 'welcome.never_transit': 'Er gaat niets doorheen: geen inhoud, geen indexering',
- 'welcome.never_stored': 'Geen van uw bestanden of berichten wordt er opgeslagen',
- 'welcome.never_e2e': 'End-to-end versleuteling, van elk apparaat tot uw node',
- 'welcome.hub_free': 'De dienst is gratis. Er is bewust geen verdienmodel, geen tracking en geen advertenties. Veel plezier!',
+ 'welcome.private_title': 'Privé van nature',
+ 'welcome.private_body': 'Uw bestanden en berichten reizen versleuteld, rechtstreeks van uw apparaten naar uw eigen computer. meshbay.org helpt uw apparaten alleen elkaar te vinden: uw inhoud komt er nooit langs.',
+ 'welcome.badge_free': 'Gratis',
+ 'welcome.badge_open': 'Open source',
+ 'welcome.badge_no_ads': 'Geen advertenties',
+ 'welcome.badge_no_tracking': 'Geen tracking',
+ 'welcome.docs_title': 'Documentatie',
+ 'welcome.docs_source': 'Broncode',
+ 'welcome.docs_repo': 'git.meshbay.org',
+ 'welcome.docs_user': 'Gebruikersdocs',
+ 'welcome.docs_quickstart': 'Snelstart',
+ 'welcome.docs_userguide': 'Gebruikershandleiding',
+ 'welcome.docs_devel': 'Ontwikkelaarsdocs',
+ 'welcome.docs_design': 'Ontwerp',
+ 'welcome.docs_protocol': 'Protocol',
'welcome.download': 'Downloaden (bèta)',
'welcome.legal': 'Juridische informatie',
'register.err_mismatch': 'De wachtwoorden komen niet overeen',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
index 6e88a65..1d5f05f 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
@@ -96,26 +96,36 @@ export default {
'login.locked': "Zbyt wiele błędnych haseł-fraz dla tego konta. Spróbuj ponownie za {minutes} min lub zresetuj hasło-frazę.",
// Sign-in page: what MeshBay is (browser only)
'welcome.title': 'Internet taki, jaki miał być.',
- 'welcome.lead': 'MeshBay to oprogramowanie open source, które daje zdalny dostęp do Twoich plików osobistych i uruchamia aplikacje korzystające z pamięci Twojego własnego komputera:',
+ 'welcome.lead': 'Twoje zdjęcia, muzyka, filmy i rozmowy zostają w domu, na Twoim własnym komputerze. MeshBay pozwala korzystać z nich z dowolnego miejsca i dzielić się nimi z wybranymi osobami.',
'welcome.app_chat': 'Czat',
'welcome.app_photos': 'Zdjęcia',
'welcome.app_media': 'Centrum multimedialne',
'welcome.app_video': 'Odtwarzacz wideo',
'welcome.app_music': 'Odtwarzacz muzyki',
- 'welcome.groups': 'Twórz grupy, aby dać bliskim dostęp do wybranych aplikacji.',
- 'welcome.e2e': 'Twoje dane są szyfrowane end-to-end i przesyłane bezpośrednio (peer-to-peer) z każdego urządzenia do komputera, który je przechowuje (Twojego węzła). Nigdy nie przechodzą przez hub meshbay.org.',
+ 'welcome.how_title': 'Jak to działa',
+ 'welcome.step_home': 'Zainstaluj MeshBay na komputerze w domu. Twoje pliki zostają tam, gdzie są.',
+ 'welcome.step_anywhere': 'Zaloguj się z telefonu, laptopa lub dowolnej przeglądarki, gdziekolwiek jesteś.',
+ 'welcome.step_share': 'Twórz grupy i zapraszaj do nich rodzinę i przyjaciół.',
'welcome.uses_title': 'Co możesz z nim robić',
'welcome.use_chat': 'Komunikator w obrębie grupy',
'welcome.use_photos': 'Udostępnianie albumów ze zdjęciami rodzinie i znajomym',
'welcome.use_media': 'Zdalny dostęp do domowego centrum multimedialnego dla Ciebie i domowników (ze streamingiem i Chromecastem w aplikacji desktopowej)',
'welcome.use_apps': 'Tworzenie własnych aplikacji na węźle, który hostujesz',
- 'welcome.hub_title': 'Czym zajmuje się meshbay.org',
- 'welcome.hub_body': 'meshbay.org to wyłącznie usługa sygnalizacyjna: uwierzytelnia Cię i łączy uczestników ze sobą, bez konfiguracji, także przez sieci domowe.',
- 'welcome.hub_never': 'Twoje treści nigdy do niego nie trafiają:',
- 'welcome.never_transit': 'Nic przez niego nie przechodzi: żadnych treści, żadnego indeksowania',
- 'welcome.never_stored': 'Żadne Twoje pliki ani wiadomości nie są na nim przechowywane',
- 'welcome.never_e2e': 'Szyfrowanie end-to-end, od każdego urządzenia do Twojego węzła',
- 'welcome.hub_free': 'Usługa jest bezpłatna. Celowo nie ma modelu biznesowego, śledzenia ani reklam. Miłego korzystania!',
+ 'welcome.private_title': 'Prywatność w standardzie',
+ 'welcome.private_body': 'Twoje pliki i wiadomości podróżują zaszyfrowane, prosto z Twoich urządzeń do Twojego komputera. meshbay.org jedynie pomaga urządzeniom się odnaleźć: Twoje treści nigdy przez niego nie przechodzą.',
+ 'welcome.badge_free': 'Za darmo',
+ 'welcome.badge_open': 'Open source',
+ 'welcome.badge_no_ads': 'Bez reklam',
+ 'welcome.badge_no_tracking': 'Bez śledzenia',
+ 'welcome.docs_title': 'Dokumentacja',
+ 'welcome.docs_source': 'Kod źródłowy',
+ 'welcome.docs_repo': 'git.meshbay.org',
+ 'welcome.docs_user': 'Dla użytkowników',
+ 'welcome.docs_quickstart': 'Szybki start',
+ 'welcome.docs_userguide': 'Przewodnik',
+ 'welcome.docs_devel': 'Dla programistów',
+ 'welcome.docs_design': 'Architektura',
+ 'welcome.docs_protocol': 'Protokół',
'welcome.download': 'Pobierz (beta)',
'welcome.legal': 'Informacje prawne',
'register.err_mismatch': 'Hasła nie są zgodne',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
index edb9146..c2cb35e 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
@@ -94,26 +94,36 @@ export default {
'login.locked': "Muitas frases secretas incorretas para esta conta. Tente novamente em {minutes} min ou redefina sua frase secreta.",
// Sign-in page: what MeshBay is (browser only)
'welcome.title': 'A internet como deveria ser.',
- 'welcome.lead': 'O MeshBay é um software de código aberto que dá acesso remoto aos seus arquivos pessoais e executa aplicativos sobre o armazenamento do seu próprio computador:',
+ 'welcome.lead': 'Suas fotos, músicas, vídeos e conversas ficam em casa, no seu próprio computador. O MeshBay permite que você aproveite tudo de qualquer lugar e compartilhe com as pessoas que escolher.',
'welcome.app_chat': 'Chat',
'welcome.app_photos': 'Fotos',
'welcome.app_media': 'Central de mídia',
'welcome.app_video': 'Player de vídeo',
'welcome.app_music': 'Player de música',
- 'welcome.groups': 'Crie grupos para dar às pessoas próximas acesso a alguns desses aplicativos.',
- 'welcome.e2e': 'Seus dados são criptografados de ponta a ponta e trafegam ponto a ponto, direto de cada dispositivo para o computador que os hospeda: o seu nó. Eles nunca passam pelo hub meshbay.org.',
+ 'welcome.how_title': 'Como funciona',
+ 'welcome.step_home': 'Instale o MeshBay em um computador de casa. Seus arquivos ficam onde estão.',
+ 'welcome.step_anywhere': 'Entre pelo celular, pelo notebook ou por qualquer navegador, onde você estiver.',
+ 'welcome.step_share': 'Crie grupos e convide sua família e seus amigos para participar.',
'welcome.uses_title': 'O que você pode fazer com ele',
'welcome.use_chat': 'Mensagens instantâneas dentro de um grupo',
'welcome.use_photos': 'Compartilhar álbuns de fotos com a família e os amigos',
'welcome.use_media': 'Acessar de qualquer lugar a sua central de mídia, para você e sua casa, com streaming e Chromecast pelo app para desktop',
'welcome.use_apps': 'Criar seus próprios aplicativos no nó que você hospeda',
- 'welcome.hub_title': 'O que o meshbay.org faz',
- 'welcome.hub_body': 'O meshbay.org é apenas um serviço de sinalização: ele autentica você e conecta as partes entre si, plug-and-play, através de redes domésticas.',
- 'welcome.hub_never': 'Seu conteúdo nunca chega até ele:',
- 'welcome.never_transit': 'Nada passa por ele: nenhum conteúdo, nenhuma indexação',
- 'welcome.never_stored': 'Nenhum dos seus arquivos ou mensagens fica armazenado nele',
- 'welcome.never_e2e': 'Criptografia de ponta a ponta, de cada dispositivo até o seu nó',
- 'welcome.hub_free': 'O serviço é gratuito. Não há modelo de negócio (de propósito), rastreamento nem anúncios. Aproveite!',
+ 'welcome.private_title': 'Privado por natureza',
+ 'welcome.private_body': 'Seus arquivos e mensagens viajam criptografados, direto dos seus dispositivos para o seu próprio computador. O meshbay.org só ajuda seus dispositivos a se encontrarem: seu conteúdo nunca passa por ele.',
+ 'welcome.badge_free': 'Gratuito',
+ 'welcome.badge_open': 'Código aberto',
+ 'welcome.badge_no_ads': 'Sem anúncios',
+ 'welcome.badge_no_tracking': 'Sem rastreamento',
+ 'welcome.docs_title': 'Documentação',
+ 'welcome.docs_source': 'Código-fonte',
+ 'welcome.docs_repo': 'git.meshbay.org',
+ 'welcome.docs_user': 'Docs do usuário',
+ 'welcome.docs_quickstart': 'Início rápido',
+ 'welcome.docs_userguide': 'Guia do usuário',
+ 'welcome.docs_devel': 'Docs de desenvolvimento',
+ 'welcome.docs_design': 'Arquitetura',
+ 'welcome.docs_protocol': 'Protocolo',
'welcome.download': 'Baixar (beta)',
'welcome.legal': 'Informações legais',
'register.err_mismatch': 'As senhas não coincidem',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
index 4f5034d..37c57aa 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
@@ -93,26 +93,36 @@ export default {
'login.locked': "此账户输错密码短语的次数过多。请在 {minutes} 分钟后重试,或重置密码短语。",
// Sign-in page: what MeshBay is (browser only)
'welcome.title': '互联网本该有的样子。',
- 'welcome.lead': 'MeshBay 是一款开源软件,让您远程访问个人文件,并在您自己电脑的存储之上运行应用:',
+ 'welcome.lead': '您的照片、音乐、视频和聊天都留在家里,保存在您自己的电脑上。MeshBay 让您随时随地享用它们,并与您选择的人分享。',
'welcome.app_chat': '聊天',
'welcome.app_photos': '照片',
'welcome.app_media': '媒体中心',
'welcome.app_video': '视频播放器',
'welcome.app_music': '音乐播放器',
- 'welcome.groups': '创建群组,让亲友使用其中部分应用。',
- 'welcome.e2e': '您的数据经过端到端加密,以点对点方式从每台设备直接传输到存放数据的电脑(也就是您的节点)。数据从不经过 meshbay.org 中心服务器。',
+ 'welcome.how_title': '使用方式',
+ 'welcome.step_home': '在家里的电脑上安装 MeshBay。您的文件原地不动。',
+ 'welcome.step_anywhere': '无论身在何处,都可以用手机、笔记本或任意浏览器登录。',
+ 'welcome.step_share': '创建群组,邀请家人和朋友加入。',
'welcome.uses_title': '您可以用它做什么',
'welcome.use_chat': '群组内即时通讯',
'welcome.use_photos': '与家人朋友分享相册',
'welcome.use_media': '随时随地访问家中的媒体中心,供您和家人使用(支持流媒体播放,桌面应用还支持 Chromecast)',
'welcome.use_apps': '在您托管的节点上开发自己的应用',
- 'welcome.hub_title': 'meshbay.org 的作用',
- 'welcome.hub_body': 'meshbay.org 仅是一个信令服务:它负责登录验证,并以即插即用的方式跨家庭网络连接各方。',
- 'welcome.hub_never': '您的内容永远不会到达这里:',
- 'welcome.never_transit': '没有任何数据经过它:没有内容,也没有索引',
- 'welcome.never_stored': '不存储您的任何文件或消息',
- 'welcome.never_e2e': '从每台设备到您的节点全程端到端加密',
- 'welcome.hub_free': '本服务免费。我们刻意不设商业模式,没有跟踪,也没有广告。尽情享用吧!',
+ 'welcome.private_title': '生来私密',
+ 'welcome.private_body': '您的文件和消息经过加密,从您的设备直接传到您自己的电脑。meshbay.org 只负责帮您的设备找到彼此:您的内容从不经过它。',
+ 'welcome.badge_free': '免费',
+ 'welcome.badge_open': '开源',
+ 'welcome.badge_no_ads': '无广告',
+ 'welcome.badge_no_tracking': '无追踪',
+ 'welcome.docs_title': '文档',
+ 'welcome.docs_source': '源代码',
+ 'welcome.docs_repo': 'git.meshbay.org',
+ 'welcome.docs_user': '用户文档',
+ 'welcome.docs_quickstart': '快速入门',
+ 'welcome.docs_userguide': '用户指南',
+ 'welcome.docs_devel': '开发文档',
+ 'welcome.docs_design': '设计',
+ 'welcome.docs_protocol': '协议',
'welcome.download': '下载(测试版)',
'welcome.legal': '法律信息',
'register.err_mismatch': '两次输入的密码不一致',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/og-image.jpg b/packages/meshbay-hub/src/meshbay_hub/static/og-image.jpg
new file mode 100644
index 0000000..99fd7e1
--- /dev/null
+++ b/packages/meshbay-hub/src/meshbay_hub/static/og-image.jpg
Binary files differ
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/robots.txt b/packages/meshbay-hub/src/meshbay_hub/static/robots.txt
new file mode 100644
index 0000000..a2ef43d
--- /dev/null
+++ b/packages/meshbay-hub/src/meshbay_hub/static/robots.txt
@@ -0,0 +1,9 @@
+# Served by the hub at the root of its origin, meshbay.org's included (the
+# Caddyfile sends every path the public site does not name to the hub).
+#
+# The sign-in page at /app may be indexed; nothing behind it can be, since it
+# is a signed-in view, and /v1/ is the API. /a/ stays open on purpose: a
+# crawler that renders /app needs the scripts and the stylesheet.
+User-agent: *
+Disallow: /app/
+Disallow: /v1/
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css
index 5f16a6c..37775d8 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/style.css
+++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css
@@ -670,7 +670,47 @@ a:hover { text-decoration: underline; }
keyboard) and `row-reverse` is what moves it right. Headings are
`.welcome-pitch .welcome-h` rather than a bare class so they outrank
`.main h2`. */
+/* The whole sign-in page sits on one dark backdrop, in either theme: steel
+ blue at the top left, night blue pooling at the bottom left, charcoal to near
+ black at the bottom right. Drawn in CSS alone, a fixed layer under the
+ page. The theme tokens are redefined for everything on it, so the form, the
+ cards and the chips keep their own rules and still read. */
+.welcome-backdrop {
+ position: fixed;
+ inset: 0;
+ z-index: 0;
+ overflow: hidden;
+ pointer-events: none;
+ background: linear-gradient(155deg,
+ #86a3c4 0%, #6a819b 18%, #3d4d61 42%, #232b36 66%, #0f1113 100%);
+}
+/* The night-blue pool. */
+.welcome-backdrop::before {
+ content: '';
+ position: absolute;
+ left: -25%;
+ bottom: -30%;
+ width: 85%;
+ height: 90%;
+ background: radial-gradient(closest-side, rgba(26, 46, 110, 0.95), transparent);
+ filter: blur(36px);
+}
+
.welcome {
+ --text: #f4f6fa;
+ --text-secondary: rgba(236, 241, 248, 0.80);
+ --text-dim: rgba(226, 233, 243, 0.60);
+ --border: rgba(255, 255, 255, 0.14);
+ --bg-base: rgba(255, 255, 255, 0.06);
+ --bg-surface: rgba(255, 255, 255, 0.07);
+ --accent-bg: rgba(255, 255, 255, 0.10);
+ --accent: #8fd0ff;
+ --accent-hover: #b5e0ff;
+ --accent-text: #0b1220;
+ --border-focus: #8fd0ff;
+ position: relative;
+ z-index: 1;
+ color: var(--text);
display: flex;
flex-direction: row-reverse;
align-items: flex-start;
@@ -679,7 +719,16 @@ a:hover { text-decoration: underline; }
width: 100%;
max-width: 1180px;
}
-.welcome > .login-card { flex: 0 0 380px; }
+.welcome-side { display: flex; flex: 0 0 380px; flex-direction: column; gap: 16px; }
+/* Frosted glass over the backdrop rather than a white sheet on it. */
+.welcome .login-card {
+ border-color: rgba(255, 255, 255, 0.16);
+ background: rgba(12, 16, 22, 0.45);
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
+ -webkit-backdrop-filter: blur(14px);
+ backdrop-filter: blur(14px);
+}
+.welcome .login-card h2 { color: var(--text); }
.welcome-pitch {
flex: 1 1 0;
@@ -691,13 +740,14 @@ a:hover { text-decoration: underline; }
}
.welcome-pitch p { margin-bottom: 10px; }
-.welcome-apps, .welcome-uses, .welcome-never { list-style: none; }
+.welcome-apps, .welcome-steps, .welcome-uses, .welcome-badges,
+.welcome-docs ul { list-style: none; }
.welcome-title {
- margin-bottom: 10px;
+ margin-bottom: 12px;
color: var(--text);
- font-size: 1.9em;
- font-weight: 700;
+ font-size: 2.3em;
+ font-weight: 500;
line-height: 1.2;
letter-spacing: -0.02em;
text-wrap: balance;
@@ -718,18 +768,6 @@ a:hover { text-decoration: underline; }
}
.welcome-apps .icon { color: var(--accent); }
-.welcome-e2e {
- display: flex;
- align-items: flex-start;
- gap: 10px;
- padding: 8px 14px;
- border-left: 3px solid var(--accent);
- border-radius: 0 6px 6px 0;
- background: var(--accent-bg);
- color: var(--text);
-}
-.welcome-e2e .icon { margin-top: 0.3em; color: var(--accent); }
-
.welcome-pitch .welcome-h {
margin: 18px 0 8px;
color: var(--text-dim);
@@ -748,7 +786,39 @@ a:hover { text-decoration: underline; }
font-size: 0.92em;
line-height: 1.45;
}
-.welcome-use-icon {
+/* How it works: three cards side by side, numbered by the counter so the
+ translation only carries the sentence. */
+.welcome-steps {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 10px;
+ counter-reset: welcome-step;
+}
+.welcome-steps li {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ padding: 14px 14px 12px;
+ border: 1px solid var(--border);
+ border-radius: 10px;
+ background: var(--bg-surface);
+ color: var(--text);
+ font-size: 0.9em;
+ line-height: 1.45;
+ counter-increment: welcome-step;
+}
+.welcome-steps li::after {
+ content: counter(welcome-step);
+ position: absolute;
+ top: 10px;
+ right: 12px;
+ color: var(--text-dim);
+ font-size: 1.3em;
+ font-weight: 700;
+ opacity: 0.5;
+}
+.welcome-step-icon, .welcome-use-icon {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
@@ -761,57 +831,108 @@ a:hover { text-decoration: underline; }
font-size: 17px;
}
-.welcome-hub {
- margin-top: 18px;
+/* Privacy, said once: a warm card rather than a list of denials. */
+.welcome-private {
+ display: flex;
+ align-items: flex-start;
+ gap: 14px;
+ margin-top: 20px;
+ padding: 16px 18px;
+ border-radius: 12px;
+ background: var(--accent-bg);
+ color: var(--text);
+}
+.welcome-private-icon {
+ display: inline-flex;
+ flex: 0 0 auto;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ background: var(--accent);
+ color: var(--accent-text);
+ font-size: 20px;
+}
+.welcome-pitch .welcome-private-title {
+ margin: 2px 0 4px;
+ color: var(--text);
+ font-size: 1.05em;
+ font-weight: 600;
+}
+.welcome-pitch .welcome-private p { margin-bottom: 10px; }
+.welcome-badges { display: flex; flex-wrap: wrap; gap: 6px; }
+.welcome-badges li {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ padding: 3px 10px;
+ border-radius: 999px;
+ background: var(--bg-surface);
+ color: var(--text);
+ font-size: 0.82em;
+ font-weight: 500;
+}
+.welcome-badges .icon { color: var(--success); }
+
+.welcome-docs {
+ margin-top: 16px;
padding: 12px 18px;
border: 1px solid var(--border);
- border-radius: 8px;
+ border-radius: 10px;
background: var(--bg-surface);
- font-size: 0.92em;
+ font-size: 0.9em;
}
-.welcome-pitch .welcome-hub .welcome-h { margin-top: 0; }
-.welcome-pitch .welcome-hub-never { margin-bottom: 6px; color: var(--text); font-weight: 600; }
-.welcome-never { display: flex; flex-direction: column; gap: 4px; color: var(--text); }
-.welcome-never li { display: flex; align-items: flex-start; gap: 8px; }
-.welcome-never .icon { margin-top: 0.3em; color: var(--success); }
-.welcome-pitch .welcome-hub-free {
- margin: 10px 0 0;
- padding-top: 8px;
- border-top: 1px solid var(--border);
- color: var(--text);
- font-weight: 500;
+.welcome-pitch .welcome-docs .welcome-h { margin-top: 0; }
+.welcome-docs ul { display: flex; flex-direction: column; gap: 6px; }
+.welcome-docs li {
+ display: grid;
+ grid-template-columns: auto 10em 1fr;
+ align-items: baseline;
+ gap: 2px 8px;
}
+.welcome-docs .icon { color: var(--accent); }
+.welcome-docs-label { color: var(--text); font-weight: 500; }
+.welcome-docs-links { min-width: 0; overflow-wrap: anywhere; }
.welcome-links {
display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 12px 20px;
- margin-top: 14px;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 10px;
+ text-align: center;
}
.welcome-cta {
- display: inline-flex;
+ display: flex;
align-items: center;
+ justify-content: center;
gap: 8px;
padding: 10px 18px;
border-radius: 6px;
- background: var(--accent);
- color: var(--accent-text);
+ /* Green, so it does not compete with the blue sign-in button above it.
+ #16a34a rather than --success: white text is still legible on it. */
+ background: #16a34a;
+ color: #ffffff;
font-size: 0.95em;
font-weight: 600;
}
-.welcome-cta:hover { background: var(--accent-hover); text-decoration: none; }
+.welcome-cta:hover { background: #15803d; text-decoration: none; }
.welcome-legal { color: var(--text-secondary); font-size: 0.9em; }
.welcome-legal:hover { color: var(--accent); }
@media (max-width: 1000px) {
.welcome { flex-direction: column; align-items: center; gap: 36px; }
- .welcome > .login-card { flex: none; }
+ .welcome-side { flex: none; width: 100%; max-width: 380px; }
.welcome-pitch { flex: none; width: 100%; max-width: 520px; }
}
@media (max-width: 520px) {
.welcome-title { font-size: 1.6em; }
.welcome-uses { grid-template-columns: 1fr; }
+ .welcome-steps { grid-template-columns: 1fr; }
+ .welcome-steps li { flex-direction: row; align-items: center; padding-right: 36px; }
+ .welcome-steps li::after { top: 50%; transform: translateY(-50%); }
+ .welcome-docs li { grid-template-columns: auto 1fr; }
+ .welcome-docs-links { grid-column: 2; }
}
/* ── Form elements ────────────────────────────────────────────────────────── */
diff --git a/packages/meshbay-hub/tests/test_site_basics.py b/packages/meshbay-hub/tests/test_site_basics.py
new file mode 100644
index 0000000..a3da39a
--- /dev/null
+++ b/packages/meshbay-hub/tests/test_site_basics.py
@@ -0,0 +1,81 @@
+"""
+The files every website is expected to have at the root of its origin.
+
+They live in the hub's static directory, which is mounted at "/", so every hub
+serves them — meshbay.org included, because its Caddyfile hands the hub every
+path the public site does not name.
+"""
+
+import re
+from pathlib import Path
+
+import pytest
+from fastapi.testclient import TestClient
+from meshbay_hub.app import create_app
+
+CADDYFILE = Path(__file__).resolve().parents[3] / "packaging" / "caddy" / "meshbay.org.Caddyfile"
+
+
+@pytest.fixture(scope="module")
+def client():
+ return TestClient(create_app())
+
+
+def test_robots_keeps_crawlers_out_of_the_signed_in_views(client):
+ r = client.get("/robots.txt")
+ assert r.status_code == 200
+ assert r.headers["content-type"].startswith("text/plain")
+ rules = re.findall(r"^Disallow:\s*(\S+)", r.text, re.M)
+ assert "/app/" in rules and "/v1/" in rules
+ # A crawler rendering the sign-in page needs its scripts and stylesheet.
+ assert not any(rule in ("/", "/a/", "/app") for rule in rules), rules
+
+
+@pytest.mark.parametrize("path, magic", [
+ ("/favicon.ico", b"\x00\x00\x01\x00"),
+ ("/apple-touch-icon.png", b"\x89PNG"),
+])
+def test_the_icons_are_served_from_the_root(client, path, magic):
+ """Browsers ask for both at the root whether or not a page links them."""
+ r = client.get(path)
+ assert r.status_code == 200
+ assert r.content.startswith(magic), f"{path} is not the image it claims to be"
+
+
+@pytest.mark.skipif(not CADDYFILE.exists(), reason="no Caddyfile in this tree")
+@pytest.mark.parametrize("path", ["/robots.txt", "/favicon.ico", "/apple-touch-icon.png"])
+def test_meshbay_org_sends_them_to_the_hub(path):
+ """The public site owns only the paths its matcher names; a file claimed
+ there would be looked for in /srv/meshbay/site and 404."""
+ m = re.search(r"^\s*@site path (.+)$", CADDYFILE.read_text(), re.M)
+ assert m, "the @site matcher is gone"
+ assert path not in m.group(1).split()
+
+
+def _og(html: str, prop: str) -> str | None:
+ m = re.search(rf'<meta property="og:{prop}" content="([^"]*)">', html)
+ return m and m.group(1)
+
+
+def test_a_link_to_the_hub_previews_with_the_logo():
+ """Messengers draw a link from og:title and og:image, and resolve only an
+ absolute image URL — so it names the hub's public name, and the file is
+ one the hub serves."""
+ from meshbay_hub.config import load_config
+ cfg = load_config()
+ cfg.identity.id = "hub.example.org"
+ client = TestClient(create_app(cfg))
+ for path in ("/", "/app"):
+ html = client.get(path).text
+ assert _og(html, "title") == "MeshBay"
+ assert _og(html, "image") == "https://hub.example.org/og-image.jpg", path
+ image = client.get("/og-image.jpg")
+ assert image.status_code == 200 and image.content.startswith(b"\xff\xd8")
+ assert len(image.content) < 300_000, "too heavy for some messengers to fetch"
+
+
+def test_the_preview_description_fits_in_a_preview():
+ """A preview shows a line or two and cuts the rest; a cut sentence says
+ nothing."""
+ from meshbay_hub.api.webapp import PREVIEW_DESCRIPTION
+ assert len(PREVIEW_DESCRIPTION) <= 60
diff --git a/packages/meshbay-hub/tests/test_welcome_layout_measured.py b/packages/meshbay-hub/tests/test_welcome_layout_measured.py
index da72f34..5a77cb9 100644
--- a/packages/meshbay-hub/tests/test_welcome_layout_measured.py
+++ b/packages/meshbay-hub/tests/test_welcome_layout_measured.py
@@ -40,34 +40,46 @@ def _items(*keys: str) -> str:
def _page() -> str:
li = _items
+ # The source row carries the full URL rather than the host name it shows:
+ # an unbreakable string longer than the real one.
+ docs = "".join(
+ f'<li><span class="welcome-docs-label">{_en(label)}</span>'
+ f'<span class="welcome-docs-links">{links}</span></li>'
+ for label, links in [
+ ("welcome.docs_source", "https://git.meshbay.org/meshbay.git/about/"),
+ ("welcome.docs_user", f"{_en('welcome.docs_quickstart')} · {_en('welcome.docs_userguide')}"),
+ ("welcome.docs_devel", f"{_en('welcome.docs_design')} · {_en('welcome.docs_protocol')}"),
+ ])
return textwrap.dedent(f"""
<nav class="nav"><div class="nav-left"><a class="nav-brand" href="#/">MeshBay</a></div>
<div class="nav-right"><button class="nav-btn">Login</button></div></nav>
<div class="layout"><main class="main"><div class="page-center"><div class="welcome">
- <div class="card login-card"><h2>Login</h2>
+ <div class="welcome-side"><div class="card login-card"><h2>Login</h2>
<form><input type="text" placeholder="Username" />
<input type="password" placeholder="Password" /><button>Login</button></form>
<div class="login-footer">No account? <a href="#/register">Register</a></div>
<div class="login-footer"><a href="#/reset">Forgot your passphrase?</a></div>
</div>
+ <div class="welcome-links"><a class="welcome-cta" href="#">{_en('welcome.download')}</a>
+ <a class="welcome-legal" href="#">{_en('welcome.legal')}</a></div></div>
<section class="welcome-pitch">
<h1 class="welcome-title">{_en('welcome.title')}</h1>
<p class="welcome-lead">{_en('welcome.lead')}</p>
<ul class="welcome-apps">{li('welcome.app_chat', 'welcome.app_photos',
'welcome.app_media', 'welcome.app_video', 'welcome.app_music')}</ul>
- <p>{_en('welcome.groups')}</p>
- <p class="welcome-e2e"><span>{_en('welcome.e2e')}</span></p>
+ <h2 class="welcome-h">{_en('welcome.how_title')}</h2>
+ <ol class="welcome-steps">{li('welcome.step_home', 'welcome.step_anywhere',
+ 'welcome.step_share')}</ol>
<h2 class="welcome-h">{_en('welcome.uses_title')}</h2>
<ul class="welcome-uses">{li('welcome.use_chat', 'welcome.use_photos',
'welcome.use_media', 'welcome.use_apps')}</ul>
- <div class="welcome-hub"><h2 class="welcome-h">{_en('welcome.hub_title')}</h2>
- <p>{_en('welcome.hub_body')}</p>
- <p class="welcome-hub-never">{_en('welcome.hub_never')}</p>
- <ul class="welcome-never">{li('welcome.never_transit', 'welcome.never_stored',
- 'welcome.never_e2e')}</ul>
- <p class="welcome-hub-free">{_en('welcome.hub_free')}</p></div>
- <div class="welcome-links"><a class="welcome-cta" href="#">{_en('welcome.download')}</a>
- <a class="welcome-legal" href="#">{_en('welcome.legal')}</a></div>
+ <div class="welcome-private"><span class="welcome-private-icon"></span><div>
+ <h2 class="welcome-private-title">{_en('welcome.private_title')}</h2>
+ <p>{_en('welcome.private_body')}</p>
+ <ul class="welcome-badges">{li('welcome.badge_free', 'welcome.badge_open',
+ 'welcome.badge_no_ads', 'welcome.badge_no_tracking')}</ul></div></div>
+ <div class="welcome-docs"><h2 class="welcome-h">{_en('welcome.docs_title')}</h2>
+ <ul>{docs}</ul></div>
</section>
</div></div></main></div>
""")
@@ -76,7 +88,8 @@ def _page() -> str:
PHONES = [320, 360, 412]
DESKTOPS = [1100, 1440]
WIDTHS = PHONES + [768] + DESKTOPS
-SELECTORS = [".welcome", ".login-card", ".welcome-pitch"]
+SELECTORS = [".welcome", ".login-card", ".welcome-pitch", ".welcome-steps", ".welcome-docs",
+ ".welcome-links"]
@pytest.fixture(scope="module")
@@ -140,3 +153,14 @@ def test_the_pair_is_centred_in_the_window(measured):
middle = measured["1440"]["docScrollW"] / 2
centre = box["left"] + box["width"] / 2
assert abs(centre - middle) <= 2, f"the page is centred on x={centre}, not {middle}"
+
+
+@pytest.mark.parametrize("width", WIDTHS)
+def test_the_download_and_legal_links_sit_under_the_form(measured, width):
+ card, links = _box(measured, width, ".login-card"), _box(measured, width, ".welcome-links")
+ assert links["top"] >= card["top"] + card["height"], (
+ f"at {width} px the links are not below the sign-in form")
+ assert links["top"] - (card["top"] + card["height"]) <= 40, (
+ f"at {width} px the links are far below the form")
+ assert abs(links["left"] - card["left"]) <= 1 and abs(links["width"] - card["width"]) <= 1, (
+ f"at {width} px the links are not aligned with the form")