diff options
Diffstat (limited to 'packages/meshbay-hub')
13 files changed, 601 insertions, 2 deletions
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 4c00137..9ded87b 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js @@ -6,6 +6,7 @@ import { hubFetch, navigate, session, HUB, loadAuth, _storeRecoveryKey, } from './hub-client.js'; import * as platform from './platform.js'; +import { Icon } from './icon.js'; const PASSWORD_MIN_BITS = 60; const PASSWORD_MIN_LEN = 12; @@ -166,6 +167,7 @@ export function LoginPage({ onLogin }) { return html` <div class="page-center"> + <div class="welcome"> <div class="card login-card"> <h2>${t('login.title')}</h2> <form onSubmit=${onSubmit}> @@ -192,10 +194,70 @@ export function LoginPage({ onLogin }) { <a href="#/reset">${t('login.forgot')}</a> </div> </div> + ${!platform.isNative && html`<${WelcomePitch} />`} + </div> </div> `; } +// What MeshBay is, beside the sign-in form. Browser only: inside the desktop +// 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. +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_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']; + +function WelcomePitch() { + return html` + <section class="welcome-pitch" aria-labelledby="welcome-title"> + <h1 id="welcome-title" class="welcome-title">${t('welcome.title')}</h1> + <p class="welcome-lead">${t('welcome.lead')}</p> + <ul class="welcome-apps"> + ${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.uses_title')}</h2> + <ul class="welcome-uses"> + ${WELCOME_USES.map(([icon, key]) => html` + <li key=${key}><span class="welcome-use-icon"><${Icon} name=${icon} /></span> + <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> + + <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> + </section> + `; +} + export function RegisterPage() { const [username, setUsername] = useState(''); const [email, setEmail] = useState(''); 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 9d4aef8..116bbe9 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js @@ -86,6 +86,30 @@ export default { 'register.resend_sent': 'Ein neuer Code wurde gesendet.', 'login.pending_verification': 'Ihre E-Mail-Adresse ist noch nicht bestätigt. Bitte prüfen Sie Ihr Postfach auf den Bestätigungscode.', 'login.verify_link': 'E-Mail bestätigen', + // 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.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.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.download': 'Herunterladen (Beta)', + 'welcome.legal': 'Rechtliche Hinweise', 'register.err_mismatch': 'Die Passwörter stimmen nicht überein', 'register.err_min_len': { one: 'Verwenden Sie mindestens {n} Zeichen', 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 8385caf..80dd819 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js @@ -89,6 +89,30 @@ export default { 'register.resend_sent': 'A new code has been sent.', 'login.pending_verification': 'Your email is not yet verified. Please check your inbox for the verification code.', 'login.verify_link': 'Verify email', + // 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.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.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.download': 'Download (beta)', + 'welcome.legal': 'Legal information', 'register.err_mismatch': 'Passwords do not match', 'register.err_min_len': { one: 'Use at least {n} character', 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 cb8f312..9f8b60e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js @@ -85,6 +85,30 @@ export default { 'register.resend_sent': 'Se ha enviado un nuevo código.', 'login.pending_verification': 'Su correo electrónico aún no ha sido verificado. Revise su bandeja de entrada para obtener el código de verificación.', 'login.verify_link': 'Verificar correo', + // 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.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.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.download': 'Descargar (beta)', + 'welcome.legal': 'Información legal', 'register.err_mismatch': 'Las contraseñas no coinciden', 'register.err_min_len': { one: 'Use al menos {n} carácter', 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 608da62..d928f52 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js @@ -85,6 +85,30 @@ export default { 'register.resend_sent': 'Un nouveau code a été envoyé.', 'login.pending_verification': 'Votre e-mail n\'est pas encore vérifié. Consultez votre boîte de réception pour le code de vérification.', 'login.verify_link': 'Vérifier l\'e-mail', + // 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.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.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.download': 'Télécharger (bêta)', + 'welcome.legal': 'Informations légales', 'register.err_mismatch': 'Les mots de passe ne correspondent pas', 'register.err_min_len': { one: 'Utilisez au moins {n} caractère', 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 be5fe29..58e2389 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js @@ -86,6 +86,30 @@ export default { 'register.resend_sent': 'Un nuovo codice è stato inviato.', 'login.pending_verification': 'Il suo indirizzo e-mail non è ancora verificato. Controlli la sua casella di posta per il codice di verifica.', 'login.verify_link': 'Verifica e-mail', + // 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.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.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.download': 'Scarica (beta)', + 'welcome.legal': 'Note legali', 'register.err_mismatch': 'Le password non coincidono', 'register.err_min_len': { one: 'Usi almeno {n} carattere', 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 f2d94d9..234b238 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js @@ -86,6 +86,30 @@ export default { 'register.resend_sent': '新しいコードを送信しました。', 'login.pending_verification': 'メールアドレスがまだ確認されていません。受信トレイで確認コードをご確認ください。', 'login.verify_link': 'メールを確認', + // Sign-in page: what MeshBay is (browser only) + 'welcome.title': '本来あるべき姿のインターネット。', + '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.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.download': 'ダウンロード(ベータ版)', + 'welcome.legal': '法的情報', 'register.err_mismatch': 'パスワードが一致しません', 'register.err_min_len': { other: '{n} 文字以上でご入力ください', 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 f7f0775..72511f5 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js @@ -86,6 +86,30 @@ export default { 'register.resend_sent': 'Er is een nieuwe code verzonden.', 'login.pending_verification': 'Uw e-mailadres is nog niet geverifieerd. Controleer uw inbox voor de verificatiecode.', 'login.verify_link': 'E-mail verifiëren', + // 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.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.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.download': 'Downloaden (bèta)', + 'welcome.legal': 'Juridische informatie', 'register.err_mismatch': 'De wachtwoorden komen niet overeen', 'register.err_min_len': { one: 'Gebruik minstens {n} teken', 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 8c50790..12a31aa 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js @@ -89,6 +89,30 @@ export default { 'register.resend_sent': 'Nowy kod został wysłany.', 'login.pending_verification': 'Twój adres e-mail nie został jeszcze zweryfikowany. Sprawdź skrzynkę odbiorczą.', 'login.verify_link': 'Zweryfikuj e-mail', + // 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.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.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.download': 'Pobierz (beta)', + 'welcome.legal': 'Informacje prawne', 'register.err_mismatch': 'Hasła nie są zgodne', 'register.err_min_len': { one: 'Proszę użyć co najmniej {n} znaku', 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 8f903ba..c5f826b 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 @@ -87,6 +87,30 @@ export default { 'register.resend_sent': 'Um novo código foi enviado.', 'login.pending_verification': 'Seu e-mail ainda não foi verificado. Verifique sua caixa de entrada.', 'login.verify_link': 'Verificar e-mail', + // 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.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.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.download': 'Baixar (beta)', + 'welcome.legal': 'Informações legais', 'register.err_mismatch': 'As senhas não coincidem', 'register.err_min_len': { one: 'Use pelo menos {n} caractere', 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 c836de3..e06c5c6 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 @@ -86,6 +86,30 @@ export default { 'register.resend_sent': '新验证码已发送。', 'login.pending_verification': '您的邮箱尚未验证。请查看收件箱中的验证码。', 'login.verify_link': '验证邮箱', + // Sign-in page: what MeshBay is (browser only) + 'welcome.title': '互联网本该有的样子。', + '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.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.download': '下载(测试版)', + 'welcome.legal': '法律信息', 'register.err_mismatch': '两次输入的密码不一致', 'register.err_min_len': { other: '请至少使用 {n} 个字符', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css index d51e5b0..ff6fe57 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/style.css +++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css @@ -478,8 +478,11 @@ a:hover { text-decoration: underline; } exactly that much — 48px, and a scrollbar on sign-in at every window size. Measured, not deduced: `.page-center` ended at 795 and `main` at 819. The page brings its own 24px, so dropping main's here changes nothing - visible and removes the duplicated arithmetic rather than adding more. */ -.main:has(> .page-center) { padding: 0; } + visible and removes the duplicated arithmetic rather than adding more. + `max-width` goes too: signed out there is no sidebar, so the 960px column sat + at the left of the window and a "centred" form was centred in that — at + x=290 in a 1440px window. */ +.main:has(> .page-center) { padding: 0; max-width: none; } /* ── Sticky chrome ────────────────────────────────────────────────────────── */ @@ -631,6 +634,158 @@ a:hover { text-decoration: underline; } color: var(--text-dim); } +/* ── Welcome (sign-in page, browser only) ─────────────────────────────────── */ + +/* What MeshBay is on the left, the form on the right and level with the + title; stacked, form first, once the text would be narrower than the form. + The form comes first in the markup (first on a phone, first to the + 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`. */ +.welcome { + display: flex; + flex-direction: row-reverse; + align-items: flex-start; + justify-content: center; + gap: 96px; + width: 100%; + max-width: 1180px; +} +.welcome > .login-card { flex: 0 0 380px; } + +.welcome-pitch { + flex: 1 1 0; + min-width: 0; + max-width: 660px; + color: var(--text-secondary); + font-size: 0.95rem; + line-height: 1.5; +} +.welcome-pitch p { margin-bottom: 10px; } + +.welcome-apps, .welcome-uses, .welcome-never { list-style: none; } + +.welcome-title { + margin-bottom: 10px; + color: var(--text); + font-size: 1.9em; + font-weight: 700; + line-height: 1.2; + letter-spacing: -0.02em; + text-wrap: balance; +} +.welcome-lead { font-size: 1.05em; } + +.welcome-apps { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 14px; } +.welcome-apps li { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 5px 12px; + border: 1px solid var(--border); + border-radius: 999px; + background: var(--bg-surface); + color: var(--text); + font-size: 0.88em; +} +.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); + font-size: 0.78em; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.welcome-uses { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 18px; } +.welcome-uses li { + display: flex; + align-items: flex-start; + gap: 10px; + color: var(--text); + font-size: 0.92em; + line-height: 1.45; +} +.welcome-use-icon { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 8px; + background: var(--accent-bg); + color: var(--accent); + font-size: 17px; +} + +.welcome-hub { + margin-top: 18px; + padding: 12px 18px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--bg-surface); + font-size: 0.92em; +} +.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-links { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 12px 20px; + margin-top: 14px; +} +.welcome-cta { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 18px; + border-radius: 6px; + background: var(--accent); + color: var(--accent-text); + font-size: 0.95em; + font-weight: 600; +} +.welcome-cta:hover { background: var(--accent-hover); 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-pitch { flex: none; width: 100%; max-width: 520px; } +} +@media (max-width: 520px) { + .welcome-title { font-size: 1.6em; } + .welcome-uses { grid-template-columns: 1fr; } +} + /* ── Form elements ────────────────────────────────────────────────────────── */ input[type="text"], diff --git a/packages/meshbay-hub/tests/test_welcome_layout_measured.py b/packages/meshbay-hub/tests/test_welcome_layout_measured.py new file mode 100644 index 0000000..da72f34 --- /dev/null +++ b/packages/meshbay-hub/tests/test_welcome_layout_measured.py @@ -0,0 +1,142 @@ +""" +The sign-in page with the welcome text beside it, measured in a browser. + +Two columns on a desktop, one on a phone with the form first, and nothing wider +than the screen at any width. The text is the English catalogue's, at its real +length: a fixture shorter than the real copy measures the fixture. + +The markup is `LoginPage` and `WelcomePitch` as `auth-page.js` renders them, +inside the shell `app.js` puts every page in. What is under test is the +stylesheet, which is served as it ships. +""" + +import json +import re +import shutil +import subprocess +import textwrap +from pathlib import Path + +import pytest + +HARNESS = Path(__file__).parent / "harness" / "layout_probe.py" +STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static" + +pytestmark = pytest.mark.skipif( + shutil.which("google-chrome") is None or not (STATIC / "style.css").exists(), + reason="Chrome or the SPA stylesheet is not available") + + +def _en(key: str) -> str: + source = (STATIC / "locales" / "en.js").read_text(encoding="utf-8") + m = re.search(rf"^\s*'{re.escape(key)}': '([^']*)',$", source, re.M) + assert m, f"{key} is missing from en.js" + return m.group(1) + + +def _items(*keys: str) -> str: + return "".join(f"<li><span>{_en(k)}</span></li>" for k in keys) + + +def _page() -> str: + li = _items + 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> + <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> + <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.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> + </section> + </div></div></main></div> + """) + + +PHONES = [320, 360, 412] +DESKTOPS = [1100, 1440] +WIDTHS = PHONES + [768] + DESKTOPS +SELECTORS = [".welcome", ".login-card", ".welcome-pitch"] + + +@pytest.fixture(scope="module") +def measured(tmp_path_factory): + fragment = tmp_path_factory.mktemp("welcome") / "fragment.html" + fragment.write_text(_page(), encoding="utf-8") + proc = subprocess.run( + ["python3", str(HARNESS), ",".join(str(w) for w in WIDTHS), + str(fragment), *SELECTORS], + capture_output=True, text=True, timeout=180) + assert proc.returncode == 0, f"probe failed: {proc.stdout}{proc.stderr}" + out = json.loads(proc.stdout) + assert "error" not in out, f"no measurement: {out}" + for w in WIDTHS: + assert out[str(w)]["viewport"]["w"] == w + return out + + +def _box(measured, width, selector): + box = measured[str(width)]["boxes"][selector] + assert box is not None, f"{selector} did not render at {width} px" + return box + + +@pytest.mark.parametrize("width", WIDTHS) +def test_nothing_is_wider_than_the_screen(measured, width): + """A document wider than the screen unpins every sticky header on Android.""" + assert measured[str(width)]["docScrollW"] <= width, ( + f"the page is {measured[str(width)]['docScrollW']} px wide on a {width} px screen") + for sel in SELECTORS: + box = _box(measured, width, sel) + assert box["offLeft"] == 0 and box["offRight"] == 0, f"{sel} hangs off at {width} px" + + +@pytest.mark.parametrize("width", PHONES + [768]) +def test_a_narrow_screen_shows_the_form_first(measured, width): + card, pitch = _box(measured, width, ".login-card"), _box(measured, width, ".welcome-pitch") + assert pitch["top"] >= card["top"] + card["height"], ( + f"at {width} px the welcome text is not below the sign-in form") + + +@pytest.mark.parametrize("width", DESKTOPS) +def test_a_desktop_puts_the_form_right_of_the_text(measured, width): + """Text on the left, the form on the right with room between them, and + the form up by the title rather than centred on a column far taller.""" + card, pitch = _box(measured, width, ".login-card"), _box(measured, width, ".welcome-pitch") + gap = card["left"] - (pitch["left"] + pitch["width"]) + assert gap >= 80, f"at {width} px the form is {gap} px right of the text" + assert pitch["width"] >= 480, f"the text column is {pitch['width']} px at {width} px" + assert 0 <= card["top"] - pitch["top"] <= 80, ( + f"at {width} px the form starts {card['top'] - pitch['top']} px below the text") + + +def test_the_pair_is_centred_in_the_window(measured): + """Signed out there is no sidebar, and `.main` is capped at 960 px — so + before `.page-center` lifted the cap, a centred form sat centred in the + left 960 px of the window: x=290 in 1440.""" + # Against the document's width, not the window's: the probe's frame is + # shorter than the page, so a vertical scrollbar takes its share. + box = _box(measured, 1440, ".welcome") + 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}" |