diff --git a/apps/web/src/components/PageHero.astro b/apps/web/src/components/PageHero.astro new file mode 100644 index 0000000..51d7055 --- /dev/null +++ b/apps/web/src/components/PageHero.astro @@ -0,0 +1,8 @@ +--- +const { eyebrow, title, description } = Astro.props as { eyebrow: string; title: string; description?: string }; +--- +
+ {eyebrow} +

{title}

+ {description &&

{description}

} +
diff --git a/apps/web/src/pages/fish/[slug].astro b/apps/web/src/pages/fish/[slug].astro index 22dd218..4fc8a1c 100644 --- a/apps/web/src/pages/fish/[slug].astro +++ b/apps/web/src/pages/fish/[slug].astro @@ -1,5 +1,6 @@ --- import ActivityCard from "../../components/ActivityCard.astro"; +import PageHero from "../../components/PageHero.astro"; import Layout from "../../layouts/Layout.astro"; import { api, plural, type Activity, type DictionaryItem, type PaginatedActivity } from "../../lib/api"; const { slug } = Astro.params; @@ -20,6 +21,6 @@ const schema = fish ? { "@context":"https://schema.org", "@type":"CollectionPage --- ← Все виды рыб -
Свежие данные за 72 часа

{fish?.name_ru ?? "Рыба не найдена"}

{fish &&

{items.length} {plural(items.length,["активная точка","активные точки","активных точек"])} на {waters.length} {plural(waters.length,["водоёме","водоёмах","водоёмах"])}.

}
+ {unavailable ?

Данные временно недоступны

: !fish ?

Такой рыбы нет в справочнике

Открыть каталог
:
{items.length ? items.map(item => ) :

Свежих точек пока нет

Проверьте позже или посмотрите полевые сигналы на главной.

}
}
diff --git a/apps/web/src/pages/fish/index.astro b/apps/web/src/pages/fish/index.astro index eec099c..4fa548c 100644 --- a/apps/web/src/pages/fish/index.astro +++ b/apps/web/src/pages/fish/index.astro @@ -1,6 +1,7 @@ --- import Layout from "../../layouts/Layout.astro"; import FishSilhouette from "../../components/FishSilhouette.astro"; +import PageHero from "../../components/PageHero.astro"; import { api, type DictionaryItem } from "../../lib/api"; let fishes: DictionaryItem[] = [], unavailable = false; try { fishes = await api("/api/v1/fishes?limit=500"); } catch { unavailable = true; } @@ -11,6 +12,6 @@ if (unavailable) { } --- -
Справочник RF4

Рыбы

Выберите вид, чтобы увидеть свежие подтверждённые точки и полевые сигналы.

+ {unavailable ?

Каталог временно недоступен

: }
diff --git a/apps/web/src/pages/status.astro b/apps/web/src/pages/status.astro index 4162a04..f0723a0 100644 --- a/apps/web/src/pages/status.astro +++ b/apps/web/src/pages/status.astro @@ -2,13 +2,14 @@ import Layout from "../layouts/Layout.astro"; import SourceBadge from "../components/SourceBadge.astro"; import DataLegend from "../components/DataLegend.astro"; +import PageHero from "../components/PageHero.astro"; import { ago, api, plural, type SourceStatus } from "../lib/api"; let sources: SourceStatus[] = [], unavailable = false; try { sources = await api("/api/v1/source-status"); } catch { unavailable = true; } const labels = { healthy:"Актуален", stale:"Данные устарели", temporarily_limited:"Временная пауза", source_changed:"Источник изменился", waiting:"Ожидает запуска", disabled:"Выключен" }; --- -
Прозрачность данных

Статус источников

Пауза или изменение страницы не удаляют уже опубликованные данные.

+ {unavailable ?

Статус временно недоступен

:
{sources.map(source =>
{labels[source.status]}

{source.name}

{source.observations} {plural(source.observations,["наблюдение","наблюдения","наблюдений"])}

{source.last_success_at ? `Последнее успешное обновление ${ago(source.last_success_at)}` : "Успешных запусков пока нет"}

)}
}
diff --git a/apps/web/src/pages/waterbodies/[slug].astro b/apps/web/src/pages/waterbodies/[slug].astro index ca5b753..c38618e 100644 --- a/apps/web/src/pages/waterbodies/[slug].astro +++ b/apps/web/src/pages/waterbodies/[slug].astro @@ -1,5 +1,6 @@ --- import ActivityCard from "../../components/ActivityCard.astro"; +import PageHero from "../../components/PageHero.astro"; import Layout from "../../layouts/Layout.astro"; import { api, plural, type Activity, type DictionaryItem, type PaginatedActivity } from "../../lib/api"; const { slug } = Astro.params; @@ -20,6 +21,6 @@ const schema = water ? { "@context":"https://schema.org", "@type":"CollectionPag --- ← Все водоёмы -
Свежие данные за 72 часа

{water?.name_ru ?? "Водоём не найден"}

{water &&

{items.length} {plural(items.length,["активная точка","активные точки","активных точек"])} для {fishes.length} {plural(fishes.length,["вида рыбы","видов рыб","видов рыб"])}.

}
+ {unavailable ?

Данные временно недоступны

: !water ?

Такого водоёма нет в справочнике

Открыть каталог
:
{items.length ? items.map(item => ) :

Свежих точек пока нет

Проверьте позже или посмотрите полевые сигналы на главной.

}
}
diff --git a/apps/web/src/pages/waterbodies/[slug]/[fish].astro b/apps/web/src/pages/waterbodies/[slug]/[fish].astro index 2ee256a..61fd741 100644 --- a/apps/web/src/pages/waterbodies/[slug]/[fish].astro +++ b/apps/web/src/pages/waterbodies/[slug]/[fish].astro @@ -1,5 +1,6 @@ --- import ActivityCard from "../../../components/ActivityCard.astro"; +import PageHero from "../../../components/PageHero.astro"; import Layout from "../../../layouts/Layout.astro"; import { api, plural, type Activity, type DictionaryItem, type PaginatedActivity } from "../../../lib/api"; const { slug, fish: fishSlug } = Astro.params; @@ -20,6 +21,6 @@ const schema = valid ? { "@type":"CollectionPage", name:`${fish!.name_ru} на $ --- ← К водоёму -
{water?.name_ru ?? "Каталог RF4"}

{fish?.name_ru ?? "Данные не найдены"}

{valid &&

{items.length} {plural(items.length,["свежая точка","свежие точки","свежих точек"])} за последние 72 часа.

}
+ {unavailable ?

Данные временно недоступны

: !valid ?

Такого сочетания нет в справочнике

Вернуться на главную
:
{items.length ? items.map(item => ) :

Свежих точек пока нет

Данные появятся после новых подтверждённых наблюдений.

}
}
diff --git a/apps/web/src/pages/waterbodies/index.astro b/apps/web/src/pages/waterbodies/index.astro index 622c0f8..804ab2d 100644 --- a/apps/web/src/pages/waterbodies/index.astro +++ b/apps/web/src/pages/waterbodies/index.astro @@ -1,5 +1,6 @@ --- import Layout from "../../layouts/Layout.astro"; +import PageHero from "../../components/PageHero.astro"; import { api, type DictionaryItem } from "../../lib/api"; let waters: DictionaryItem[] = [], unavailable = false; try { waters = await api("/api/v1/waterbodies?limit=500"); } catch { unavailable = true; } @@ -10,6 +11,6 @@ if (unavailable) { } --- -
Карта водоёмов RF4

Водоёмы

Откройте водоём, чтобы увидеть активные виды рыб и последние подтверждённые точки.

+ {unavailable ?

Каталог временно недоступен

: }
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 0e0c35a..cf3f081 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 · Компонентная подпись.** В работе: добавлен общий `SectionHeading` и применён к рекордам и полевым сигналам; далее унифицировать hero, CTA и системные состояния публичных/admin-страниц. +- [ ] **B04 · Компонентная подпись.** В работе: общие `SectionHeading` и `PageHero` унифицируют рекорды, сигналы, каталоги и статус; далее унифицировать 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 8806ac5..8242247 100644 --- a/docs/brand-system.md +++ b/docs/brand-system.md @@ -44,6 +44,8 @@ Секции данных используют `SectionHeading`: короткий uppercase eyebrow задаёт контекст, антиквенный заголовок называет сущность, необязательное описание объясняет правила, а счётчик всегда расположен справа или под текстом на мобильном. Так число результатов не конкурирует с названием раздела и одинаково читается в рекордах и полевых сигналах. +Каталоги, detail-выборки и страницы прозрачности используют `PageHero`: один контекст, один `h1`, одно короткое пояснение. Это сохраняет одинаковый вертикальный ритм и SEO-иерархию независимо от наличия данных. + ## План внедрения Рабочие статусы B01–B06 ведутся в [ROADMAP.md](ROADMAP.md). После каждого пакета выполняются Astro check/build и адресная визуальная проверка 320/390/768/1280 px. Новые bitmap-иллюстрации не добавляются, пока существующие SVG/CSS-мотивы решают задачу без лишнего веса.