# Portal Redesign — Quiet Luxury (Forest extended)

**Дата:** 2026-05-12
**Статус:** Approved (через `superpowers:brainstorming`)
**Целевая ветка реализации:** `plan5-frontend-projects` (текущая) → план реализации создаётся отдельно через `superpowers:writing-plans`
**Источники истины:**

- Бренд: `liderra_v8_handoff/docs/BRANDBOOK_v2.md` (v2 Forest) — hard-override на цвет/шрифт/иконографию/aesthetic (PSR_v1 R6.1)
- Тулчейн: `docs/Tooling_v8_3.md` Прил. Н v1.16
- Правила: `docs/Pravila_raboty_Claude_v1_1.md` v1.11, `docs/Plugin_stack_rules_v1.md` v2.0
- Текущая палитра/тема: `app/resources/js/plugins/vuetify.ts`
- Статус-слаги: `db/schema.sql` (таблица `lead_statuses`, 14 строк)

---

## 1. Цель

Превратить интерфейс Лидерры из функционального Vuetify-дефолта в **узнаваемо «дорогой и технологичный»** портал, **не утомляющий при 8-часовой работе**. Без перестройки IA: 28 существующих view'ов, 3 layout'а, маршруты, store, фичи — все остаются. Меняется только визуальный слой: токены, типографика, shell, components defaults, status-pills, motion.

---

## 2. Эстетическое направление

**Quiet Luxury** (направление A из брейншторминга) с точечным заимствованием Pro Console (направление B) для sidebar/cmdk. Editorial Bento (C) отложен до post-MVP для Reports/Dashboard.

| Принцип | Реализация |
|---|---|
| Whitespace > graphics | 4pt grid, 8 spacing-токенов, page-padding 24–28px, row-height 44px |
| Типографика — главный декор | Inter (variable, axis `opsz` 14..32) + JetBrains Mono с `tnum` для всех цифр/ID/timestamp/телефонов |
| Один акцент на экран | Только primary-CTA + активный статус в основном цвете Teal `#0F6E56` |
| Двухтоновый shell | Sidebar теало-нуар `#012019` + main ivory `#F6F3EC` (уже намечено в Brandbook) |
| Motion = смысл | 7 паттернов, длительность 200–600ms, easing `cubic-bezier(0.16,1,0.3,1)`, обязательный `prefers-reduced-motion` wrapper |
| WCAG AA | Контраст ≥ 4.5 на body, ≥ 3.0 на large; проверяется Pa11y в CI |

---

## 3. Палитра — Forest, расширенная (12 токенов)

Базовая палитра Brandbook v2 + 5 расширений для data viz и тонких поверхностей.

| Токен | HEX | Использование |
|---|---|---|
| `--liderra-teal` | `#0F6E56` | Primary CTA, активные элементы, status `new`/`postponed`, акценты |
| `--liderra-teal-deep` | `#0A5A47` | Hover/pressed state primary, gradient sidebar bottom |
| `--liderra-noir` | `#012019` | Sidebar, pill `archived`, modal overlay base |
| `--liderra-ivory` | `#F6F3EC` | Page background, sidebar text, surface accent |
| `--liderra-surface` | `#FFFFFF` | Cards, table, drawer, input bg |
| `--liderra-muted` | `#6B6356` | Secondary text, captions, placeholders, label uppercase |
| `--liderra-success` | `#2E8B57` | Status `quality`/`won`, delta positive |
| `--liderra-saffron` | `#D9A441` | Status `callback`, channel CALL, warning |
| `--liderra-error` | `#B83A3A` | Status `junk`, delta negative, destructive |
| `--liderra-info` | `#3F7C95` | Status `in_progress`, channel SITE, info banner |
| `--liderra-plum` | `#7A5BA3` | Status `meeting_set`, channel SMS, data viz |
| `--liderra-salmon` | `#CC6E50` | Status `refund`, data viz accent |

**Тонкие поверхности:**

- `--liderra-line: rgba(1,32,25,0.08)` — стандартные разделители
- `--liderra-line-strong: rgba(1,32,25,0.14)` — фокус-границы input, hover-border на чипах

**Vuetify theme:** перечисленные токены маппятся в `liderraForest` ThemeDefinition. Раздел `colors` файла [`app/resources/js/plugins/vuetify.ts`](../../../app/resources/js/plugins/vuetify.ts) расширяется до 12 ключей; CSS vars (`--liderra-*`) объявляются в одном корневом файле `app/resources/css/tokens.css` (новый).

---

## 4. Типографика

**Inter** (variable, `opsz` 14..32) — UI/контент. **JetBrains Mono** (с feature `tnum`) — цифры, ID, timestamp, телефоны, коды.

| Уровень | Размер / Высота строки / Вес / Tracking | Применение |
|---|---|---|
| Label-XS | 11 / 14 / 500 / +0.10em / uppercase | Колонки таблиц, labels форм, breadcrumb-меток |
| Body-S | 13 / 20 / 400 / 0 | Текст таблиц, форм, описаний (база) |
| Body-M | 15 / 22 / 500 / 0 | Активные пункты sidebar, заголовки секций |
| Heading-3 | 17 / 24 / 600 / -0.01em | Карточки, заголовки модалок |
| Heading-2 | 22 / 28 / 600 / -0.015em | Заголовок страницы (Сделки, Биллинг, и т.д.) |
| Heading-1 | 28 / 36 / 600 / -0.02em | Dashboard, Reports |
| Hero-Display | `clamp(30px, 5vw, 48px)` / 600 / -0.025em | Только эмпти-стейты и landing-секции в портале (если будут) |
| Mono-XL | 28 (JetBrains) / -0.02em / 500 / `tnum` | Большие KPI-числа, балансы |
| Mono-S | 11 (JetBrains) / `tnum` | Timestamp, ID `#LDR-2841-X`, телефоны |

**font-feature-settings:** `'tnum' 1, 'cv11' 1` для Inter; `'tnum' 1` для JetBrains Mono.

Vuetify font defaults меняются через `defaults.global.style` + добавление SCSS overrides в `app/resources/css/typography.css` (новый файл).

---

## 5. Сетка и плотность

**База:** 4pt grid. **8 spacing-токенов:** `4 / 8 / 12 / 16 / 24 / 32 / 48 / 64` (CSS vars `--space-1..16`).

**Density:**

- **Comfortable (default):** row-height 44px, card-padding 16–18px, button-height 38px.
- **Compact (toggle):** row-height 36px, card-padding 12px, button-height 32px. Доступен в Сделках/Канбане/Отчётах через `density-toggle` в filterbar; сохраняется в `localStorage['liderra:density']`.

**Контент-ширина:** max 1440px, центрируется в main.

**Радиусы:** 6 (мелкие чипы) / 8 (кнопки, inputs, чипы) / 10 (стенды на ivory) / 12 (карточки, таблицы) / 14 (drawer/modal) / 999 (pills, avatars).

**Тени:** двухслойные ambient + key.

- `--shadow-1: 0 1px 2px rgba(1,32,25,0.04)` — карточки/таблицы по умолчанию
- `--shadow-2: 0 4px 12px rgba(1,32,25,0.06), 0 1px 2px rgba(1,32,25,0.04)` — hover lift на карточках
- `--shadow-3: 0 12px 28px rgba(1,32,25,0.10)` — модалки, всплывающие меню
- `--shadow-4: 0 24px 48px rgba(1,32,25,0.16)` — drawer, command palette

---

## 6. Layout — двухтоновый shell

| Зона | Цвет | Размер | Содержание |
|---|---|---|---|
| Sidebar | gradient `#012019 → #04261E` | 232px шир. | Brand + avatar, ⌘K stub, nav-groups (Работа / Аналитика / Профиль), nav-items с badge-счётчиками |
| Topbar | ivory | 56px выс., нижняя граница `--line` | Breadcrumb (JetBrains Mono Label-XS) + utility-buttons (notifications, user-menu) |
| Main | ivory | flex-1 | Page-head + KPI strip (если есть) + filter bar + content (table/cards/canvas) |

**Sidebar — детали:**

- Nav-item active: фон `rgba(15,110,86,0.22)`, текст `#F6F3EC`, левая вертикальная полоса 2px Teal `#0F6E56` (motion #7 — `transform: scaleY(0→1)` 250ms ease-out при смене раздела).
- Badge: `font-family: JetBrains Mono`, `font-size: 10px`, `background: rgba(255,255,255,0.08)`.
- Group-label: `font-size: 10px`, `text-transform: uppercase`, `letter-spacing: 0.14em`, `color: #6B7470`.
- ⌘K stub: `background: rgba(255,255,255,0.06)`, hover до `rgba(255,255,255,0.10)`. В Iteration 1 это stub без real-search; в Iteration 2 — Cmdk.

**AppLayout/AuthLayout/AdminLayout:** архитектура без изменений. AdminLayout получает теало-нуар sidebar тоже, но nav-group метка «SaaS-уровень»; AuthLayout без sidebar.

---

## 7. Компоненты — Vuetify defaults override

Глобальный override через `createVuetify({ defaults: { ... } })` + кастомные SCSS-фрагменты, где Vuetify не покрывает.

### 7.1. Buttons (3 уровня)

| Уровень | Variant | Цвет | Hover |
|---|---|---|---|
| Primary | `flat` | bg Teal, text white | bg Teal-deep + `translateY(-1px)` + Teal-glow shadow |
| Secondary | `outlined` | text default, border `--line` | border `--line-strong`, bg `rgba(1,32,25,0.04)` |
| Ghost | `text` | text Muted | text default, bg `rgba(1,32,25,0.04)` |

Height: 38px comfortable / 32px compact. Padding: 14px L/R. Radius: 8.

### 7.2. Inputs (VTextField, VTextarea)

- Variant: `outlined`
- Density: `comfortable` default
- Border: `--line` resting, `--liderra-teal` focus + 3px focus-ring `rgba(15,110,86,0.10)`
- Radius: 8
- Font-size: 13 (Body-S)
- Error: border `--liderra-error`, helper-text Error
- Success: border `--liderra-success` (только для validated state, не сразу при mount)

### 7.3. Cards (VCard)

- Default: `variant: 'flat'`, `border: 1px solid --line`, `border-radius: 12`, `padding: 16`
- Hover (если интерактивная): `translateY(-2px)` 200ms ease-out + `--shadow-2`

### 7.4. Chips & Pills (VChip)

- Status pill: `border-radius: 999`, `padding: 3px 9px`, `font-size: 11`, `font-weight: 500`. Цветовая карта — см. §8.
- Filter chip (в filterbar): `border-radius: 8`, `padding: 7px 12px`, `font-size: 12`, `background: --liderra-surface`, `border: 1px solid --line`. Active: Teal border + Teal-tinted bg.

### 7.5. Tables (VDataTable)

- Header: `height: 38px`, `background: rgba(1,32,25,0.025)`, Label-XS uppercase, `letter-spacing: 0.12em`.
- Row: `height: 44/36 px` (density), `border-bottom: 1px solid --line`.
- Hover: `background: rgba(15,110,86,0.03)`, more-actions menu opacity 0→1 200ms.
- Selected: `background: rgba(15,110,86,0.06)` + checkbox Teal-filled.
- Stagger animation на mount (motion #2, см. §9).

### 7.6. Dialogs & Drawers

- VDialog: `border-radius: 14`, `--shadow-3`, overlay `rgba(1,32,25,0.32)`. Mount-анимация: scale 0.94→1 + 8px translateY, 350ms spring `cubic-bezier(0.34,1.4,0.64,1)`.
- VNavigationDrawer: `border-radius: 14 0 0 14` если right-side, `--shadow-4`.

### 7.7. Toasts (VSnackbar)

- Background: `--liderra-noir`, text `--liderra-ivory`, `border-radius: 10`, `--shadow-3`. Mount: slide-up 400ms cubic-bezier.
- Variants: default (Teal dot), success (Success dot), error (Error dot), warning (Saffron dot).

---

## 8. 14 статус-пилюль (точные slugs из `db/schema.sql`)

| Slug | bg | text | Особенность |
|---|---|---|---|
| `new` | `rgba(15,110,86,0.12)` | `#0F6E56` | — |
| `in_progress` | `rgba(63,124,149,0.12)` | `#3F7C95` | — |
| `callback` | `rgba(217,164,65,0.18)` | `#A07820` | — |
| `quality` | `rgba(46,139,87,0.15)` | `#2E8B57` | — |
| `meeting_set` | `rgba(122,91,163,0.15)` | `#7A5BA3` | — |
| `won` | `rgba(46,139,87,0.22)` | `#1F6940` | `font-weight: 600` |
| `refund` | `rgba(204,110,80,0.15)` | `#B0563D` | — |
| `duplicate` | `rgba(1,32,25,0.08)` | `#3A3A3A` | — |
| `junk` | `rgba(184,58,58,0.10)` | `#B83A3A` | — |
| `no_answer` | `rgba(107,99,86,0.15)` | `#6B6356` | — |
| `cancelled` | `rgba(107,99,86,0.18)` | `#6B6356` | `text-decoration: line-through` |
| `closed` | `rgba(1,32,25,0.10)` | `#3A3A3A` | — |
| `postponed` | `rgba(15,110,86,0.06)` | `#6B6356` | — |
| `archived` | `#012019` | `#E8E2D4` | Контрастный inverse |

Маппинг хранится в `app/resources/js/composables/useStatusPill.ts` (новый): функция возвращает `{ bg, color, fontWeight?, textDecoration? }` по slug; используется в `<StatusPill :slug="deal.status" />` (новый компонент, replace text-only chip).

---

## 9. Motion — 7 паттернов

Все паттерны — CSS `@keyframes` + Vue native `<Transition>` + View Transitions API + `prefers-reduced-motion` global wrapper. Без motion-runtime библиотек на Iteration 1 (mot-v добавляется в Iteration 2 только если потребуется FLIP для канбана).

| # | Имя | Длительность | Easing | Триггер | Применение |
|---|---|---|---|---|---|
| 1 | **Count-up KPI** | 600ms | `cubic-bezier(0.16,1,0.3,1)` (ease-out) | onMount + on value-change >5% | Dashboard, Reports, KPI strip |
| 2 | **Stagger list** | 400ms + stagger 50ms | `cubic-bezier(0.16,1,0.3,1)` | onMount only (не на сорт/пагинацию) | Сделки, Reports таблицы |
| 3 | **Status pill morph** | 300ms | `cubic-bezier(0.34,1.56,0.64,1)` (bounce on target) | onChange `status` prop | StatusPill компонент |
| 4 | **Hover lift** | 200ms | `cubic-bezier(0.16,1,0.3,1)` | `:hover` | Карточки, строки таблиц, primary-кнопки |
| 5 | **Skeleton shimmer** | 1400ms loop | `linear` | На время `pending` запроса | Все async-зоны (deals list, dashboard KPI) |
| 6 | **Page transition** | 280ms | `cubic-bezier(0.16,1,0.3,1)` | Vue Router beforeEach | Router-view (с fallback на CSS fade-up) |
| 7 | **Sidebar active marker** | 250ms | `cubic-bezier(0.16,1,0.3,1)` | onChange active nav-item | Sidebar nav-item active state |

**Глобальный wrapper** (`app/resources/css/motion.css`):

```css
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
```

**Count-up имплементация:** новый composable `useCountUp(targetRef, options)` использует `requestAnimationFrame` для 600ms tween от текущего до target value; учитывает `prefers-reduced-motion` (мгновенный set без анимации).

**Page transition:** через CSS View Transitions API когда доступно (Chrome 111+, Safari 18+), CSS-fallback на `@keyframes fadeup` для остальных. Активируется через router-meta `transition: 'page'`.

---

## 10. Charts — минималистичная data viz

- Тип по умолчанию: **area** (линия + градиент-заливка `linear-gradient(180deg, --liderra-teal alpha 0.15 → transparent)`) для трендов; **horizontal bar** для рейтингов.
- **Нет grid-lines.** Только baseline по X.
- Tooltip: card `border-radius: 10`, `background: --liderra-noir`, `color: --liderra-ivory`, `padding: 10 14`, `font-family: JetBrains Mono`, `font-size: 12`, `--shadow-3`.
- Palette для категорий: Teal → Saffron → Plum → Salmon → Info (Sea-blue). Никогда не использовать Error/Success в чартах — они для семантики.
- Library: `chart.js` (уже в стеке) либо `apexcharts` — решение в плане реализации (после оценки текущей зависимости).

---

## 11. Scope первой итерации vs Iteration 2

### Iteration 1 (текущая работа)

1. CSS-токены (`tokens.css`) — 12 цветов + 8 spacing + 6 радиусов + 4 shadow
2. Vuetify theme — расширение `liderraForest` до 12 ключей + global defaults для VBtn/VTextField/VCard/VChip/VDataTable
3. Типографика — `typography.css` + подключение Inter variable (`opsz` axis) + JetBrains Mono `tnum`
4. AppLayout — двухтоновый shell, sidebar с ⌘K stub, nav-groups, badge-счётчики, active marker (motion #7)
5. StatusPill компонент + `useStatusPill` composable (14 slugs)
6. Filter chip + density toggle компоненты
7. Motion-инфраструктура: `motion.css` + `useCountUp` composable + Vue Transition wrappers для page/list
8. Применение к 4 ключевым view'ам: Dashboard, Сделки (DealsView), Канбан, Проекты (ProjectsView с Plan 5)

### Iteration 2 (post-MVP, после стабилизации I1)

1. ⌘K command palette (real fuzzy-search, маршруты + actions)
2. Применение к остальным view'ам (Reports, Billing, Admin/*, Settings/*, Reminders)
3. Drag-feedback в Канбане (опционально motion-v FLIP)
4. Иллюстрации empty states (SVG)
5. Onboarding tour
6. Расширенный data viz (heatmap, sparkline в строках таблиц)

---

## 12. Файлы — план изменений (high-level)

Новые:

- `app/resources/css/tokens.css` — CSS vars (цвета, spacing, radii, shadows)
- `app/resources/css/typography.css` — font-family, font-feature-settings, font-imports
- `app/resources/css/motion.css` — keyframes + reduced-motion wrapper
- `app/resources/js/composables/useStatusPill.ts` — slug → стилевые токены
- `app/resources/js/composables/useCountUp.ts` — RAF tween для KPI
- `app/resources/js/components/ui/StatusPill.vue`
- `app/resources/js/components/ui/FilterChip.vue`
- `app/resources/js/components/ui/DensityToggle.vue`
- `app/resources/js/components/ui/Kbd.vue` — клавишные стабы (для ⌘K, Esc)

Изменяемые:

- `app/resources/js/plugins/vuetify.ts` — расширить theme + defaults
- `app/resources/js/layouts/AppLayout.vue` — двухтоновый shell, новый sidebar, ⌘K stub, breadcrumb
- `app/resources/js/components/layout/AppSidebar.vue` — nav-groups, badges, active marker (motion #7)
- `app/resources/js/views/DashboardView.vue` — KPI count-up (motion #1), live pulse
- `app/resources/js/views/DealsView.vue` — filterbar, density toggle, table-stagger (motion #2), hover lift (motion #4)
- `app/resources/js/views/KanbanView.vue` — pill цвета, hover lift
- `app/resources/js/views/ProjectsView.vue` — карточки в новом стиле

---

## 13. Acceptance criteria (Iteration 1)

- [ ] 12 CSS-токенов определены, все компоненты используют только их (никакого хардкода цветов в `.vue`)
- [ ] Inter variable + JetBrains Mono `tnum` подключены, все цифры/ID/timestamp на JetBrains Mono
- [ ] AppLayout: sidebar теало-нуар + main ivory, ⌘K stub, nav-groups, badge-счётчики, active marker анимируется
- [ ] StatusPill применён ко всем 14 slugs в Сделках, Канбане, Dashboard
- [ ] Все 7 motion-паттернов работают; `prefers-reduced-motion` отключает их полностью (проверка в DevTools)
- [ ] Density toggle сохраняется в localStorage и применяется к row-height/padding/button-height
- [ ] Pa11y в CI — 0 violations на 4 ключевых view'ах (Dashboard, Deals, Kanban, Projects)
- [ ] Vitest unit-тесты на `useStatusPill`, `useCountUp`, `StatusPill.vue` (snapshot + 14 slugs)
- [ ] Histoire stories для всех новых ui-компонентов
- [ ] Lighthouse Performance ≥ 90 (mobile profile)

---

## 14. Out of scope

- Перестройка IA (маршруты, store, deals.* модель, формы добавления сделки).
- Backend changes — все 28 ProjectController/DealController/* endpoints остаются.
- Schema changes — `lead_statuses` slugs и `projects.signal_type` enum значения не меняются.
- Замена иконографии — остаёмся на Lucide (уже в стеке).
- Темизация (light/dark toggle) — отложено в Iteration 2.
- Cmdk real-search (только stub на I1).
- Изменение Plan 5 frontend кода вне UI/UX layer (поведение `ProjectsView`, `projectsStore`, dialog flow — без изменений).

---

## 15. Iteration-1 Known Issues / Iteration-2 Backlog

Captured during Subagent-Driven Development of the Iteration 1 plan (commits `38b985a..<final>`).

### Functional regressions / gaps to address

1. **Slug reconciliation (Task 5 finding).** Spec §8 defines 14 design-language slugs (`new`, `in_progress`, `callback`, ...), but `db/schema.sql` `lead_statuses` seed uses different slugs (`viewed`, `worked`, `base`, `missed`, `negotiations`, `waiting_payment`, ...). `StatusPill` falls back to neutral style for unknown slugs — in production, all real deals will hit the fallback. **Resolution path (Iteration 2):** either (a) add a slug-translation map in `useStatusPill`, OR (b) rename `lead_statuses` slugs to match spec, OR (c) update `useStatusPill` STYLES keys to real schema slugs.

2. **AppSidebar mobile drawer collapse regression (Task 12 finding).** Old `<v-navigation-drawer>` was replaced with plain `<aside>` + `data-open` attribute. `drawerOpen` model still wired, but there's no CSS rule to hide/translate the sidebar when `data-open="false"`. Mobile drawer toggle button has no visual effect.
   **Resolution path:** add `.ld-sidebar[data-open="false"] { transform: translateX(-100%); transition: transform 280ms; }` OR restore a thin Vuetify wrapper for drawer mechanics.

3. **DealsView filter chip stubs (Task 15 finding).** 3 redesign FilterChips (Статус/Проект/Менеджер) coexist with existing `DealsFilters` (VSelects). Chip click handlers are `console.log` placeholders. **Resolution:** either wire chips as popover triggers for VSelects, OR remove redesign chiprow if VSelects suffice.

4. **DealsView status-legend strip (Task 15 finding).** 4 sample StatusPills rendered as visual legend; partially driven by test design (VDataTable stub). **Resolution:** decide whether to keep as UX-meaningful palette legend OR move behind a "show legend" toggle.

5. **KanbanCard hover overlap (Task 16 finding).** Existing `.kanban-card:hover { transform: translateY(-1px) }` may double up with `.ld-hover-lift:hover { translateY(-2px) }`. **Resolution:** remove inline hover from KanbanCard.vue, let `.ld-hover-lift` own it.

6. **Token discipline leak — `useStatusPill.ts` hardcodes 14 hex/rgba values (final review finding).** STYLES table duplicates Forest palette values (`#0F6E56`, `rgba(15,110,86,0.12)`, etc.) instead of referencing `tokens.css` (.ts files can't read CSS vars at runtime, but the duplication risks token drift). **Resolution:** add comment cross-referencing tokens.css OR generate STYLES from a shared TS palette module.

7. **AppSidebar hardcodes sidebar dark-mode palette (final review finding).** `#9B9484`, `#6B7470`, `#B8B0A0`, `#04261E`, `#E8E2D4` are inlined in `AppSidebar.vue` scoped CSS — sidebar-specific dark tokens missing from `tokens.css`. **Resolution:** add `--liderra-sidebar-text`, `--liderra-sidebar-eyebrow`, `--liderra-sidebar-bg-bottom` etc. to tokens.css, OR document as "sidebar-local" with a `/* sidebar dark palette */` comment block.

8. **DealsView `.page-stats .sep { color: #92907b }` one-off hex (final review finding).** Should be `var(--liderra-muted)` (#6B6356) or a new `--liderra-muted-soft` token.

9. **Stagger animation breaks past row 11 (final review finding).** `motion.css` `.ld-stagger-row:nth-child(N)` rules cover rows 1–10 only; row 11+ has no animation-delay (instant appearance). Acceptable for pagination ≤10 rows; consider CSS `:nth-child(n+11)` catch-all or JS-driven approach if pagination size increases.

10. **`useStatusPill` naming suggests reactivity (final review finding).** Function is pure (returns snapshot, not reactive). The `use*` prefix is the Vue Composable convention which typically returns refs. **Resolution:** rename to `getStatusPillStyle` OR add JSDoc clarifying it's a pure mapping function.

### Test infrastructure / coverage gaps

1. **Sidebar marker test regex (Task 12 finding).** `KanbanRedesign.spec.ts` regex `/ld-mono|nav-item__badge/` is overly permissive — `nav-item__badge` always matches the selector even if `ld-mono` were dropped. Tighten to `/\bld-mono\b/`.

2. **No prefers-reduced-motion test for `ld-marker-grow` keyframe (Task 12 finding).** Global motion.css wrapper does target `*::before` selectors, so it IS caught — but no dedicated test asserts the override applies.

### Skipped in Iteration 1 per plan scope

1. **Pa11y CI sweep on 4 ключевых view'ах** — current Pa11y config covers static HTML only; needs SPA-mode setup (dev server + URL targets). Iteration 2 task.

2. **Lighthouse Performance ≥ 90 (mobile)** — not validated in I1.
