From de5ab9186ced4fa4d223d42232e86b1acfe1b6d1 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Mon, 13 Jul 2026 13:22:56 +0200 Subject: [PATCH] feat(legal): bilingual (RU + EN) legal pages with a language + theme switcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add English versions of the privacy policy, EULA and offer (ui/legal/*_en.md) and render each legal page as one self-contained bilingual document: both language bodies plus a header language toggle and theme toggle (no back), a small inline ES5 script that switches language client-side (no reload, default Russian + persisted) and applies the theme (system default + persisted override) — mirroring the landing. The offer's English view transliterates the Russian product names spliced from the live catalog. renderLegalHtml now takes both language sources; renderOffer splices the price list into both; the renderer bakes and reads the _en sources and pre-renders the static pages at boot. Also wrap the /offer/ contour probe in the same retry+timeout as the legal probe (the offer page is now bilingual and larger). Docs + renderer/ui tests updated. --- .gitea/workflows/ci.yaml | 19 +- docs/ARCHITECTURE.md | 8 +- docs/FUNCTIONAL.md | 7 +- docs/FUNCTIONAL_ru.md | 8 +- renderer/Dockerfile | 2 +- renderer/README.md | 24 +- renderer/src/legal.mjs | 27 ++- renderer/src/offer.mjs | 22 +- renderer/src/server.mjs | 33 +-- renderer/test/legal.test.mjs | 22 +- renderer/test/offer.test.mjs | 24 +- ui/legal/eula_en.md | 417 +++++++++++++++++++++++++++++++++++ ui/legal/offer_en.md | 155 +++++++++++++ ui/legal/privacy_en.md | 140 ++++++++++++ ui/src/lib/offer.test.ts | 74 ++++--- ui/src/lib/offer.ts | 176 ++++++++++++--- 16 files changed, 1018 insertions(+), 140 deletions(-) create mode 100644 ui/legal/eula_en.md create mode 100644 ui/legal/offer_en.md create mode 100644 ui/legal/privacy_en.md diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 43242d8..a045d17 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -522,10 +522,21 @@ jobs: # (the seller INN, §11) AND that the pricing marker was substituted (proves the fetch + # splice ran), never just the status. Data-independent: an empty catalog still substitutes # the marker with nothing, so "pricing_template" must be absent either way. - out="$(docker run --rm --network edge alpine:3.20 wget -q -O - http://scrabble/offer/ 2>&1 || true)" - if echo "$out" | grep -q "290210610742" && ! echo "$out" | grep -q "pricing_template"; then - echo "ok: /offer/ serves the rendered offer with the price list spliced in" - else + # Full body is needed (the INN is in §11 and the marker check spans the page), so this + # cannot be a head-only probe like the legal one. Retry with a timeout instead: the offer is + # now bilingual (RU + EN, larger), and a single-shot fetch can race the renderer's restart in + # the same deploy. + ok= + for i in $(seq 1 20); do + out="$(docker run --rm --network edge alpine:3.20 wget -q -T 10 -O - http://scrabble/offer/ 2>&1 || true)" + if echo "$out" | grep -q "290210610742" && ! echo "$out" | grep -q "pricing_template"; then + echo "ok: /offer/ serves the rendered offer with the price list spliced in" + ok=1 + break + fi + sleep 3 + done + if [ -z "$ok" ]; then echo "FAIL: /offer/ did not serve the rendered offer (route fell through, or the price splice failed)" docker logs --tail 50 scrabble-renderer || true docker logs --tail 50 scrabble-backend || true diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 753ee65..2204315 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -970,9 +970,11 @@ image option is not offered. The same sidecar also serves the **public legal pages** — the offer at `/offer/`, the privacy policy at `/privacy/` and the EULA at `/eula/` — the edge-exposed routes on it (caddy routes them here; its `/render` stays internal). All three reuse the shared `ui/src/lib/offer.ts` -`renderLegalHtml` (one renderer, no drift) over their owner-edited `ui/legal/*_ru.md`, baked into -the image. `/privacy/` and `/eula/` are static; the offer additionally splices in the **live price -list** (§4.4): it +`renderLegalHtml` (one renderer, no drift) over their owner-edited `ui/legal/*_{ru,en}.md` — each is +**bilingual (RU + EN)** with a client-side language + theme switcher (default Russian, persisted; +the offer's EN view transliterates the Russian product names) — baked into the image. `/privacy/` +and `/eula/` are static; the offer additionally splices in the **live price list** (§4.4) into both +languages: it fetches the two catalog tables as markdown from the backend's internal `/api/v1/internal/offer/pricing` at the `<#pricing_template#>` marker, then renders the page. The backend projects the tables from the active catalog through `payments.Money` (no float reaches the diff --git a/docs/FUNCTIONAL.md b/docs/FUNCTIONAL.md index 2265b32..32d31e6 100644 --- a/docs/FUNCTIONAL.md +++ b/docs/FUNCTIONAL.md @@ -33,9 +33,10 @@ pinned to the production origin, JSON-LD, the favicon set and `robots.txt`), whi shell is `noindex` — the landing is the only indexable page. The footer links to the three **legal documents** — the **user agreement** (`/eula/`), the **privacy policy** (`/privacy/`) and the **public offer** (`/offer/`) — and, beside them, **feedback** (the Telegram bot the documents name as -the seller's contact). Each is rendered on demand from its `ui/legal/*_ru.md` source by the render -sidecar; the offer additionally splices in its **price list** (§4.4) generated from the live product -catalog, so the published prices always match what is currently on sale — no redeploy to update them. +the seller's contact). Each is **bilingual (RU/EN)** with a language + theme switcher and is rendered +from its `ui/legal/*_{ru,en}.md` sources by the render sidecar; the offer additionally splices in its +**price list** (§4.4) generated from the live product catalog, so the published prices always match +what is currently on sale — no redeploy to update them. On the plain web the client is an **installable PWA**: a logged-out player sees an install call-to-action under the login form (and at the bottom of Settings) that installs the app to the diff --git a/docs/FUNCTIONAL_ru.md b/docs/FUNCTIONAL_ru.md index 9572ced..c8808cd 100644 --- a/docs/FUNCTIONAL_ru.md +++ b/docs/FUNCTIONAL_ru.md @@ -35,10 +35,10 @@ Open Graph, которую используют превью ссылок в Tel `noindex` — индексируется только посадочная страница. В подвале — ссылки на три **юридических документа** — **пользовательское соглашение** (`/eula/`), **политику конфиденциальности** (`/privacy/`) и **публичную оферту** (`/offer/`) — и рядом на **обратную связь** (тот самый -Telegram-бот, который документы указывают как контакт Продавца). Каждый рендерится по запросу из -своего исходника `ui/legal/*_ru.md` сайдкаром-рендерером; оферта дополнительно подставляет -**перечень стоимости** (§4.4), формируемый из живого каталога товаров, поэтому опубликованные цены -всегда совпадают с тем, что сейчас в продаже — без передеплоя. +Telegram-бот, который документы указывают как контакт Продавца). Каждый — **двуязычный (RU/EN)** с +переключателем языка и темы, рендерится из исходников `ui/legal/*_{ru,en}.md` сайдкаром-рендерером; +оферта дополнительно подставляет **перечень стоимости** (§4.4), формируемый из живого каталога +товаров, поэтому опубликованные цены всегда совпадают с тем, что сейчас в продаже — без передеплоя. В обычном вебе клиент — **устанавливаемое PWA**: незалогиненный игрок видит призыв к установке под формой входа (и внизу «Настроек»), который в один тап ставит приложение на рабочий стол diff --git a/renderer/Dockerfile b/renderer/Dockerfile index c8f7fc2..be570e4 100644 --- a/renderer/Dockerfile +++ b/renderer/Dockerfile @@ -27,7 +27,7 @@ COPY renderer/src/server.mjs renderer/src/render.mjs renderer/src/offer.mjs rend # The public legal pages: the owner-edited markdown, read once at boot. GET /offer/ also splices in # the live price list fetched from the backend at request time; GET /privacy/ and GET /eula/ are # static (no dynamic data). -COPY ui/legal/offer_ru.md ui/legal/privacy_ru.md ui/legal/eula_ru.md ./legal/ +COPY ui/legal/offer_ru.md ui/legal/offer_en.md ui/legal/privacy_ru.md ui/legal/privacy_en.md ui/legal/eula_ru.md ui/legal/eula_en.md ./legal/ USER node EXPOSE 8090 CMD ["node", "src/server.mjs"] diff --git a/renderer/README.md b/renderer/README.md index bb25521..73b6537 100644 --- a/renderer/README.md +++ b/renderer/README.md @@ -4,7 +4,8 @@ An internal Node service that runs shared `ui/src/lib` renderers server-side — image build time (`src/entry.ts` → esbuild → `dist/gameimage.mjs`) so there is one renderer and no drift from the browser. It serves two surfaces: the finished-game export **PNG** (on [skia-canvas](https://github.com/samizdatco/skia-canvas), pixel-identical to the design the owner -signed off) and the public **legal pages** (the offer, the privacy policy and the EULA). +signed off) and the public **legal pages** (the offer, the privacy policy and the EULA), each +bilingual (RU + EN) with a language + theme switcher. ## Interface @@ -16,13 +17,16 @@ signed off) and the public **legal pages** (the offer, the privacy policy and th - `GET /offer/` — the public offer page as `text/html`: the owner-edited `ui/legal/offer_ru.md` (baked into the image, read at boot) with the live catalog **price list** (§4.4) fetched as markdown from the backend's internal `/api/v1/internal/offer/pricing` (`RENDERER_BACKEND_URL`) - and spliced in at the `<#pricing_template#>` marker, then rendered by the shared - `ui/src/lib/offer.ts` `renderLegalHtml`. `GET /offer` → 301 `/offer/`. Caddy routes `/offer/` - here; a backend outage yields a 502, never a stale price list. + and spliced in at the `<#pricing_template#>` marker of both language sources (`offer_ru.md` / + `offer_en.md`), then rendered as one bilingual page by the shared `ui/src/lib/offer.ts` + `renderLegalHtml` (the English view transliterates the Russian product names client-side). + `GET /offer` → 301 `/offer/`. Caddy routes `/offer/` here; a backend outage yields a 502, never a + stale price list. - `GET /privacy/` — the privacy policy, and `GET /eula/` the end-user licence agreement, both as - `text/html`: the owner-edited `ui/legal/privacy_ru.md` / `ui/legal/eula_ru.md` (baked in, read at - boot), rendered once at boot by the same shared `renderLegalHtml` and served from cache. **Static** — no backend fetch. `GET /privacy` - / `GET /eula` → 301 to the trailing-slash form. Caddy routes these here too. + `text/html`: the owner-edited `ui/legal/{privacy,eula}_{ru,en}.md` (baked in, read at boot), + rendered once at boot as one bilingual page by the same shared `renderLegalHtml` and served from + cache. **Static** — no backend fetch. `GET /privacy` / `GET /eula` → 301 to the trailing-slash + form. Caddy routes these here too. - `GET /healthz` — liveness (the compose healthcheck the backend's `depends_on` gates on). The service renders and nothing else: for the PNG, authentication, the participant check and the @@ -40,8 +44,10 @@ pnpm test # bundles, then node --test (PNG smoke + offer/legal rende # local run on :8090 (RENDERER_PORT overrides). The server reads all three legal sources at boot, so # point each at its ui/legal source (the baked paths do not exist in a local checkout); /offer/ also # needs a reachable backend for the price fetch: -RENDERER_OFFER_MD=../ui/legal/offer_ru.md RENDERER_PRIVACY_MD=../ui/legal/privacy_ru.md \ - RENDERER_EULA_MD=../ui/legal/eula_ru.md RENDERER_BACKEND_URL=http://localhost:8080 node src/server.mjs +RENDERER_OFFER_MD=../ui/legal/offer_ru.md RENDERER_OFFER_EN_MD=../ui/legal/offer_en.md \ + RENDERER_PRIVACY_MD=../ui/legal/privacy_ru.md RENDERER_PRIVACY_EN_MD=../ui/legal/privacy_en.md \ + RENDERER_EULA_MD=../ui/legal/eula_ru.md RENDERER_EULA_EN_MD=../ui/legal/eula_en.md \ + RENDERER_BACKEND_URL=http://localhost:8080 node src/server.mjs ``` The runtime image (`renderer/Dockerfile`, `node:22-slim`) bakes in Liberation Sans (the diff --git a/renderer/src/legal.mjs b/renderer/src/legal.mjs index f19899e..941e66e 100644 --- a/renderer/src/legal.mjs +++ b/renderer/src/legal.mjs @@ -1,21 +1,24 @@ -// The public legal pages (GET /privacy/, GET /eula/): the owner-edited markdown under ui/legal/, -// rendered by the SAME shared renderLegalHtml the offer uses (bundled from ui/src/lib/offer). Unlike -// the offer, these carry no dynamic data — no backend fetch, no marker splice. Kept out of server.mjs -// so the per-page title/canonical presets are unit-testable without HTTP (test/legal.test.mjs). +// The public legal pages (GET /privacy/, GET /eula/): the owner-edited RU + EN markdown under +// ui/legal/, rendered by the SAME shared renderLegalHtml the offer uses (bundled from ui/src/lib/offer) +// into one bilingual page with a language + theme switcher. Unlike the offer, these carry no dynamic +// data — no backend fetch, no marker splice. Kept out of server.mjs so the per-page title/canonical +// presets are unit-testable without HTTP (test/legal.test.mjs). import { renderLegalHtml } from '../dist/gameimage.mjs'; -// renderPrivacy renders the privacy-policy markdown as the standalone /privacy/ page. -export function renderPrivacy(markdown) { - return renderLegalHtml(markdown, { - title: 'Политика конфиденциальности — Эрудит', +// renderPrivacy renders the privacy-policy markdown (RU + EN) as the standalone /privacy/ page. +export function renderPrivacy(ruMd, enMd) { + return renderLegalHtml({ + ru: { md: ruMd, title: 'Политика конфиденциальности — Эрудит' }, + en: { md: enMd, title: 'Privacy Policy — Erudit' }, canonical: 'https://erudit-game.ru/privacy/', }); } -// renderEula renders the end-user licence agreement markdown as the standalone /eula/ page. -export function renderEula(markdown) { - return renderLegalHtml(markdown, { - title: 'Пользовательское соглашение — Эрудит', +// renderEula renders the end-user licence agreement markdown (RU + EN) as the standalone /eula/ page. +export function renderEula(ruMd, enMd) { + return renderLegalHtml({ + ru: { md: ruMd, title: 'Пользовательское соглашение — Эрудит' }, + en: { md: enMd, title: 'Terms of Use — Erudit' }, canonical: 'https://erudit-game.ru/eula/', }); } diff --git a/renderer/src/offer.mjs b/renderer/src/offer.mjs index f464d6f..d0d981d 100644 --- a/renderer/src/offer.mjs +++ b/renderer/src/offer.mjs @@ -1,17 +1,21 @@ -// The public-offer page renderer: splices the live catalog price list into the owner-edited offer -// markdown and renders it with the SAME renderOfferHtml the landing build used to invoke (bundled -// from ui/src/lib/offer). Kept out of server.mjs so the substitution is unit-testable without HTTP. +// The public-offer page renderer: splices the live catalog price list into BOTH language sources of +// the owner-edited offer markdown at the pricing marker, and renders the bilingual /offer/ page with +// the SAME renderOfferHtml the browser build uses (bundled from ui/src/lib/offer). The English view +// transliterates the Russian product names client-side (renderLegalHtml). Kept out of server.mjs so +// the substitution is unit-testable without HTTP. import { renderOfferHtml } from '../dist/gameimage.mjs'; -// pricingMarker is the token ui/legal/offer_ru.md carries at §4.4 where the price list belongs. It +// pricingMarker is the token ui/legal/offer_{ru,en}.md carry at §4.4 where the price list belongs. It // mirrors the backend marker (backend/internal/payments/offer.go) and is replaced with the fetched // tables before rendering. export const pricingMarker = '<#pricing_template#>'; -// renderOffer returns the standalone /offer/ HTML: the offer markdown with the pricing marker +// renderOffer returns the standalone /offer/ HTML: the RU + EN offer markdown with the pricing marker // replaced by pricingTables (the two markdown tables the backend projects from the active catalog), -// rendered to a self-contained document. The replacement is literal (a function replacer) so a "$" -// in a product title is never read as a replacement pattern; a missing marker leaves the text as is. -export function renderOffer(offerMarkdown, pricingTables) { - return renderOfferHtml(offerMarkdown.replace(pricingMarker, () => pricingTables)); +// rendered to a self-contained bilingual document. The replacement is literal (a function replacer) +// so a "$" in a product title is never read as a replacement pattern; a missing marker leaves the +// text as is. +export function renderOffer(ruMarkdown, enMarkdown, pricingTables) { + const splice = (md) => md.replace(pricingMarker, () => pricingTables); + return renderOfferHtml(splice(ruMarkdown), splice(enMarkdown)); } diff --git a/renderer/src/server.mjs b/renderer/src/server.mjs index bd1b3e5..858c4a8 100644 --- a/renderer/src/server.mjs +++ b/renderer/src/server.mjs @@ -24,21 +24,22 @@ const PORT = Number(process.env.RENDERER_PORT || 8090); // A render request is a finished game's journal — generously capped. const MAX_BODY = 2 * 1024 * 1024; -// The offer prose is baked into the image (renderer/Dockerfile) and read once at boot; only the -// price list is dynamic (fetched per request). The backend endpoint is internal (off the edge -// allow-list) and serves the tables from its in-memory cache. RENDERER_OFFER_MD overrides the baked -// path for a local run (point it at ../ui/legal/offer_ru.md). -const OFFER_MD = readFileSync(process.env.RENDERER_OFFER_MD || new URL('../legal/offer_ru.md', import.meta.url), 'utf8'); -// The privacy policy (GET /privacy/) and EULA (GET /eula/): static owner-edited markdown, read once at -// boot, no dynamic data. RENDERER_PRIVACY_MD / RENDERER_EULA_MD override the baked path for a local run. -const PRIVACY_MD = readFileSync(process.env.RENDERER_PRIVACY_MD || new URL('../legal/privacy_ru.md', import.meta.url), 'utf8'); -const EULA_MD = readFileSync(process.env.RENDERER_EULA_MD || new URL('../legal/eula_ru.md', import.meta.url), 'utf8'); -// Render the static legal pages once at boot and serve the cached HTML: they carry no dynamic data, -// and re-parsing the large EULA markdown on every request is slow enough under deploy-time host -// contention (the renderer is CPU/memory-capped) to flake the contour probe. The offer stays -// per-request because it splices in the live price list. -const PRIVACY_HTML = renderPrivacy(PRIVACY_MD); -const EULA_HTML = renderEula(EULA_MD); +// Each legal page is bilingual (RU + EN); both markdown sources are baked into the image +// (renderer/Dockerfile) and read once at boot. The offer's price list is the only dynamic part +// (fetched per request from the backend's internal endpoint and spliced in); privacy + eula carry no +// dynamic data. The RENDERER_*_MD / RENDERER_*_EN_MD env vars override the baked paths for a local run. +const rd = (p) => readFileSync(p, 'utf8'); +const OFFER_MD = rd(process.env.RENDERER_OFFER_MD || new URL('../legal/offer_ru.md', import.meta.url)); +const OFFER_EN_MD = rd(process.env.RENDERER_OFFER_EN_MD || new URL('../legal/offer_en.md', import.meta.url)); +const PRIVACY_MD = rd(process.env.RENDERER_PRIVACY_MD || new URL('../legal/privacy_ru.md', import.meta.url)); +const PRIVACY_EN_MD = rd(process.env.RENDERER_PRIVACY_EN_MD || new URL('../legal/privacy_en.md', import.meta.url)); +const EULA_MD = rd(process.env.RENDERER_EULA_MD || new URL('../legal/eula_ru.md', import.meta.url)); +const EULA_EN_MD = rd(process.env.RENDERER_EULA_EN_MD || new URL('../legal/eula_en.md', import.meta.url)); +// Render the static legal pages once at boot and serve the cached HTML: re-parsing the large EULA on +// every request is slow enough under deploy-time host contention (the renderer is CPU/memory-capped) +// to flake the contour probe. The offer stays per-request because it splices in the live price list. +const PRIVACY_HTML = renderPrivacy(PRIVACY_MD, PRIVACY_EN_MD); +const EULA_HTML = renderEula(EULA_MD, EULA_EN_MD); const OFFER_PRICING_URL = (process.env.RENDERER_BACKEND_URL || 'http://backend:8080') + '/api/v1/internal/offer/pricing'; @@ -82,7 +83,7 @@ const server = createServer(async (req, res) => { return; } if (req.method === 'GET' && path === '/offer/') { - const html = renderOffer(OFFER_MD, await fetchOfferPricing()); + const html = renderOffer(OFFER_MD, OFFER_EN_MD, await fetchOfferPricing()); res .writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-cache' }) .end(html); diff --git a/renderer/test/legal.test.mjs b/renderer/test/legal.test.mjs index 9432d7a..d6a29bf 100644 --- a/renderer/test/legal.test.mjs +++ b/renderer/test/legal.test.mjs @@ -1,21 +1,27 @@ -// Unit test for the static legal pages: renderPrivacy / renderEula wrap the owner-edited markdown in -// the shared legal-page chrome (bundled renderLegalHtml) with the right title + canonical URL and no -// dynamic data. The prose rendering itself is unit-tested in ui/ (offer.test.ts); here we assert the -// per-page presets. +// Unit test for the static legal pages: renderPrivacy / renderEula wrap the owner-edited RU + EN +// markdown in the shared bilingual legal-page chrome (bundled renderLegalHtml) with the right title + +// canonical URL and no dynamic data. The prose rendering itself is unit-tested in ui/ (offer.test.ts); +// here we assert the per-page presets and that both languages reach the document. import test from 'node:test'; import assert from 'node:assert/strict'; import { renderPrivacy, renderEula } from '../src/legal.mjs'; -test('renderPrivacy wraps the markdown in the privacy-page chrome', () => { - const html = renderPrivacy('# Политика конфиденциальности\n\n**1.1.** ИНН 290210610742.'); +test('renderPrivacy wraps the RU + EN markdown in the privacy-page chrome', () => { + const html = renderPrivacy( + '# Политика конфиденциальности\n\n**1.1.** ИНН 290210610742.', + '# Privacy Policy\n\n**1.1.** TIN 290210610742.', + ); assert.ok(html.includes(''), 'a standalone document'); assert.ok(html.includes('Политика конфиденциальности — Эрудит'), 'the privacy title'); + assert.ok(html.includes('data-title-en="Privacy Policy — Erudit"'), 'the English title for the toggle'); assert.ok(html.includes('href="https://erudit-game.ru/privacy/"'), 'the privacy canonical URL'); assert.ok(html.includes('290210610742'), 'the prose reached the document'); + assert.ok(html.includes('
'), 'the English body is present, hidden by default'); }); -test('renderEula wraps the markdown in the EULA-page chrome', () => { - const html = renderEula('# Лицензионное соглашение\n\nтекст'); +test('renderEula wraps the RU + EN markdown in the EULA-page chrome', () => { + const html = renderEula('# Лицензионное соглашение\n\nтекст', '# Terms of Use\n\ntext'); assert.ok(html.includes('Пользовательское соглашение — Эрудит'), 'the EULA title'); + assert.ok(html.includes('data-title-en="Terms of Use — Erudit"'), 'the English title for the toggle'); assert.ok(html.includes('href="https://erudit-game.ru/eula/"'), 'the EULA canonical URL'); }); diff --git a/renderer/test/offer.test.mjs b/renderer/test/offer.test.mjs index a82cf5b..1d4d90b 100644 --- a/renderer/test/offer.test.mjs +++ b/renderer/test/offer.test.mjs @@ -1,7 +1,7 @@ -// Unit test for the public-offer page assembly: renderOffer splices the backend's price-list -// markdown into the offer at the pricing marker and renders it with the shared renderOfferHtml -// (bundled from ui/src/lib/offer). The prose rendering itself is unit-tested in ui/ (offer.test.ts); -// here we assert the substitution and that the tables reach the rendered HTML. +// Unit test for the public-offer page assembly: renderOffer splices the backend's price-list markdown +// into BOTH language sources of the offer at the pricing marker and renders them with the shared +// renderOfferHtml (bundled from ui/src/lib/offer). The prose rendering itself is unit-tested in ui/ +// (offer.test.ts); here we assert the substitution reaches both languages and the tables render. import test from 'node:test'; import assert from 'node:assert/strict'; import { renderOffer, pricingMarker } from '../src/offer.mjs'; @@ -11,19 +11,21 @@ const tables = '| --- | --- | --- | --- |\n' + '| 50 «Фишек» | 200.00 | 30 | 100 |'; -test('splices the price list into the offer and renders the tables to HTML', () => { - const md = `# Публичная оферта\n\n**4.4.** Стоимость Товаров:\n\n${pricingMarker}\n`; - const html = renderOffer(md, tables); - // The marker is gone and the projected table reached the document as a real HTML table. - assert.ok(!html.includes(pricingMarker), 'pricing marker must be substituted'); +test('splices the price list into both language sources and renders the tables', () => { + const ru = `# Публичная оферта\n\n**4.4.** Стоимость Товаров:\n\n${pricingMarker}\n`; + const en = `# Public offer\n\n**4.4.** Cost of the Goods:\n\n${pricingMarker}\n`; + const html = renderOffer(ru, en, tables); + // The marker is gone from both languages and the projected table reached the document. + assert.ok(!html.includes(pricingMarker), 'the pricing marker must be substituted in both languages'); assert.ok(html.includes(''), 'the price list must render as an HTML table'); assert.ok(html.includes(''), 'a rouble price cell must be present'); assert.ok(html.includes('50 «Фишек»'), 'the product title must be present'); - // The offer chrome from the shared renderer is intact. + assert.ok(html.includes('

Публичная оферта

'), 'the Russian heading rendered'); + assert.ok(html.includes('

Public offer

'), 'the English heading rendered'); assert.ok(html.includes('')); }); test('a "$" in a title is substituted literally, not as a replacement pattern', () => { - const html = renderOffer(`x ${pricingMarker} y`, '| $5 pack | 1 |'); + const html = renderOffer(`ru ${pricingMarker}`, `en ${pricingMarker}`, '| $5 pack | 1 |'); assert.ok(html.includes('$5 pack'), 'a "$" in the tables must survive substitution'); }); diff --git a/ui/legal/eula_en.md b/ui/legal/eula_en.md new file mode 100644 index 0000000..15221e3 --- /dev/null +++ b/ui/legal/eula_en.md @@ -0,0 +1,417 @@ +# End-User Licence Agreement for the Game "Erudit" + +## Preamble + +This Licence Agreement governs the relationship between the Company and Users in connection with their use of the Game "Erudit". + +Only natural persons have the right to download, purchase and use any Game. + +By downloading, installing or otherwise using the Game, the User (A) confirms that they have read, understood and unconditionally accepted the terms of this Licence Agreement, as well as the terms of other relevant agreements and rules available at `https://erudit-game.ru`, and warrants that they will comply with them for the entire duration of their use of the Game; (B) acknowledges and agrees that they have independently assessed the need to use the Game and do not rely on any representations, warranties or statements other than those expressly set out in this Licence Agreement; and (C) represents and warrants that they may lawfully enter into contracts (for example, the User has reached the age of legal capacity provided for by applicable law). + +If the User is a minor, that User must review this Licence Agreement with the help of their parents or legal representatives. The Company recommends that parents and legal representatives monitor their children's online activity and ensure that their children never disclose their personal data without the prior consent of their parents or legal representatives. The Company reserves the right to restrict access to some services depending on age, and may permit minors to register for some services only with the written consent of a parent or legal representative. The Company reserves the right to request written proof of parental or legal-representative consent in respect of any User or potential User whom the Company has reason to believe may be a minor. In all cases the use of the Games by minors must be carried out under the responsibility of their parents or legal representatives, and any use of the Services is presumed to have been approved by them. + +Otherwise, the installation or other use of the Game is prohibited. + +The reference to this Licence Agreement also includes the relevant agreements and rules associated with a particular Game, namely: the [Privacy Policy](/privacy/), other documents available on the Website's pages, as well as pages, applications, policies, guides, specifications, user manuals and supporting materials that the Company makes available to the User, unless the context requires otherwise. If the User downloads or purchases the Game through any Third-Party Platform, they are advised to review the terms of the relevant platform, which may change from time to time and provide for certain additional requirements applicable to downloading, installing and using the Game through that platform. + +## 1. Terms and definitions + +In this Licence Agreement the following definitions, when capitalised, have the following meanings: + +**"Account"** — the User's personal account in the Game. + +**"Company"** — Ilya Arkadyevich Denisov, TIN 290210610742, acting in accordance with the law of the Russian Federation, address: 236020, Kaliningrad, Pribrezhny district, Parkovaya St. 1, recipient — Ilya Arkadyevich Denisov. + +**"Client part of the Game"** — the software required for the User to participate in the Game, which is installed on the User's computer or run on the User's computer in a browser when using the web version of the Game. The Client part of the Game is installed by the User on a personal computer or mobile device independently. The Client part of the Game may be distributed by the Company and/or its authorised persons both via the Internet and on physical media. The Client part of the Game distributed via the Internet is provided to the User free of charge with the right of reproduction, unless otherwise provided by this Licence Agreement. Copies of the Client part of the Game distributed on physical media may be provided to the User for a fee. + +**"Licence Agreement"** — this End-User Licence Agreement for the Game "Erudit", being a legal document defining the terms and procedure for the User's use of the relevant Game and all associated services. + +**"Forum Rules"** — a legal document, being Appendix No. 2 to this Licence Agreement, defining the rules the User must observe when using the official forum of the Game (if applicable). + +**"Game Rules"** — a legal document, being Appendix No. 1 to this Licence Agreement, defining the rules the User must observe when using the Game. + +**"Game"** — the game "Erudit" for mobile and desktop devices, owned by the Company, its affiliates and/or its partners and/or used by them, as indicated on the online storefront of the Games on the Website and/or on a Third-Party Platform (as the case may be). + +**"Materials"** — all content, all information and all other materials within the Game, including, without limitation, trademarks and logos, visual interfaces, graphics, design, compilation, information, software, computer code (including source code or object code), text, articles, images, information, data, music, sound files, photographs, headings, themes, objects, characters, character names, stories, dialogues, key phrases, concepts, artwork, animation, audiovisual effects, methods of operation, documentation. + +**"Third-Party Platform"** — any platform operated by a third party where the User may access and download the Game, including third-party mobile platforms, for example the App Store operated by Apple, the Google Play platform operated by Google, and the RuStore platform (if the Game is intended for mobile devices). + +**"In-game items"** — virtual in-game items and other goods and accompanying services that may be available for purchase in the Game. + +**"In-game currency"** — virtual in-game currency that has no monetary value and is not subject to monetary valuation, although it may have a price at the moment of purchase. + +**"Territory"** — the territory on which the Game is available for installation and other use, as indicated on the online storefront of the Game on the Website and/or on a Third-Party Platform. + +**"Unacceptable content"** — any kind of content or behaviour in the course of using the Game that is either illegal or unacceptable under generally accepted moral norms, including, among others, the following examples: + +- (i) participation in or facilitation of any illegal activity or activity that infringes the rights of others; +- (ii) content that is or may reasonably be regarded as illegal, harmful, offensive, defamatory, slanderous, indecent or otherwise undesirable and unacceptable; +- (iii) providing information that is false, misleading or inaccurate; +- (iv) disclosure of any personal or private information of another User or any other person, or otherwise intruding into the private life of another person; +- (v) abuse, harassment, stalking, threats, public display or intimidation of any person or organisation; +- (vi) profanity or the use of derogatory, discriminatory, hateful or excessively graphic language; +- (vii) any content that may harm minors; +- (viii) distribution or promotion of hatred, intolerance, discrimination, harm, racial or ethnic hatred, violence, crime or war; +- (ix) offensive, vulgar, sexually explicit or pornographic content; +- (x) promotion of the use of alcohol, tobacco or any narcotic or prohibited substances, or the use of firearms; +- (xi) transmission of software viruses, worms or any other kind of malicious software; +- (xii) advertising not agreed with the recipient or unauthorised, promotional materials, "junk mail", "spam", "chain letters", "pyramid schemes" or any other forms of unwanted advertising; +- (xiii) hacking; +- (xiv) infringement of any intellectual-property rights or unlawful provision/disclosure of information (insider information, confidential information, other private information); +- (xv) other unacceptable content or behaviour. + +**"User"** — a natural person who has reached the age that allows them, in accordance with applicable law, to bear full responsibility for their own actions (a person with full legal capacity) and to use the Game, and who meets all the criteria listed in this User Agreement, or, if the User is a minor, meets all the criteria listed in this User Agreement. + +**"User content"** — any comments, text or voice messages, photographs, graphic images, videos, sounds, musical works and other materials, as well as links to them, uploaded, transmitted, published or otherwise distributed by the User to other Users and/or to the Company while using the Games (except for the User's personal data, which is subject to the Privacy Policy). + +**"Website"** — `erudit-game.ru` and all domains and subdomains of the following levels. + +## 2. General provisions + +**2.1.** The Game is part of the `erudit-game.ru` Ecosystem. The availability of the Game and its functionality depends on the country, and not all functionality may be available in the User's country. + +**2.2.** Any use of the Game, except in cases specifically stipulated in this Licence Agreement, without the prior written permission of the Company is strictly prohibited and may infringe intellectual-property rights or applicable law. The Company may terminate the licence granted to the User under this Licence Agreement at any time, having given prior notice to the User, including if the Company reasonably believes that: (a) the User's use of the Game violates this Licence Agreement or applicable law; (b) the User uses the Game fraudulently or uses the Game improperly; or (c) the Company is unable to continue to provide the Game to the User for technical or lawful commercial reasons. + +## 3. User Account + +**3.1.** To use the Game, the User must create an Account in accordance with the instructions set out on the Website and, in particular, fill in a registration form or create an Account using their social-network account. + +When registering an Account, the User may fill in the registration form with data they consider sufficient to identify themselves in the Game as a unique user, except for the mandatory fields of the registration form, which are mandatory for the User when using the Game. + +The Company, its branches and/or partners may confirm receipt of the user's online application to create an Account electronically to the email address or by means of an SMS message to the telephone number indicated by the user (this does not apply to an Account created by the user using their social-network account). + +**3.2.** If the User accesses and downloads the Game through a Third-Party Platform, the User may use the Game without creating any Account. However, in this case the User must acknowledge that they bear full responsibility for saving their game progress in the Game. In order to save game progress, the User is strongly advised to create an in-game Account or attach their game profile to their Account on the relevant Third-Party Platform through which the User accesses the Game. + +**3.3.** The User's Account is intended for their personal non-commercial use. Users are informed of and agree that the information provided when opening their Account is presumed to establish their identity. Users warrant that all information provided is accurate and up to date. Users undertake to update this information in their account immediately after it changes, so that it always meets these criteria. The User may not share the Account or their login and password, nor allow anyone to access their Account or perform any other actions that may threaten the security of the Account. Users must keep their login and password secret. + +**3.4.** If the User learns of or reasonably suspects any breach of security, including, among others, any loss, theft or unauthorised disclosure of the login and password, the User must immediately notify the Company and change their login and password in the Game, if the Game has the corresponding functionality. In the absence of such timely notification, the Company cannot guarantee the security of the game process. + +**3.5.** The User is prohibited from distributing, using or intentionally obtaining any information providing access to another User's Account, as well as distributing links to third-party resources containing such information. It is prohibited to use or attempt to use another User's Account without the permission of the User and the Company, in particular, to log in to an Account registered by another User, having obtained such information or otherwise. + +## 4. In-game items and in-game currency + +**4.1.** The User acknowledges that the Company may provide the User with the opportunity to purchase additional In-game items and/or In-game currency within certain Games. + +**4.2.** In-game currency is not a means of payment and serves the sole purpose of being a means of exchange for In-game items. In-game currency cannot be exchanged for money or other valuables, except for In-game items in the course of ordinary gameplay. Any unused In-game currency cannot be converted back into money under any circumstances. + +**4.3.** The User may be given the opportunity to purchase, for money, a limited, personal, non-transferable, non-sublicensable, revocable licence to use In-game items and/or In-game currency exclusively from the Company and/or its authorised partners, using one of the approved payment methods provided for each relevant Game. + +**4.4.** The Company will credit the In-game currency to the User's Account after receiving payment. The crediting of In-game items and/or in-game currency to the User's Account must be carried out as soon as possible. However, due to circumstances beyond the Company's control, delays in receiving payment information from the payment-processing system in respect of the User's in-game purchases are possible. + +**4.5.** The Company does not guarantee that: (i) the In-game items desired by the User will be available at the time the In-game currency is credited to their Account; (ii) the User will be able to use the In-game items for an indefinite or desired period; (iii) the User will be able to exchange the In-game currency for any or specific In-game items; (iv) the characteristics or intended use of the In-game items will remain unchanged for the entire duration of use of the Game, or will meet the User's expectations or preferences. + +**4.6.** The Company is not liable for the User's loss, during the game process, of In-game items and/or In-game currency obtained as a result of participation in the Game. + +**4.7.** Taking into account the technical complexity of the Game and the resources used for its operation, the Company carries out regular diagnostics of the Game during its maintenance. The Company has the right to remove from the User's Account In-game items and/or In-game currency displayed in the User's Account, if the aforementioned diagnostics reveal that the In-game items and/or In-game currency were displayed in the User's Account by mistake, including as a result of a defect or error in the Game or on the Company's Website, or as a consequence of the fraudulent actions of any Users or third parties. + +## 5. Right of withdrawal + +**5.1.** All fees payable for Games, In-game items and/or In-game currency are non-refundable, except in cases expressly stipulated in accordance with applicable law. All in-game sales are final. Games, In-game items and/or In-game currency are not subject to return or exchange, unless otherwise provided by this Licence Agreement. By purchasing Games, In-game items and/or In-game currency, and by exchanging In-game currency for In-game items, the User understands and agrees that (i) the User's access to the Game may be terminated in accordance with this Licence Agreement and/or (ii) the Game may be terminated at any time for any reason, and that such events do not entitle the User to receive a refund of any amounts paid for any used or unused Games, In-game items and/or In-game currency. In addition, expenses and purchases are non-refundable if the User is dissatisfied with the Game. + +**5.2.** The transfer of In-game items and/or In-game currency is prohibited, except where expressly permitted in the Game. Except as expressly permitted in the Game, the User has no right to sublicense, sell, buy back or otherwise transfer or attempt to transfer In-game items and/or In-game currency to any natural or legal person. Any such transfer or attempt to transfer is prohibited and void, and may lead to the termination of the User's right to access their Account and/or the Game. Where the functionality of other services of the Company and/or its affiliates provides such an opportunity, Users may be permitted to exchange In-game items with each other, including for In-game currency, and/or to exchange In-game currency for money or other valuables. + +## 6. Limited licence + +**6.1.** From the moment the User accepts this Licence Agreement, the Company grants the User a personal, limited, non-exclusive, non-assignable and non-transferable licence to install and use the Game on the Territory within its functional capabilities and exclusively for personal non-commercial use and in full compliance with this Licence Agreement and any other documentation attached to or included in the Game. + +**6.2.** The User agrees and acknowledges that any and all intellectual-property rights (including, without limitation, in the Game and any associated Materials) belong to the Company and/or its partners/affiliates (as the case may be). The intellectual-property rights granted under this Licence Agreement are granted by licence, not sold. The licence granted in accordance with this Licence Agreement does not confer ownership of the `erudit-game.ru` Ecosystem. + +**6.3.** The User is expressly prohibited from the following: + +- sublicensing, renting, leasing, transferring, reselling, gifting, exchanging, distributing or otherwise using the Game or its copies and/or their Account, as well as distributing information about the intention to perform the actions listed above, by the User or any other third parties; +- modifying, combining, adapting, decompiling, disassembling, altering, translating into other languages or otherwise changing the Game or any of its components; +- creating derivative works based on the Game; +- removing, altering or concealing any notices of product identification, copyright or other intellectual property in the Game; +- using the Game in any way that may hinder, disrupt, adversely affect or objectively hinder other Users' full enjoyment of the Game, or that may damage, disable, overload the Game or disrupt the functioning of the Game in any way; +- using the Game in any way that violates this Licence Agreement, including the Game Rules and the Forum Rules (if applicable), any applicable local, national or international law, any rules and policies; +- using the Game for any purpose or in any way that the Company considers a violation of this Licence Agreement. + +**6.4.** In accordance with this Licence Agreement, no other rights to the Game or its parts are granted to the User, except for the rights expressly specified in this Licence Agreement. + +## 7. User content + +**7.1.** By transmitting or submitting any User content, the User confirms, represents and warrants that such transmission or submission is (a) accurate and non-confidential; (b) does not violate the Game Rules, the Forum Rules, contractual restrictions, any applicable law and rules or the rights of third parties, and that the User has permission from any third party whose personal information or intellectual property is included in the User content; (c) such User content does not contain viruses, adware, spyware, worms or other malicious code; (d) the User acknowledges and agrees that any of their personal information within such content will always be processed by the Company and/or its partners/affiliates in accordance with the Privacy Policy; (e) the User grants the Company and its affiliates a non-exclusive, worldwide, perpetual, irrevocable, transferable, sublicensable, limited licence to use such User content by any lawful means, in particular, for reproduction, distribution, transmission, transcoding, translation, broadcasting, public display, public performance, making available to the public, modification, creation of derivative works in respect of it; this licence is deemed granted to the Company for the entire duration of the intellectual-property rights in such User content, as soon as it is uploaded to the `erudit-game.ru` Ecosystem or from the moment the Company otherwise acquires the rights, in particular, from its affiliates. + +**7.2.** The Company reserves the right, at its own discretion and for a valid reason, to review, monitor, prohibit, edit, delete, disable access to or otherwise make unavailable any User content without prior notice. The Company is not responsible for the conduct of any User providing any User content, and is not responsible for monitoring the Game for Unacceptable content or improper conduct of Users. The Company does not conduct preliminary verification and monitoring, and cannot pre-verify or monitor all User content. + +**7.3.** The User acknowledges and agrees that they use the Game at their own risk. By using the Game, the User may encounter Unacceptable content of other Users that is offensive, indecent or otherwise does not meet their expectations. The User bears all risks associated with the use of any User content of other Users available within the Game. At the Company's discretion, its representatives or technologies may monitor and/or record the User's interaction with the Game or interactions with other Users (including, among others, messages) when the User uses the Game. By concluding this Licence Agreement, the User hereby gives their irrevocable consent to such monitoring and recording. If at any time the Company, at its own discretion, decides to monitor the Game, the Company nevertheless bears neither full nor limited responsibility for User content. The Company has the right, at its discretion, to edit any User content, refuse to publish it or delete any User content. + +## 8. Sanctions + +**8.1.** The Company independently establishes the fact of a violation. In the event of a violation by the User of the Licence Agreement, including the Game Rules and/or the Forum Rules, the Company has the right to apply the following sanctions to the User, depending on the degree of the violation committed by the User and its adverse impact on the game process and other Users: + +- issue warnings in any form, including by email; +- delete any User content that violates any provision of applicable law or violates the Licence Agreement, in particular, the Game Rules and/or the Forum Rules; +- rename, only if necessary (for example, an offensive name), their character, community or organisation of players; +- temporarily restrict certain functionality of the Account and/or the forum account (if applicable); +- suspend access to their Account(s) and/or forum account(s) (if applicable) in full; +- restrict the use of the Game and/or the forum in whole or in part; +- temporarily restrict or permanently disable access to a character or some of its characteristics; +- temporarily restrict or permanently disable in-game communication services and/or use of the forum; +- restrict the number of connections to the server, as well as the duration of each connection during a certain period of time; +- block IP addresses, MAC addresses or proxy servers used to access the Game; +- delete their character and/or Account. + +**8.2.** The Company will make reasonable efforts to provide the User with explanations of which provisions of this Licence Agreement, in particular, the Game Rules and/or the Forum Rules, were violated by the User, as a result of which the Company applied sanctions. + +**8.3.** The User is not permitted to register a new Account in the event of the User's violation of this Licence Agreement, in particular the Game Rules and/or the Forum Rules. In this case the Company reserves the right to apply any of the above sanctions to all Accounts of such User, both together and separately. + +## 9. Users' health + +Users must observe the following precautions: + +- Avoid playing if you are tired or sleep-deprived. +- Play at a good distance from the screen. +- Play in a well-lit room and reduce the screen brightness. +- Take breaks of ten (10) to fifteen (15) minutes every hour. + +WARNING: some people are prone to epileptic seizures, including, in some cases, loss of consciousness, especially when exposed to strong light stimuli (a rapid sequence of images or the repetition of simple geometric shapes, flashes or exposures). Such people are at risk of seizures when they play certain video games containing such light stimuli; the Company strongly recommends that Users consult their doctor before any use. Parents should also pay especially close attention to their children when they play video games. If a User experiences one of the following symptoms — dizziness, vision problems, eye or muscle twitching, disorientation, involuntary movements or convulsions, or momentary loss of consciousness — the User must immediately stop playing and consult a doctor, or their parents must make their children do so. + +## 10. Automatic updating of the Game + +**10.1.** In order to improve the Game, the Company reserves the right to introduce automatic updates and changes to the Game if the User's device is connected to the Internet, in which case the User does not need to install those updates and changes manually. The User acknowledges and agrees that some updates and changes to the Game may lead to an increase in system requirements. In order to ensure the effectiveness of the said updates and modifications and to enable the User to continue using the Game, the User hereby consents to the introduction of such updates and modifications by the Company. The User bears full responsibility for ensuring that their device has sufficient system requirements and memory to use and store the Game. + +**10.2.** This Licence Agreement extends to any automatic updates (supplements, modifications) of the Game provided by the Company via the Internet and not accompanied by a separate licence or other agreement. + +## 11. Disclaimer of warranties + +**If the User resides in the European Union or the European Economic Area, the following provision applies to that User:** + +The Game is provided on an "as is" and "as available" basis. Thus, Users acknowledge that the Game may not meet their individual preferences and expectations. The Company will make all commercially reasonable efforts to ensure the continuous operation of the Game; accordingly, Users acknowledge that the Game is not error-free and may be interrupted. + +The Company gives no warranties or representations as to the accuracy or completeness of the Materials, the game content or the content of any websites associated with the Game. + +The Company disclaims any express or implied warranties of security, freedom from viruses, freedom from errors, legality and/or reliability of information, data or materials. The Company does not warrant that the performance of Users' personal computers or other devices is sufficient to use the Game. Users are advised to determine in advance the computer-system requirements for a specific Game and whether their computer system meets those requirements. The Company does not warrant, endorse or assume responsibility for any product or service advertised or offered by a third party through the Game, any hyperlinked website or any website or mobile application placed in any banner or other advertising. The Company will not be a party to, or in any way responsible for monitoring, any transaction between the User and any third-party suppliers of goods or services. + +**If the User resides outside the European Union or the European Economic Area, the following provision applies to that User:** + +The Game is provided on an "as is" and "as available" basis. To the fullest extent permitted by law, the Company disclaims all warranties, express or implied, in connection with the Game and its use by the User, including, among others, implied warranties of merchantability, fitness for a particular purpose, title and non-infringement. The Company gives no warranties or representations as to the accuracy or completeness of the Materials, the game content or the content of any websites associated with the Game, and the Company bears no liability or obligation for any (A) errors, mistakes or inaccuracies of content and materials; (B) personal injury or property damage of any nature resulting from the User's access to and use of the Game; (C) any unauthorised access to or use of the Company's protected servers and/or any and all personal information and/or financial information stored on them; (D) any interruption or cessation of data transmission to or from the Game; (E) any errors, viruses, Trojan horses and the like that may be transmitted to the Game or through it by any third party; and/or (F) any errors or omissions in any content and materials or any losses, or damage of any kind, incurred as a result of the use of any posted, transmitted content or content otherwise accessed through the Game. + +## 12. Liability + +**If the User resides in the European Union or the European Economic Area, the following provision applies to that User:** + +The Company undertakes to act with the care and diligence usually employed in this profession to ensure the provision of the services rendered to the User. In the event that the Company is liable, it may be released from part or all of its liability, however, by proving that the non-performance or improper performance of the contract was caused by the consumer, an unforeseen and insurmountable act of a third party or an event of force majeure. + +**If the User resides outside the European Union or the European Economic Area, the following provision applies to that User:** + +To the maximum extent permitted by applicable law, neither the Company, nor its affiliates, nor their officers, directors, employees, licensors or partners bear any liability to the User for any damage (including, without limitation, actual damages, incidental damages, consequential damages, lost profits or lost data, regardless of whether such damage was foreseeable) arising in connection with this Licence Agreement and with the User's use of the Game. + +The Company is not liable for the inability to install or run the Game on the User's device, or for possible errors and malfunctions in the operation of the Game. The User must connect to the Internet to use the Game. All costs of connecting to the Internet are borne by the User. The Company is not liable for any damage caused to the User as a result of connecting to the Internet or installing malicious software on the User's device. + +If the limitation or exclusion of liability is prohibited by applicable law, the Company's liability must be limited to the maximum extent permitted. + +## 13. Data and information security + +**13.1.** The Company's personal-data protection rules can be found at [`erudit-game.ru/privacy/`](/privacy/). + +The Company cares about the protection of personal data. Personal data collected by the Company in the context of this document is subject to automated processing in accordance with applicable law. All information collected as part of providing the service is registered by the Company, which is the data controller. This is very important for the functioning of the services offered by the Company. + +To exercise one or more of their rights, the User must provide an identity document and contact the person responsible for data protection at the Company (via service support on Telegram: [@Erudit_GameBot](https://t.me/Erudit_GameBot), or send us your request in writing to: 236020, Kaliningrad, Pribrezhny district, Parkovaya St. 1, recipient — Ilya Arkadyevich Denisov). + +In the event of a complaint, the User may contact the personal-data supervisory authority of their country of residence. + +**13.2.** Information provided by the User in any way must be accurate. + +Although the Company does everything possible to ensure data confidentiality and has implemented appropriate technical and organisational measures to ensure and demonstrate that processing is carried out in accordance with data-protection rules, the User understands that no security measures are perfect and such measures can be circumvented. + +**13.3.** The User understands and acknowledges that even after the deletion of data and User content provided by the User, such data or User content may remain available in the cache or web archives, as well as in the search results of search engines, and may also be available to other persons if other Users have copied and saved the User's data or User content. + +**13.4.** The Company cannot control the actions of other Users with whom the User wishes to share their credentials (login and password). Therefore, the Company cannot guarantee that any User content the User posts in the Game will not be available for viewing by unauthorised persons. + +**13.5.** Information provided by the User is used by the Company and/or its partners/affiliates in accordance with the [Privacy Policy](/privacy/). + +## 14. Applicable law and jurisdiction + +**14.1.** Unless expressly provided otherwise by the applicable law of the User's country of residence, this Licence Agreement is governed by and construed in accordance with the law of the Russian Federation. The Parties seek to resolve all disputes arising in connection with this Licence Agreement through correspondence and negotiations; the pre-trial dispute-resolution procedure is mandatory. If the User and the Company cannot reach agreement without going to court within 60 (sixty) business days from the date of receipt of the relevant claim, the dispute shall be resolved by the state court at the Company's location in accordance with the law of the Russian Federation. + +**14.2.** If the User resides in France, this Licence Agreement is governed by the law of France, and any dispute arising in connection with the formation, interpretation or performance of this Licence Agreement is subject to the exclusive jurisdiction of the courts of France. In accordance with Article 14 of Regulation (EU) No. 524/2013, the European Commission provides consumers with an online dispute-resolution platform available at: `https://ec.europa.eu/consumers/odr/`. + +**14.3.** If the User resides outside the Russian Federation and is not a resident of a country for which this section provides otherwise, this Licence Agreement is governed by and construed in accordance with the laws of England and Wales, unless expressly provided otherwise by the applicable law of the User's country of residence. The Parties seek to resolve all disputes arising in connection with this Licence Agreement through correspondence and negotiations without going to court; if agreement is not reached within 60 (sixty) business days from the date of receipt of the relevant claim, the disputes shall be resolved by the state court of the relevant jurisdiction at the Company's location, unless expressly provided otherwise by applicable law. + +## 15. Miscellaneous + +**15.1.** This Licence Agreement enters into force from the moment the User first downloads, installs or otherwise uses the Game, and remains in force until its termination in accordance with this Licence Agreement. The User may terminate this Licence Agreement at any time by deleting the Game. The Company may terminate the Licence Agreement by notifying the User of the termination by any means available to the Company; in this case the User must immediately delete the Game. + +**15.2.** The Company may change the functionality and information content of the Game, as well as any associated Materials, at any time at its discretion. In the event that this entails a reduction in the User's rights, the Company will notify the User of such a change, in which case the notified User has the right to terminate the Licence Agreement. + +**15.3.** Except in cases where such assignment may lead to a restriction of the User's rights, the Company may, at its discretion, at any time assign and/or delegate its rights and obligations under this Licence Agreement or any part of it to any third party with prior notice to the User. The User's rights and obligations under this Licence Agreement are personal and not subject to assignment. + +**15.4.** In the event of termination of this Licence Agreement, articles 11, 12, 13, 14 and 15 remain in force. + +**15.5.** This Licence Agreement constitutes the entire agreement between the User and the Company with respect to the User's use of the Game and supersedes any previous or contemporaneous oral and written agreements concerning the User's use of the Game. + +**15.6.** If any provision of this Licence Agreement is or becomes illegal or unenforceable, that provision shall apply to the maximum extent permissible and/or be modified to achieve the maximum possible effect of the original condition, and the remaining provisions of this Licence Agreement remain in full force and effect. + +**15.7.** This Licence Agreement may be changed by the Company at any time. Any change to the Licence Agreement must be brought to the attention of Users. The User is advised to check the Licence Agreement periodically for such changes. If the User does not agree with the changes, the User has the right to stop using their Account. + +**15.8.** The Company reserves the right to revise the terms of this Licence Agreement, in particular, the Game Rules and/or the Forum Rules, by updating the Licence Agreement at [`erudit-game.ru/eula/`](/eula/) or by notifying the User by email. The revised Licence Agreement enters into force from the day of its publication. The User is advised to check the aforementioned website periodically for notices of such changes. The User's failure to take steps to review does not serve as grounds for the User's failure to perform their obligations and non-compliance with the restrictions established by this Licence Agreement. The User's continued use of the Game is deemed acceptance of any revised terms. + +**15.9.** On matters related to the performance of this Licence Agreement and/or the use of the Game, the User may contact the Company on Telegram: [@Erudit_GameBot](https://t.me/Erudit_GameBot) and at ilia.denissov@gmail.com. + +Denisov I.A. | 2026 + +--- + +# Appendix No. 1 to the Licence Agreement — Game Rules + +## Preamble + +This document is considered an integral part of the Licence Agreement and governs the rules of participation and conduct of the User in the Game, restrictions on Users' actions in the Game, the User's responsibility for non-compliance with such rules and restrictions, the Company's rights to apply measures established by the Licence Agreement to the User, and the conditions for applying such measures. Full agreement with these Game Rules and acceptance of the obligation to fully comply with them is a mandatory condition for the User's participation in the Game. + +The Rules are valid and establish the conduct of Users in the Game and during the use of auxiliary game services. The rules of participation and conduct are established to ensure the most comfortable presence in the game world for each User. Non-compliance with the Game Rules may lead to a restriction of functionality (in any form, including: the use of characters, In-game items, In-game currency, interaction with other characters, the game world and its functionality, etc.) or of access to the User's Account for a long period without reimbursement of the User's costs (if any). + +For violation of the Game Rules, the Company may apply to the User the sanctions specified in the Licence Agreement. By using the Game, the User expresses their trust in the Company in making any decision related to the interpretation and observance of the Game Rules. + +The Company reserves the right to identify and locate all of the User's Accounts, determined by hardware, IP or other information obtained directly or indirectly by the Company and its affiliates, as well as to extend sanctions applied to one of the User's Accounts to any or all of that User's Accounts. + +## 1. Game character + +**1.1.** In the Game, the User is prohibited from performing the following actions with their characters: sale, purchase, exchange, transfer, gifting, as well as the distribution of information about the intention to perform the specified actions by the User themselves or by any third party. + +**1.2.** The User is prohibited from using, as the name of a game character, a game-clan name and other groups, the following designations (including blurred and hidden spelling using special characters, for example, @#$%): + +- **1.2.1.** offensive or rude words, incitement to words of a discriminatory nature, obscene words and phrases, swearing in any language composed of letters of any alphabet; +- **1.2.2.** proper names and other words and phrases used in religions or cults that may offend the feelings of believers (the use in the name of game clans and game groups of such general religious concepts (other than proper names) as "paradise", "hell", "angel", "devil", "voodoo", etc., is not prohibited); +- **1.2.3.** the names of historical figures and politicians; +- **1.2.4.** words and expressions directly or indirectly related to drugs, methods of their preparation, use and acquisition; +- **1.2.5.** words and phrases that may mislead other Users into thinking that the User registered under such a name is a representative of the Company or otherwise has a direct or indirect relationship to it, or has any rights to administer the Game; +- **1.2.6.** unpronounceable letter combinations; +- **1.2.7.** words and phrases containing advertising of goods or services, including any domain names and trademarks; +- **1.2.8.** words and phrases that infringe the rights of third parties (including, without limitation, intellectual-property rights) or the requirements of applicable law. + +## 2. Game Account + +**2.1.** The User's Account is intended for their personal non-commercial use. In the Game, the User is prohibited from performing the following actions with their Account: selling, buying, exchanging, transferring, gifting, as well as distributing information about the intention to perform actions specified by the User themselves or by a third party. + +**2.2.** The User has no right to share the Account or their login and password, nor to allow anyone to access their Account or perform any other actions that may threaten the security of the Account. The User is responsible for keeping their login and password confidential. The User bears full responsibility for any use of their login and password, including any purchases or other changes in the Account, regardless of whether they are permitted by the User. The User is responsible for all actions performed through their Account. The Company is not liable for anything that happens through the Account or with the Account as a result of the User allowing third parties access to their login and password and/or Account. + +**2.3.** If the User learns of or reasonably suspects any breach of security, including, among others, any loss, theft or unauthorised disclosure of the login and password, the User must immediately notify the Company and change their login and password. In the absence of such timely notification, the Company cannot guarantee the security of the User's gameplay. + +**2.4.** The User is prohibited from distributing, using or intentionally obtaining any information providing access to another User's Account, on the Game website, Game forums, Game support services, as well as distributing links to third-party resources containing such information. It is prohibited to use or attempt to use another User's Account without the permission of the User and the Company, in particular, to log in to an Account registered by another User, having obtained such information or otherwise. + +**2.5.** The Company reserves the right to provide that the User is permitted to participate in the relevant Game with only one Account ("multi-account ban"). Even in those Games where the User is permitted to create more than one Account, connecting and interacting in any way between several Accounts with each other is prohibited ("data-exchange ban"). In particular, the User is not permitted to use these Accounts to create an advantage for one of their Accounts. Violation of the multi-account ban and/or the data-exchange ban may lead to the deletion of all Accounts of such User. + +## 3. In-game items and in-game currency + +**3.1.** The User is prohibited from performing or encouraging the following actions in the Game with any In-game item and/or any In-game currency: sale, purchase or exchange for non-game valuables, including money and other means of payment, items, services, obligations. The User is prohibited from carrying out the sale, purchase or exchange for In-game items and/or In-game currency, as well as distributing information about the intention to perform the aforementioned actions by the User themselves or by any third party. Where the functionality of other services of the Company and/or its affiliates provides such an opportunity, Users may be permitted to exchange In-game items with each other, including for In-game currency, and/or to exchange In-game currency for money or other valuables. + +## 4. Payments + +**4.1.** The User is prohibited from using bonus forms of payment provided solely within the terms set by the organiser, as well as credit forms of payment without timely compensation/return of the credit part, as well as any other activity aimed at concealing the fact of use or obtaining a benefit without timely compensation/return of the payments made, as well as any attempt to perform the specified actions or use In-game items and/or In-game currency obtained by other Users as a result of violating the Game Rules and the Licence Agreement. In the event of such a violation, the Company, at its discretion, withdraws such In-game currency, In-game items and/or their equivalent in In-game currency from the User's Account, restricting the functionality and access to the Account. + +**4.2.** The User is prohibited from making payments both with means whose liquidity is temporarily restricted and by methods where it would be impossible to confirm the legality of the transaction performed. Payments for which the User cannot provide confirmation of the legality of possession of the means of payment and its provision with real funds may be grounds for restricting the functionality or access to the User's Account. + +## 5. Cheating + +**5.1.** The User is prohibited from creating and/or using in the Game bots (third-party software allowing a character/Game to be controlled automatically), other software, technical and/or other means capable of changing the game process provided for by the Game scenario, or imitating the User's actions. + +**5.2.** The User is prohibited from performing any actions that impede or hinder other Users from accessing the Game or the Company from performing its obligations. It is prohibited to create obstacles for other Users in the Game not covered by the game process, and to perform any actions that hinder the Game or the servers, or networks connected to the Game, or that violate any requirements, procedures, policies or rules of the networks connected to the Game. + +**5.3.** The User is prohibited from directly or indirectly disabling or otherwise hindering the operation of programs for detecting and preventing the use of third-party software or hardware resources, providing the User with an unforeseen advantage in the Game. + +**5.4.** The User is prohibited from attempting to benefit from deliberate (or repeated) participation in the game process as part of a game group (team) with other Users who have violated paragraphs 5.1, 5.5 and/or 5.6 of the Game Rules. + +**5.5.** The User is prohibited from using and distributing information, calling for the use of and publicly distributing any errors, both within the Game and in any other software. A User who discovers such errors in the Game must stop using it and report them to the Company on Telegram: [@Erudit_GameBot](https://t.me/Erudit_GameBot) and at ilia.denissov@gmail.com, setting out in detail and truthfully all the circumstances of such discovery and use. If the User has any doubts as to whether the functioning of any particular game process, In-game items or In-game currency is currently normal or has deviations, malfunctions or errors, the User must stop using such process, In-game items or In-game currency and contact the Company at ilia.denissov@gmail.com for the relevant explanations. + +**5.6.** The User is prohibited from decompiling, decoding and reconstructing data, bypassing data-protection systems, hacking or attempting to hack the software components of the Game or its services, and/or intercepting data coming to or from the server. Prohibited are: (in particular) any modification, alteration, decompilation, decoding, sale or distribution of modified materials of the Game in whole or in part (or the means and materials necessary to perform such actions), the use of programming errors, making changes to the program code and obtaining unauthorised access to the server and database of the Game. In certain special cases, the Company has the right to immediately suspend the User's access to the Game and send a request to the relevant authorities to prevent any violation of the Licence Agreement and/or provisions of applicable law. + +## 6. Unacceptable content + +**6.1.** The Company reserves the right to provide its own linguistic assessment of the compliance of any phrases and words with these Game Rules. In the event of ambiguity of exact phrases or words and to avoid disputes, it is necessary to first request an official response regarding the appropriateness of their use from the Company at ilia.denissov@gmail.com. + +**6.2.** The User is prohibited from distributing rumours, slander, defamatory information about the Company, other Users, affiliates of the Company and the Game as a whole. + +**6.3.** The User is prohibited from using any rude, offensive, provocative, advertising or unacceptable words and symbols in any form in the names or descriptions of characters, In-game items, guilds and any other communities and organisations of players. + +**6.4.** The User is prohibited from using rude words, insults, in the course of the Game, on the general channel and through communication services informing several Users simultaneously, as well as from applying threats of violence or physical reprisal, promotion of drugs, pornographic materials or third-party resources containing such publicly available materials, promotion of racial, national, religious, cultural, ideological, gender, linguistic or political intolerance across all channels and in all types of messages without exception, as well as from encouraging such actions and expressions by other Users. + +**6.5.** The User is prohibited from participating in the creation of a community or organisation of gamers or otherwise supporting any community and organisation of gamers whose ideology implies the rejection of religious, national or gender status (or has a similar ideology of such a tendency), relates to a nationalist, racial or sexist philosophy. + +**6.6.** The User is prohibited from publishing information (links, tags, microblogs, descriptions of methods, etc.) or uploading files containing malicious programs (viruses, Trojans, etc.), corrupted and altered files or data, other similar software causing damage to the Game, disrupting the operation of other computers and communication devices or the integrity of other Users' Accounts. + +**6.7.** The User is prohibited from sending spam (informational links and announcements unrelated to the game process), flooding (repeated repetition, reproduction, copying of information, etc.) within any form of the Game's information services (chats, personal messages, in-game letters, message boards, etc.), as well as from using the Game and/or the Game services to organise illegal activity or activity unrelated to the Game. + +**6.8.** The User is prohibited from making any advertising announcements in any form, including the reproduction of any links to Internet pages in the Game without the prior consent of the Company. + +**6.9.** The User is prohibited from using any information services of the Game to distribute information about political parties, public and religious organisations and movements, as well as about their activities to promote them, campaigns, demonstrations, etc., from calling for participation in them or performing similar actions in one form or another in the Game, as deliberately provoking disputes and conflicts between other Users. + +**6.10.** The User is prohibited, within the Game's means of communication (chat, mail, notifications), from behaving in a way that may mislead other Users into thinking that the User registered under such a name is a representative of the Company or otherwise has a direct or indirect relationship to it, or has any rights to administer the Game. + +## 7. Interaction between Users + +**7.1.** The User is obliged to respect other Users' right to participate in the Game and must in no case create situations where the rights of other Users in the Game may be violated and/or restricted. The Company reserves the right to provide its own legal assessment of actions and the compliance of the situation with this paragraph. + +**7.2.** The User bears full responsibility for their interaction with other Users of the Game. The Company reserves the right, but is not obliged, to participate in any way in the resolution of these disputes. The User undertakes to fully cooperate with the Company for the investigation of any allegedly illegal, fraudulent or improper actions, including, among others, providing the Company with access to any password-protected parts of their Account. + +## 8. Miscellaneous + +**8.1.** The User is prohibited from using any data-collection methods, robots or similar methods of collecting or extracting data. + +**8.2.** The User is prohibited from offering such arguments as "in accordance with the role"/"role-play" in defence of unlawful actions of any kind. + +**8.3.** The User is prohibited from deliberately providing false information when contacting the Company on Telegram: [@Erudit_GameBot](https://t.me/Erudit_GameBot) or at ilia.denissov@gmail.com, as well as from falsifying the data provided. + +--- + +# Appendix No. 2 to the Licence Agreement — Forum Rules + +> This Appendix applies if and when an official forum of the Game exists. At the time of publication the forum may be unavailable; the rules are provided in advance and take effect from the moment the forum is launched. + +## Preamble + +This document is an integral part of the Licence Agreement and governs the conduct of Users on the Game forum (if it exists) and during the use of auxiliary game services, restrictions on Users' actions on the Game forum, Users' responsibility for non-compliance with the specified rules and restrictions, the Company's rights to apply measures established by the Licence Agreement to Users, and the conditions for applying such measures. Full agreement with these Forum Rules and acceptance of the obligation to fully comply with them is a mandatory condition for the User's use of the Game forum. + +The Forum Rules are established to ensure the most comfortable presence of each User on the game resources. Non-compliance with the Forum Rules may lead to a restriction of functionality or of access to the User's forum account for a long period of time without compensation for the User's costs (if any). + +For violation of the Forum Rules, the Company may apply to the User the sanctions provided for in the Licence Agreement. By posting messages on the forum, the User expresses their trust in the Company in making any decision related to the interpretation and observance of the Forum Rules. + +The Company reserves the right to identify and locate all of the User's forum accounts, determined by hardware, IP or other information obtained directly or indirectly by the Company and its affiliates, as well as to extend sanctions applied to one of the User's forum accounts to any or all of that User's forum accounts. + +## 1. General provisions + +**1.1.** These Forum Rules apply to all sections of the Game forum, as well as to social groups, personal messages, public messages, and Users' signatures. + +**1.2.** The forum is intended for comfortable communication between registered Users. The forum may also have a corresponding part open for reading by guests (unregistered Users). The forum is intended for discussing the Game, hardware and software compatibility, computer settings, as well as for exchanging other information related to the Game. + +**1.3.** Forum participants should be addressed by their pseudonym (nickname) used on the forum or in the Game, or by their name, if such Users do not object to such address. + +**1.4.** The forum allows one account to be registered per User. Possible exceptions are provided for in paragraph 3.1. A User's login to the forum is allowed only through their forum account. + +**1.5.** Ignorance of the Forum Rules does not release the User from responsibility for violations committed. + +## 2. Moderation on the forum and communication with the Company + +**2.1.** The Company provides Users with the technical ability to post messages and exchange messages. + +**2.2.** The Company and any persons authorised by the Company may perform any actions related to moderation (deletion, blocking, moving, etc.). Nevertheless, the Company is not responsible for User content uploaded, transmitted, published or otherwise distributed on the Game forum, but should, where possible, stop all violations in accordance with these Forum Rules. + +## 3. Prohibited conduct on the forum + +**3.1.** Registration of more than one account on the forum by one User, even if such User wishes to continue posting messages after their main forum account has been blocked for violating the Forum Rules. Although in exceptional cases a User may create another forum account for emergency communication by contacting the Company via personal messages. Note: the forum is open to guests in read-only mode. + +**3.2.** Logging in to the forum through someone else's login, regardless of the method of obtaining it. A forum User has no right to use other people's forum accounts for emergency communication with the Company. + +**3.3.** The use of obscene words (including blurred, hidden by special characters, for example, @#$%), offensive, threatening, in any way discriminating words in the pseudonym (nickname), on the avatar, in the signature, status, topic headings, messages, in personal correspondence with other Users. + +**3.4.** Indirect or explicit provocations of Users that cause indecency and/or arguments in forum topics, even if the User's message or part of it is out of context, is considered a violation of these Forum Rules. + +**3.5.** Publication of images, links to images, links to Internet sources with elements of pornography, violence, promotion of terrorism, neo-Nazism, any discrimination, alcohol or drugs, or containing obscene, offensive words. + +**3.6.** Promotion, in any form justifying the consumption or distribution of drugs, alcoholic products, psychotropic substances. + +**3.7.** Publication of materials or links to Internet resources containing unlicensed content, violations of the Game Rules, "cracks" (hacked software), "warez" (unlicensed software), "no CD" and others. + +**3.8.** Publication or discussion of advertising materials in any form on the forum, including links to websites, referral links, spam. Open advertising of other games and companies (prohibited advertising). + +**3.9.** Trading (discussion of trading) in game characters, In-game items, In-game currency, forum accounts, character level boosting. + +**3.10.** Discussion of vulnerabilities or shortcomings of the Game, as well as any actions that in any way violate the Game Rules, or their discussion. Upon discovering vulnerabilities or shortcomings, the User must report them to the Company on Telegram: [@Erudit_GameBot](https://t.me/Erudit_GameBot) or at ilia.denissov@gmail.com. + +**3.11.** Publication of messages causing negative consequences for the game process; provocation of Users to violate the Game Rules. + +**3.12.** Creating topics with a "SHOUTING" heading or a heading partially typed in capital letters (CAPS). + +**3.13.** Creating topics with an ambiguous heading (for example, "Help", "Attention!", "Urgent", "Look", etc.). + +**3.14.** Creating topics on subforums not intended for discussing such topics (for example, creating topics to discuss the completion of any task on subforums for technical questions about the Game). diff --git a/ui/legal/offer_en.md b/ui/legal/offer_en.md new file mode 100644 index 0000000..e916d6d --- /dev/null +++ b/ui/legal/offer_en.md @@ -0,0 +1,155 @@ +# Public offer + +Public offer to conclude a contract of sale. + +## 1. General provisions + +This Public Offer contains the terms for concluding a Contract of Sale (hereinafter the "Contract of Sale" and/or the "Contract"). This offer is recognised as a proposal addressed to one or more specific persons that is sufficiently definite and expresses the intention of the person making the proposal to consider themselves as having concluded a Contract with the addressee who accepts the proposal. + +The performance of the actions specified in this Offer is confirmation of the consent of both Parties to conclude the Contract of Sale on the terms, in the manner and to the extent set out in this Offer. + +The text of the Public Offer set out below is the Seller's official public proposal, addressed to an interested circle of persons, to conclude a Contract of Sale in accordance with the provisions of paragraph 2 of Article 437 of the Civil Code of the Russian Federation. + +The Contract of Sale is deemed concluded and enters into force from the moment the Parties perform the actions provided for in this Offer, signifying the unconditional and full acceptance of all terms of this Offer without any exceptions or restrictions on the terms of accession. + +### Terms and definitions + +**Contract** — the text of this Offer with the Appendices that are an integral part of this Offer, accepted by the Buyer by performing the implied actions provided for by this Offer. + +**Implied actions** — conduct that expresses agreement with the counterparty's proposal to conclude, amend or terminate a contract. The actions consist of the full or partial performance of the conditions proposed by the counterparty. + +**The Seller's Website on the Internet** — a set of computer programs and other information contained in an information system, access to which is provided via the Internet by the domain name and network address: `erudit-game.ru`. + +**The Seller's Application** — software provided by the Seller for downloading from the Internet, voluntarily downloaded by the Buyer and executed on the Buyer's computing machine (device), providing game or other functions and allowing the Buyer to interact with the Seller through transactions of sale and purchase of in-game valuables. The Application may be distributed in formats including, but not limited to: a web application on the Seller's Website, a mini-application in the VK ecosystem, a mini-application in the Telegram ecosystem, an Android application in the Google Play ecosystem, an Android application in the RuStore ecosystem, an iOS application in the Apple App Store ecosystem, and other distribution formats. + +**Parties to the Contract (Parties)** — the Seller and the Buyer. + +**Goods** — the goods under a contract of sale may be any items subject to the rules provided for by Article 129 of the Civil Code of the Russian Federation. + +## 2. Subject of the Contract + +**2.1.** Under this Contract the Seller undertakes to transfer the item (the Goods) into the ownership of the Buyer, and the Buyer undertakes to accept the Goods and pay a certain sum of money for them. + +**2.2.** The name, quantity and range of the Goods, their cost, delivery procedure and other conditions are determined on the basis of the Seller's information at the time the Buyer places an order, or are established on the Seller's Website on the Internet or in the Seller's Application. + +**2.3.** Acceptance of this Offer is expressed in the performance of implied actions, in particular: + +- actions related to registering an account on the Seller's Website on the Internet or in the Seller's Application where registration of an account is necessary; +- by drawing up and filling in an application for placing an order for Goods; +- by communicating the information required to conclude the Contract by telephone or email indicated on the Seller's Website on the Internet or in the Seller's Application, including when the Seller calls back in response to the Buyer's application; +- payment for the Goods by the Buyer. + +This list is not exhaustive; there may be other actions that clearly express a person's intention to accept the counterparty's proposal. + +## 3. Rights and obligations of the Parties + +### 3.1. Rights and obligations of the Seller + +**3.1.1.** The Seller has the right to demand payment for the Goods and their delivery in the manner and on the terms provided for by the Contract; + +**3.1.2.** To refuse to conclude the Contract on the basis of this Offer with the Buyer in the event of their unfair conduct, in particular in the event of: + +- more than 2 (two) returns of Goods of proper quality within a year; +- provision of knowingly inaccurate personal information; +- return of Goods spoiled by the Buyer or Goods that have been used; +- other cases of unfair conduct indicating that the Buyer has concluded the Contract for the purpose of abusing rights, and the absence of the usual economic purpose of the Contract — the acquisition of Goods. + +**3.1.3.** The Seller undertakes to transfer to the Buyer Goods of proper quality and in proper packaging; + +**3.1.4.** To transfer the Goods free from the rights of third parties; + +**3.1.5.** To organise the delivery of the Goods to the Buyer; + +**3.1.6.** To provide the Buyer with all necessary information in accordance with the requirements of the current legislation of the Russian Federation and this Offer; + +### 3.2. Rights and obligations of the Buyer + +**3.2.1.** The Buyer has the right to demand the transfer of the Goods in the manner and on the terms provided for by the Contract. + +**3.2.2.** To demand the provision of all necessary information in accordance with the requirements of the current legislation of the Russian Federation and this Offer; + +**3.2.3.** To refuse the Goods on the grounds provided for by the Contract and the current legislation of the Russian Federation. + +**3.2.4.** The Buyer undertakes to provide the Seller with accurate information necessary for the proper performance of the Contract; + +**3.2.5.** To accept and pay for the Goods in accordance with the terms of the Contract; + +**3.2.6.** The Buyer warrants that all terms of the Contract are clear to them; the Buyer accepts the terms without reservations and in full. + +## 4. Price and payment procedure + +**4.1.** The cost and the procedure for paying for the Goods are determined on the basis of the Seller's information at the time the Buyer places an order, or are established on the Seller's Website on the Internet as well as in the Seller's Application. + +**4.2.** All settlements under the Contract are made by non-cash means. + +**4.3.** Settlement procedure. The Goods purchased are the in-game currency "Chip" — a conventional unit of account used exclusively within the Seller's Website or the Seller's Application. "Chips" give the Buyer the opportunity to obtain in-game benefits and additional features, including but not limited to: opting out of advertising display, purchasing hints in the game, and other in-game features. A "Chip" is not an electronic means of payment or funds, is not subject to exchange for funds and cannot be used outside the Seller's Website or the Seller's Application. "Chips" are credited to the Buyer's in-game account at once upon receipt of payment; their further use to obtain in-game benefits is carried out by the Buyer independently within the used Seller's Website or Seller's Application. + +**4.4.** Cost of the Goods: + +<#pricing_template#> + +## 5. Exchange and return of Goods + +**5.1.** The Buyer has the right to return (exchange) to the Seller Goods purchased by distance means, except for the list of goods not subject to exchange and return in accordance with the current legislation of the Russian Federation. The conditions, terms and procedure for returning Goods of proper and improper quality are established in accordance with the Civil Code of the Russian Federation, the Law of the Russian Federation of 07.02.1992 No. 2300-1 "On the Protection of Consumer Rights", and the Rules approved by Decree of the Government of the Russian Federation of 31.12.2020 No. 2463. + +**5.2.** The Buyer's demand for the exchange or return of Goods is considered individually, provided that the purchased goods have not been used and there are valid reasons (a technical failure, an error in the description of the goods). + +## 6. Confidentiality and security + +**6.1.** In implementing this Contract, the Parties ensure the confidentiality and security of personal data in accordance with the current version of Federal Law No. 152-FZ of 27.07.2006 "On Personal Data" and Federal Law No. 149-FZ of 27.07.2006 "On Information, Information Technologies and the Protection of Information". + +**6.2.** The Parties undertake to maintain the confidentiality of information obtained in the course of performing this Contract, and to take all possible measures to protect the obtained information from disclosure. + +**6.3.** Confidential information means any information transmitted by the Seller and the Buyer in the course of implementing the Contract and subject to protection; exceptions are indicated below. + +**6.4.** Such information may be contained in local regulations, contracts, letters, reports, analytical materials, research results, schemes, graphs, specifications and other documents provided by the Seller, drawn up both on paper and on electronic media. + +## 7. Force majeure + +**7.1.** The Parties are released from liability for non-performance or improper performance of obligations under the Contract if proper performance proved impossible due to force majeure, that is, extraordinary and unavoidable circumstances under the given conditions, which are understood to mean: prohibitive actions of the authorities, epidemics, blockade, embargo, earthquakes, floods, fires or other natural disasters. + +**7.2.** In the event of these circumstances, the Party is obliged, within 30 (thirty) business days, to notify the other Party. + +**7.3.** A document issued by an authorised state body is sufficient confirmation of the existence and duration of force majeure. + +**7.4.** If force-majeure circumstances continue for more than 60 (sixty) business days, then each Party has the right to unilaterally withdraw from this Contract. + +## 8. Liability of the Parties + +**8.1.** In the event of non-performance and/or improper performance of their obligations under the Contract, the Parties bear liability in accordance with the terms of this Offer. + +**8.2.** A Party that has not performed or has improperly performed its obligations under the Contract is obliged to compensate the other Party for the losses caused by such violations. + +## 9. Duration of this Offer + +**9.1.** The Offer enters into force from the moment of its placement on the Seller's Website and is valid until it is withdrawn by the Seller. + +**9.2.** The Seller reserves the right to make changes to the terms of the Offer and/or to withdraw the Offer at any time at their discretion. Information about the change or withdrawal of the Offer is brought to the Buyer's attention, at the Seller's choice, by placement on the Seller's Website on the Internet, in the Buyer's Personal Account, or by sending a corresponding notification to the email or postal address indicated by the Buyer when concluding the Contract or in the course of its performance. + +**9.3.** The Contract enters into force from the moment of Acceptance of the terms of this Offer by the Buyer and is valid until the Parties have fully performed their obligations under the Contract. + +**9.4.** Changes made by the Seller to the Contract and published on the Seller's Website in the form of an updated Offer are deemed accepted by the Buyer in full upon payment for the Goods. + +## 10. Additional conditions + +**10.1.** The Contract, its conclusion and performance are governed by the current legislation of the Russian Federation. All matters not settled or not fully settled by this Offer are governed in accordance with the substantive law of the Russian Federation. + +**10.2.** In the event of a dispute that may arise between the Parties in the course of performing their obligations under the Contract concluded on the terms of this Offer, the Parties are obliged to settle the dispute amicably before the start of court proceedings. + +Court proceedings are carried out in accordance with the legislation of the Russian Federation. + +Disputes or disagreements on which the Parties have not reached agreement are subject to resolution in accordance with the legislation of the Russian Federation. The pre-trial dispute-resolution procedure is mandatory. + +**10.3.** As the language of the Contract concluded on the terms of this Offer, as well as the language used in any interaction between the Parties (including correspondence, provision of demands / notifications / clarifications, provision of documents, etc.), the Parties have determined the Russian language. + +**10.4.** All documents to be provided in accordance with the terms of this Offer must be drawn up in Russian or have a translation into Russian, certified in the established manner. + +**10.5.** Inaction by one of the Parties in the event of a violation of the terms of this Offer does not deprive the interested Party of the right to protect its interests later, nor does it mean a waiver of its rights in the event that one of the Parties commits similar or comparable violations in the future. + +**10.6.** If the Seller's Website on the Internet or the Seller's Application contains links to other websites and third-party materials, such links are placed solely for informational purposes, and the Seller has no control over the content of such sites or materials. The Seller is not liable for any losses or damage that may arise as a result of using such links. + +## 11. Seller's details + +Ilya Arkadyevich Denisov, TIN 290210610742. + +Feedback on Telegram: [@Erudit_GameBot](https://t.me/Erudit_GameBot). diff --git a/ui/legal/privacy_en.md b/ui/legal/privacy_en.md new file mode 100644 index 0000000..07e1a33 --- /dev/null +++ b/ui/legal/privacy_en.md @@ -0,0 +1,140 @@ +# Privacy Policy of the Game "Erudit" + +## 1. About the service + +**1.1.** The Game and its accompanying services are provided by Ilya Arkadyevich Denisov, TIN 290210610742 (hereinafter "we", "us", "our"). + +**1.2.** This Privacy Policy supplements the End-User Licence Agreement available at [«Terms»](/eula/) and must be read together with it. + +**1.3.** This Privacy Policy establishes how we collect and use your personal information when you (i) use the Game and the Website defined in the Terms, and (ii) use the mobile, online and downloadable products and services (hereinafter the "Services") offered by `erudit-game.ru` and its partners and affiliates on the Website, as well as the options available to you in connection with our use of your personal information (hereinafter the "Privacy Policy"). + +**1.4.** In the event of any conflict between this Privacy Policy and the Terms, this Privacy Policy prevails with respect to the processing of personal data. + +## 2. About this Privacy Policy + +**2.1.** In providing the Services, acting reasonably and in good faith, we assume that you: + +- (a) have all the rights necessary to register on the Website and use the Services; +- (b) provide truthful information about yourself to the extent necessary to use the Services; +- (c) understand that, by publishing your personal information where the Services technically allow it and where it is available to other Users of the Services, you have expressly made that information public, and it may become available to other Users of the Website and to Internet users and be copied and distributed by them; +- (d) understand that certain types of information you transmit to other Users of the Service cannot be deleted by you or by us; +- (e) are aware of this Privacy Policy and accept it. + +**2.2.** We do not verify the information you provide about Users, except where such verification is necessary for us to perform our obligations to you. + +## 3. Information we collect about you + +**3.1.** In order to give effect to the agreement between you and us and to provide you with access to the Services, we will improve, develop and introduce new features for our Services and expand the functionality of the available Services. To achieve these goals and in accordance with applicable law, we will collect, store, aggregate, systematise, extract, compare, use and supplement your data (hereinafter "processing"). We will also receive and transmit this data and the results of its automated analysis to our partners, as indicated in the table below and in section 4 of this Privacy Policy. + +**3.2.** Below we describe in more detail the information we collect when you use our Services, why we collect and process it, and the legal grounds for doing so. + +**3.3.** General provisions that apply to your use of the Website and the Services related to the online products of `erudit-game.ru` and its downloadable products, and those of its partners and affiliates. + +We do not collect the data listed below by default. Some of it — in particular, an identity document and payment details — is processed **only if you voluntarily provide it**, as a rule when contacting Support or to confirm your identity. Other data (for example, technical device parameters) is processed automatically to the extent necessary for the Services to function. + +| Information collected | Purpose | Legal basis | +|---|---|---| +| Data received from third parties, including social-network identifiers, app-store identifiers, your social-network nickname, email address and friends list, when you connect your social account (such as VK, Telegram, Apple Game Center and others) to our Services. | We import this information into your profile; use it to control and administer the Services and for certain social features (for example, to show which of your friends play the same game, or to let you publish your achievements to your social network); and to store data about your use of the Services (game progress and achievements) across different devices connected to the same social account. | Legitimate interests; performance of our contract with you. | +| Data you provide voluntarily, in particular when contacting Support or to confirm your identity: a copy of an identity document (first name, surname, photograph, document number), payment details and other data needed for verification. We request this only when necessary and do not collect it during ordinary use of the Game. | Identification, verification of your account and prevention of abuse or infringement of your rights or the rights of others (for example, to confirm your identity if you lose access to your account). | Legitimate interests; performance of our contract with you; processing necessary to comply with a legal obligation. | +| Information obtained from your behaviour while using the Services (including your in-game actions, achievements and badges). This information may be available to other users (for example, in leaderboards). | Control and administration of the Services; tailoring and improving the advertising offered to you and measuring its effectiveness. | Legitimate interests. | +| Information obtained from your use of the Services' payment features (for example, the first and last four digits of your bank-card number, needed to match a payment to your account). The full card number and other payment details are processed by the payment provider and are not stored by us. | Control and administration of the Services; investigating complaints on your behalf and improving service quality. | Legitimate interests; performance of our contract with you. | +| Information you create while using the Services (where the Services technically allow it), including information you publish in in-game chats. Depending on the Service and where it is placed, this information may be available to some or all other users. | Control and administration of the Services. | Legitimate interests (in particular, processing of data you have expressly made public); performance of our contract with you. | +| Information you create when submitting requests to our Support service. | Confirming your identity and fulfilling your request; investigating complaints and improving service quality. | Legitimate interests; performance of our contract with you. | +| Additional data obtained when you access the Services: technical details of your interaction with the Service, such as your IP address, time of registration, device identifiers, country and language settings, device model, operating system, browser type, Internet provider and/or telephone-network operator, network type and screen resolution. | Internal analysis to continuously improve the content of our Services and web pages, optimise your experience, understand errors, notify you of changes and personalise the Services; tailoring and measuring the effectiveness of advertising. | Legitimate interests; performance of our contract with you. | + +**3.4.** Our legitimate interests include (1) maintaining and administering the Services; (2) providing you with the Services; (3) improving the content of the Services and web pages; (4) processing data you have expressly made public where it is available to other Users of the Services; (5) ensuring appropriate protection of your account; and (6) complying with any contractual, legal or regulatory obligations under any applicable law. + +**3.5.** As part of servicing and administering the Services, we use this information to analyse user activity and to ensure that the rules and Terms of use of the Services are not violated. + +**3.6.** Your personal information may also be processed where required by a law-enforcement or regulatory authority or institution, or to defend against or bring legal claims. We will not delete personal information where it is relevant to an investigation or dispute — it will be retained until those matters are fully resolved and/or for the period required and/or permitted under applicable/relevant law. + +**3.7.** If you have given us consent to send you marketing information, you may withdraw your consent by changing the privacy settings of your account. The ability to unsubscribe will also be included in every email sent to you by us or our selected partners. + +**3.8.** Please note that if you do not want us to process confidential and special categories of data about you (including data concerning your health, racial or ethnic origin, political opinions, religious or philosophical beliefs, sex life and/or sexual orientation), you should not post that information or transmit that data on the Website and/or in the Services. Once you provide this data, it will be available to other users of the Website, and it will be difficult for us to delete it. + +**3.9.** Please note that if you withdraw your consent to the processing of data or do not provide the data we require to service and administer the Services, you will not be able to access the Services or register on the Website's web pages. + +**3.10.** If we intend to process your data for any purposes other than those specified in this Privacy Policy, we will provide you with information about such additional purposes before we begin processing. + +## 4. Data sharing + +**4.1.** Publicly available data. Your username and other information you provide or post while using the Services may be available to all Users of the Services. By posting personal information in publicly available areas (resources available to other Users of the Services), you expressly make that information public, and it may become available to other Users of the Services and to Internet users and may be copied or distributed by them. Please note that from the moment other users have accessed or copied your data, neither you nor we can delete or withdraw such data from the possession of those other users. + +**4.2.** Sharing with third parties. We may share your personal information with third parties only in the ways specified in this Privacy Policy. From time to time we may need to transfer your data to a third party in order to provide and operate the Services, to manage billing services, or to personalise, customise and improve our Services, and only in accordance with the purposes described in this Privacy Policy. We do not sell your personal information to third parties. + +The transfer of personal data to recipients (regardless of their legal status) is carried out securely and in accordance with an agreement between us and each recipient, as may be required under applicable law. We undertake to ensure that each recipient is aware of the governing principles of personal-data protection and complies with them in accordance with the law and/or a specific contract. + +**4.3.** Confidentiality obligations. Where we transfer your data to individual third parties, including third-party contractors and application developers, we always guarantee that such parties assume confidentiality obligations with respect to your personal data collected when you use the services or applications they offer. We will not transfer your personal data beyond the purposes specified in this Privacy Policy without your prior consent. + +**4.4.** Advertising notice. Our advertising and recommendation management system is designed so that your information is not transferred directly to our third-party advertisers. An advertiser or recommendation creator may only choose to target advertising to groups of users falling under criteria such as age, gender, location (country, city) or others, or to target communities according to their type. If you fall into one of the target groups, you will receive advertising or a recommendation from such third-party partners or our affiliates. Nevertheless, such third-party advertisers or our affiliates may collect certain information about you if you interact in any way with the advertisements they provide. + +**4.5.** Retargeting notice. An advertiser or recommendation creator may also upload a list of identifiers (for example, email addresses, telephone numbers) and identification data into our systems so that we (but not the consultant or recommendation creator) can check the match of users. They will see the number of matches, but not the matches themselves. + +**4.6.** Disclosure to tax authorities. We reserve the right to disclose your personal information to the tax authorities where necessary in connection with your participation in public tournaments. We may also publish your data as part of tournament results lists on our Website and on third-party websites. + +**4.7.** Disclosure in accordance with the law. We reserve the right to disclose your personal information in accordance with the requirements of the law, by court decision, or in special cases where we have grounds to believe that disclosure of such information is necessary to identify, contact or bring legal action against you or third parties, if you or such third parties violate the Terms, any other terms of service provided by us or our affiliates, or any applicable law, in order to protect our rights and interests. We also reserve the right to disclose your personal information if we believe in good faith that it is necessary to prevent fraud or other illegal actions. + +## 5. Privacy settings + +**5.1.** The Services may contain links to websites operated by third parties. We are not responsible for the privacy of your data when you access these links or interact with third-party services, and you should make sure you have read the relevant third party's privacy statement, which will govern your data-privacy rights. + +**5.2.** We are not responsible for the actions of third parties who, as a result of your use of the Internet or the Services, gain access to your information in accordance with the level of privacy you have chosen. + +**5.3.** We are not responsible for the consequences of using information that, by the nature of the Services, is available to any Internet user. We ask you to be responsible in your choice of information posted on the Website. + +## 6. Transfer of information between countries + +**6.1.** We may transfer and store some of your personal information on our servers or in databases outside the European Economic Area (EEA), including in Russia. + +**6.2.** The countries to which we transfer your data may not have the same data-protection laws as those that exist in your jurisdiction. We take reasonable cybersecurity measures and/or apply standard contractual clauses to ensure appropriate protection of your data. + +## 7. Retention periods + +**7.1.** We will retain your personal information for as long as necessary to achieve the purposes for which the data was collected, depending on the legal basis on which the data was obtained, and/or on whether additional legal/regulatory obligations require the retention of your personal information for a period that is mandatory and/or permitted under applicable/relevant law. + +**7.2.** You can delete your personal data by removing the data from your account; in addition, you can delete your account. + +**7.3.** You can request the deletion of your account and data in our Services by contacting the Services' Support service (for details, see section 11). + +**7.4.** We may delete your account or the information you publish in accordance with the Terms. + +## 8. Your rights + +**8.1.** In certain circumstances you have the following rights with respect to your personal information: + +- (a) The right to access your personal information. +- (b) The right to rectification of your personal information: you may require us to update, block or delete your personal data if the data is incomplete, outdated, incorrect, unlawfully obtained or no longer relevant to the purpose of processing. +- (c) The right to restrict the use of your personal information. +- (d) The right to require that your personal information be erased if: its processing is no longer required in connection with the purposes for which it was collected or otherwise processed; you withdraw your consent where processing is based on consent; you reasonably object to the processing; such information has been processed unlawfully; or this is mandatory under the law. +- (e) The right to object to the processing of your personal information. +- (f) The right to data portability (in certain circumstances). +- (g) The right not to be subject to automated decision-making. +- (h) The right to lodge a complaint with a supervisory authority. +- (i) For processing based on your consent, the right to withdraw that consent at any time. +- (j) You may have other rights under the law of your country of residence, including the right to determine instructions concerning the processing of your personal data after your death. + +**8.2.** You also have the right to delete personal information from your account yourself and to make changes and corrections to your information, provided that such changes and corrections contain up-to-date and accurate information. You may also review an overview of the information we hold about you. To exercise the rights provided for by data-protection law, you may contact the personal-data supervisory authority of your country of residence. + +**8.3.** If you wish to exercise these rights, contact Support at the address indicated in section 11 of this Privacy Policy. We will try to respond to you within 30 days of receiving your request. We will need to confirm your identity before we can disclose any personal data to you. + +## 9. Security measures + +**9.1.** We take technical, organisational and legal measures, including, where possible, encryption, to protect your personal data from unauthorised or accidental access, deletion, alteration, blocking, copying and distribution. + +**9.2.** Access to the Services is authorised using your login (email address or, where applicable, mobile telephone number) and password. You are responsible for keeping this information confidential. You must not share your credentials with third parties, and we recommend that you take measures to keep them confidential. + +**9.3.** If you have forgotten your login details, you can ask us to send you an email (or, where applicable, an SMS) containing a recovery code. + +**9.4.** To reduce the likelihood of unauthorised access by third parties, if you log in to your account from an unusual location or after several failed attempts to provide valid login details, we may block access to your account. After that, you will need to contact the Services' Support service and provide certain additional information to confirm your credentials and regain access to your account. + +## 10. Changes to this Policy + +**10.1.** At any appropriate time we may change and/or update this Privacy Policy. If this Privacy Policy changes, we will publish the updated version on this page. We will keep previous versions in our documentation archive. We recommend that you review this page regularly so that you are always aware of our information-handling practices and any changes to them. + +## 11. Contact us + +**11.1.** If you have any questions, please contact Support on Telegram: [@Erudit_GameBot](https://t.me/Erudit_GameBot), or send us your request in writing to: 236020, Kaliningrad, Pribrezhny district, Parkovaya St. 1, recipient — Ilya Arkadyevich Denisov. Please refer to this Privacy Policy so that we can process your request effectively. We will try to respond to you within 30 days of receiving your request. + +**11.2.** All correspondence we receive from you (written or electronic requests) is classified as restricted-access information and may not be disclosed without your written consent. Personal data and other information about you may not be used without your consent for any purpose other than responding to the request, except in cases expressly provided for by law. + +**11.3.** The email address of our data-protection officer: ilia.denissov@gmail.com. diff --git a/ui/src/lib/offer.test.ts b/ui/src/lib/offer.test.ts index 0e7fd58..c8c5228 100644 --- a/ui/src/lib/offer.test.ts +++ b/ui/src/lib/offer.test.ts @@ -1,52 +1,62 @@ import { describe, it, expect } from 'vitest'; import { renderOfferHtml, renderLegalHtml } from './offer'; -describe('renderOfferHtml', () => { - it('wraps rendered markdown in a standalone Russian HTML document', () => { - const html = renderOfferHtml('# Публичная оферта\n\nо заключении договора купли-продажи'); - expect(html).toContain(''); - expect(html).toContain('lang="ru"'); - expect(html).toContain('Публичная оферта — Эрудит'); - // The markdown heading is rendered, not left as literal source. - expect(html).toContain('

Публичная оферта

'); - expect(html).not.toContain('# Публичная оферта'); - // No in-page navigation: the standalone offer carries no "back" link. - expect(html).not.toContain('class="back"'); - }); - - it('renders headings, bold clause numbers and lists', () => { - const html = renderOfferHtml('## 2. Предмет\n\n**2.1.** Текст пункта.\n\n- первый\n- второй'); - expect(html).toContain('

2. Предмет

'); - expect(html).toContain('2.1.'); - expect(html).toContain('
  • первый
  • '); - }); -}); - describe('renderLegalHtml', () => { - it('applies the given title and canonical URL to the shared chrome', () => { - const html = renderLegalHtml('# Политика конфиденциальности\n\nтекст', { - title: 'Политика конфиденциальности — Эрудит', + it('renders both language bodies into one standalone document with a switcher', () => { + const html = renderLegalHtml({ + ru: { md: '# Политика\n\nрусский текст', title: 'Политика — Эрудит' }, + en: { md: '# Policy\n\nenglish text', title: 'Policy — Erudit' }, canonical: 'https://erudit-game.ru/privacy/', }); expect(html).toContain(''); - expect(html).toContain('Политика конфиденциальности — Эрудит'); + expect(html).toContain('Политика — Эрудит'); // Russian is the default expect(html).toContain('href="https://erudit-game.ru/privacy/"'); - expect(html).toContain('

    Политика конфиденциальности

    '); + // Both bodies are present; English is hidden until toggled. + expect(html).toContain('
    '); + expect(html).toContain('
    '); + expect(html).toContain('

    Политика

    '); + expect(html).toContain('

    Policy

    '); + // The language + theme switcher and its script. + expect(html).toContain('id="legal-lang"'); + expect(html).toContain('id="legal-theme"'); + expect(html).toContain('erudit.legal.lang'); + // Titles carried on so the toggle can update document.title. + expect(html).toContain('data-title-ru="Политика — Эрудит"'); + expect(html).toContain('data-title-en="Policy — Erudit"'); }); - it('renderOfferHtml is the offer preset over it', () => { - const html = renderOfferHtml('# Публичная оферта'); - expect(html).toContain('Публичная оферта — Эрудит'); - expect(html).toContain('href="https://erudit-game.ru/offer/"'); + it('renders markdown headings, bold clause numbers and lists in both languages', () => { + const html = renderLegalHtml({ + ru: { md: '## 2. Предмет\n\n**2.1.** Текст.\n\n- первый\n- второй', title: 't' }, + en: { md: '## 2. Subject\n\n**2.1.** Text.', title: 't' }, + canonical: 'c', + }); + expect(html).toContain('

    2. Предмет

    '); + expect(html).toContain('2.1.'); + expect(html).toContain('
  • первый
  • '); + expect(html).toContain('

    2. Subject

    '); }); it('scopes the shrink-to-fit first-column table rule to the offer only', () => { // The offer opts into the nowrap product-name column via body.offer; other legal pages (the // privacy data table) must NOT get it, or their prose first column cannot wrap. - expect(renderOfferHtml('# x')).toContain(''); - const generic = renderLegalHtml('# x', { title: 't', canonical: 'c' }); + const generic = renderLegalHtml({ ru: { md: '# x', title: 't' }, en: { md: '# x', title: 't' }, canonical: 'c' }); expect(generic).toContain(''); expect(generic).not.toContain(' { + it('is the offer preset: both languages, body.offer, offer title + canonical, EN transliteration', () => { + const html = renderOfferHtml('# Публичная оферта\n\nрусский', '# Public offer\n\nenglish'); + expect(html).toContain(''); + expect(html).toContain('Публичная оферта — Эрудит'); + expect(html).toContain('data-title-en="Public offer — Erudit"'); + expect(html).toContain('href="https://erudit-game.ru/offer/"'); + expect(html).toContain('

    Публичная оферта

    '); + expect(html).toContain('

    Public offer

    '); + // The offer page transliterates the Russian product names left in the English (price-list) view. + expect(html).toContain('translitEn'); + }); +}); diff --git a/ui/src/lib/offer.ts b/ui/src/lib/offer.ts index 78d3352..b112437 100644 --- a/ui/src/lib/offer.ts +++ b/ui/src/lib/offer.ts @@ -1,29 +1,36 @@ import { marked } from 'marked'; +/** LegalDoc is one language variant of a legal document: its markdown source and page title. */ +export interface LegalDoc { + md: string; + title: string; +} + /** - * renderLegalHtml renders a legal-document markdown source into a standalone, - * self-contained HTML document. It backs every public legal page — the offer at - * `/offer/`, the privacy policy at `/privacy/` and the EULA at `/eula/` — served by - * the render sidecar (`renderer/src/{offer,legal}.mjs`), which reads the owner-edited - * markdown under `ui/legal/` and, for the offer only, splices the live catalog price - * list in before calling this. One renderer shared with the browser build, kept - * unit-tested here (`offer.test.ts`). + * renderLegalHtml renders a bilingual (RU + EN) legal document as a standalone, self-contained HTML + * page with a header language + theme switcher, mirroring the landing: a 🌐 language toggle and a + * ☼/☾ theme toggle, no "back". Both language bodies are rendered into the page; a small inline + * ES5 script toggles between them client-side (no reload), defaults to Russian (never the browser + * language, like the landing) and persists the choice, and applies the theme (system default plus a + * persisted manual override). On the offer page (`bodyClass === 'offer'`) it also transliterates the + * Cyrillic left in the EN body — the live price list is spliced in Russian, so the product names are + * transliterated for the English view. It backs every public legal page — the offer at `/offer/`, the + * privacy policy at `/privacy/`, the EULA at `/eula/` — served by the render sidecar. * - * `title` becomes the document ``; `canonical` its absolute canonical URL. - * The input `markdown` is trusted repository content plus the backend's own catalog - * projection, not user input, so the rendered HTML is deliberately not sanitised. The - * page carries its own minimal light/dark styling so it needs neither the app bundle - * nor `app.css`. + * The markdown is trusted repository content plus the backend's own catalog projection, not user + * input, so the rendered HTML is deliberately not sanitised. The page carries its own minimal styling + * and script, so it needs neither the app bundle nor `app.css`. */ -export function renderLegalHtml(markdown: string, opts: { title: string; canonical: string; bodyClass?: string }): string { - const body = marked.parse(markdown, { async: false }) as string; +export function renderLegalHtml(doc: { ru: LegalDoc; en: LegalDoc; canonical: string; bodyClass?: string }): string { + const ruBody = marked.parse(doc.ru.md, { async: false }) as string; + const enBody = marked.parse(doc.en.md, { async: false }) as string; return `<!doctype html> -<html lang="ru"> +<html lang="ru" data-title-ru="${attr(doc.ru.title)}" data-title-en="${attr(doc.en.title)}"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <title>${opts.title} - + ${doc.ru.title} + - -
    - ${body} + +
    + + +
    +
    + ${ruBody}
    +
    + ${enBody} +
    + `; } +/** attr escapes a trusted string for use inside a double-quoted HTML attribute value. */ +function attr(s: string): string { + return s.replace(/&/g, '&').replace(/"/g, '"').replace(/
    200.00