diff --git a/apps/web/src/components/SectionHeading.astro b/apps/web/src/components/SectionHeading.astro new file mode 100644 index 0000000..9a21c51 --- /dev/null +++ b/apps/web/src/components/SectionHeading.astro @@ -0,0 +1,18 @@ +--- +const { eyebrow, title, description, count, id, editorial = false } = Astro.props as { + eyebrow?: string; + title: string; + description?: string; + count?: string; + id?: string; + editorial?: boolean; +}; +--- +
+
+ {eyebrow && {eyebrow}} +

{title}

+ {description &&

{description}

} +
+ {count && {count}} +
diff --git a/apps/web/src/components/SignalFeed.astro b/apps/web/src/components/SignalFeed.astro index c26e0d6..bfca633 100644 --- a/apps/web/src/components/SignalFeed.astro +++ b/apps/web/src/components/SignalFeed.astro @@ -1,5 +1,6 @@ --- import SourceBadge from "./SourceBadge.astro"; +import SectionHeading from "./SectionHeading.astro"; import { ago, kg, type PublicObservation } from "../lib/api"; const { signals } = Astro.props as { signals: PublicObservation[] }; const groups = [...signals.reduce((map, signal) => { @@ -17,7 +18,7 @@ const groups = [...signals.reduce((map, signal) => { }, new Map()).values()]; ---
-
Сырые данные источников

Полевые сигналы

Показываем сразу, даже если часть полей отсутствует. Эти карточки не участвуют в расчёте активности до полного подтверждения.

{signals.length} сигналов · {groups.length} сюжетов
+
{groups.map(signal => { const provenance = [...new Map(signal.observations.map(item => [`${item.source_system}|${item.source_url}`, item])).values()]; return
diff --git a/apps/web/src/pages/records.astro b/apps/web/src/pages/records.astro index 1a4bcca..9c4e5be 100644 --- a/apps/web/src/pages/records.astro +++ b/apps/web/src/pages/records.astro @@ -1,6 +1,7 @@ --- import Layout from "../layouts/Layout.astro"; import SourceBadge from "../components/SourceBadge.astro"; +import SectionHeading from "../components/SectionHeading.astro"; import { api, kg, type DictionaryItem, type ImportRun, type OfficialRecord, type PaginatedOfficialRecord } from "../lib/api"; const params = Astro.url.searchParams; const fish = params.get("fish") ?? ""; @@ -21,7 +22,7 @@ const last = runs[0];
Публичные данные RF4

Официальные
рекорды

{last ? `Импорт: ${last.status}` : "Импорт ещё не запускался"}{last?.finished_at && {new Date(last.finished_at).toLocaleString("ru-RU")} · {last.rows_seen} строк}
{(fish || waterbody) && Сбросить}
-
Официальный источник

Последние записи

{items.length} из {totalRecords} записей
+
{unavailable &&

Источник временно недоступен

} {!unavailable && items.length &&
РыбаВесВодоёмПриманкаИгрокДата и источник
{items.map(record =>
{record.fish}{kg(record.weight_g)}{record.waterbody}{record.bait ?? "—"}{record.player_name ?? "—"}
)}
} {!unavailable && items.length && offset + items.length < totalRecords && { const p = new URLSearchParams(params); p.delete("offset"); p.set("offset", String(offset + items.length)); return p.toString(); })()}`}>Показать ещё {offset + items.length} из {totalRecords}} diff --git a/apps/web/src/styles/global.css b/apps/web/src/styles/global.css index 43c9755..9da1ad2 100644 --- a/apps/web/src/styles/global.css +++ b/apps/web/src/styles/global.css @@ -44,3 +44,6 @@ footer{min-height:118px;background:var(--deep);color:#dbe4df;padding:28px max(32 /* Brand grammar: radar belongs to coordinates; incomplete signals use a field-note edge, not decorative ripples. */ .detail-card:before,.signal-card:after{content:none}.signal-card{border-top:3px solid #c9ad55} + +/* Shared editorial signature for data sections. */ +.section-heading{gap:24px}.section-heading>div{min-width:0}.section-heading p{max-width:700px;margin:10px 0 0;color:var(--text-muted);line-height:1.55}.section-heading--editorial{padding-top:68px;border-top:1px solid var(--border);margin-bottom:25px}.section-heading--editorial h2{font-size:46px;line-height:.95}.section-heading--editorial .result-count{align-self:end}@media(max-width:720px){.section-heading--editorial{display:block;padding-top:45px}.section-heading--editorial .result-count{display:inline-block;margin-top:14px}} diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index fc1401e..0e0c35a 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -23,7 +23,7 @@ - [x] **B01 · Иерархия имени.** RF4 Spotter — единое имя продукта в UI, metadata, manifest и документации; «Ни хвоста, ни чешуи» — поддерживающий слоган. Обновлены wordmark в header/footer и подпись выпуска. - [x] **B02 · Дизайн-токены.** Добавлены семантические роли поверхностей, текста, границ, фокуса, success/warning/danger и всех источников. На токены переведены паспорта данных, status-карточки, skeleton и focus; контрастные пары зафиксированы в brand system. - [x] **B03 · Графическая грамматика.** Мотивы закреплены за функциями: крючок — бренд, поплавок — активность/ожидание, леска — время, радар — координаты, силуэт — сущность рыбы. Убраны ложная рябь неполных сигналов и дублирующие радар круги карточки лидера; неполнота теперь подчёркнута спокойной полевой меткой. -- [ ] **B04 · Компонентная подпись.** Унифицировать заголовки секций, CTA, карточки, плашки источников, состояния качества и пустые экраны на публичных и admin-страницах. +- [ ] **B04 · Компонентная подпись.** В работе: добавлен общий `SectionHeading` и применён к рекордам и полевым сигналам; далее унифицировать hero, CTA и системные состояния публичных/admin-страниц. - [ ] **B05 · Motion-система.** Оставить только смысловые микроанимации поклёвки/пульса/перехода, задать общие длительности и полностью поддержать reduced motion. - [ ] **B06 · Brand QA.** Проверить favicon, maskable icons, manifest, OG 1200×630, светлый/тёмный фон, 16/32 px и mobile/desktop screenshots; записать правила в [brand-system.md](brand-system.md). diff --git a/docs/brand-system.md b/docs/brand-system.md index 101cb52..8806ac5 100644 --- a/docs/brand-system.md +++ b/docs/brand-system.md @@ -40,6 +40,10 @@ Неполные данные получают тёплую верхнюю полевую метку и текстовый статус. Ошибка не изображается «мёртвым поплавком»: она передаётся системным danger-состоянием и понятным действием пользователя. +## Компонентная подпись + +Секции данных используют `SectionHeading`: короткий uppercase eyebrow задаёт контекст, антиквенный заголовок называет сущность, необязательное описание объясняет правила, а счётчик всегда расположен справа или под текстом на мобильном. Так число результатов не конкурирует с названием раздела и одинаково читается в рекордах и полевых сигналах. + ## План внедрения Рабочие статусы B01–B06 ведутся в [ROADMAP.md](ROADMAP.md). После каждого пакета выполняются Astro check/build и адресная визуальная проверка 320/390/768/1280 px. Новые bitmap-иллюстрации не добавляются, пока существующие SVG/CSS-мотивы решают задачу без лишнего веса.