From 20a260d0cc4a8024c5f3ab98e67d005582626f2d Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 9 Jun 2026 00:56:40 +0700 Subject: [PATCH] Revert "feat(ui): redesign desktop experience" This reverts commit f61e076e461fbd923d8a08bd15d1e8523d78620b. --- 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, 440 insertions(+), 488 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0940dad..9075d36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,24 +6,6 @@ ## [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 @@ -188,7 +170,7 @@ clearHistory/import`, Dashboard на него подписан. ### Fixed (P1 — UX просадки) - **Удаление упражнения теперь спрашивает подтверждение.** Раньше - один клик в menu «Удалить» сразу удалял. Сейчас ConfirmModal + один клик в menu «Удалить» сразу удалял. Сейчас iOS-style ConfirmModal с destructive-кнопкой. - **Daily goal закрыт — больше не «25 часов 13 минут».** Когда дневная цель достигнута, ExerciseCard показывает «Цель закрыта · 100/100» @@ -197,7 +179,7 @@ clearHistory/import`, Dashboard на него подписан. молча — пользователь не понимал почему через 12 мин ничего не пришло. Сейчас info-баннер активной встречи с указанием закрыть Zoom/Teams/etc. -- **Native `window.confirm()` → ConfirmModal** в restore-операции. +- **Native `window.confirm()` → iOS-style ConfirmModal** в restore-операции. Раньше всплывал серый системный диалог. ### Fixed (P2 — полировка) @@ -223,7 +205,7 @@ clearHistory/import`, Dashboard на него подписан. ### Added - `src/renderer/src/components/ui/ConfirmModal.tsx` — переиспользуемый - confirm с focus-trap'ом через Modal. + iOS-style confirm с focus-trap'ом через Modal. - IPC `markChallengeDone(challengeId, reps)` — handler в main, метод в preload (раньше канал был в IPC enum, handler не зарегистрирован). - IPC `getMeetingActive` + event `evtMeetingChanged` — meeting-detect @@ -550,9 +532,9 @@ days=[Mon..Fri]` теперь правильно проверяется день ## [0.3.x] — 2026-05-17 -Серия мелких релизов с ранними дизайн-итерациями: +Серия мелких релизов с дизайн-итерациями (Apple iOS / macOS aesthetic): шрифты Plus Jakarta Sans + Bricolage Grotesque, светлая/тёмная/системная -тема, полупрозрачный сайдбар, grouped lists, spring-анимации. +тема, vibrancy sidebar, iOS-grouped lists, spring-анимации. ## [0.2.0] — 2026-05-16 @@ -568,8 +550,7 @@ days=[Mon..Fri]` теперь правильно проверяется день иконки), системный трей, автозапуск с Windows, native-уведомления, NSIS-инсталлятор, auto-update через electron-updater. -[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 +[Unreleased]: https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/compare/v0.6.6...HEAD [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 4a858da..b8ad1e3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Windows desktop приложение, которое помогает делать короткие перерывы без потери фокуса: держит план дня, напоминает размяться, ведёт недельные челленджи и превращает Dota 2 статистику после матча в игровые долги. -[![release](https://img.shields.io/badge/release-v0.7.0-orange)](https://git.xn--90adajar8af4h.xn--p1ai/AnRil/laude/releases/latest) +[![release](https://img.shields.io/badge/release-v0.6.6-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 приседаний`). -- **Desktop-интерфейс** — темный сайдбар, плотные рабочие панели, чёткие строки списков, светлая/тёмная/системная тема. +- **Apple-style интерфейс** — Plus Jakarta Sans + Bricolage Grotesque, iOS-палитра, vibrancy sidebar, spring-анимации, светлая/тёмная/системная тема. - **Два языка** — русский и английский, переключение мгновенное. - **Auto-update** — приложение само скачивает новые версии из фиксированного `update-channel` (проверка каждый час, силент-ретрай при сетевых сбоях). diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index de3003a..b3ae5c6 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 88b76a4..35d5d7b 100644 --- a/src/renderer/src/ReminderApp.tsx +++ b/src/renderer/src/ReminderApp.tsx @@ -226,16 +226,18 @@ 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]) + 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 } - modeRef.current = nextMode - return nextMode - }) + ) }} onClose={close} /> @@ -328,7 +330,7 @@ function ExerciseReminder({ transition={{ type: 'spring', stiffness: 300, damping: 24 }} className="relative mb-6" > -
+
@@ -336,7 +338,7 @@ function ExerciseReminder({
{t(`category.${exercise.category ?? 'exercise'}.cta`)}
-

+

{exercise.name}

@@ -344,7 +346,7 @@ function ExerciseReminder({
@@ -450,10 +452,7 @@ 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() } @@ -482,7 +481,7 @@ function MealReminder({ transition={{ type: 'spring', stiffness: 300, damping: 24 }} className="relative mb-6" > -
+
@@ -490,7 +489,7 @@ function MealReminder({
{t('meal.cta')}
-

+

{meal.name}

@@ -501,13 +500,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 cb5bbec..76014c4 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-9 px-2 rounded-lg text-[13px] font-semibold transition-colors active:translate-y-px truncate', + 'h-10 px-2 rounded-xl text-[13px] font-semibold transition-all active:scale-95 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,7 +200,9 @@ 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" />
@@ -214,14 +216,14 @@ export function ExerciseEditor({ -
+
{ICON_CHOICES.map((name) => (
-
+
{effectiveTitle}
@@ -63,7 +63,9 @@ 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 ? ( @@ -100,10 +102,10 @@ function WinBtn({ onClick={onClick} aria-label={label} className={[ - 'titlebar-nodrag w-9 h-7 grid place-items-center rounded-md transition-colors text-sidebar-muted', + 'titlebar-nodrag w-9 h-7 grid place-items-center rounded-md transition-colors text-text/55', danger ? 'hover:bg-destructive hover:text-white' - : 'hover:bg-white/10 hover:text-sidebar-text' + : 'hover:bg-text/[0.08] hover:text-text' ].join(' ')} > {children} diff --git a/src/renderer/src/components/WhatsNewModal.tsx b/src/renderer/src/components/WhatsNewModal.tsx index 637fc1e..6c9f131 100644 --- a/src/renderer/src/components/WhatsNewModal.tsx +++ b/src/renderer/src/components/WhatsNewModal.tsx @@ -25,7 +25,11 @@ 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 4982363..2689c56 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' /** - * Desktop button. Three primary flavours cover filled, bordered, and plain actions. - * Press feedback stays subtle so dense tool surfaces do not jump around. + * iOS-style button. Three primary flavours mirror iOS's filled / tinted / plain. + * Press feedback is a subtle scale, mirroring UIKit's button highlight. */ type Variant = 'filled' | 'tinted' | 'plain' | 'destructive' | 'success' type Size = 'sm' | 'md' | 'lg' @@ -24,21 +24,19 @@ const legacyMap: Record = { } const variantClasses: Record = { - filled: - 'bg-accent text-white hover:bg-accent/90 active:bg-accent/85 shadow-[0_1px_0_rgb(255_255_255_/_0.18)_inset]', + filled: 'bg-accent text-white hover:brightness-105 active:brightness-95', tinted: - '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', + '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', destructive: - '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' + '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' } const sizeClasses: Record = { - 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' + 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' } export const Button = forwardRef(function Button( @@ -51,9 +49,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 e36234b..08f678b 100644 --- a/src/renderer/src/components/ui/Switch.tsx +++ b/src/renderer/src/components/ui/Switch.tsx @@ -7,6 +7,9 @@ type Props = { 'aria-label'?: string } +/** + * iOS UISwitch — 51×31 spec, green when on, smooth spring knob. + */ export function Switch({ checked, onChange, @@ -22,18 +25,17 @@ export function Switch({ onClick={() => !disabled && onChange(!checked)} disabled={disabled} className={[ - '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', + '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', 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.title} +

+ + {header.status} +
+

+ {header.subtitle} +

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

+

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

@@ -608,29 +606,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( @@ -708,7 +706,7 @@ function WeeklyQuestRow({
+
-
+
-

+

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

@@ -803,7 +801,7 @@ function TodayPlanPanel({
0 ? 'bg-accent/12 text-accent' : 'bg-success/12 text-success' @@ -860,7 +858,7 @@ function TodayPlanPanel({ ) : (
-
+
{t('dashboard.plan.clear.title')}
@@ -925,7 +923,7 @@ function TodayPlanPanel({
@@ -975,7 +973,7 @@ function PlanItemGlyph({ item }: { item: PlanItem }): JSX.Element { return (
@@ -1006,7 +1004,7 @@ function PlanListRow({ {planItemMeta(item, t)}
-
+
{planItemTiming(item, paused, lang, t)}
@@ -1133,25 +1131,23 @@ 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 388b911..0672bef 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 6301f09..c9d1dcb 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 060d9cf..05225b2 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-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" + 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" > {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 12e6a53..f901ba8 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 2261201..7779dd4 100644 --- a/src/renderer/src/styles/globals.css +++ b/src/renderer/src/styles/globals.css @@ -20,59 +20,56 @@ @tailwind components; @tailwind utilities; -/* ===== Design tokens: desktop workstation ===== */ +/* ===== Design tokens — Apple HIG with Manrope/Fraunces ===== */ :root { - --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; + /* 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 */ color-scheme: light dark; } +/* Light — polished iOS groupedBackground with warm undertone */ :root { - --bg: 244 246 247; + --bg: 245 245 249; /* slightly warmer than 242,242,247 */ --surface: 255 255 255; - --surface-2: 236 240 242; - --text: 22 27 29; - --text-secondary: 72 82 87; - --text-tertiary: 103 113 118; - --hairline: 174 185 190; + --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; --vibrancy: 255 255 255; - --sidebar: 26 31 33; - --sidebar-text: 245 247 248; - --sidebar-muted: 156 170 176; - --accent-soft: 16 118 110; - --bg-deep: 226 231 233; + --accent-soft: 255 107 53; + --bg-deep: 232 232 238; --surface-elevated: 255 255 255; - --border: 174 185 190; - --muted: 72 82 87; - --victory: 22 163 74; - --defeat: 220 38 38; - --xp: 217 119 6; + --border: 60 60 67; + --muted: 60 60 67; + --victory: 52 199 89; + --defeat: 255 59 48; + --xp: 255 159 10; } +/* Dark — true black with grey elevation */ .dark { - --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: 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-deep: 12 14 15; - --surface-elevated: 36 42 45; - --border: 84 96 101; - --muted: 203 211 215; + --bg-deep: 0 0 0; + --surface-elevated: 44 44 46; + --border: 84 84 88; + --muted: 235 235 245; } /* ===== Base ===== */ @@ -88,31 +85,50 @@ body, body { font-family: - -apple-system, 'Segoe UI Variable Text', 'Segoe UI', 'Plus Jakarta Sans', - system-ui, sans-serif; + 'Plus Jakarta Sans', + -apple-system, + 'SF Pro Text', + 'Segoe UI Variable Text', + 'Segoe UI', + 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; + letter-spacing: -0.005em; } +/* Display — Bricolage Grotesque for headings and brand. Variable opsz axis + gives bigger glyphs slightly more character. */ .font-display { font-family: - -apple-system, 'Segoe UI Variable Display', 'Segoe UI', 'Plus Jakarta Sans', - system-ui, sans-serif; + 'Bricolage Grotesque', + 'Plus Jakarta Sans', + -apple-system, + 'SF Pro Display', + system-ui, + sans-serif; font-optical-sizing: auto; - letter-spacing: 0; + font-variation-settings: 'opsz' 24; + letter-spacing: -0.02em; } +/* 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: - -apple-system, 'Segoe UI Variable Display', 'Segoe UI', 'Plus Jakarta Sans', - system-ui, sans-serif; + 'Bricolage Grotesque', + 'Plus Jakarta Sans', + -apple-system, + 'SF Pro Display', + system-ui, + sans-serif; font-optical-sizing: auto; - letter-spacing: 0; + font-variation-settings: 'opsz' 96; + letter-spacing: -0.035em; } .font-mono-num { @@ -120,14 +136,7 @@ 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; -} - -.tracking-tight, -.tracking-wider, -.tracking-\[0\.06em\], -.tracking-\[0\.18em\] { - letter-spacing: 0 !important; + letter-spacing: -0.01em; } /* Custom titlebar drag region */ @@ -140,47 +149,36 @@ body { app-region: no-drag; } -/* Crisp panel separators for dense desktop surfaces */ +/* iOS 0.5px-style hairlines */ .hairline-b { - box-shadow: inset 0 -1px 0 rgb(var(--hairline) / 0.34); + box-shadow: inset 0 -0.5px 0 rgb(var(--hairline) / 0.18); } .hairline-t { - box-shadow: inset 0 1px 0 rgb(var(--hairline) / 0.34); + 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); } +/* macOS vibrancy */ .vibrancy { - background-color: rgb(var(--vibrancy) / 0.96); - backdrop-filter: none; - -webkit-backdrop-filter: none; + background-color: rgb(var(--vibrancy) / 0.72); + backdrop-filter: saturate(180%) blur(30px); + -webkit-backdrop-filter: saturate(180%) blur(30px); } +/* Soft iOS card shadow with subtle warmth */ .shadow-card { box-shadow: - 0 0 0 1px rgb(var(--hairline) / 0.24), - 0 10px 28px -24px rgb(15 23 42 / 0.45); + 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); } .dark .shadow-card { box-shadow: - 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; + 0 0.5px 0 rgb(255 255 255 / 0.04) inset, + 0 1px 2px rgb(0 0 0 / 0.4); } /* Scrollbar */ @@ -210,12 +208,12 @@ body { .reminder-shell { position: relative; - border: 1px solid rgb(var(--hairline) / 0.35); - border-radius: 12px; + border: 0.5px solid rgb(var(--hairline) / 0.25); + border-radius: 22px; background: rgb(var(--surface)); box-shadow: - 0 1px 2px rgb(0 0 0 / 0.08), - 0 24px 70px -28px rgb(0 0 0 / 0.55); + 0 1px 2px rgb(0 0 0 / 0.06), + 0 20px 50px -16px rgb(0 0 0 / 0.4); overflow: hidden; height: 100%; } diff --git a/src/shared/release-notes.ts b/src/shared/release-notes.ts index b83f4d9..b37cffd 100644 --- a/src/shared/release-notes.ts +++ b/src/shared/release-notes.ts @@ -21,60 +21,6 @@ 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 a706f3f..f6a3d5e 100644 --- a/tailwind.config.mjs +++ b/tailwind.config.mjs @@ -5,7 +5,7 @@ export default { theme: { extend: { colors: { - // Desktop semantic palette + // iOS semantic palette accent: 'rgb(var(--accent) / )', 'accent-soft': 'rgb(var(--accent-soft) / )', 'accent-2': 'rgb(var(--accent-2) / )', @@ -20,9 +20,6 @@ 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) / )', @@ -71,14 +68,15 @@ export default { ] }, borderRadius: { - xl: '8px', - '2xl': '10px', - '3xl': '12px' + // iOS-specific radii + xl: '14px', + '2xl': '18px', + '3xl': '22px' }, boxShadow: { - ios: '0 1px 2px rgb(15 23 42 / 0.06), 0 10px 28px -24px rgb(15 23 42 / 0.35)', + 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)', sheet: - '0 1px 2px rgb(15 23 42 / 0.08), 0 24px 60px -28px rgb(15 23 42 / 0.45)' + '0 1px 2px rgb(0 0 0 / 0.06), 0 20px 50px -16px rgb(0 0 0 / 0.4)' } } },