feat(legal): publish Privacy Policy and account-deletion pages with About-screen link and Play Store feature graphic

This commit is contained in:
heaven 2026-05-13 22:53:58 +03:00
parent 5c16649e0c
commit 5d4a50f593
7 changed files with 866 additions and 0 deletions

388
public/delete-account.html Normal file
View file

@ -0,0 +1,388 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="theme-color" content="#0d0e11" />
<meta name="robots" content="index,follow" />
<title>Vojo — Account deletion</title>
<style>
:root {
--bg: #0d0e11;
--panel: #181a20;
--surface: #21232b;
--text: #e6e6e9;
--text-strong: #f4f4f6;
--muted: rgba(230, 230, 233, 0.62);
--faint: rgba(230, 230, 233, 0.38);
--divider: rgba(255, 255, 255, 0.08);
--fleet: #9580ff;
--fleet-soft: #a59cff;
color-scheme: dark;
}
*, *::before, *::after { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--text);
font-family: -apple-system, "SF Pro Text", "Inter", system-ui, sans-serif;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
body {
min-height: 100vh;
line-height: 1.7;
font-size: 16px;
}
.frame {
max-width: 680px;
margin: 0 auto;
padding: 56px 28px 120px;
}
header.doc {
padding-bottom: 28px;
margin-bottom: 40px;
border-bottom: 1px solid var(--divider);
}
.brand {
display: flex;
align-items: center;
gap: 9px;
margin-bottom: 22px;
color: var(--muted);
font-size: 13px;
letter-spacing: 0.2px;
}
.brand-dot {
width: 8px;
height: 8px;
border-radius: 99px;
background: var(--fleet);
}
.brand-name { color: var(--text-strong); font-weight: 600; }
.brand-sep { color: var(--faint); }
h1 {
font-size: 34px;
line-height: 1.15;
font-weight: 600;
margin: 0 0 10px;
letter-spacing: -0.6px;
color: var(--text-strong);
}
.effective { color: var(--muted); font-size: 14px; margin: 0; }
.lang-switch {
display: inline-flex;
margin-top: 24px;
font-size: 13px;
color: var(--muted);
}
.lang-switch button {
appearance: none;
-webkit-appearance: none;
background: transparent;
border: 0;
padding: 4px 0;
font: inherit;
color: var(--muted);
cursor: pointer;
transition: color .15s ease;
}
.lang-switch button:hover { color: var(--text); }
.lang-switch button[aria-pressed="true"] {
color: var(--text-strong);
font-weight: 600;
}
.lang-switch .sep {
padding: 0 10px;
color: var(--faint);
user-select: none;
}
h2 {
font-size: 20px;
font-weight: 600;
margin: 44px 0 12px;
letter-spacing: -0.2px;
color: var(--text-strong);
scroll-margin-top: 24px;
}
p { margin: 0 0 14px; color: var(--text); }
ul, ol {
margin: 0 0 18px;
padding-left: 22px;
}
ul li, ol li { margin: 8px 0; padding-left: 4px; }
ul li::marker, ol li::marker { color: var(--faint); }
ul li b, ol li b, p b { color: var(--text-strong); font-weight: 600; }
a {
color: var(--fleet-soft);
text-decoration: none;
border-bottom: 1px solid rgba(165, 156, 255, 0.35);
transition: color .15s ease, border-color .15s ease;
}
a:hover {
color: #c0b9ff;
border-bottom-color: rgba(192, 185, 255, 0.7);
}
.callout {
background: var(--panel);
border: 1px solid var(--divider);
border-radius: 12px;
padding: 18px 20px;
margin: 14px 0 20px;
}
.callout p:last-child { margin-bottom: 0; }
.callout a.email {
font-family: ui-monospace, "JetBrains Mono", monospace;
font-size: 0.95em;
}
section[hidden] { display: none; }
section > h2:first-of-type { margin-top: 0; }
footer.doc {
margin-top: 64px;
padding-top: 24px;
border-top: 1px solid var(--divider);
font-size: 13px;
color: var(--faint);
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
footer.doc .copy {
font-family: ui-monospace, "JetBrains Mono", monospace;
}
footer.doc a { color: var(--muted); border-bottom-color: transparent; }
footer.doc a:hover { color: var(--text); border-bottom-color: var(--divider); }
@media (max-width: 560px) {
.frame { padding: 36px 20px 96px; }
h1 { font-size: 28px; }
h2 { font-size: 18px; margin: 36px 0 10px; }
body { font-size: 15.5px; line-height: 1.65; }
}
</style>
</head>
<body>
<div class="frame">
<header class="doc">
<div class="brand">
<span class="brand-dot" aria-hidden="true"></span>
<span class="brand-name">Vojo</span>
<span class="brand-sep">·</span>
<span>Account deletion</span>
</div>
<h1 data-i18n-h1>Delete your account</h1>
<p class="effective" data-i18n-effective>Vojo Project · vojo.chat</p>
<div class="lang-switch" role="group" aria-label="Language">
<button type="button" data-lang="en" aria-pressed="true">English</button>
<span class="sep" aria-hidden="true">/</span>
<button type="button" data-lang="ru" aria-pressed="false">Русский</button>
</div>
</header>
<section lang="en" data-lang="en">
<p>This page explains how to request deletion of your <b>Vojo</b> account and the
data associated with it on the <code>vojo.chat</code> homeserver.</p>
<p>Vojo is maintained by the Vojo Project, an independent developer. The application
does not yet expose an in-app "Delete account" button; until it does, deletion is
handled by request to the address below. We reply to every request.</p>
<h2>How to request deletion</h2>
<ol>
<li>Send an email from any address to
<a class="email" href="mailto:vojochatdev@gmail.com?subject=Delete%20account">vojochatdev@gmail.com</a>
with the subject <b>“Delete account”</b>.</li>
<li>In the body of the email, include your full Matrix user ID — the
<code>@username:vojo.chat</code> identifier shown in Settings → Account.
If you have lost access to the account, describe enough detail (approximate
creation date, the email or recovery info you remember) so we can identify it
with reasonable certainty.</li>
<li>We acknowledge the request within a few business days and complete deletion
within <b>thirty days</b> of the original request.</li>
</ol>
<div class="callout">
<p><b>Contact:</b>
<a class="email" href="mailto:vojochatdev@gmail.com">vojochatdev@gmail.com</a></p>
<p style="margin-top: 6px;"><b>Subject line:</b> Delete account</p>
</div>
<h2>What gets deleted</h2>
<ul>
<li>Your account record on the <code>vojo.chat</code> homeserver (user profile,
display name, avatar).</li>
<li>Your active sessions and authentication tokens.</li>
<li>Your encryption keys held server-side.</li>
<li>Media files you uploaded to the <code>vojo.chat</code> media storage.</li>
<li>The push-notification registration (Firebase Cloud Messaging token) bound
to the account.</li>
</ul>
<h2>What we cannot delete on your behalf</h2>
<ul>
<li><b>Messages already delivered to other servers.</b> Matrix is a federated
network: when you sent a message into a room that included participants on
other homeservers, those messages were replicated to their servers and are
no longer under our control.</li>
<li><b>Copies held by other participants.</b> Anything you sent into a
conversation has been received by the people you sent it to. We cannot
reach into their devices or accounts.</li>
<li><b>Messages in rooms you no longer participate in.</b> A few residual
events (membership records, room state) may remain on the homeserver for
room consistency, but they no longer link to your deleted account.</li>
<li><b>Bridged third-party networks.</b> If you used a Telegram, Discord or
WhatsApp bridge, those networks hold their own copies of your messages
governed by their own retention policies; deactivating your Vojo account
does not delete data on those services.</li>
</ul>
<h2>Data retained after deletion</h2>
<p>After your account is deactivated, server access logs may retain your IP address
and request timestamps for up to thirty additional days as part of normal abuse-
prevention rotation. Backup snapshots covering the period before deletion are
rotated out within thirty days. After that period, no personal data attributable to
your account remains on our infrastructure.</p>
<h2>If you'd prefer to stay but stop receiving notifications</h2>
<p>If you only want notifications to stop and not to lose your account entirely,
you can simply sign out of the application on your device — this removes the
push-notification binding without deactivating the account.</p>
<h2>Privacy Policy</h2>
<p>For a fuller description of what we hold and why, see our
<a href="https://vojo.chat/privacy">Privacy Policy</a>.</p>
</section>
<section lang="ru" data-lang="ru" hidden>
<p>На этой странице описано, как запросить удаление вашей учётной записи <b>Vojo</b>
и связанных с ней данных на homeserver-е <code>vojo.chat</code>.</p>
<p>Vojo поддерживается проектом Vojo, независимым разработчиком. В приложении пока
нет кнопки «Удалить аккаунт»; до её появления удаление выполняется по запросу на
адрес ниже. Мы отвечаем на каждое обращение.</p>
<h2>Как запросить удаление</h2>
<ol>
<li>Напишите письмо с любого адреса на
<a class="email" href="mailto:vojochatdev@gmail.com?subject=Delete%20account">vojochatdev@gmail.com</a>
с темой <b>«Delete account»</b>.</li>
<li>В тексте письма укажите полный Matrix user ID — идентификатор вида
<code>@username:vojo.chat</code>, который виден в Настройки → Аккаунт.
Если доступа к аккаунту больше нет, опишите достаточно деталей
(примерная дата создания, email или recovery-информация, которую помните),
чтобы мы могли надёжно его опознать.</li>
<li>Мы подтверждаем получение запроса в течение нескольких рабочих дней и
завершаем удаление в течение <b>тридцати дней</b> с момента
первоначального обращения.</li>
</ol>
<div class="callout">
<p><b>Контакт:</b>
<a class="email" href="mailto:vojochatdev@gmail.com">vojochatdev@gmail.com</a></p>
<p style="margin-top: 6px;"><b>Тема письма:</b> Delete account</p>
</div>
<h2>Что будет удалено</h2>
<ul>
<li>Запись вашего аккаунта на homeserver-е <code>vojo.chat</code> (профиль,
отображаемое имя, аватар).</li>
<li>Активные сессии и токены аутентификации.</li>
<li>Ключи шифрования, хранящиеся на сервере.</li>
<li>Медиа-файлы, которые вы загружали в медиа-хранилище
<code>vojo.chat</code>.</li>
<li>Регистрация push-уведомлений (Firebase Cloud Messaging токен),
привязанная к аккаунту.</li>
</ul>
<h2>Что мы не можем удалить за вас</h2>
<ul>
<li><b>Сообщения, уже доставленные на другие серверы.</b> Matrix — федеративная
сеть: когда вы отправляли сообщение в комнату, где были участники с других
homeserver-ов, эти сообщения реплицировались на их серверы и больше не
находятся под нашим контролем.</li>
<li><b>Копии у других участников.</b> Всё, что вы отправили в переписку, уже
получили те, кому вы это отправляли. Мы не можем добраться до их устройств
и учётных записей.</li>
<li><b>Сообщения в комнатах, где вас больше нет.</b> Несколько остаточных
событий (записи о членстве, состояние комнаты) могут оставаться на
homeserver-е для целостности комнаты, но они уже не связаны с вашим
удалённым аккаунтом.</li>
<li><b>Подключённые сторонние сети.</b> Если вы пользовались мостами в
Telegram, Discord или WhatsApp, эти сети хранят собственные копии ваших
сообщений по своим политикам; деактивация аккаунта Vojo не удаляет данные
на этих сервисах.</li>
</ul>
<h2>Данные, остающиеся после удаления</h2>
<p>После деактивации аккаунта серверные журналы доступа могут содержать ваш IP-
адрес и время запросов ещё до тридцати дней в рамках обычной ротации. Резервные
копии, охватывающие период до удаления, ротируются и пропадают в течение
тридцати дней. По истечении этого срока никакие персональные данные, относящиеся
к вашему аккаунту, на нашей инфраструктуре не остаются.</p>
<h2>Если хотите остаться, но прекратить уведомления</h2>
<p>Если вы хотите только перестать получать уведомления, не теряя аккаунт целиком —
просто выйдите из приложения на своём устройстве. Это снимет привязку к push-
сервису без деактивации учётной записи.</p>
<h2>Политика конфиденциальности</h2>
<p>Более полное описание того, что у нас хранится и зачем — в
<a href="https://vojo.chat/privacy">Политике конфиденциальности</a>.</p>
</section>
<footer class="doc">
<span class="copy">© Vojo Project · 2026</span>
<a href="https://vojo.chat">vojo.chat</a>
</footer>
</div>
<script>
(function () {
var buttons = document.querySelectorAll('.lang-switch button');
var sections = document.querySelectorAll('section[data-lang]');
var h1 = document.querySelector('[data-i18n-h1]');
var eff = document.querySelector('[data-i18n-effective]');
var H1 = { en: 'Delete your account', ru: 'Удалить аккаунт' };
var EFF = { en: 'Vojo Project · vojo.chat', ru: 'Проект Vojo · vojo.chat' };
function setLang(lang) {
buttons.forEach(function (b) {
b.setAttribute('aria-pressed', String(b.dataset.lang === lang));
});
sections.forEach(function (s) {
s.hidden = s.dataset.lang !== lang;
});
if (h1 && H1[lang]) h1.textContent = H1[lang];
if (eff && EFF[lang]) eff.textContent = EFF[lang];
document.documentElement.lang = lang;
document.title = (lang === 'ru' ? 'Vojo — Удаление аккаунта' : 'Vojo — Account deletion');
try { localStorage.setItem('vojo-delete-lang', lang); } catch (e) {}
}
buttons.forEach(function (b) {
b.addEventListener('click', function () { setLang(b.dataset.lang); });
});
var stored = null;
try { stored = localStorage.getItem('vojo-delete-lang'); } catch (e) {}
setLang(stored || 'en');
})();
</script>
</body>
</html>

View file

@ -269,6 +269,10 @@
"clear_cache_title": "Clear Cache & Reload",
"clear_cache_desc": "Clear all your locally stored data and reload from server.",
"clear_cache": "Clear Cache",
"legal": "Legal",
"privacy_policy_title": "Privacy Policy",
"privacy_policy_desc": "How your data is handled.",
"privacy_policy_open": "Open",
"credits": "Credits",
"devtools_title": "Developer Tools",

View file

@ -269,6 +269,10 @@
"clear_cache_title": "Очистить кэш и перезагрузить",
"clear_cache_desc": "Удалить все локально сохранённые данные и загрузить заново с сервера.",
"clear_cache": "Очистить кэш",
"legal": "Юридическое",
"privacy_policy_title": "Политика конфиденциальности",
"privacy_policy_desc": "Как обрабатываются ваши данные.",
"privacy_policy_open": "Открыть",
"credits": "Благодарности",
"devtools_title": "Инструменты разработчика",

400
public/privacy.html Normal file
View file

@ -0,0 +1,400 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="theme-color" content="#0d0e11" />
<meta name="robots" content="index,follow" />
<title>Vojo — Privacy Policy</title>
<style>
:root {
--bg: #0d0e11;
--panel: #181a20;
--surface: #21232b;
--text: #e6e6e9;
--text-strong: #f4f4f6;
--muted: rgba(230, 230, 233, 0.62);
--faint: rgba(230, 230, 233, 0.38);
--divider: rgba(255, 255, 255, 0.08);
--fleet: #9580ff;
--fleet-soft: #a59cff;
color-scheme: dark;
}
*, *::before, *::after { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--text);
font-family: -apple-system, "SF Pro Text", "Inter", system-ui, sans-serif;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
body {
min-height: 100vh;
line-height: 1.7;
font-size: 16px;
}
.frame {
max-width: 680px;
margin: 0 auto;
padding: 56px 28px 120px;
}
header.doc {
padding-bottom: 28px;
margin-bottom: 40px;
border-bottom: 1px solid var(--divider);
}
.brand {
display: flex;
align-items: center;
gap: 9px;
margin-bottom: 22px;
color: var(--muted);
font-size: 13px;
letter-spacing: 0.2px;
}
.brand-dot {
width: 8px;
height: 8px;
border-radius: 99px;
background: var(--fleet);
}
.brand-name { color: var(--text-strong); font-weight: 600; }
.brand-sep { color: var(--faint); }
h1 {
font-size: 34px;
line-height: 1.15;
font-weight: 600;
margin: 0 0 10px;
letter-spacing: -0.6px;
color: var(--text-strong);
}
.effective { color: var(--muted); font-size: 14px; margin: 0; }
.lang-switch {
display: inline-flex;
margin-top: 24px;
font-size: 13px;
color: var(--muted);
}
.lang-switch button {
appearance: none;
-webkit-appearance: none;
background: transparent;
border: 0;
padding: 4px 0;
font: inherit;
color: var(--muted);
cursor: pointer;
transition: color .15s ease;
}
.lang-switch button:hover { color: var(--text); }
.lang-switch button[aria-pressed="true"] {
color: var(--text-strong);
font-weight: 600;
}
.lang-switch .sep {
padding: 0 10px;
color: var(--faint);
user-select: none;
}
h2 {
font-size: 20px;
font-weight: 600;
margin: 44px 0 12px;
letter-spacing: -0.2px;
color: var(--text-strong);
scroll-margin-top: 24px;
}
p { margin: 0 0 14px; color: var(--text); }
ul {
margin: 0 0 18px;
padding-left: 22px;
}
ul li { margin: 8px 0; padding-left: 4px; }
ul li::marker { color: var(--faint); }
ul li b, p b { color: var(--text-strong); font-weight: 600; }
a {
color: var(--fleet-soft);
text-decoration: none;
border-bottom: 1px solid rgba(165, 156, 255, 0.35);
transition: color .15s ease, border-color .15s ease;
}
a:hover {
color: #c0b9ff;
border-bottom-color: rgba(192, 185, 255, 0.7);
}
section[hidden] { display: none; }
section > h2:first-of-type { margin-top: 0; }
footer.doc {
margin-top: 64px;
padding-top: 24px;
border-top: 1px solid var(--divider);
font-size: 13px;
color: var(--faint);
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
footer.doc .copy {
font-family: ui-monospace, "JetBrains Mono", monospace;
}
footer.doc a { color: var(--muted); border-bottom-color: transparent; }
footer.doc a:hover { color: var(--text); border-bottom-color: var(--divider); }
@media (max-width: 560px) {
.frame { padding: 36px 20px 96px; }
h1 { font-size: 28px; }
h2 { font-size: 18px; margin: 36px 0 10px; }
body { font-size: 15.5px; line-height: 1.65; }
}
</style>
</head>
<body>
<div class="frame">
<header class="doc">
<div class="brand">
<span class="brand-dot" aria-hidden="true"></span>
<span class="brand-name">Vojo</span>
<span class="brand-sep">·</span>
<span>Legal</span>
</div>
<h1 data-i18n-h1>Privacy Policy</h1>
<p class="effective" data-i18n-effective>Effective 13 May 2026</p>
<div class="lang-switch" role="group" aria-label="Language">
<button type="button" data-lang="en" aria-pressed="true">English</button>
<span class="sep" aria-hidden="true">/</span>
<button type="button" data-lang="ru" aria-pressed="false">Русский</button>
</div>
</header>
<section lang="en" data-lang="en">
<p>This is the privacy policy for <b>Vojo</b>, a chat app built on the open
<a href="https://matrix.org" rel="noopener">Matrix</a> protocol. It's maintained by
the Vojo Project, an independent developer. If you have questions about anything
here, write to <a href="mailto:vojochatdev@gmail.com">vojochatdev@gmail.com</a>.</p>
<p>We try to keep this short and readable. If something is unclear, ask.</p>
<h2>How Vojo works, briefly</h2>
<p>Your messages, profile and rooms live on a Matrix server. By default that's
<code>vojo.chat</code>, which we run. You can also sign in to any other Matrix
server you trust — if you do, the operator of that server is the one holding your
data, not us.</p>
<h2>What we hold and what we use it for</h2>
<p>To make the app work we keep the obvious things: your account, the messages and
rooms you send and receive, the media you share, and basic technical data (IP
address, connection times) generated when your device talks to our servers. Your
device also caches messages and keys locally so you can read them offline and stay
signed in.</p>
<p>Direct conversations are end-to-end encrypted by default. In an encrypted room
we can see who's talking to whom and when, but not what they're saying. In an
unencrypted room we see the content too.</p>
<p>Voice calls are encrypted between participants. When your device can't reach the
other side directly, the audio is relayed through our infrastructure on its way
through — we don't record it and we don't keep it.</p>
<p>We use this data to run the service: deliver messages, sync your devices, ring
your phone for incoming calls, keep limited logs to fight abuse and spam. That's
the whole list. No advertising, no analytics, no resale, no profiling.</p>
<h2>Who else is involved</h2>
<ul>
<li><b>Our hosting provider.</b> The
<a href="https://www.hostinger.com" rel="noopener">Hostinger</a>
infrastructure carrying <code>vojo.chat</code> sits in the European Union.</li>
<li><b>Google's push service.</b> Push notifications go through Google so your
phone can wake up and ring or buzz. For end-to-end encrypted chats the
notification only carries the routing info needed to fetch the message
locally — the content stays on your Matrix server. For unencrypted chats
Google may see a short preview (who, where, snippet). This is the only
routine reason data leaves the EU; we rely on the Standard Contractual
Clauses for that transfer.</li>
<li><b>Bot checks.</b> Signing up, and a couple of optional features, briefly
load a third-party "are you a human" check. That provider sees your
interaction with the puzzle and is governed by its own privacy policy.</li>
<li><b>Optional bridges.</b> If you choose to connect Telegram, Discord or
WhatsApp through Vojo, your messages with those networks have to pass
through bridge infrastructure we run, and the network itself sees them
too. None of this turns on unless you opt in.</li>
</ul>
<h2>Permissions on your phone</h2>
<p>On Android we ask for: the microphone (only used during calls); notifications
(so we can show you messages and ring for calls); permission to show calls over
the lock screen and to keep a call running with the screen off; and network access.
That's it. We don't touch your address book, photo library, SMS, precise location
or call log.</p>
<h2>How long we keep things</h2>
<p>Your messages and account stay on your Matrix server until you delete them or
ask us to deactivate the account. Deletion is processed within about thirty days.
Server access logs are kept for no more than thirty days and then rotated out.</p>
<p>Data cached on your device goes away when you uninstall Vojo or clear its data
in your phone's settings. Signing out ends your session but doesn't always scrub
every cached message immediately — the cleanest reset is an uninstall.</p>
<h2>Your rights</h2>
<p>If you live in the EU/EEA (and in many other places the law works similarly),
you can ask us to show you what we hold, fix something that's wrong, delete your
data, hand it over in a portable form, or stop a particular use. You can also
withdraw any consent you've given for optional features, and complain to your
local data-protection authority if you think we're handling things badly. Email
the address at the top and we'll take it from there.</p>
<h2>Kids, changes, contact</h2>
<p>Vojo isn't aimed at anyone under 16, and we don't knowingly collect data from
children. If we change this policy in a way that actually affects you, we'll
update the date above and try to flag it inside the app. The current version
always lives at <a href="https://vojo.chat/privacy">vojo.chat/privacy</a>. For
anything else: <a href="mailto:vojochatdev@gmail.com">vojochatdev@gmail.com</a>.</p>
</section>
<section lang="ru" data-lang="ru" hidden>
<p>Это политика конфиденциальности <b>Vojo</b> — чат-приложения на открытом
протоколе <a href="https://matrix.org" rel="noopener">Matrix</a>. Его поддерживает
проект Vojo, независимый разработчик. Если по тексту возникают вопросы — пишите
на <a href="mailto:vojochatdev@gmail.com">vojochatdev@gmail.com</a>.</p>
<p>Постарались уложиться в нормальный читаемый объём. Если что-то непонятно —
спрашивайте.</p>
<h2>Как устроено</h2>
<p>Ваши сообщения, профиль и список комнат живут на Matrix-сервере. По умолчанию
это <code>vojo.chat</code>, который держим мы. Вы можете войти на любой другой
Matrix-сервер, которому доверяете — если так, оператором ваших данных будет тот
сервер, не мы.</p>
<h2>Что у нас лежит и зачем</h2>
<p>Чтобы приложение работало, у нас лежат предсказуемые вещи: ваш аккаунт, ваши
сообщения и комнаты, медиа, и базовые технические данные (IP, время запросов),
которые возникают, когда устройство разговаривает с нашими серверами. На самом
устройстве лежит локальный кэш сообщений и ключей — чтобы можно было читать
офлайн и не входить заново каждый раз.</p>
<p>Личные переписки по умолчанию защищены end-to-end шифрованием. В зашифрованной
комнате мы видим, кто кому пишет и когда, но не видим, что именно. В
незашифрованных комнатах мы видим и содержимое.</p>
<p>Голосовые звонки шифруются между участниками. Если устройство не может
дотянуться до собеседника напрямую, аудио ретранслируется через нашу
инфраструктуру по пути — мы его не записываем и не храним.</p>
<p>Все эти данные мы используем для того, чтобы сервис работал: доставка
сообщений, синхронизация устройств, входящие звонки, ограниченные логи для борьбы
со спамом и злоупотреблениями. Это весь список. Никакой рекламы, аналитики,
перепродажи или профилирования.</p>
<h2>Кто ещё в этом участвует</h2>
<ul>
<li><b>Наш хостинг.</b>
<a href="https://www.hostinger.com" rel="noopener">Hostinger</a> держит
инфраструктуру <code>vojo.chat</code> в Европейском союзе.</li>
<li><b>Push-сервис Google.</b> Push-уведомления идут через Google, чтобы
телефон проснулся и зазвонил. Для зашифрованных переписок уведомление
несёт только маршрутную информацию, нужную для того чтобы подгрузить
сообщение локально — содержимое остаётся на Matrix-сервере. Для
незашифрованных Google может видеть короткий предпросмотр (кто, где,
фрагмент). Это единственный регулярный случай, когда данные выходят за
пределы ЕС; передача идёт по Стандартным договорным условиям Европейской
комиссии.</li>
<li><b>Капча.</b> При регистрации и в паре дополнительных функций ненадолго
подгружается сторонняя проверка «вы не робот». Этот провайдер видит ваше
взаимодействие с капчей и регулируется собственной политикой.</li>
<li><b>Опциональные мосты.</b> Если вы решите подключить Telegram, Discord
или WhatsApp через Vojo, сообщения с этими сетями неизбежно проходят
через мостовую инфраструктуру, которую держим мы, и сама сеть тоже их
видит. Без вашего явного действия это не включается.</li>
</ul>
<h2>Разрешения на телефоне</h2>
<p>На Android приложение просит: микрофон (используется только во время звонка);
уведомления (чтобы показывать сообщения и звонки); право показывать звонок поверх
локскрина и держать его при выключенном экране; доступ к сети. И всё. Мы не
трогаем адресную книгу, фотогалерею, SMS, точную геолокацию и журнал вызовов.</p>
<h2>Сколько мы это храним</h2>
<p>Сообщения и аккаунт лежат на Matrix-сервере до тех пор, пока вы их не
удалите или не попросите деактивировать аккаунт. Удаление обрабатывается в
течение тридцати дней. Журналы доступа на сервере хранятся не более тридцати
дней, затем уходят на ротацию.</p>
<p>Кэш на устройстве пропадает, когда вы удаляете Vojo или очищаете его данные в
настройках телефона. Выход из аккаунта прекращает сессию, но не всегда подчищает
весь кэш сразу — самый чистый способ обнулиться — это переустановка.</p>
<h2>Ваши права</h2>
<p>Если вы живёте в ЕС/ЕЭЗ (а во многих других местах закон работает похоже), вы
можете попросить нас показать, что у нас лежит, поправить неверное, удалить,
отдать в переносимом виде, остановить конкретное использование. Можно отозвать
согласие на дополнительные функции и пожаловаться в местный надзорный орган, если
кажется, что мы что-то делаем не так. Напишите на адрес сверху, и пойдём
разбираться.</p>
<h2>Дети, изменения, контакты</h2>
<p>Vojo не рассчитан на людей младше 16 лет, и мы сознательно не собираем данные
детей. Если что-то поменяется так, что это вас реально касается, обновим дату в
начале и постараемся отметить это в самом приложении. Текущая версия всегда
лежит по адресу <a href="https://vojo.chat/privacy">vojo.chat/privacy</a>. По
любым другим вопросам:
<a href="mailto:vojochatdev@gmail.com">vojochatdev@gmail.com</a>.</p>
</section>
<footer class="doc">
<span class="copy">© Vojo Project · 2026</span>
<a href="https://vojo.chat">vojo.chat</a>
</footer>
</div>
<script>
(function () {
var buttons = document.querySelectorAll('.lang-switch button');
var sections = document.querySelectorAll('section[data-lang]');
var h1 = document.querySelector('[data-i18n-h1]');
var eff = document.querySelector('[data-i18n-effective]');
var H1 = { en: 'Privacy Policy', ru: 'Политика конфиденциальности' };
var EFF = { en: 'Effective 13 May 2026', ru: 'Действует с 13 мая 2026 г.' };
function setLang(lang) {
buttons.forEach(function (b) {
b.setAttribute('aria-pressed', String(b.dataset.lang === lang));
});
sections.forEach(function (s) {
s.hidden = s.dataset.lang !== lang;
});
if (h1 && H1[lang]) h1.textContent = H1[lang];
if (eff && EFF[lang]) eff.textContent = EFF[lang];
document.documentElement.lang = lang;
document.title = (lang === 'ru' ? 'Vojo — Политика конфиденциальности' : 'Vojo — Privacy Policy');
try { localStorage.setItem('vojo-privacy-lang', lang); } catch (e) {}
}
buttons.forEach(function (b) {
b.addEventListener('click', function () { setLang(b.dataset.lang); });
});
var stored = null;
try { stored = localStorage.getItem('vojo-privacy-lang'); } catch (e) {}
setLang(stored || 'en');
})();
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

View file

@ -80,6 +80,35 @@ export function About({ requestClose }: AboutProps) {
/>
</SequenceCard>
</Box>
<Box direction="Column" gap="100">
<Text size="L400">{t('Settings.legal')}</Text>
<SequenceCard
className={SequenceCardStyle}
variant="Background"
direction="Column"
gap="400"
>
<SettingTile
title={t('Settings.privacy_policy_title')}
description={t('Settings.privacy_policy_desc')}
after={
<Button
as="a"
href="https://vojo.chat/privacy"
target="_blank"
rel="noopener noreferrer"
variant="Secondary"
fill="Soft"
size="300"
radii="300"
outlined
>
<Text size="B300">{t('Settings.privacy_policy_open')}</Text>
</Button>
}
/>
</SequenceCard>
</Box>
<Box direction="Column" gap="100">
<Text size="L400">{t('Settings.credits')}</Text>
<SequenceCard

View file

@ -92,6 +92,14 @@ const copyFiles = {
src: 'public/locales',
dest: 'public/',
},
{
src: 'public/privacy.html',
dest: '',
},
{
src: 'public/delete-account.html',
dest: '',
},
],
};
@ -163,6 +171,38 @@ function serveLocalConfigOverlay() {
};
}
// Dev-mirror for the Caddy directives that serve static legal pages in
// prod:
// try_files {path} {path}.html /index.html (extensionless URLs)
// redir /<page>/* /<page> permanent (collapse subpaths)
// vite-plugin-static-copy maps a single fileMap key per target, so it only
// answers /<page>.html — every other shape (/<page>, /<page>/,
// /<page>/lobby) falls into vite's SPA fallback, cinny's router reads the
// prefix as a Matrix space alias, and redirects to /<page>/lobby. This
// middleware short-circuits the entire /<page>* prefix to the static file.
// Add a new entry here when a new static legal/info page is introduced.
function serveStaticPagesDevMirror() {
const pages = [
{ prefix: '/privacy', file: 'public/privacy.html' },
{ prefix: '/delete-account', file: 'public/delete-account.html' },
];
return {
name: 'vite-plugin-serve-static-pages-dev-mirror',
apply: 'serve',
configureServer(server) {
pages.forEach(({ prefix, file }) => {
server.middlewares.use(prefix, (_req, res, next) => {
const filePath = path.resolve(file);
if (!fs.existsSync(filePath)) return next();
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.setHeader('Cache-Control', 'no-cache');
res.end(fs.readFileSync(filePath));
});
});
},
};
}
function serverMatrixSdkCryptoWasm(wasmFilePath) {
return {
name: 'vite-plugin-serve-matrix-sdk-crypto-wasm',
@ -209,6 +249,7 @@ export default defineConfig({
},
plugins: [
serveLocalConfigOverlay(),
serveStaticPagesDevMirror(),
serverMatrixSdkCryptoWasm('/node_modules/.vite/deps/pkg/matrix_sdk_crypto_wasm_bg.wasm'),
topLevelAwait({
// The export name of top-level await promise for each chunk module