diff --git a/package.json b/package.json index fa6b414..d9d6864 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ttrpg-player-landing", "private": true, - "version": "1.0.2", + "version": "1.0.3", "description": "Marketing landing for TTRPG Player (НРИ Плеер)", "type": "module", "scripts": { diff --git a/src/components/layout/Footer.module.css b/src/components/layout/Footer.module.css index 4c731ac..cfcf712 100644 --- a/src/components/layout/Footer.module.css +++ b/src/components/layout/Footer.module.css @@ -34,3 +34,18 @@ .link:hover { color: var(--color-gold); } + +.linkButton { + padding: 0; + border: none; + background: none; + font: inherit; + font-size: 14px; + color: var(--color-text-muted); + cursor: pointer; + transition: color 0.15s ease; +} + +.linkButton:hover { + color: var(--color-gold); +} diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index dfa0369..707e748 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -1,5 +1,6 @@ import { SITE_LINKS } from "../../data/siteContent"; import { useLocale } from "../../i18n/LocaleProvider"; +import { useEulaModal } from "../../providers/EulaModalProvider"; import styles from "./Footer.module.css"; @@ -21,6 +22,7 @@ const FOOTER_HREFS: Record<(typeof FOOTER_LINK_KEYS)[number], string> = { export function Footer() { const { copy } = useLocale(); + const { openModal } = useEulaModal(); return ( diff --git a/src/components/legal/EulaMarkdown.module.css b/src/components/legal/EulaMarkdown.module.css new file mode 100644 index 0000000..49c24a4 --- /dev/null +++ b/src/components/legal/EulaMarkdown.module.css @@ -0,0 +1,42 @@ +.root { + font-size: 14px; + line-height: 1.65; + color: var(--color-text-muted); +} + +.h1 { + margin: 0 0 16px; + font-size: 18px; + font-weight: 700; + line-height: 1.35; + color: var(--color-text); +} + +.h2 { + margin: 24px 0 10px; + font-size: 15px; + font-weight: 700; + line-height: 1.4; + color: var(--color-text); +} + +.h2:first-child { + margin-top: 0; +} + +.paragraph { + margin: 0 0 12px; +} + +.list { + margin: 0 0 12px; + padding-left: 20px; +} + +.list li { + margin-bottom: 6px; +} + +.list li:last-child { + margin-bottom: 0; +} diff --git a/src/components/legal/EulaMarkdown.tsx b/src/components/legal/EulaMarkdown.tsx new file mode 100644 index 0000000..53b8df4 --- /dev/null +++ b/src/components/legal/EulaMarkdown.tsx @@ -0,0 +1,51 @@ +import type { ReactNode } from "react"; + +import styles from "./EulaMarkdown.module.css"; + +function renderBlock(block: string, index: number): ReactNode { + const trimmed = block.trim(); + if (!trimmed) return null; + + if (trimmed.startsWith("# ")) { + return ( +
+ {lines.join(" ")} +
+ ); +} + +export function EulaMarkdown({ markdown }: { markdown: string }) { + const blocks = markdown.split(/\n\n+/u); + + return ( +{copy.pricing.footnote}
-{copy.pricing.donationNote}
+{copy.pricing.supportNote}
); } diff --git a/src/i18n/messages.ts b/src/i18n/messages.ts index 7598268..74e3950 100644 --- a/src/i18n/messages.ts +++ b/src/i18n/messages.ts @@ -24,9 +24,9 @@ const ru = { navAria: "Основная навигация", cta: { download: "Скачать", - buy: "Купить", + buy: "Поддержать", downloadFree: "Скачать бесплатно", - buyLicense: "Купить лицензию", + buyLicense: "Поддержать проект", downloadForOs: "Скачать для моей ОС", }, hero: { @@ -199,9 +199,9 @@ const ru = { }, exchangeRateNote: "", footnote: - "Ключ формата TTRPG-… · Привязка к устройству · Лицензионное соглашение в приложении", - donationNote: - "Оплата оформляется как добровольное пожертвование (donation) через внешний сервис; после подтверждения вы получаете продуктовый ключ TTRPG-….", + "Ключ формата TTRPG-… · Привязка к устройству · Лицензионное соглашение — в футере сайта", + supportNote: + "Вы можете добровольно поддержать проект. В знак благодарности правообладатель вручную выдаёт продуктовый ключ TTRPG-… соответствующего уровня доступа.", }, comparison: { title: "От разрозненных вкладок — к пульту мастера", @@ -251,14 +251,20 @@ const ru = { messageAfter: ", следите за новостями.", close: "Понятно", }, + eulaModal: { + title: "Лицензионное соглашение", + localeNote: "", + close: "Закрыть", + }, footer: { aria: "Подвал", links: { download: "Скачать", - buy: "Купить", + buy: "Поддержать", faq: "FAQ", enterKey: "Выбрать ключ", support: "Поддержка", + eula: "Лицензия", }, }, localeSwitcher: { @@ -292,9 +298,9 @@ const en: SiteCopy = { navAria: "Main navigation", cta: { download: "Download", - buy: "Buy", + buy: "Support", downloadFree: "Download free", - buyLicense: "Buy license", + buyLicense: "Support the project", downloadForOs: "Download for my OS", }, hero: { @@ -467,9 +473,9 @@ const en: SiteCopy = { exchangeRateNote: "USD equivalent at the current RUB exchange rate (updated when you open the site).", footnote: - "Key format TTRPG-… · Device-bound · License agreement in the app", - donationNote: - "Payment is processed as a voluntary donation through an external service; after confirmation you receive a TTRPG-… product key.", + "Key format TTRPG-… · Device-bound · License agreement in the site footer", + supportNote: + "You can voluntarily support the project. As a thank-you, the rights holder manually issues a TTRPG-… product key for the corresponding access tier.", }, comparison: { title: "From scattered tabs to a GM panel", @@ -517,14 +523,20 @@ const en: SiteCopy = { messageAfter: " — stay tuned for updates.", close: "Got it", }, + eulaModal: { + title: "End User License Agreement", + localeNote: "The binding legal text below is in Russian.", + close: "Close", + }, footer: { aria: "Footer", links: { download: "Download", - buy: "Buy", + buy: "Support", faq: "FAQ", enterKey: "Choose key", support: "Support", + eula: "EULA", }, }, localeSwitcher: { diff --git a/src/legal/eulaRu.ts b/src/legal/eulaRu.ts new file mode 100644 index 0000000..26edcf8 --- /dev/null +++ b/src/legal/eulaRu.ts @@ -0,0 +1,103 @@ +/** Текст EULA (как в приложении). Не заменяет консультацию юриста. */ +export const EULA_RU_MARKDOWN = ` +# Лицензионное соглашение с конечным пользователем (EULA) + +Настоящее соглашение регулирует использование программы «НРИ Плеер» («Программа»). + +Правообладатель: Фонтош И.С., физическое лицо. + +Используя Программу, устанавливая её, активируя лицензионный ключ или нажимая кнопку принятия условий, пользователь подтверждает, что прочитал и принимает настоящее соглашение. + +## 1. Предоставление прав + +Правообладатель предоставляет пользователю простую (неисключительную), непередаваемую, ограниченную лицензию на установку и использование Программы в личных некоммерческих целях, связанных с подготовкой и проведением настольных ролевых игр. + +Исключительные права на Программу не передаются пользователю. Все права, прямо не предоставленные настоящим соглашением, сохраняются за правообладателем. + +## 2. Активация, срок, устройства + +Доступ к функциям Программы может предоставляться на основании лицензионного ключа, пробного периода или иного разрешения правообладателя. + +Доступные варианты лицензии: + +- Базовый ключ: доступ на 1 год, до 3 устройств. +- Ключ мастера: доступ на 2 года, до 5 устройств. +- Легенда НРИ: доступ на 99 лет, до 15 устройств. +- Пробный доступ: предоставляется бесплатно на условиях, указанных правообладателем. + +Срок доступа и число устройств определяются выданным лицензионным ключом и отображаются в разделе «О лицензии». + +## 3. Поддержка проекта + +Пользователь может добровольно поддержать проект. Такая поддержка является добровольным взносом в развитие Программы и не является обязательным условием заключения договора купли-продажи. + +В знак благодарности за поддержку проекта правообладатель может вручную предоставить пользователю лицензионный ключ соответствующего уровня доступа. Условия предоставления ключей публикуются правообладателем или сообщаются пользователю отдельно. + +## 4. Активация и проверка лицензии + +Для активации Программа отправляет на сервер лицензирования лицензионный ключ и технический идентификатор устройства (deviceId). Для проверки отзыва лицензии Программа может отправлять идентификатор лицензии (sub). + +Программа не отправляет на сервер имя пользователя, адрес электронной почты, содержимое проектов, сцены, изображения, музыку, заметки, кампании или иные пользовательские материалы. + +Лицензионный токен хранится локально на устройстве пользователя с использованием защищённого хранилища операционной системы, если оно доступно. + +## 5. Ограничения + +Пользователь не вправе: + +- передавать, продавать, сдавать в аренду или публиковать лицензионный ключ; +- обходить активацию, технические ограничения или механизмы защиты; +- модифицировать, декомпилировать, дизассемблировать или иным образом пытаться получить исходный код Программы, кроме случаев, прямо разрешённых законом; +- распространять копии Программы от имени правообладателя без разрешения; +- использовать Программу способом, нарушающим законодательство РФ или права третьих лиц. + +## 6. Пользовательские материалы + +Проекты, сцены, изображения, музыка, тексты и иные материалы, загружаемые пользователем в Программу, остаются у пользователя. Правообладатель не получает прав на такие материалы и не имеет доступа к ним через механизм лицензирования. + +Пользователь самостоятельно отвечает за наличие прав на материалы, которые он использует в своих проектах. + +## 7. Обновления + +Правообладатель может выпускать обновления Программы. Все обновления Программы предоставляются бесплатно, если правообладатель отдельно не объявит иные условия для будущих продуктов или отдельных редакций. + +Правообладатель не обязан выпускать обновления в определённые сроки. + +## 8. Отзыв и ограничение доступа + +Правообладатель вправе отозвать или заблокировать лицензию при нарушении настоящего соглашения, передаче ключа третьим лицам, попытке обхода защиты, злоупотреблении числом устройств или ином недобросовестном использовании. + +После отзыва лицензии Программа может ограничить доступ к функциям при наличии сетевого подключения и успешной проверке статуса лицензии. + +## 9. Отказ от гарантий + +Программа предоставляется «как есть». Правообладатель не гарантирует, что Программа будет работать без ошибок, соответствовать всем ожиданиям пользователя или быть совместимой с любым оборудованием и программным окружением. + +Настоящий раздел применяется в пределах, допускаемых законодательством РФ. + +## 10. Ограничение ответственности + +Правообладатель не несёт ответственности за потерю пользовательских данных, невозможность проведения игры, косвенные убытки, упущенную выгоду или последствия использования пользовательских материалов, если иное прямо не установлено обязательными нормами законодательства РФ. + +Ответственность правообладателя в любом случае ограничивается суммой фактически полученной от пользователя поддержки за соответствующий лицензионный ключ, если иное не установлено законом. + +## 11. Персональные данные и конфиденциальность + +Программа не запрашивает и не передаёт персональные данные пользователя при активации лицензии. Для работы лицензирования используются только технические данные: лицензионный ключ, deviceId, идентификатор лицензии и технические данные сетевого соединения, которые могут автоматически обрабатываться сервером. + +Если в будущем Программа или связанные с ней сервисы начнут собирать персональные данные, их обработка будет регулироваться отдельной политикой конфиденциальности. + +## 12. Применимое право и споры + +К настоящему соглашению применяется право Российской Федерации. + +Стороны стремятся решать споры путём переговоров. Если спор не урегулирован, он рассматривается в соответствии с законодательством РФ. + +Если пользователь является потребителем, его права, предоставленные императивными нормами законодательства о защите прав потребителей, не ограничиваются настоящим соглашением. + +## 13. Изменение соглашения + +Правообладатель вправе изменять настоящее соглашение. При существенном изменении условий Программа может запросить повторное принятие новой версии соглашения. + +Продолжение использования Программы после принятия новой версии означает согласие пользователя с обновлёнными условиями. +`.trim(); diff --git a/src/main.tsx b/src/main.tsx index 3dac449..da62ec5 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,6 +4,7 @@ import { createRoot } from "react-dom/client"; import { App } from "./App"; import { LocaleProvider } from "./i18n/LocaleProvider"; import { CrowdfundingModalProvider } from "./providers/CrowdfundingModalProvider"; +import { EulaModalProvider } from "./providers/EulaModalProvider"; import { ExchangeRateProvider } from "./providers/ExchangeRateProvider"; import "./styles/global.css"; @@ -17,7 +18,9 @@ createRoot(rootEl).render({copy.eulaModal.localeNote}
+ ) : null} +