From f61e076e461fbd923d8a08bd15d1e8523d78620b Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 8 Jun 2026 14:01:45 +0700 Subject: [PATCH] feat(ui): redesign desktop experience --- CHANGELOG.md | 31 +++- README.md | 4 +- src/renderer/src/App.tsx | 4 +- src/renderer/src/ReminderApp.tsx | 63 +++---- .../src/components/AchievementsCard.tsx | 21 ++- src/renderer/src/components/ExerciseCard.tsx | 49 +++-- .../src/components/ExerciseEditor.tsx | 24 ++- .../src/components/HistoryHeatmap.tsx | 2 +- src/renderer/src/components/MealEditor.tsx | 18 +- src/renderer/src/components/PageScaffold.tsx | 16 +- src/renderer/src/components/Sidebar.tsx | 104 ++++++----- src/renderer/src/components/Titlebar.tsx | 14 +- src/renderer/src/components/WhatsNewModal.tsx | 8 +- src/renderer/src/components/ui/Button.tsx | 26 +-- src/renderer/src/components/ui/Card.tsx | 28 +-- .../src/components/ui/ConfirmModal.tsx | 8 +- src/renderer/src/components/ui/Modal.tsx | 32 +--- src/renderer/src/components/ui/Switch.tsx | 14 +- src/renderer/src/pages/Challenges.tsx | 20 +- src/renderer/src/pages/Dashboard.tsx | 144 ++++++++------- src/renderer/src/pages/Exercises.tsx | 6 +- src/renderer/src/pages/Games.tsx | 22 +-- src/renderer/src/pages/Meals.tsx | 8 +- src/renderer/src/pages/Settings.tsx | 20 +- src/renderer/src/styles/globals.css | 172 +++++++++--------- src/shared/release-notes.ts | 54 ++++++ tailwind.config.mjs | 16 +- 27 files changed, 488 insertions(+), 440 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9075d36..0940dad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,24 @@ ## [Unreleased] +## [0.7.0] — 2026-06-08 + +### Added + +- Текущая версия `v0.6.6` сохранена отдельным тегом `последнее-удачное` + как точка отката перед большим редизайном. + +### Changed + +- Интерфейс приложения пересобран в новом desktop-направлении: темный сайдбар, + плотный рабочий контент, нейтральная палитра, чёткие панели и радиусы 8–10px. +- Главный экран стал операционным overview: верхняя панель состояния, компактные + status tiles, план ближайшего шага, цели, питание, режим и игровые долги. +- Страницы упражнений, питания, игр, челленджей и настроек получили единый + desktop-контейнер, более плотные списки и менее декоративные summary-блоки. +- Редакторы, модальные окна, кнопки, переключатели, heatmap, достижения и окно + напоминания приведены к новой desktop-системе. + ## [0.6.6] — 2026-06-08 ### Added @@ -170,7 +188,7 @@ clearHistory/import`, Dashboard на него подписан. ### Fixed (P1 — UX просадки) - **Удаление упражнения теперь спрашивает подтверждение.** Раньше - один клик в menu «Удалить» сразу удалял. Сейчас iOS-style ConfirmModal + один клик в menu «Удалить» сразу удалял. Сейчас ConfirmModal с destructive-кнопкой. - **Daily goal закрыт — больше не «25 часов 13 минут».** Когда дневная цель достигнута, ExerciseCard показывает «Цель закрыта · 100/100» @@ -179,7 +197,7 @@ clearHistory/import`, Dashboard на него подписан. молча — пользователь не понимал почему через 12 мин ничего не пришло. Сейчас info-баннер активной встречи с указанием закрыть Zoom/Teams/etc. -- **Native `window.confirm()` → iOS-style ConfirmModal** в restore-операции. +- **Native `window.confirm()` → ConfirmModal** в restore-операции. Раньше всплывал серый системный диалог. ### Fixed (P2 — полировка) @@ -205,7 +223,7 @@ clearHistory/import`, Dashboard на него подписан. ### Added - `src/renderer/src/components/ui/ConfirmModal.tsx` — переиспользуемый - iOS-style confirm с focus-trap'ом через Modal. + confirm с focus-trap'ом через Modal. - IPC `markChallengeDone(challengeId, reps)` — handler в main, метод в preload (раньше канал был в IPC enum, handler не зарегистрирован). - IPC `getMeetingActive` + event `evtMeetingChanged` — meeting-detect @@ -532,9 +550,9 @@ days=[Mon..Fri]` теперь правильно проверяется день ## [0.3.x] — 2026-05-17 -Серия мелких релизов с дизайн-итерациями (Apple iOS / macOS aesthetic): +Серия мелких релизов с ранними дизайн-итерациями: шрифты Plus Jakarta Sans + Bricolage Grotesque, светлая/тёмная/системная -тема, vibrancy sidebar, iOS-grouped lists, spring-анимации. +тема, полупрозрачный сайдбар, grouped lists, spring-анимации. ## [0.2.0] — 2026-05-16 @@ -550,7 +568,8 @@ days=[Mon..Fri]` теперь правильно проверяется день иконки), системный трей, автозапуск с Windows, native-уведомления, NSIS-инсталлятор, auto-update через electron-updater. -[Unreleased]: https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/compare/v0.6.6...HEAD +[Unreleased]: https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/compare/v0.7.0...HEAD +[0.7.0]: https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/compare/v0.6.6...v0.7.0 [0.6.6]: https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/compare/v0.6.5...v0.6.6 [0.6.5]: https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/compare/v0.6.4...v0.6.5 [0.6.4]: https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/compare/v0.6.3...v0.6.4 diff --git a/README.md b/README.md index b8ad1e3..4a858da 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Windows desktop приложение, которое помогает делать короткие перерывы без потери фокуса: держит план дня, напоминает размяться, ведёт недельные челленджи и превращает Dota 2 статистику после матча в игровые долги. -[![release](https://img.shields.io/badge/release-v0.6.6-orange)](https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/releases/latest) +[![release](https://img.shields.io/badge/release-v0.7.0-orange)](https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/releases/latest) [![tests](https://img.shields.io/badge/tests-245%20passing-green)]() [![platform](https://img.shields.io/badge/platform-Windows%2010%2F11-blue)]() @@ -15,7 +15,7 @@ Windows desktop приложение, которое помогает делат - **Тихие часы** — окно времени когда напоминания подавляются (например `22:00 → 08:00`), с выбором дней недели. - **Сделал частично** — степпер `−/+` в окне напоминания: если ты сделал 5 из 10, в историю запишется честное число. - **Игровая интеграция (Dota 2)** — Game State Integration читает статистику матча, после Победа/Поражение показывает экран с «причитающимися» повторениями (например `10 смертей × 3 = 30 приседаний`). -- **Apple-style интерфейс** — Plus Jakarta Sans + Bricolage Grotesque, iOS-палитра, vibrancy sidebar, spring-анимации, светлая/тёмная/системная тема. +- **Desktop-интерфейс** — темный сайдбар, плотные рабочие панели, чёткие строки списков, светлая/тёмная/системная тема. - **Два языка** — русский и английский, переключение мгновенное. - **Auto-update** — приложение само скачивает новые версии из фиксированного `update-channel` (проверка каждый час, силент-ретрай при сетевых сбоях). diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index b3ae5c6..de3003a 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -89,14 +89,14 @@ export default function App(): JSX.Element { return ( -
+
setCompactNavOpen(true)} />
setCompactNavOpen(false)} /> -
+
{hydrated ? ( setCompactNavOpen(false)} /> diff --git a/src/renderer/src/ReminderApp.tsx b/src/renderer/src/ReminderApp.tsx index 35d5d7b..88b76a4 100644 --- a/src/renderer/src/ReminderApp.tsx +++ b/src/renderer/src/ReminderApp.tsx @@ -226,18 +226,16 @@ export default function ReminderApp(): JSX.Element { void window.api.markChallengeDone(id, result.reps) } // 2) Functional update: rapid-click race-safe. - setMode((m) => - { - if (m.kind !== 'match') return m - const nextMode: Mode = { - kind: 'match', - summary: m.summary, - done: new Set([...m.done, id]) - } - modeRef.current = nextMode - return nextMode + setMode((m) => { + if (m.kind !== 'match') return m + const nextMode: Mode = { + kind: 'match', + summary: m.summary, + done: new Set([...m.done, id]) } - ) + modeRef.current = nextMode + return nextMode + }) }} onClose={close} /> @@ -330,7 +328,7 @@ function ExerciseReminder({ transition={{ type: 'spring', stiffness: 300, damping: 24 }} className="relative mb-6" > -
+
@@ -338,7 +336,7 @@ function ExerciseReminder({
{t(`category.${exercise.category ?? 'exercise'}.cta`)}
-

+

{exercise.name}

@@ -346,7 +344,7 @@ function ExerciseReminder({
@@ -452,7 +450,10 @@ function MealReminder({ } else if (e.key === 'Escape') { e.preventDefault() onClose() - } else if ((e.key === ' ' || e.code === 'Space') && targetTag !== 'BUTTON') { + } else if ( + (e.key === ' ' || e.code === 'Space') && + targetTag !== 'BUTTON' + ) { e.preventDefault() void snooze() } @@ -481,7 +482,7 @@ function MealReminder({ transition={{ type: 'spring', stiffness: 300, damping: 24 }} className="relative mb-6" > -
+
@@ -489,7 +490,7 @@ function MealReminder({
{t('meal.cta')}
-

+

{meal.name}

@@ -500,13 +501,13 @@ function MealReminder({
-
+
{t('editor.exercise.preview.meta', { reps: exercise.reps, min: exercise.intervalMinutes })}
- {/* Countdown + switch */} -
+
-
+
{goalReached ? ( <>
{t('btn.done')} diff --git a/src/renderer/src/components/ExerciseEditor.tsx b/src/renderer/src/components/ExerciseEditor.tsx index 76014c4..cb5bbec 100644 --- a/src/renderer/src/components/ExerciseEditor.tsx +++ b/src/renderer/src/components/ExerciseEditor.tsx @@ -85,12 +85,12 @@ export function ExerciseEditor({ } >
-
-
+
+
-
+
{draft.name || t('editor.exercise.preview.placeholder')}
@@ -120,7 +120,7 @@ export function ExerciseEditor({ type="button" onClick={() => setDraft({ ...draft, category: c })} className={[ - 'h-10 px-2 rounded-xl text-[13px] font-semibold transition-all active:scale-95 truncate', + 'h-9 px-2 rounded-lg text-[13px] font-semibold transition-colors active:translate-y-px truncate', draft.category === c ? 'bg-accent text-white' : 'bg-surface-2 text-text/65 hover:text-text' @@ -185,7 +185,7 @@ export function ExerciseEditor({ @@ -200,9 +200,7 @@ export function ExerciseEditor({ - setDraft({ ...draft, adaptive: e.target.checked }) - } + onChange={(e) => setDraft({ ...draft, adaptive: e.target.checked })} className="mt-0.5 w-4 h-4 accent-accent" />
@@ -216,14 +214,14 @@ export function ExerciseEditor({ -
+
{ICON_CHOICES.map((name) => (
-
+
{effectiveTitle}
@@ -63,9 +63,7 @@ export function Titlebar({ title, onMenuClick }: Props): JSX.Element { window.api.toggleMaximizeMain()} label={ - maximized - ? t('titlebar.restore_aria') - : t('titlebar.maximize_aria') + maximized ? t('titlebar.restore_aria') : t('titlebar.maximize_aria') } > {maximized ? ( @@ -102,10 +100,10 @@ function WinBtn({ onClick={onClick} aria-label={label} className={[ - 'titlebar-nodrag w-9 h-7 grid place-items-center rounded-md transition-colors text-text/55', + 'titlebar-nodrag w-9 h-7 grid place-items-center rounded-md transition-colors text-sidebar-muted', danger ? 'hover:bg-destructive hover:text-white' - : 'hover:bg-text/[0.08] hover:text-text' + : 'hover:bg-white/10 hover:text-sidebar-text' ].join(' ')} > {children} diff --git a/src/renderer/src/components/WhatsNewModal.tsx b/src/renderer/src/components/WhatsNewModal.tsx index 6c9f131..637fc1e 100644 --- a/src/renderer/src/components/WhatsNewModal.tsx +++ b/src/renderer/src/components/WhatsNewModal.tsx @@ -25,11 +25,7 @@ const TAG_META = { * (a) автоматически после апдейта (когда `lastSeenVersion` != `currentVersion`) * и (b) вручную из Settings. */ -export function WhatsNewModal({ - open, - versions, - onClose -}: Props): JSX.Element { +export function WhatsNewModal({ open, versions, onClose }: Props): JSX.Element { const { t, lang } = useT() return ( -
+
v{version}
diff --git a/src/renderer/src/components/ui/Button.tsx b/src/renderer/src/components/ui/Button.tsx index 2689c56..4982363 100644 --- a/src/renderer/src/components/ui/Button.tsx +++ b/src/renderer/src/components/ui/Button.tsx @@ -1,8 +1,8 @@ import { ButtonHTMLAttributes, forwardRef } from 'react' /** - * iOS-style button. Three primary flavours mirror iOS's filled / tinted / plain. - * Press feedback is a subtle scale, mirroring UIKit's button highlight. + * Desktop button. Three primary flavours cover filled, bordered, and plain actions. + * Press feedback stays subtle so dense tool surfaces do not jump around. */ type Variant = 'filled' | 'tinted' | 'plain' | 'destructive' | 'success' type Size = 'sm' | 'md' | 'lg' @@ -24,19 +24,21 @@ const legacyMap: Record = { } const variantClasses: Record = { - filled: 'bg-accent text-white hover:brightness-105 active:brightness-95', + filled: + 'bg-accent text-white hover:bg-accent/90 active:bg-accent/85 shadow-[0_1px_0_rgb(255_255_255_/_0.18)_inset]', tinted: - 'bg-accent/12 text-accent hover:bg-accent/18 active:bg-accent/22 dark:bg-accent/20 dark:hover:bg-accent/25', - plain: 'text-accent hover:bg-accent/10 active:bg-accent/15', + 'bg-surface border border-hairline/45 text-text hover:border-accent/45 hover:text-accent active:bg-surface-2', + plain: + 'text-text/70 hover:bg-surface-2 hover:text-text active:bg-hairline/20', destructive: - 'bg-destructive/12 text-destructive hover:bg-destructive/18 active:bg-destructive/22 dark:bg-destructive/20', - success: 'bg-success text-white hover:brightness-105 active:brightness-95' + 'bg-destructive/10 border border-destructive/20 text-destructive hover:bg-destructive/14 active:bg-destructive/18', + success: 'bg-success text-white hover:bg-success/90 active:bg-success/80' } const sizeClasses: Record = { - sm: 'h-8 px-3.5 text-[13px] rounded-xl', - md: 'h-10 px-4 text-[14px] rounded-2xl', - lg: 'h-12 px-6 text-[16px] rounded-2xl' + sm: 'h-8 px-3 text-[13px] rounded-lg', + md: 'h-9 px-3.5 text-[14px] rounded-lg', + lg: 'h-10 px-4 text-[15px] rounded-lg' } export const Button = forwardRef(function Button( @@ -49,9 +51,9 @@ export const Button = forwardRef(function Button(
-
- {children} -
+
{children}
{footer && ( -
+
{footer}
)} diff --git a/src/renderer/src/components/ui/Switch.tsx b/src/renderer/src/components/ui/Switch.tsx index 08f678b..e36234b 100644 --- a/src/renderer/src/components/ui/Switch.tsx +++ b/src/renderer/src/components/ui/Switch.tsx @@ -7,9 +7,6 @@ type Props = { 'aria-label'?: string } -/** - * iOS UISwitch — 51×31 spec, green when on, smooth spring knob. - */ export function Switch({ checked, onChange, @@ -25,17 +22,18 @@ export function Switch({ onClick={() => !disabled && onChange(!checked)} disabled={disabled} className={[ - 'relative inline-flex h-[31px] w-[51px] shrink-0 cursor-pointer rounded-full transition-colors duration-200 ease-out', - checked ? 'bg-success' : 'bg-hairline/25 dark:bg-hairline/50', + 'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full transition-colors duration-150 ease-out border', + checked + ? 'bg-success border-success' + : 'bg-surface-2 border-hairline/55', disabled ? 'opacity-40 cursor-not-allowed' : '' ].join(' ')} style={{ padding: 2 }} > -
-
+
+
+
{t('challenges.kicker')}
-

+

{t('challenges.title')}

@@ -92,7 +92,7 @@ export default function ChallengesPage(): JSX.Element {

{noGamesActive && ( -
+
@@ -178,7 +178,7 @@ export default function ChallengesPage(): JSX.Element { ) : (
-
+
@@ -342,7 +342,7 @@ function ChallengeEditor({ -
+
{ICON_CHOICES.map((name) => ( +
-

- {header.subtitle} -

-
- - -
-
+ -
+
@@ -336,7 +338,7 @@ export default function Dashboard(): JSX.Element {
)} -
+
{exercises.map((ex) => ( -
+
@@ -544,17 +546,17 @@ function MomentumPanel({ : t('momentum.game.off') return ( -
-
+
+
-
+
{t('momentum.level.title')}
-
+
{t(momentum.level.key)}
@@ -582,13 +584,13 @@ function MomentumPanel({
-
+
{t('momentum.week.kicker')}
-

+

{t('momentum.week.title')}

@@ -606,29 +608,29 @@ function MomentumPanel({
-
+
-
+
{t('momentum.game.kicker')}
-

+

{t('momentum.game.title')}

-
+
{t('momentum.game.status')}
-
+
{momentum.gameDebt.lastMatchAt ? t('momentum.game.last', { date: new Date( @@ -706,7 +708,7 @@ function WeeklyQuestRow({
+
-
+
-

+

{t('dashboard.plan.title')}

@@ -801,7 +803,7 @@ function TodayPlanPanel({
0 ? 'bg-accent/12 text-accent' : 'bg-success/12 text-success' @@ -858,7 +860,7 @@ function TodayPlanPanel({ ) : (
-
+
{t('dashboard.plan.clear.title')}
@@ -923,7 +925,7 @@ function TodayPlanPanel({
@@ -973,7 +975,7 @@ function PlanItemGlyph({ item }: { item: PlanItem }): JSX.Element { return (
@@ -1004,7 +1006,7 @@ function PlanListRow({ {planItemMeta(item, t)}
-
+
{planItemTiming(item, paused, lang, t)}
@@ -1131,23 +1133,25 @@ function HeroStat({ : 'bg-text/40' return ( -
-
+
+
{icon}
-
{label}
+
+ {label} +
-
+
{value}
{subvalue && ( -
+
{subvalue}
)} diff --git a/src/renderer/src/pages/Exercises.tsx b/src/renderer/src/pages/Exercises.tsx index 0672bef..388b911 100644 --- a/src/renderer/src/pages/Exercises.tsx +++ b/src/renderer/src/pages/Exercises.tsx @@ -27,8 +27,8 @@ export default function Exercises(): JSX.Element { const totalReps = enabled.reduce((sum, e) => sum + e.reps, 0) return ( -
-
+
+
-
+
diff --git a/src/renderer/src/pages/Games.tsx b/src/renderer/src/pages/Games.tsx index c9d1dcb..6301f09 100644 --- a/src/renderer/src/pages/Games.tsx +++ b/src/renderer/src/pages/Games.tsx @@ -66,14 +66,14 @@ export default function GamesPage(): JSX.Element { ).length return ( -
-
-
+
+
+
{t('games.kicker')}
-

+

{t('games.title')}

@@ -124,7 +124,7 @@ export default function GamesPage(): JSX.Element { -

+
{games.map((g, i) => ( +
-

+

{game.name}

@@ -220,7 +220,7 @@ function GameCard({
{game.integrationActive && game.launchOptionStatus === 'queued' && ( -
+
+
diff --git a/src/renderer/src/pages/Meals.tsx b/src/renderer/src/pages/Meals.tsx index 05225b2..060d9cf 100644 --- a/src/renderer/src/pages/Meals.tsx +++ b/src/renderer/src/pages/Meals.tsx @@ -64,8 +64,8 @@ export default function Meals(): JSX.Element { } return ( -
-
+
+
addPreset(p)} - className="inline-flex items-center gap-2 h-10 px-3.5 rounded-2xl bg-surface-2 hover:bg-accent/15 hover:text-accent text-text/80 text-[14px] font-semibold transition-colors active:scale-95" + className="inline-flex items-center gap-2 h-9 px-3 rounded-lg bg-surface border border-hairline/35 hover:border-accent/45 hover:text-accent text-text/80 text-[14px] font-semibold transition-colors active:translate-y-px" > {t(p.nameKey)} @@ -157,7 +157,7 @@ export default function Meals(): JSX.Element { {meals.length === 0 && (
-
+
diff --git a/src/renderer/src/pages/Settings.tsx b/src/renderer/src/pages/Settings.tsx index f901ba8..12e6a53 100644 --- a/src/renderer/src/pages/Settings.tsx +++ b/src/renderer/src/pages/Settings.tsx @@ -36,7 +36,7 @@ export default function SettingsPage(): JSX.Element { if (!settings) return (
@@ -52,13 +52,13 @@ export default function SettingsPage(): JSX.Element { } return ( -
-
+
+
{t('settings.kicker')}
-

+

{t('settings.title')}

@@ -260,27 +260,27 @@ function SettingsStatusPanel({ }): JSX.Element { const { t } = useT() return ( -
+
-
+
{t('settings.status.kicker')}
-

+

{settings.globalEnabled ? t('settings.status.title.on') : t('settings.status.title.off')}

-

+

{settingsStatusHint(settings, t)}

@@ -352,7 +352,7 @@ function StatusPill({ }): JSX.Element { const activeClass = tone === 'info' ? 'text-info' : 'text-success' return ( -
+
{label}
diff --git a/src/renderer/src/styles/globals.css b/src/renderer/src/styles/globals.css index 7779dd4..2261201 100644 --- a/src/renderer/src/styles/globals.css +++ b/src/renderer/src/styles/globals.css @@ -20,56 +20,59 @@ @tailwind components; @tailwind utilities; -/* ===== Design tokens — Apple HIG with Manrope/Fraunces ===== */ +/* ===== Design tokens: desktop workstation ===== */ :root { - /* Brand & semantic colors (iOS system palette) */ - --accent: 255 107 53; /* Apple Fitness Move orange */ - --accent-2: 255 45 85; /* systemPink */ - --success: 52 199 89; /* systemGreen */ - --warning: 255 159 10; /* systemOrange dark */ - --destructive: 255 59 48; /* systemRed */ - --info: 0 122 255; /* systemBlue */ + --accent: 16 118 110; + --accent-2: 225 29 72; + --success: 22 163 74; + --warning: 217 119 6; + --destructive: 220 38 38; + --info: 37 99 235; color-scheme: light dark; } -/* Light — polished iOS groupedBackground with warm undertone */ :root { - --bg: 245 245 249; /* slightly warmer than 242,242,247 */ + --bg: 244 246 247; --surface: 255 255 255; - --surface-2: 240 240 245; /* subtle separation for inputs/sections */ - --text: 17 17 19; /* not pure black — softer */ - --text-secondary: 60 60 67; - --text-tertiary: 60 60 67; - --hairline: 60 60 67; + --surface-2: 236 240 242; + --text: 22 27 29; + --text-secondary: 72 82 87; + --text-tertiary: 103 113 118; + --hairline: 174 185 190; --vibrancy: 255 255 255; + --sidebar: 26 31 33; + --sidebar-text: 245 247 248; + --sidebar-muted: 156 170 176; - --accent-soft: 255 107 53; - --bg-deep: 232 232 238; + --accent-soft: 16 118 110; + --bg-deep: 226 231 233; --surface-elevated: 255 255 255; - --border: 60 60 67; - --muted: 60 60 67; - --victory: 52 199 89; - --defeat: 255 59 48; - --xp: 255 159 10; + --border: 174 185 190; + --muted: 72 82 87; + --victory: 22 163 74; + --defeat: 220 38 38; + --xp: 217 119 6; } -/* Dark — true black with grey elevation */ .dark { - --bg: 0 0 0; - --surface: 28 28 30; - --surface-2: 44 44 46; - --text: 255 255 255; - --text-secondary: 235 235 245; - --text-tertiary: 235 235 245; - --hairline: 84 84 88; - --vibrancy: 28 28 30; + --bg: 16 18 19; + --surface: 28 32 34; + --surface-2: 40 46 49; + --text: 245 247 248; + --text-secondary: 203 211 215; + --text-tertiary: 152 164 170; + --hairline: 84 96 101; + --vibrancy: 28 32 34; + --sidebar: 17 22 24; + --sidebar-text: 248 250 250; + --sidebar-muted: 146 161 168; - --bg-deep: 0 0 0; - --surface-elevated: 44 44 46; - --border: 84 84 88; - --muted: 235 235 245; + --bg-deep: 12 14 15; + --surface-elevated: 36 42 45; + --border: 84 96 101; + --muted: 203 211 215; } /* ===== Base ===== */ @@ -85,50 +88,31 @@ body, body { font-family: - 'Plus Jakarta Sans', - -apple-system, - 'SF Pro Text', - 'Segoe UI Variable Text', - 'Segoe UI', - system-ui, - sans-serif; + -apple-system, 'Segoe UI Variable Text', 'Segoe UI', 'Plus Jakarta Sans', + system-ui, sans-serif; background-color: rgb(var(--bg)); color: rgb(var(--text)); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-size: 14px; line-height: 1.5; - letter-spacing: -0.005em; + letter-spacing: 0; } -/* Display — Bricolage Grotesque for headings and brand. Variable opsz axis - gives bigger glyphs slightly more character. */ .font-display { font-family: - 'Bricolage Grotesque', - 'Plus Jakarta Sans', - -apple-system, - 'SF Pro Display', - system-ui, - sans-serif; + -apple-system, 'Segoe UI Variable Display', 'Segoe UI', 'Plus Jakarta Sans', + system-ui, sans-serif; font-optical-sizing: auto; - font-variation-settings: 'opsz' 24; - letter-spacing: -0.02em; + letter-spacing: 0; } -/* Serif → repurposed for the "hero" big titles. Same Bricolage but with the - opsz axis pushed to 96 for distinctive display feel. */ .font-serif { font-family: - 'Bricolage Grotesque', - 'Plus Jakarta Sans', - -apple-system, - 'SF Pro Display', - system-ui, - sans-serif; + -apple-system, 'Segoe UI Variable Display', 'Segoe UI', 'Plus Jakarta Sans', + system-ui, sans-serif; font-optical-sizing: auto; - font-variation-settings: 'opsz' 96; - letter-spacing: -0.035em; + letter-spacing: 0; } .font-mono-num { @@ -136,7 +120,14 @@ body { 'JetBrains Mono', ui-monospace, 'SF Mono', 'Cascadia Code', Menlo, monospace; font-variant-numeric: tabular-nums; font-feature-settings: 'ss02', 'ss19', 'zero'; - letter-spacing: -0.01em; + letter-spacing: 0; +} + +.tracking-tight, +.tracking-wider, +.tracking-\[0\.06em\], +.tracking-\[0\.18em\] { + letter-spacing: 0 !important; } /* Custom titlebar drag region */ @@ -149,36 +140,47 @@ body { app-region: no-drag; } -/* iOS 0.5px-style hairlines */ +/* Crisp panel separators for dense desktop surfaces */ .hairline-b { - box-shadow: inset 0 -0.5px 0 rgb(var(--hairline) / 0.18); + box-shadow: inset 0 -1px 0 rgb(var(--hairline) / 0.34); } .hairline-t { - box-shadow: inset 0 0.5px 0 rgb(var(--hairline) / 0.18); -} -.dark .hairline-b, -.dark .hairline-t { - box-shadow: inset 0 -0.5px 0 rgb(var(--hairline) / 0.4); + box-shadow: inset 0 1px 0 rgb(var(--hairline) / 0.34); } -/* macOS vibrancy */ .vibrancy { - background-color: rgb(var(--vibrancy) / 0.72); - backdrop-filter: saturate(180%) blur(30px); - -webkit-backdrop-filter: saturate(180%) blur(30px); + background-color: rgb(var(--vibrancy) / 0.96); + backdrop-filter: none; + -webkit-backdrop-filter: none; } -/* Soft iOS card shadow with subtle warmth */ .shadow-card { box-shadow: - 0 0.5px 0 rgb(0 0 0 / 0.03), - 0 1px 2px rgb(15 23 42 / 0.04), - 0 6px 14px -4px rgb(15 23 42 / 0.05); + 0 0 0 1px rgb(var(--hairline) / 0.24), + 0 10px 28px -24px rgb(15 23 42 / 0.45); } .dark .shadow-card { box-shadow: - 0 0.5px 0 rgb(255 255 255 / 0.04) inset, - 0 1px 2px rgb(0 0 0 / 0.4); + 0 0 0 1px rgb(var(--hairline) / 0.38), + 0 16px 40px -30px rgb(0 0 0 / 0.9); +} + +.app-shell { + background: linear-gradient( + 180deg, + rgb(var(--bg)) 0%, + rgb(var(--bg-deep)) 100% + ); +} + +.app-main-surface { + background: + linear-gradient(180deg, rgb(var(--surface) / 0.78), transparent 220px), + rgb(var(--bg)); +} + +.desktop-scroll { + scrollbar-gutter: stable; } /* Scrollbar */ @@ -208,12 +210,12 @@ body { .reminder-shell { position: relative; - border: 0.5px solid rgb(var(--hairline) / 0.25); - border-radius: 22px; + border: 1px solid rgb(var(--hairline) / 0.35); + border-radius: 12px; background: rgb(var(--surface)); box-shadow: - 0 1px 2px rgb(0 0 0 / 0.06), - 0 20px 50px -16px rgb(0 0 0 / 0.4); + 0 1px 2px rgb(0 0 0 / 0.08), + 0 24px 70px -28px rgb(0 0 0 / 0.55); overflow: hidden; height: 100%; } diff --git a/src/shared/release-notes.ts b/src/shared/release-notes.ts index b37cffd..b83f4d9 100644 --- a/src/shared/release-notes.ts +++ b/src/shared/release-notes.ts @@ -21,6 +21,60 @@ export type ReleaseNoteItem = { export type ReleaseNotes = Record export const RELEASE_NOTES: Record = { + '0.7.0': { + ru: [ + { + title: 'Новый desktop-дизайн', + detail: + 'Приложение стало рабочей панелью: темный сайдбар, четкие панели, меньше декоративности и больше полезной плотности.', + tag: 'new' + }, + { + title: 'Обзор стал операционным экраном', + detail: + 'Главный экран теперь быстрее показывает ближайший шаг, статус, цели, питание, режим и игровые долги.', + tag: 'new' + }, + { + title: 'Настройки и списки стали спокойнее', + detail: + 'Упражнения, питание, игры, челленджи и настройки получили единый desktop-контейнер и компактные строки.', + tag: 'new' + }, + { + title: 'Модалки и напоминания обновлены', + detail: + 'Редакторы, переключатели, кнопки и окно напоминания приведены к новой desktop-системе.', + tag: 'new' + } + ], + en: [ + { + title: 'New desktop design', + detail: + 'The app now feels like a focused workstation: dark sidebar, crisp panels, less decoration and better density.', + tag: 'new' + }, + { + title: 'Overview is more operational', + detail: + 'The main screen surfaces the next action, status, goals, meals, mode and game debt faster.', + tag: 'new' + }, + { + title: 'Settings and lists are calmer', + detail: + 'Exercises, meals, games, challenges and settings now share a tighter desktop container and compact rows.', + tag: 'new' + }, + { + title: 'Dialogs and reminders were refreshed', + detail: + 'Editors, switches, buttons and the reminder window now follow the new desktop system.', + tag: 'new' + } + ] + }, '0.6.6': { ru: [ { diff --git a/tailwind.config.mjs b/tailwind.config.mjs index f6a3d5e..a706f3f 100644 --- a/tailwind.config.mjs +++ b/tailwind.config.mjs @@ -5,7 +5,7 @@ export default { theme: { extend: { colors: { - // iOS semantic palette + // Desktop semantic palette accent: 'rgb(var(--accent) / )', 'accent-soft': 'rgb(var(--accent-soft) / )', 'accent-2': 'rgb(var(--accent-2) / )', @@ -20,6 +20,9 @@ export default { surface: 'rgb(var(--surface) / )', 'surface-2': 'rgb(var(--surface-2) / )', 'surface-elevated': 'rgb(var(--surface-elevated) / )', + sidebar: 'rgb(var(--sidebar) / )', + 'sidebar-text': 'rgb(var(--sidebar-text) / )', + 'sidebar-muted': 'rgb(var(--sidebar-muted) / )', // Text & lines text: 'rgb(var(--text) / )', @@ -68,15 +71,14 @@ export default { ] }, borderRadius: { - // iOS-specific radii - xl: '14px', - '2xl': '18px', - '3xl': '22px' + xl: '8px', + '2xl': '10px', + '3xl': '12px' }, boxShadow: { - ios: '0 0.5px 0 rgb(0 0 0 / 0.04), 0 1px 2px rgb(0 0 0 / 0.05), 0 4px 12px rgb(0 0 0 / 0.04)', + ios: '0 1px 2px rgb(15 23 42 / 0.06), 0 10px 28px -24px rgb(15 23 42 / 0.35)', sheet: - '0 1px 2px rgb(0 0 0 / 0.06), 0 20px 50px -16px rgb(0 0 0 / 0.4)' + '0 1px 2px rgb(15 23 42 / 0.08), 0 24px 60px -28px rgb(15 23 42 / 0.45)' } } },