Files
simplex-chat/website/src/index.html
Evgeny f0799ef2a5 website: improve first page load (#6680)
* website: improve first page load

* remove low res images

* simplify

* fix buttons jitter

* fix color scheme toggling

* fix other pages

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
2026-03-16 19:23:00 +00:00

384 lines
21 KiB
HTML

---
title: "SimpleX Chat: private and secure messenger without any user IDs (not even random)"
description: "SimpleX Chat - a private and encrypted messenger without any user IDs (not even random ones)! Make a private connection via link / QR code to send messages and make calls."
templateEngineOverride: njk
active_home: true
---
<!DOCTYPE html>
<html class="bg-[#C1E3FE] dark:bg-[#050920]" lang="{{ page.url | getlang }}"
{% for language in languages.languages %}
{% if language.label == page.url | getlang %}
dir="{{ "rtl" if language.rtl else "ltr" }}"
{% endif %}
{% endfor %}>
<head>
<meta charset="UTF-8">
{% include "dark-mode.html" %}
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>{{ title }}</title>
<meta name="description" content="{{ description }}"/>
<meta name="Content-Type" content="text/html;charset=utf-8"/>
{% if path %}
<meta http-equiv="onion-location" content="{% cfg 'onionLocation' %}{{ path }}" />
<meta property="og:url" content="{% cfg 'siteLocation' %}{{ path }}" />
{% else %}
<meta http-equiv="onion-location" content="{% cfg 'onionLocation' %}/" />
<meta property="og:url" content="{% cfg 'siteLocation' %}/" />
{% endif %}
<meta property="og:type" content="website"/>
<meta property="og:title" content="{{ title }}"/>
<meta property="og:description" content="{{ description }}"/>
<meta property="og:image" content="{% cfg 'siteLocation' %}/img/share_simplex.jpg"/>
<meta name="twitter:card" content="summary"/>
<link rel="icon" type="image/png" sizes="96x96" href="/img/favicon.ico"/>
<meta name="theme-color" content="#C1E3FE">
<link rel="preload" href="/img/design_3/cover.webp" as="image" type="image/webp">
<link rel="preload" href="/img/design_3/cover-light.webp" as="image" type="image/webp" media="(prefers-color-scheme: light)">
<link rel="preload" href="/img/new/logo-dark.png" as="image">
<link rel="preload" href="/img/new/logo-light.png" as="image" media="(prefers-color-scheme: light)">
<link rel="preload" href="/img/design_3/publications/trail-of-bits.png" as="image">
<link rel="preload" href="/img/design_3/publications/privacy-guides.png" as="image">
<link rel="preload" href="/img/design_3/publications/whonix.png" as="image">
<link rel="preload" href="/img/design_3/publications/heise.png" as="image">
<link rel="preload" href="/img/design_3/publications/kuketz-blog.png" as="image">
<link rel="preload" href="/img/design_3/publications/optout.png" as="image">
<link rel="preload" href="/fonts/GT-Walsheim/GT-Walsheim-LC-Bold.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/fonts/GT-Walsheim/GT-Walsheim-LC-Regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/fonts/Manrope/Manrope-ExtraLight.ttf" as="font" type="font/ttf" crossorigin>
<link rel="preload" href="/fonts/Manrope/Manrope-Light.ttf" as="font" type="font/ttf" crossorigin>
<link rel="preload" href="/fonts/Manrope/Manrope-Medium.ttf" as="font" type="font/ttf" crossorigin>
<link rel="preload" href="/fonts/Manrope/Manrope-Bold.ttf" as="font" type="font/ttf" crossorigin>
<link rel="preload" href="/img/new/apple_store.svg" as="image">
<link rel="preload" href="/img/new/google_play.svg" as="image">
<link rel="preload" href="/img/new/f_droid.svg" as="image" media="(max-width: 959px)">
<link rel="preload" href="/img/design_3/testflight-dark.png" as="image" media="(max-width: 959px)">
<link rel="preload" href="/img/design_3/android-dark.png" as="image" media="(max-width: 959px)">
<link rel="preload" href="/img/design_3/socials/linux.png" as="image" media="(min-width: 960px)">
<link rel="preload" href="/img/design_3/socials/apple.png" as="image" media="(min-width: 960px)">
<link rel="preload" href="/img/design_3/socials/windows.png" as="image" media="(min-width: 960px)">
<link href="/css/tailwind.css" rel="stylesheet"/>
<link rel="stylesheet" href="/css/design3.css">
<link rel="stylesheet" href="/css/design3-nav.css">
<script src="/js/flag-anchor.js"></script>
<script>
function detectMobileBrowser() {
const ua = navigator.userAgent;
// Chrome on iOS
if (/CriOS/.test(ua)) {
document.documentElement.classList.add('chrome-ios');
}
// Safari on iOS
else if (/Safari/.test(ua) && /iPhone|iPad|iPod/.test(ua) && !/CriOS|FxiOS|EdgiOS/.test(ua)) {
document.documentElement.classList.add('safari-ios');
}
// Chrome on Android
else if (/Chrome/.test(ua) && /Android/.test(ua)) {
document.documentElement.classList.add('chrome-android');
}
}
detectMobileBrowser();
</script>
</head>
<body class="bg-[#C1E3FE] dark:bg-[#050920]">
{% include "navbar.html" %}
{% set lang = page.url | getlang %}
<div class="screen">
<section class="page-1 cover page">
<div class="area">
<div class="content">
<h1>{{ "index-hero-h1" | i18n({}, lang) | safe }}</h1>
<h2 class="gradient-text">{{ "index-hero-h2" | i18n({}, lang) | safe }}</h2>
<p>{{ "index-hero-p1" | i18n({}, lang) | safe }}</p>
<div class="socials">
<a href="/downloads" class="desktop-app-btn hidden" title="{{ 'index-hero-download-desktop-btn-title' | i18n({}, lang) }}">
<div class="btn-content">
<div>
<img class="no-border" src="/img/design_3/socials/linux.png">
<img class="no-border" src="/img/design_3/socials/apple.png" alt="">
<img class="no-border" src="/img/design_3/socials/windows.png" alt="">
</div>
<p>Download<br>Desktop App</p>
</div>
</a>
<a class="apple-store-btn hidden" href="https://apps.apple.com/us/app/simplex-chat/id1605771084" target="_blank"><img src="/img/new/apple_store.svg"></a>
<a class="google-play-btn hidden" href="https://play.google.com/store/apps/details?id=chat.simplex.app" target="_blank"><img src="/img/new/google_play.svg"></a>
<a class="f-droid-btn hidden" href="{{ '' if lang == 'en' else '/' ~ lang }}/fdroid" title="{{ 'index-f-droid-title' | i18n({}, lang) }}"><img src="/img/new/f_droid.svg"></a>
<a class="testflight-btn hidden" href="https://testflight.apple.com/join/DWuT2LQu" target="_blank"><img class="no-border" src="/img/design_3/testflight-dark.png" title="{{ 'index-testflight-title' | i18n({}, lang) }}"></a>
<a class="android-btn hidden" href="https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-aarch64.apk" target="_blank"><img src="/img/design_3/android-dark.png"></a>
</div>
<div class="security-btns">
<a title="{{ 'index-security-assessment-title' | i18n({}, lang) }}" href="https://www.trailofbits.com/about" target="_blank">
<img src="/img/design_3/publications/trail-of-bits.png" alt="Trail of Bits">
</a>
<a class="box-btn btn-1" title="{{ 'index-security-review-2022-title' | i18n({}, lang) }}" href="/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html">
2022
</a>
<a class="box-btn gold-gradient" title="{{ 'index-security-review-2024-title' | i18n({}, lang) }}" href="/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html">
2024
</a>
<p class="security-audits">
{{ 'index-security-audits-label' | i18n({}, lang) | safe }}
</p>
</div>
<div class="publications-btns">
<a title="{{ 'index-publications-privacy-guides-title' | i18n({}, lang) }}" href="https://www.privacyguides.org/en/real-time-communication/#simplex-chat" target="_blank">
<img src="/img/design_3/publications/privacy-guides.png" alt="Privacy Guides">
</a>
<a title="{{ 'index-publications-whonix-title' | i18n({}, lang) }}" href="https://www.whonix.org/wiki/Chat#Recommendation" target="_blank">
<img src="/img/design_3/publications/whonix.png" alt="Whonix" class="whonix">
</a>
<a title="{{ 'index-publications-heise-title' | i18n({}, lang) }}" href="https://www.heise.de/suche/?q=simplex+chat&sort_by=date&rm=search" target="_blank">
<img src="/img/design_3/publications/heise.png" alt="Heise">
</a>
<a title="{{ 'index-publications-kuketz-title' | i18n({}, lang) }}" href="https://www.kuketz-blog.de/simplex-eindruecke-vom-messenger-ohne-identifier/" target="_blank">
<img src="/img/design_3/publications/kuketz-blog.png" alt="Kuketz Blog" class="kuketz">
</a>
<a title="{{ 'index-publications-optout-title' | i18n({}, lang) }}" href="https://optoutpod.com/episodes/s3e02-simplexchat/" target="_blank">
<img src="/img/design_3/publications/optout.png" alt="Opt Out">
</a>
</div>
</div>
</div>
</section>
<main>
<div class="section-bg"></div>
<section id="messaging" class="page-2 page">
<div class="area">
<div class="text-container">
<h2>{{ "worlds-most-secure-messaging" | i18n({}, lang ) | safe }}</h2>
<p>{{ "index-messaging-p1" | i18n({}, lang) }}</p>
<p>{{ "index-messaging-p2" | i18n({}, lang) | safe }}</p>
<a class="gradient-text" href="{{ '' if lang == 'en' else '/' ~ lang }}/messaging">{{ "index-messaging-cta" | i18n({}, lang) }}</a>
</div>
</div>
</section>
<section id="nextweb" class="page-3 page">
<div class="area">
<div class="text-container">
<h2>{{ "index-nextweb-h2" | i18n({}, lang) | safe }}</h2>
<p>{{ "index-nextweb-p1" | i18n({}, lang) | safe }}</p>
<p>{{ "index-nextweb-p2" | i18n({}, lang) }}</p>
<a class="gradient-text" href="{{ '' if lang == 'en' else '/' ~ lang }}/why">{{ "why-footer-link" | i18n({}, lang) | safe }}</a>
</div>
</div>
</section>
<section id="token" class="page-4 page">
<div class="area">
<div class="text-container">
<h2>{{ "index-token-h2" | i18n({}, lang) }}</h2>
<p>{{ "index-token-p1" | i18n({}, lang) }}</p>
<p>{{ "index-token-p2" | i18n({}, lang) }}</p>
<a class="gradient-text" href="/vouchers">{{ "index-token-cta" | i18n({}, lang) | safe }}</a>
</div>
</div>
</section>
<section id="roadmap" class="page-5 page">
<div class="area">
<div class="text-container">
<h2>{{ "index-roadmap-h2" | i18n({}, lang) }}</h2>
<div class="roadmap">
<p>{{ "index-roadmap-2025" | i18n({}, lang) }} </p>
<p class="title"><span class="ltr:order-first rtl:order-last">: </span>{{ "index-roadmap-2025-title" | i18n({}, lang) }}</p>
<p>{{ "index-roadmap-2025-desc" | i18n({}, lang) }}</p>
</div>
<div class="roadmap">
<p>{{ "index-roadmap-2026" | i18n({}, lang) }} </p>
<p class="title"><span class="ltr:order-first rtl:order-last">: </span>{{ "index-roadmap-2026-title" | i18n({}, lang) }}</p>
<p>{{ "index-roadmap-2026-desc" | i18n({}, lang) }}</p>
</div>
<div class="roadmap">
<p>{{ "index-roadmap-2027" | i18n({}, lang) }} </p>
<p class="title"><span class="ltr:order-first rtl:order-last">: </span>{{ "index-roadmap-2027-title" | i18n({}, lang) }}</p>
<p>{{ "index-roadmap-2027-desc" | i18n({}, lang) }}</p>
</div>
</div>
</div>
</section>
</main>
<section id="directory" class="page-6 page">
<div class="area">
<div class="group-images">
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="https://smp4.simplex.im/g#hr4lvFeBmndWMKTwqiodPz3VBo_6UmdGWocXd1SupsM" class="simplex-image" title="{{ 'index-directory-users-group-title' | i18n({}, lang) }}" target="_blank">
<img class="block dark:hidden" src="/img/new/logo-symbol-light.svg">
<img class="hidden dark:block" src="/img/new/logo-symbol-dark.svg">
</a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
<a href="/directory" class="group-image" target="_blank"><img src="/img/group.svg"></a>
</div>
<div class="text-container">
<h2>{{ "index-directory-h2" | i18n({}, lang) }}</h2>
<p>{{ "index-directory-p1" | i18n({}, lang) }}</p>
<p>{{ "index-directory-p2" | i18n({}, lang) }}</p>
<a class="gradient-text" href="/directory">{{ "index-directory-cta" | i18n({}, lang) }}</a>
</div>
</div>
</section>
{% include "footer.html" %}
</div>
<script src="/js/directory.js"></script>
<script src="/js/design3.js"></script>
<script>
(function navColorOnScroll(){
const header = document.querySelector('header#navbar');
const cover = document.querySelector('.page-1.cover');
const footer = document.querySelector('.footer.page');
if (!header || !cover || !footer || !('IntersectionObserver' in window)) {
window.addEventListener('load', () => header && document.body.classList.toggle('change-nav-color', window.scrollY > 10));
window.addEventListener('scroll', () => header && document.body.classList.toggle('change-nav-color', window.scrollY > 10));
return;
}
const io = new IntersectionObserver((entries) => {
// if cover or footer is mostly visible, keep white nav; else switch to #001796
const onObserved = entries.some((e) => e.isIntersecting && e.intersectionRatio >= 0.02);
document.body.classList.toggle('change-nav-color', !onObserved);
}, { threshold: [0, 0.02, 1] });
io.observe(cover);
io.observe(footer);
})();
</script>
<script>
(function sectionHeaderState(){
const mobileHeader = document.getElementById('mobile-header');
if (!mobileHeader) return;
const cover = document.querySelector('.page-1.cover');
const messaging = document.getElementById('messaging');
const nextweb = document.getElementById('nextweb');
const token = document.getElementById('token');
const roadmap = document.getElementById('roadmap');
const directory = document.getElementById('directory');
const footer = document.querySelector('footer, .footer, #footer');
const observed = [
{ key: 'cover', el: cover },
{ key: 'main', el: messaging },
{ key: 'main', el: nextweb },
{ key: 'main', el: token },
{ key: 'main', el: roadmap },
{ key: 'main', el: directory },
{ key: 'footer', el: footer }
].filter(t => t.el);
if (!observed.length) return;
const MIN_COVER = 0.02;
function clear(){
mobileHeader.classList.remove('cover','main','footer');
}
function apply(key){
clear();
if (key) mobileHeader.classList.add(key);
}
function viewportCoverageY(el){
const r = el.getBoundingClientRect();
const vh = window.innerHeight || document.documentElement.clientHeight;
const visibleY = Math.max(0, Math.min(vh, r.bottom) - Math.max(0, r.top));
return visibleY / Math.max(1, vh);
}
function computeKeyMax(){
const keyMax = { cover: 0, main: 0, footer: 0 };
for (const t of observed) {
const ratio = viewportCoverageY(t.el);
if (ratio > keyMax[t.key]) keyMax[t.key] = ratio;
}
return keyMax;
}
let lastKey = null;
let rafId = null;
function update(){
rafId = null; // Reset RAF flag
const keyMax = computeKeyMax();
let bestKey = null, best = MIN_COVER;
for (const [k, r] of Object.entries(keyMax)) {
if (r > best) { best = r; bestKey = k; }
}
// Only update DOM if state actually changed
if (bestKey !== lastKey) {
if (bestKey) {
apply(bestKey);
} else {
clear();
}
lastKey = bestKey;
}
}
function scheduleUpdate(){
if (rafId) return; // Already scheduled
rafId = requestAnimationFrame(update);
}
let observer = null;
if ('IntersectionObserver' in window) {
observer = new IntersectionObserver(scheduleUpdate, {
threshold: [0, 0.02, 0.1, 0.25, 0.5, 0.75, 1],
rootMargin: '0px 0px -35% 0px'
});
observed.forEach(t => observer.observe(t.el));
scheduleUpdate(); // Initial update
} else {
// Fallback for older browsers
['scroll','resize','load'].forEach(ev =>
window.addEventListener(ev, scheduleUpdate, { passive: true })
);
scheduleUpdate();
}
})();
</script>
</body>
</html>