feat(legal): publish Privacy Policy and account-deletion pages with About-screen link and Play Store feature graphic
This commit is contained in:
parent
21657c75dd
commit
7b5a99cdce
7 changed files with 866 additions and 0 deletions
388
public/delete-account.html
Normal file
388
public/delete-account.html
Normal 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>
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
400
public/privacy.html
Normal 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>
|
||||
BIN
public/res/store/feature-graphic.png
Normal file
BIN
public/res/store/feature-graphic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 160 KiB |
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue