--- paths: - frontend/src/components/** - frontend/src/app/**/page.tsx - frontend/src/app/**/layout.tsx --- # UI components & data-viz conventions — GenDesign > Часть набора `ui-*.md`. Tokens см. `ui-tokens.md`, microcopy/copy — `ui-microcopy.md`. ## Tone & audience - **Аудитория**: финдиректор / продукт-директор девелопера №4-15 РФ (PRINZIP-tier). НЕ архитектор, НЕ ритейл. - **Tone of voice**: DOM.РФ × CoStar × Bloomberg-density. «Государственная аналитика, которая считает за тебя». НЕ стартап-AI-маркетинг. - **Информационная плотность > красивости.** Цифры читаются с проектора с 5 метров. ## Component conventions ### KpiCard - Использовать `` из `components/analytics/KpiCard.tsx` — НЕ городить inline. - KPI row: `display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px;` — НЕ flex+wrap (даёт ровные ряды). - 3-4 KPI выше fold — максимум. Больше → под аккордеон «Детали». - `delta.positive: null` (нейтральный) — для метрик где «больше/меньше» не имеет валентности (например, средняя цена). ### Section - Заворачивать каждый блок графика/таблицы в `
`. - `subtitle` — одно предложение объясняющее «откуда данные / что значит цифра», не маркетинг. - `right` slot — для controls (filter dropdown, export button, slicer chips). ### Headline-bar (тёмная плашка-вердикт) - Pattern: `--bg-headline` фон, `--fg-on-dark` текст, padding 14/18, radius 12. - **Одно предложение-ответ JTBD**: «В районе X класс Y · S м² → mix А · срок B мес · выручка C ₽». - Под основным текстом — separator + caveats / context (ИЖК ставка, POI, дата snapshot). - Размещать выше всех KPI-карточек. ### Badge - Использовать ``. НЕ inline-span с hex. - Если компонента нет — создать в `components/ui/Badge.tsx`, не дублировать stylesheet'ы по pages. ### Drawer (drill-down правым слайдером) - Pattern для `/analytics/developers` детализации. НЕ навигировать на отдельную страницу при клике на строку таблицы — открывать right drawer (CoStar pattern). - Width 480px desktop, full-screen на phone. ## Data-viz (ECharts) rules - **Ось Y ВСЕГДА с подписью** (`yAxis.name`). Без подписи — это нарушение review. - **Tooltip ВСЕГДА с единицей измерения**: «1 240 ₽/м²», «73%», «28 мес». - **Цвета серий** — только из `--viz-1..5` в указанном порядке. - **Forecast / prediction** — пунктирная линия + band ±p25/p75 (HouseCanary). Не показывать точку без интервала. - **Никаких 3D charts**, никаких pie с >5 сегментов (заменить на bar). Treemap — OK. - Lazy-mount тяжёлых charts через `dynamic(() => import(...), { ssr: false })` (см. `objects/[id]/page.tsx`). ## Iconography - **Lucide React only** — `lucide-react` пакет. Размеры 16 / 20 / 24px, stroke 1.5px. - **НЕ emoji в UI** — 🚀 ❄ 💼 📊 → ``, ``, ``, ``. - Trend-glyph в KPI — Unicode arrow `↑ ↓ →` или Lucide, НЕ emoji. - Иконка-без-текста → `aria-label`. ## Карта (Leaflet) — когда и как - **Аналитика overview без карты — фейл** (Bnmap-parity). На `/analytics` нужна секция «Карта рынка» с точками новостроек DOM.РФ, цвет по `sold%`. - Leaflet через `dynamic(..., { ssr: false })` — НИКОГДА не импортировать в server component. - Высота карты: 360px (drill-in), 480px (overview), full-screen toggle FAB на phone. - Layers: OSM base + наш geojson. Не подключать платные tile-серверы без согласования. ## Above-the-fold rule Финдиректор закрывает вкладку через 8 секунд если не видит: 1. Что это за страница (h1 + один subtitle). 2. **Один вердикт** одним предложением (headline-bar). 3. **3 главных числа** (KPI row). Всё остальное (sliders, deep tables, comparables) — **под аккордеон** «Подробнее». По умолчанию закрыто. ## Responsive (см. issue #66) - 3 контекста: phone `<768`, tablet `768-1280`, desktop `>1280`. НЕ Tailwind sm/md/lg напрямую. - Phone: nav-tabs → horizontal scroll + snap, НЕ hamburger. - Phone: KPI grid → 1 col full-width. - Chart высоты фиксированные, `ResizeObserver` для ECharts. - НЕ делать PWA / offline. Аудитория с десктопа. ## Accessibility (минимум, не максимум) - Контраст AA для body (4.5:1). Hex-token подобраны под AA на белом / slate-900. - `aria-label` для icon-only buttons. - Focus ring видимый (НЕ `outline: none` без замены). - Кликабельный элемент — `