From cde8334c73bf6da855263398868d3b749d770532 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 7 Jun 2026 14:17:24 +0700 Subject: [PATCH] feat(ui): refresh page summaries and brand --- CHANGELOG.md | 17 ++- README.md | 6 +- package.json | 2 +- src/renderer/index.html | 2 +- src/renderer/src/components/PageScaffold.tsx | 103 +++++++++++++++++++ src/renderer/src/components/Sidebar.tsx | 2 +- src/renderer/src/i18n/dict.ts | 80 +++++++++++++- src/renderer/src/pages/Challenges.tsx | 40 ++++++- src/renderer/src/pages/Exercises.tsx | 66 ++++++++---- src/renderer/src/pages/Games.tsx | 52 +++++++++- src/renderer/src/pages/Meals.tsx | 91 ++++++++++++---- src/renderer/src/pages/Settings.tsx | 34 +++++- src/shared/release-notes.ts | 42 ++++++++ 13 files changed, 483 insertions(+), 54 deletions(-) create mode 100644 src/renderer/src/components/PageScaffold.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 69e7577..1fd158a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,20 @@ ## [Unreleased] +## [0.6.4] — 2026-06-07 + +### Added + +- На страницах `Упражнения`, `Питание`, `Игры`, `Челленджи` и `Настройки` + добавлены верхние сводные карточки: активные элементы, нагрузка, цели, + ближайшее питание, состояние игровых интеграций и ключевые настройки. +- Добавлен общий `PageScaffold` для единых заголовков и insight-карточек в renderer. + +### Changed + +- Видимый бренд в интерфейсе сменен с “Не Залипай” на “Разомнись”. +- README, title, release notes и описание пакета обновлены под новое название. + ## [0.6.3] — 2026-06-07 ### Added @@ -491,7 +505,8 @@ days=[Mon..Fri]` теперь правильно проверяется день иконки), системный трей, автозапуск с Windows, native-уведомления, NSIS-инсталлятор, auto-update через electron-updater. -[Unreleased]: https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/compare/v0.6.3...HEAD +[Unreleased]: https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/compare/v0.6.4...HEAD +[0.6.4]: https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/compare/v0.6.3...v0.6.4 [0.6.3]: https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/compare/v0.6.2...v0.6.3 [0.6.2]: https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/compare/v0.5.8...v0.6.2 [0.5.8]: https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/releases/tag/v0.5.8 diff --git a/README.md b/README.md index bdb13e0..622d7f5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Не Залипай — Exercise Reminder +# Разомнись — Exercise Reminder -Windows desktop приложение, которое помогает не залипать за компьютером: держит план дня, напоминает размяться, ведёт недельные челленджи и превращает Dota 2 статистику после матча в игровые долги. +Windows desktop приложение, которое помогает делать короткие перерывы без потери фокуса: держит план дня, напоминает размяться, ведёт недельные челленджи и превращает Dota 2 статистику после матча в игровые долги. -[![release](https://img.shields.io/badge/release-v0.6.3-orange)](https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/releases/latest) +[![release](https://img.shields.io/badge/release-v0.6.4-orange)](https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/releases/latest) [![tests](https://img.shields.io/badge/tests-241%20passing-green)]() [![platform](https://img.shields.io/badge/platform-Windows%2010%2F11-blue)]() diff --git a/package.json b/package.json index f5fc15a..0c64e33 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "laude", "version": "0.6.3", - "description": "Не Залипай — Windows desktop rhythm and exercise reminder", + "description": "Разомнись — Windows desktop break and exercise reminder", "main": "out/main/index.js", "author": "AnRil", "private": true, diff --git a/src/renderer/index.html b/src/renderer/index.html index 78bbd44..6449b7d 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -13,7 +13,7 @@ http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data:; script-src 'self'; connect-src 'self'; base-uri 'self'; frame-ancestors 'none'" /> - Не Залипай + Разомнись
diff --git a/src/renderer/src/components/PageScaffold.tsx b/src/renderer/src/components/PageScaffold.tsx new file mode 100644 index 0000000..24f8908 --- /dev/null +++ b/src/renderer/src/components/PageScaffold.tsx @@ -0,0 +1,103 @@ +import type { ReactNode } from 'react' + +type Tone = 'accent' | 'success' | 'warning' | 'info' | 'muted' + +export function PageHeader({ + kicker, + title, + subtitle, + action +}: { + kicker: string + title: string + subtitle?: string + action?: ReactNode +}): JSX.Element { + return ( +
+
+
{kicker}
+

+ {title} +

+ {subtitle && ( +

+ {subtitle} +

+ )} +
+ {action &&
{action}
} +
+ ) +} + +export function InsightGrid({ + children, + className = '' +}: { + children: ReactNode + className?: string +}): JSX.Element { + return ( +
+ {children} +
+ ) +} + +export function InsightCard({ + icon, + label, + value, + hint, + tone = 'accent' +}: { + icon: ReactNode + label: string + value: string + hint?: string + tone?: Tone +}): JSX.Element { + const iconClass = + tone === 'accent' + ? 'bg-accent text-white' + : tone === 'success' + ? 'bg-success text-white' + : tone === 'warning' + ? 'bg-warning text-white' + : tone === 'info' + ? 'bg-info text-white' + : 'bg-text/12 text-text/55' + + return ( +
+
+
+ {icon} +
+
+
+ {label} +
+
+ {value} +
+ {hint && ( +
+ {hint} +
+ )} +
+
+
+ ) +} diff --git a/src/renderer/src/components/Sidebar.tsx b/src/renderer/src/components/Sidebar.tsx index 0aedd5b..2811e5f 100644 --- a/src/renderer/src/components/Sidebar.tsx +++ b/src/renderer/src/components/Sidebar.tsx @@ -162,7 +162,7 @@ function SidebarContent({ onNav }: { onNav?: () => void }): JSX.Element { <>
- Не Залипай + Разомнись
{t('sidebar.slogan')} diff --git a/src/renderer/src/i18n/dict.ts b/src/renderer/src/i18n/dict.ts index 63ef2e0..8a9762c 100644 --- a/src/renderer/src/i18n/dict.ts +++ b/src/renderer/src/i18n/dict.ts @@ -18,7 +18,7 @@ export const ru: Dict = { 'nav.games': 'Игры', 'nav.challenges': 'Челленджи', 'nav.settings': 'Настройки', - 'sidebar.slogan': 'Держи ритм за компом', + 'sidebar.slogan': 'Лёгкий перерыв без потери фокуса', 'sidebar.status_tracking': 'Активность отслеживается', 'titlebar.menu_aria': 'Меню', 'titlebar.minimize_aria': 'Свернуть', @@ -26,7 +26,7 @@ export const ru: Dict = { 'titlebar.restore_aria': 'Восстановить размер', 'titlebar.tray_aria': 'В трей', 'titlebar.close_aria': 'Закрыть', - 'titlebar.app_title': 'Не Залипай', + 'titlebar.app_title': 'Разомнись', // Common buttons / actions 'btn.add': 'Добавить', @@ -165,6 +165,15 @@ export const ru: Dict = { // Exercises 'exercises.kicker': 'Программа', 'exercises.title': 'Упражнения', + 'exercises.subtitle': + 'Собери короткие действия, которые легко сделать между задачами.', + 'exercises.insight.active': 'Активно', + 'exercises.insight.active.hint': 'Напоминания, которые сейчас работают', + 'exercises.insight.load': 'Нагрузка', + 'exercises.insight.load.value': '{n} раз', + 'exercises.insight.load.hint': 'Сумма повторов за один полный круг', + 'exercises.insight.goals': 'Цели', + 'exercises.insight.goals.hint': 'Упражнения с дневной нормой', 'exercises.section.active': 'Активные · {n}', 'exercises.section.disabled': 'Выключенные · {n}', 'exercises.row.meta': '{reps} раз · {interval}', @@ -173,6 +182,13 @@ export const ru: Dict = { // Meals (приёмы пищи) 'meals.kicker': 'Режим питания', 'meals.title': 'Питание', + 'meals.subtitle': 'Держи еду в расписании, чтобы не выпадать из ритма.', + 'meals.insight.active': 'Активно', + 'meals.insight.active.hint': 'Приёмы пищи с включённым напоминанием', + 'meals.insight.next': 'Следующее', + 'meals.insight.next.hint': 'Ближайшее время сегодня', + 'meals.insight.presets': 'Пресеты', + 'meals.insight.presets.hint': 'Быстрые варианты для старта', 'meals.presets': 'Быстрое добавление', 'meals.schedule': 'Расписание', 'meals.section.active': 'Активные · {n}', @@ -214,6 +230,13 @@ export const ru: Dict = { 'challenges.title': 'Челленджи', 'challenges.subtitle': 'Повторов = {formula}', 'challenges.subtitle.formula': 'статистика × коэффициент', + 'challenges.insight.active': 'Активно', + 'challenges.insight.active.hint': 'Правила, которые начисляют долг', + 'challenges.insight.games': 'Игры', + 'challenges.insight.games.hint': 'Включённые игровые интеграции', + 'challenges.insight.debt': 'Тест долга', + 'challenges.insight.debt.value': '{n} раз', + 'challenges.insight.debt.hint': 'Если каждое правило поймает 5 событий', 'challenges.warning.no_games': 'Челленджи срабатывают после матча. Подключи игру во вкладке «Игры».', 'challenges.section.all': 'Все · {n}', @@ -239,6 +262,13 @@ export const ru: Dict = { 'games.subtitle': 'Подключи игру — челленджи сработают сразу после матча', 'games.subtitle.live': '{n} live', 'games.section.supported': 'Поддерживаемые', + 'games.insight.supported': 'Поддержка', + 'games.insight.supported.hint': 'Игры, которые умеет отслеживать приложение', + 'games.insight.connected': 'Подключено', + 'games.insight.connected.hint': 'Интеграции с установленной GSI', + 'games.insight.live': 'Сигнал', + 'games.insight.live.hint': 'Live или ожидание перезапуска Steam', + 'games.insight.queued': '{n} в очереди', 'games.scanning': 'Сканируем установленные игры…', 'games.queued.body': 'Steam запущен. Параметр {opt} пропишется автоматически при следующем закрытии Steam.', @@ -255,6 +285,12 @@ export const ru: Dict = { // Settings 'settings.kicker': 'Конфигурация', 'settings.title': 'Настройки', + 'settings.insight.mode': 'Уведомления', + 'settings.insight.mode.hint': 'Как приложение говорит о перерыве', + 'settings.insight.theme': 'Тема', + 'settings.insight.theme.hint': 'Визуальный режим интерфейса', + 'settings.insight.language': 'Язык', + 'settings.insight.language.hint': 'Применяется без перезапуска', 'settings.section.reminders': 'Напоминания', 'settings.section.quiet': 'Тихие часы', 'settings.section.window': 'Окно и трей', @@ -471,7 +507,7 @@ export const en: Dict = { 'nav.games': 'Games', 'nav.challenges': 'Challenges', 'nav.settings': 'Settings', - 'sidebar.slogan': 'Keep your PC rhythm', + 'sidebar.slogan': 'A small break without losing focus', 'sidebar.status_tracking': 'Activity tracking is on', 'titlebar.menu_aria': 'Menu', 'titlebar.minimize_aria': 'Minimize', @@ -479,7 +515,7 @@ export const en: Dict = { 'titlebar.restore_aria': 'Restore size', 'titlebar.tray_aria': 'To tray', 'titlebar.close_aria': 'Close', - 'titlebar.app_title': 'Ne Zalipay', + 'titlebar.app_title': 'Razomnis', // Common buttons 'btn.add': 'Add', @@ -617,6 +653,15 @@ export const en: Dict = { // Exercises 'exercises.kicker': 'Program', 'exercises.title': 'Exercises', + 'exercises.subtitle': + 'Build short actions that are easy to do between tasks.', + 'exercises.insight.active': 'Active', + 'exercises.insight.active.hint': 'Reminders currently running', + 'exercises.insight.load': 'Load', + 'exercises.insight.load.value': '{n} reps', + 'exercises.insight.load.hint': 'Total reps in one full cycle', + 'exercises.insight.goals': 'Goals', + 'exercises.insight.goals.hint': 'Exercises with a daily target', 'exercises.section.active': 'Active · {n}', 'exercises.section.disabled': 'Disabled · {n}', 'exercises.row.meta': '{reps} reps · {interval}', @@ -625,6 +670,13 @@ export const en: Dict = { // Meals 'meals.kicker': 'Eating schedule', 'meals.title': 'Meals', + 'meals.subtitle': 'Keep food on the schedule so the day stays steady.', + 'meals.insight.active': 'Active', + 'meals.insight.active.hint': 'Meals with enabled reminders', + 'meals.insight.next': 'Next', + 'meals.insight.next.hint': 'Closest time today', + 'meals.insight.presets': 'Presets', + 'meals.insight.presets.hint': 'Fast starter options', 'meals.presets': 'Quick add', 'meals.schedule': 'Schedule', 'meals.section.active': 'Active · {n}', @@ -666,6 +718,13 @@ export const en: Dict = { 'challenges.title': 'Challenges', 'challenges.subtitle': 'Reps = {formula}', 'challenges.subtitle.formula': 'stat × multiplier', + 'challenges.insight.active': 'Active', + 'challenges.insight.active.hint': 'Rules that can add debt', + 'challenges.insight.games': 'Games', + 'challenges.insight.games.hint': 'Enabled game integrations', + 'challenges.insight.debt': 'Debt test', + 'challenges.insight.debt.value': '{n} reps', + 'challenges.insight.debt.hint': 'If each rule catches 5 events', 'challenges.warning.no_games': 'Challenges trigger after a match. Connect a game in the Games tab.', 'challenges.section.all': 'All · {n}', @@ -691,6 +750,13 @@ export const en: Dict = { 'games.subtitle': 'Connect a game — challenges fire right after the match', 'games.subtitle.live': '{n} live', 'games.section.supported': 'Supported', + 'games.insight.supported': 'Support', + 'games.insight.supported.hint': 'Games this app knows how to track', + 'games.insight.connected': 'Connected', + 'games.insight.connected.hint': 'Integrations with installed GSI', + 'games.insight.live': 'Signal', + 'games.insight.live.hint': 'Live or waiting for Steam restart', + 'games.insight.queued': '{n} queued', 'games.scanning': 'Scanning installed games…', 'games.queued.body': 'Steam is running. The {opt} option will be added automatically next time Steam closes.', @@ -707,6 +773,12 @@ export const en: Dict = { // Settings 'settings.kicker': 'Configuration', 'settings.title': 'Settings', + 'settings.insight.mode': 'Notifications', + 'settings.insight.mode.hint': 'How the app talks about a break', + 'settings.insight.theme': 'Theme', + 'settings.insight.theme.hint': 'Visual interface mode', + 'settings.insight.language': 'Language', + 'settings.insight.language.hint': 'Applied without restart', 'settings.section.reminders': 'Reminders', 'settings.section.quiet': 'Quiet hours', 'settings.section.window': 'Window & tray', diff --git a/src/renderer/src/pages/Challenges.tsx b/src/renderer/src/pages/Challenges.tsx index 3deef36..bdb52e7 100644 --- a/src/renderer/src/pages/Challenges.tsx +++ b/src/renderer/src/pages/Challenges.tsx @@ -1,7 +1,15 @@ import { useEffect, useState } from 'react' -import { Plus, ChevronRight, AlertTriangle, Gamepad2 } from 'lucide-react' +import { + AlertTriangle, + BadgeCheck, + ChevronRight, + Gamepad2, + Plus, + Swords +} from 'lucide-react' import { useAppStore } from '../store/appStore' import { Button } from '../components/ui/Button' +import { InsightCard, InsightGrid } from '../components/PageScaffold' import { Switch } from '../components/ui/Switch' import { Modal } from '../components/ui/Modal' import { Card, Row, SectionHeader } from '../components/ui/Card' @@ -49,6 +57,12 @@ export default function ChallengesPage(): JSX.Element { }, []) const noGamesActive = games.length > 0 && !games.some((g) => g.enabled) + const activeChallenges = challenges.filter((c) => c.enabled) + const enabledGames = games.filter((g) => g.enabled).length + const previewDebt = activeChallenges.reduce( + (sum, challenge) => sum + Math.round(5 * challenge.multiplier), + 0 + ) return (
@@ -88,6 +102,30 @@ export default function ChallengesPage(): JSX.Element {
)} + + } + label={t('challenges.insight.active')} + value={`${activeChallenges.length}/${challenges.length}`} + hint={t('challenges.insight.active.hint')} + tone={activeChallenges.length > 0 ? 'success' : 'muted'} + /> + } + label={t('challenges.insight.games')} + value={`${enabledGames}`} + hint={t('challenges.insight.games.hint')} + tone={enabledGames > 0 ? 'info' : 'warning'} + /> + } + label={t('challenges.insight.debt')} + value={t('challenges.insight.debt.value', { n: previewDebt })} + hint={t('challenges.insight.debt.hint')} + tone={previewDebt > 0 ? 'warning' : 'muted'} + /> + + {challenges.length > 0 ? ( <> e.enabled) const disabled = exercises.filter((e) => !e.enabled) + const goalCount = exercises.filter((e) => e.dailyGoal !== undefined).length + const totalReps = enabled.reduce((sum, e) => sum + e.reps, 0) return (
-
-
-
- {t('exercises.kicker')} -
-

- {t('exercises.title')} -

-
- -
+ { + setEditing(null) + setEditorOpen(true) + }} + > + {t('btn.add')} + + } + /> + + + } + label={t('exercises.insight.active')} + value={`${enabled.length}/${exercises.length}`} + hint={t('exercises.insight.active.hint')} + tone={enabled.length > 0 ? 'success' : 'muted'} + /> + } + label={t('exercises.insight.load')} + value={t('exercises.insight.load.value', { n: totalReps })} + hint={t('exercises.insight.load.hint')} + tone="accent" + /> + } + label={t('exercises.insight.goals')} + value={`${goalCount}`} + hint={t('exercises.insight.goals.hint')} + tone={goalCount > 0 ? 'info' : 'muted'} + /> + {enabled.length > 0 && ( <> diff --git a/src/renderer/src/pages/Games.tsx b/src/renderer/src/pages/Games.tsx index 8ba11a6..c9d1dcb 100644 --- a/src/renderer/src/pages/Games.tsx +++ b/src/renderer/src/pages/Games.tsx @@ -9,6 +9,7 @@ import { AlertTriangle } from 'lucide-react' import { motion } from 'framer-motion' +import { InsightCard, InsightGrid } from '../components/PageScaffold' import { Button } from '../components/ui/Button' import { Switch } from '../components/ui/Switch' import { Spinner } from '../components/ui/Spinner' @@ -55,7 +56,14 @@ export default function GamesPage(): JSX.Element { } } - const liveCount = games.filter((g) => g.enabled && g.integrationActive).length + const connectedCount = games.filter((g) => g.integrationActive).length + const liveCount = games.filter( + (g) => + g.enabled && g.integrationActive && g.launchOptionStatus === 'applied' + ).length + const queuedCount = games.filter( + (g) => g.integrationActive && g.launchOptionStatus === 'queued' + ).length return (
@@ -85,6 +93,36 @@ export default function GamesPage(): JSX.Element {
+ + } + label={t('games.insight.supported')} + value={`${games.length}`} + hint={t('games.insight.supported.hint')} + tone="info" + /> + } + label={t('games.insight.connected')} + value={`${connectedCount}`} + hint={t('games.insight.connected.hint')} + tone={connectedCount > 0 ? 'success' : 'muted'} + /> + } + label={t('games.insight.live')} + value={ + queuedCount > 0 + ? t('games.insight.queued', { n: queuedCount }) + : t('games.subtitle.live', { n: liveCount }) + } + hint={t('games.insight.live.hint')} + tone={ + liveCount > 0 ? 'success' : queuedCount > 0 ? 'warning' : 'muted' + } + /> + +
{games.map((g, i) => ( @@ -109,7 +147,11 @@ export default function GamesPage(): JSX.Element { className="px-5 py-12 flex flex-col items-center gap-3 text-center text-text/55 text-[14px]" role="status" > - + {t('games.scanning')}
@@ -221,7 +263,11 @@ function GameCard({ disabled={busy} size="sm" > - {busy ? : }{' '} + {busy ? ( + + ) : ( + + )}{' '} {t('btn.disconnect')} )} diff --git a/src/renderer/src/pages/Meals.tsx b/src/renderer/src/pages/Meals.tsx index 68eb937..05225b2 100644 --- a/src/renderer/src/pages/Meals.tsx +++ b/src/renderer/src/pages/Meals.tsx @@ -1,8 +1,19 @@ import { useState } from 'react' -import { Plus, ChevronRight, UtensilsCrossed } from 'lucide-react' +import { + CalendarDays, + ChevronRight, + Clock, + Plus, + UtensilsCrossed +} from 'lucide-react' import { AnimatePresence, motion } from 'framer-motion' import { useAppStore } from '../store/appStore' import { MealEditor, type MealDraft } from '../components/MealEditor' +import { + InsightCard, + InsightGrid, + PageHeader +} from '../components/PageScaffold' import { Button } from '../components/ui/Button' import { Switch } from '../components/ui/Switch' import { Card, Row, SectionHeader } from '../components/ui/Card' @@ -37,6 +48,8 @@ export default function Meals(): JSX.Element { const ordered = [...meals].sort((a, b) => a.enabled === b.enabled ? 0 : a.enabled ? -1 : 1 ) + const activeMeals = meals.filter((m) => m.enabled) + const nextMeal = getNextMealLabel(activeMeals) async function addPreset( preset: (typeof MEAL_PRESETS)[number] @@ -53,24 +66,45 @@ export default function Meals(): JSX.Element { return (
-
-
-
- {t('meals.kicker')} -
-

- {t('meals.title')} -

-
- -
+ { + setEditing(null) + setEditorOpen(true) + }} + > + {t('btn.add')} + + } + /> + + + } + label={t('meals.insight.active')} + value={`${activeMeals.length}/${meals.length}`} + hint={t('meals.insight.active.hint')} + tone={activeMeals.length > 0 ? 'success' : 'muted'} + /> + } + label={t('meals.insight.next')} + value={nextMeal ?? '—'} + hint={t('meals.insight.next.hint')} + tone={nextMeal ? 'accent' : 'muted'} + /> + } + label={t('meals.insight.presets')} + value={`${MEAL_PRESETS.length}`} + hint={t('meals.insight.presets.hint')} + tone="info" + /> + {/* Пресеты быстрого добавления */} @@ -148,6 +182,25 @@ export default function Meals(): JSX.Element { ) } +function getNextMealLabel(meals: Meal[]): string | null { + const today = new Date().getDay() + const now = new Date() + const nowMinutes = now.getHours() * 60 + now.getMinutes() + const candidates = meals + .filter((meal) => meal.days.length === 0 || meal.days.includes(today)) + .map((meal) => { + const [hh, mm] = meal.time.split(':').map(Number) + return { + meal, + minutes: (hh || 0) * 60 + (mm || 0) + } + }) + .filter((item) => item.minutes >= nowMinutes) + .sort((a, b) => a.minutes - b.minutes) + + return candidates[0]?.meal.time ?? null +} + function MealRow({ meal, last, diff --git a/src/renderer/src/pages/Settings.tsx b/src/renderer/src/pages/Settings.tsx index 00efa24..7d0c547 100644 --- a/src/renderer/src/pages/Settings.tsx +++ b/src/renderer/src/pages/Settings.tsx @@ -1,8 +1,16 @@ import { useCallback, useEffect, useState } from 'react' -import { Copy, FolderOpen, RefreshCw } from 'lucide-react' +import { + Bell, + Copy, + FolderOpen, + Languages, + Palette, + RefreshCw +} from 'lucide-react' import { useAppStore } from '../store/appStore' import { Switch } from '../components/ui/Switch' import { Button } from '../components/ui/Button' +import { InsightCard, InsightGrid } from '../components/PageScaffold' import { Card, Row, SectionHeader } from '../components/ui/Card' import { UpdaterCard } from '../components/UpdaterCard' import { WhatsNewModal } from '../components/WhatsNewModal' @@ -54,6 +62,30 @@ export default function SettingsPage(): JSX.Element {
+ + } + label={t('settings.insight.mode')} + value={t(`settings.notification_mode.${settings.notificationMode}`)} + hint={t('settings.insight.mode.hint')} + tone={settings.soundEnabled ? 'success' : 'info'} + /> + } + label={t('settings.insight.theme')} + value={t(`settings.theme.${settings.theme}`)} + hint={t('settings.insight.theme.hint')} + tone="accent" + /> + } + label={t('settings.insight.language')} + value={t(`settings.language.${settings.language}`)} + hint={t('settings.insight.language.hint')} + tone="info" + /> + + export const RELEASE_NOTES: Record = { + '0.6.4': { + ru: [ + { + title: 'Новый видимый бренд “Разомнись”', + detail: + 'Название стало короче и понятнее для русской аудитории: действие видно сразу.', + tag: 'new' + }, + { + title: 'Сводки на каждом экране', + detail: + 'Упражнения, питание, игры, челленджи и настройки теперь быстрее сканируются сверху.', + tag: 'new' + }, + { + title: 'Улучшена структура вторичных страниц', + detail: + 'Важные статусы вынесены выше списков, чтобы сразу видеть активность и проблемные места.', + tag: 'new' + } + ], + en: [ + { + title: 'New visible “Razomnis” brand', + detail: + 'The name is shorter and more action-focused for the Russian audience.', + tag: 'new' + }, + { + title: 'Overview cards on every screen', + detail: + 'Exercises, meals, games, challenges and settings are easier to scan from the top.', + tag: 'new' + }, + { + title: 'Secondary pages are more structured', + detail: + 'Important statuses moved above long lists so active and risky areas are visible immediately.', + tag: 'new' + } + ] + }, '0.6.3': { ru: [ {