# Визуальный язык (фаза 6, начата) Редизайн Flutter-клиента в духе Grimmory (self-hosted книжная библиотека): тёмный сине- индиго фон вместо нейтрального серого, плоские карточки с рамкой вместо тени, группированная боковая навигация с капс-заголовками и левой акцентной полоской у активного пункта. Обкатано целиком на одном экране — Обзор (`features/home/home_page.dart`) — и на навигационном шелле (он общий для всех экранов, поэтому не в счёт «раскатки»), прежде чем идти дальше, тем же способом, что офлайн-кэш (`offline-cache.md`). ## Решение: одна тема на обе яркости, не «тёмная как есть — светлая как получится» Тёмная и светлая тема получают один и тот же язык (форма карточек, акцент, поведение навигации), различаются только тона поверхностей. Причины: - Приложение живёт на вебе, Linux, Windows и Android одновременно — пользователь может оказаться в любой теме по системной настройке, и она уже переключается в `settings_page.dart`. Бесплатно смотрящаяся только тёмная версия читалась бы как недоделанная в светлой. - Семантика цвета (`signColor` — прибыль/убыток, `ChartColors` — ряды графиков) уже общая для обеих тем; заводить для тёмной темы отдельный «фирменный» акцент, а для светлой оставлять дефолтный Material-синий было бы второй, несогласованной системой токенов. ## Токены: `core/theme/app_theme.dart` `AppTheme.light()` / `AppTheme.dark()` заменили инлайновые `ThemeData(...)` в `app.dart`. Обе строятся одной функцией `_build(Brightness)` — нет двух параллельных наборов чисел, которые могут разъехаться. - **Seed** — `Color(0xFF2A78D6)`, тот же синий, что `ChartColors.slot1Blue` (основной ряд графиков капитала/портфеля). До редизайна seed темы (`0xFF2E6F5E`, зелёно-бирюзовый) и цвет основной линии на графиках были разными брендовыми цветами одного приложения — теперь акцент интерфейса и «главный» цвет данных на графиках — один и тот же. - **Поверхности** — не переопределены вручную: `ColorScheme.fromSeed` в Material 3 уже тонирует нейтральную палитру оттенком seed, поэтому тёмный `surface` от синего seed получается тёмным сине-индиго сам по себе («не чистый чёрный», как в референсе), без захардкоженных цветов сверх seed. Слои `surfaceContainer(Low/…/High)` идут на карточки, панель навигации и фон страницы — так они остаются согласованно тонированными при смене темы. - **`cardTheme`** — скруглённые углы (16), `elevation: 0`, тонкая рамка `outlineVariant` вместо тени. Это глобальный токен: он каскадом меняет вид `Card` **везде**, где он уже используется (`SectionCard`, `StatTile`, `GoalCard`, карточки Здоровья, `_Card` в `holdings_tab.dart` и т.д.) без правки каждого экрана — что и даёт «зафиксировать токены один раз» смысл. Экраны, которые ещё не редизайнены явно, уже выглядят новее только за счёт этого. - **`navigationRailTheme` / `navigationBarTheme`** — акцентная подсветка активного пункта (`primary`/`primaryContainer`) для свёрнутого rail (600–1200) и нижнего бара (<600). Все цвета берутся из `ColorScheme` или уже существующего `ChartColors` — ни одного нового хардкод-цвета вне них. ## Компоненты - **`core/widgets/section_header.dart` → `SectionHeader`** — жирный заголовок + короткая акцентная полоска (28×3, `colorScheme.primary`) под левой частью текста. Замена голому `Text(title, style: titleMedium)` для смысловых блоков на странице (не для карточек со своим заголовком — там по-прежнему `SectionCard`/`_Card`). - **`core/widgets/tile_carousel.dart` → `TileCarousel`** — горизонтальная прокрутка вместо `Wrap` для рядов `StatTile`. На Обзоре заменила `Wrap` у капитала, месяца, портфеля и runway. Не требует общей высоты у детей (`IntrinsicHeight` внутри), поэтому подходит для разноразмерных карточек будущих экранов (счета, холдинги). - **`SectionCard`/`StatTile`** (`core/widgets/section_card.dart`) не переписаны — их новый вид целиком идёт из `cardTheme`. `home_page.dart` перестал дублировать их локальными `_ChartCard`/`_StatTile` и использует общие виджеты, как и остальные вкладки. ## `NavSidebar` (features/shell/nav_sidebar.dart) Показывается только на широком breakpoint (≥1200, `_wideBreakpoint` в `app_shell.dart`). На 600–1200 остаётся штатный `NavigationRail` в свёрнутом виде — в иконку-без-подписи всё равно не помещаются ни капс-заголовки групп, ни счётчики, ни профиль, так что городить туда кастомный виджет незачем; на <600 остаётся штатный `NavigationBar`. Список направлений вынесен в `features/shell/nav_destinations.dart` (`NavDestination`, `navDestinations`, `NavGroup`) — общий для `app_shell.dart` и `nav_sidebar.dart`, было приватным в одном файле. - **Группы** (`NavGroup`): «ОБЗОР» (Обзор/Счета/Портфель/Аналитика — те же четыре, что сейчас основные для нижнего бара), «ОПЕРАЦИИ» (События/Импорт/Потоки/Категории/ Операции/Правила), «СИСТЕМА» (Здоровье/Настройки). - **Активный пункт** — левая полоска 3px `colorScheme.primary` + тонированный фон `primaryContainer` + акцентный цвет текста/иконки. - **Счётчик** — сейчас подключён только у «Здоровье», из `dataQualityProvider` (число строк). У «Цели» счётчика нет: это не отдельный пункт меню, а часть хаба «Аналитика» (`alsoMatches` в `NavDestination`) — добавлять его как отдельный пункт ради счётчика значило бы менять структуру навигации, что не входило в задачу «только визуальный слой». - **Профиль внизу** — аватар с инициалом + email из `meProvider`, «имя» — локальная часть email до `@` (в бэкенде нет отдельного отображаемого имени, только email/id). **Осознанный побочный эффект**: `NavSidebar` вызывает `ref.watch` на `dataQualityProvider` и `meProvider`, которые раньше запрашивались только при открытии Обзора/Здоровья/Настроек. Поскольку `AppShell` смонтирован всё время сессии, эти два запроса теперь улетают на API при каждом заходе в приложение, а не только на этих трёх экранах. `meProvider` не `autoDispose` и уже держится живым где-то ещё — здесь она просто донат к уже открытому запросу. `dataQualityProvider` — `autoDispose`; watch из шелла держит его живым, пока приложение открыто. Для одиночного пользователя на своём VPS это не проблема производительности, но это решение, а не то, о чём просили буквально — стоит знать при следующей правке `core/cache/*`. ## Что не тронуто в этой фазе - `health_page.dart`, `holdings_tab.dart`, `goal_card.dart` — контент не менялся. `SectionHeader` для «Требует внимания»/списка запусков на Здоровье и разбор карточек холдингов/целей под бейдж+прогресс-полоску (как обложки Grimmory) — следующий шаг, тем же способом: один экран, проверка, потом дальше. - Маршруты и провайдеры не менялись, кроме двух новых точек `ref.watch` в `NavSidebar` (см. выше).