Polish landing copy, footer links, and buy button behavior.

Rewrite feature descriptions in narrative style, open buy links in a new tab, update support email, rename key action to Choose key, and remove privacy policy from the footer.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-06-28 12:26:31 +08:00
parent ff652084f5
commit eb84d39bc0
8 changed files with 44 additions and 32 deletions
+8 -3
View File
@@ -9,7 +9,6 @@ const FOOTER_LINK_KEYS = [
"faq", "faq",
"enterKey", "enterKey",
"support", "support",
"privacy",
] as const; ] as const;
const FOOTER_HREFS: Record<(typeof FOOTER_LINK_KEYS)[number], string> = { const FOOTER_HREFS: Record<(typeof FOOTER_LINK_KEYS)[number], string> = {
@@ -18,7 +17,6 @@ const FOOTER_HREFS: Record<(typeof FOOTER_LINK_KEYS)[number], string> = {
faq: SITE_LINKS.faq, faq: SITE_LINKS.faq,
enterKey: SITE_LINKS.enterKey, enterKey: SITE_LINKS.enterKey,
support: SITE_LINKS.support, support: SITE_LINKS.support,
privacy: SITE_LINKS.privacy,
}; };
export function Footer() { export function Footer() {
@@ -30,7 +28,14 @@ export function Footer() {
<p className={styles.copy}>© TTRPGPlayer</p> <p className={styles.copy}>© TTRPGPlayer</p>
<nav className={styles.links} aria-label={copy.footer.aria}> <nav className={styles.links} aria-label={copy.footer.aria}>
{FOOTER_LINK_KEYS.map((key) => ( {FOOTER_LINK_KEYS.map((key) => (
<a key={key} className={styles.link} href={FOOTER_HREFS[key]}> <a
key={key}
className={styles.link}
href={FOOTER_HREFS[key]}
{...(key === "buy"
? { target: "_blank", rel: "noopener noreferrer" }
: undefined)}
>
{copy.footer.links[key]} {copy.footer.links[key]}
</a> </a>
))} ))}
+1
View File
@@ -43,6 +43,7 @@ export function Header() {
href={SITE_LINKS.buy} href={SITE_LINKS.buy}
variant="secondary" variant="secondary"
className={styles.actionBtn} className={styles.actionBtn}
newTab
> >
{copy.cta.buy} {copy.cta.buy}
</Button> </Button>
+1 -1
View File
@@ -13,7 +13,7 @@ export function CtaBanner() {
<h2 className={styles.title}>{copy.ctaBanner.title}</h2> <h2 className={styles.title}>{copy.ctaBanner.title}</h2>
<div className={styles.actions}> <div className={styles.actions}>
<Button href={SITE_LINKS.download}>{copy.cta.download}</Button> <Button href={SITE_LINKS.download}>{copy.cta.download}</Button>
<Button href={SITE_LINKS.buy} variant="secondary"> <Button href={SITE_LINKS.buy} variant="secondary" newTab>
{copy.cta.buyLicense} {copy.cta.buyLicense}
</Button> </Button>
</div> </div>
+1 -1
View File
@@ -25,7 +25,7 @@ export function Hero() {
<div className={styles.cta}> <div className={styles.cta}>
<Button href={SITE_LINKS.download}>{copy.cta.downloadFree}</Button> <Button href={SITE_LINKS.download}>{copy.cta.downloadFree}</Button>
<Button href={SITE_LINKS.buy} variant="secondary"> <Button href={SITE_LINKS.buy} variant="secondary" newTab>
{copy.cta.buyLicense} {copy.cta.buyLicense}
</Button> </Button>
</div> </div>
+1
View File
@@ -64,6 +64,7 @@ export function Pricing() {
<Button <Button
href={SITE_LINKS.buy} href={SITE_LINKS.buy}
variant={recommended ? "gold" : "secondary"} variant={recommended ? "gold" : "secondary"}
newTab
> >
{copy.cta.buy} {copy.cta.buy}
</Button> </Button>
+9 -1
View File
@@ -11,6 +11,7 @@ type ButtonProps = {
className?: string; className?: string;
onClick?: () => void; onClick?: () => void;
type?: "button" | "submit"; type?: "button" | "submit";
newTab?: boolean;
}; };
function join(...parts: (string | undefined)[]): string { function join(...parts: (string | undefined)[]): string {
@@ -24,12 +25,19 @@ export function Button({
className, className,
onClick, onClick,
type = "button", type = "button",
newTab = false,
}: ButtonProps) { }: ButtonProps) {
const cls = join(styles.button, styles[variant], className); const cls = join(styles.button, styles[variant], className);
if (href) { if (href) {
return ( return (
<a className={cls} href={href}> <a
className={cls}
href={href}
{...(newTab
? { target: "_blank", rel: "noopener noreferrer" }
: undefined)}
>
{children} {children}
</a> </a>
); );
+1 -2
View File
@@ -7,8 +7,7 @@ export const SITE_LINKS = {
download: "#platforms", download: "#platforms",
buy: "https://donate.stream/ttrpgplayer", buy: "https://donate.stream/ttrpgplayer",
enterKey: "#pricing", enterKey: "#pricing",
support: "mailto:support@ttrpgplayer.example", support: "mailto:fontosh.ivan@gmail.com",
privacy: "#",
} as const; } as const;
/** Same feed as electron-builder `publish.url` in dnd_player/package.json */ /** Same feed as electron-builder `publish.url` in dnd_player/package.json */
+22 -24
View File
@@ -118,26 +118,26 @@ const ru = {
editor: { editor: {
title: "Редактор кампаний", title: "Редактор кампаний",
description: description:
"Граф сцен и переходов, ветвления, превью медиа, музыка сцены и кампании, импорт и экспорт .ttrpg.zip, поиск и управление медиа.", "Планируйте кампанию как граф сцен: связывайте локации, закладывайте развилки сюжета, прикрепляйте карты и музыку — и собирайте материалы партии до первого броска костей.",
tags: "Граф сцен · переходы · медиа", tags: "Граф сцен · ветвления · медиа",
}, },
control: { control: {
title: "Пульт управления", title: "Пульт управления",
description: description:
"Переключение сцен одной кнопкой, предпросмотр экрана игроков, сюжетная линия, авто- и ручное управление музыкой, запуск и остановка демонстрации.", "Ведите сессию из одного окна: переключайте сцены, следите за экраном игроков, держите ход сюжета под рукой и управляйте музыкой, пока стол ждёт вашего следующего хода.",
tags: "Переключить · музыка · сюжетная линия", tags: "Сцены · музыка · сюжетная линия",
}, },
presentation: { presentation: {
title: "Экран презентации", title: "Экран презентации",
description: description:
"Отдельное окно для второго монитора, проектора или ТВ. Только карта или видео сцены, синхронные эффекты и оверлеи, поддержка поворота 0°/90°/180°/270°.", "Вынесите карту и атмосферу на второй монитор, проектор или ТВ — игроки видят только задуманное для сцены, без панелей редактора, с поворотом экрана под ваш стол.",
tags: "Карта · видео · второй монитор", tags: "Второй экран · карта · атмосфера",
}, },
atmosphere: { atmosphere: {
title: "Атмосфера за столом", title: "Атмосфера за столом",
description: description:
"Туман, дождь, огонь, вода, молния, луч света, заморозка, облако яда, эффекты действий, кисть мастера, ластик, очистка эффектов и музыка сцены плюс фон кампании.", "Оживите карту погодой и светом, дорисуйте от руки то, что видят игроки, и меняйте настроение сцены музыкой — от тихой таверны до грозы над полем боя.",
tags: "Туман · дождь · огонь · кисть", tags: "Погода · свет · музыка сцены",
}, },
}, },
}, },
@@ -173,7 +173,7 @@ const ru = {
pricing: { pricing: {
title: "Выберите ключ мастера", title: "Выберите ключ мастера",
subtitle: subtitle:
"Активируйте продуктовый ключ в приложении: Настройки → Указать ключ", "Активируйте продуктовый ключ в приложении: Настройки → Выбрать ключ",
recommendedBadge: "⭐ Рекомендуем", recommendedBadge: "⭐ Рекомендуем",
tiers: { tiers: {
basic: { basic: {
@@ -225,7 +225,7 @@ const ru = {
activate: { activate: {
question: "Как активировать лицензию?", question: "Как активировать лицензию?",
answer: answer:
"В приложении откройте Настройки → Указать ключ и вставьте ключ формата TTRPG-….", "В приложении откройте Настройки → Выбрать ключ и вставьте ключ формата TTRPG-….",
}, },
os: { os: {
question: "На каких ОС работает приложение?", question: "На каких ОС работает приложение?",
@@ -243,9 +243,8 @@ const ru = {
download: "Скачать", download: "Скачать",
buy: "Купить", buy: "Купить",
faq: "FAQ", faq: "FAQ",
enterKey: "Указать ключ", enterKey: "Выбрать ключ",
support: "Поддержка", support: "Поддержка",
privacy: "Политика конфиденциальности",
}, },
}, },
localeSwitcher: { localeSwitcher: {
@@ -373,26 +372,26 @@ const en: SiteCopy = {
editor: { editor: {
title: "Campaign editor", title: "Campaign editor",
description: description:
"Scene graph and transitions, branching, media previews, scene and campaign audio, import and export of .ttrpg.zip, search and media management.", "Plan your campaign as a scene graph: link locations, lay out story branches, attach maps and music — and gather session materials before the first roll.",
tags: "Scene graph · transitions · media", tags: "Scene graph · branching · media",
}, },
control: { control: {
title: "Control panel", title: "Control panel",
description: description:
"One-click scene switching, player screen preview, story timeline, automatic and manual music control, start and stop presentation.", "Run the session from one window: switch scenes, watch the player screen, keep the story beat at hand and control music while the table waits for your next move.",
tags: "Switch · music · story line", tags: "Scenes · music · story line",
}, },
presentation: { presentation: {
title: "Presentation screen", title: "Presentation screen",
description: description:
"Separate window for a second monitor, projector or TV. Scene map or video only, synced effects and overlays, rotation 0°/90°/180°/270°.", "Send the map and atmosphere to a second monitor, projector or TV — players see only what the scene calls for, with no editor panels and rotation to fit your table setup.",
tags: "Map · video · second monitor", tags: "Second screen · map · atmosphere",
}, },
atmosphere: { atmosphere: {
title: "Atmosphere at the table", title: "Atmosphere at the table",
description: description:
"Fog, rain, fire, water, lightning, sunbeam, freeze, poison cloud, action effects, GM brush, eraser, clear effects, scene music plus campaign ambience.", "Bring the map to life with weather and light, sketch what players see by hand, and shift the mood with scene music — from a quiet tavern to a storm over the battlefield.",
tags: "Fog · rain · fire · brush", tags: "Weather · light · scene music",
}, },
}, },
}, },
@@ -427,7 +426,7 @@ const en: SiteCopy = {
}, },
pricing: { pricing: {
title: "Choose your GM key", title: "Choose your GM key",
subtitle: "Activate your product key in the app: Settings → Enter key", subtitle: "Activate your product key in the app: Settings → Choose key",
recommendedBadge: "⭐ Recommended", recommendedBadge: "⭐ Recommended",
tiers: { tiers: {
basic: { basic: {
@@ -479,7 +478,7 @@ const en: SiteCopy = {
activate: { activate: {
question: "How do I activate a license?", question: "How do I activate a license?",
answer: answer:
"In the app, open Settings → Enter key and paste a key in the TTRPG-… format.", "In the app, open Settings → Choose key and paste a key in the TTRPG-… format.",
}, },
os: { os: {
question: "Which operating systems are supported?", question: "Which operating systems are supported?",
@@ -496,9 +495,8 @@ const en: SiteCopy = {
download: "Download", download: "Download",
buy: "Buy", buy: "Buy",
faq: "FAQ", faq: "FAQ",
enterKey: "Enter key", enterKey: "Choose key",
support: "Support", support: "Support",
privacy: "Privacy policy",
}, },
}, },
localeSwitcher: { localeSwitcher: {