diff --git a/README.md b/README.md index 61583da..e7b861c 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ RF4DB/RF4-STAT/RF4MAP/RF4 Posts сначала принимаются в изо Базовый SEO-контур готов для `rf4spotter.ru`: страницы имеют уникальные метаданные, canonical, Open Graph/Twitter Card, фирменное изображение 1200×630 и JSON-LD; доступны динамические `/robots.txt` и `/sitemap.xml`, административные и ошибочные страницы закрыты от индексации, добавлена собственная страница 404. Индексируемые каталоги рыб и водоёмов, detail-страницы и сочетания водоём + рыба строятся из актуального разрешённого справочника и включаются в sitemap. Подключены резкие favicon/app icons из SVG-мастера, отдельные полнофоновые maskable-иконки, web manifest и production-кэширование статических ресурсов. Карточки активности показывают единый паспорт данных: источники, свежесть, полноту и уровень доверия. На `/status` опубликована легенда цветов всех источников и статусов качества. -Публичные точки используют постоянные читаемые адреса вида `/spots/kuori-85x92`; старые UUID-адреса остаются совместимыми и перенаправляются на канонический URL. На странице точки координаты дополнительно показаны фирменным радаром, который не имитирует отсутствующую географию водоёма, а уловы за 72 часа — шкалой-леской с 12-часовым шагом. Каждый улов показывает источник, относительную свежесть и точное время UTC; время получения явно отделено от времени улова. Каталоги оформлены как полевой атлас: тёмный seal показывает объём справочника, карточки рыб используют смысловые SVG-силуэты, а каждый водоём — собственный детерминированный абстрактный отпечаток берега, волн, точки и индекса. На странице сочетания оба знака собираются в единую атласную эмблему. Это не карта и не игровая география. Пустые состояния используют статичную CSS-иллюстрацию поплавка; смысловые анимации полностью учитывают системное ограничение движения. +Публичные точки используют постоянные читаемые адреса вида `/spots/kuori-85x92`; старые UUID-адреса остаются совместимыми и перенаправляются на канонический URL. На странице точки координаты дополнительно показаны фирменным радаром, который не имитирует отсутствующую географию водоёма, а уловы за 72 часа — шкалой-леской с 12-часовым шагом. Каждый улов показывает источник, относительную свежесть и точное время UTC; время получения явно отделено от времени улова. Каталоги оформлены как полевой атлас: тёмный seal показывает объём справочника, карточки рыб используют смысловые SVG-силуэты, а каждый водоём — собственный детерминированный абстрактный отпечаток берега, волн, точки и индекса. На странице сочетания оба знака собираются в единую атласную эмблему, а detail-иерархию связывает breadcrumb-леска с текстовыми узлами. Это не карта и не игровая география. Пустые состояния используют статичную CSS-иллюстрацию поплавка; смысловые анимации полностью учитывают системное ограничение движения. Все пять community-парсеров подключены к отдельному scheduler-процессу. Попытка резервируется в PostgreSQL до HTTP-запроса, поэтому ошибки тоже расходуют cooldown. Блокировка и минимальный интервал 1800 секунд действуют на весь домен; endpoint одного сайта выбираются по самому давнему запуску и не голодают. Ручной production-запуск использует тот же журнал: `docker compose exec api python -m app.cli fetch-community rf4stat-fishing`. Локально scheduler включается профилем `docker compose --profile scheduler up -d`; detail-URL RF4MAP/RF4 Posts задаются переменными окружения. diff --git a/apps/web/src/components/AtlasBreadcrumbs.astro b/apps/web/src/components/AtlasBreadcrumbs.astro new file mode 100644 index 0000000..a6a2061 --- /dev/null +++ b/apps/web/src/components/AtlasBreadcrumbs.astro @@ -0,0 +1,28 @@ +--- +type Item = { label: string; href?: string }; +const { items } = Astro.props as { items: Item[] }; +--- + + + + diff --git a/apps/web/src/pages/fish/[slug].astro b/apps/web/src/pages/fish/[slug].astro index d447f9e..a1e366f 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 AtlasBreadcrumbs from "../../components/AtlasBreadcrumbs.astro"; import PageHero from "../../components/PageHero.astro"; import StatePanel from "../../components/StatePanel.astro"; import Layout from "../../layouts/Layout.astro"; @@ -21,7 +22,7 @@ const waters = [...new Map(items.map(item => [item.waterbody_slug, item.waterbod const schema = fish ? { "@context":"https://schema.org", "@type":"CollectionPage", name:`Где ловить ${fish.name_ru} в RF4`, url:`https://rf4spotter.ru/fish/${fish.slug}` } : null; --- - ← Все виды рыб + {unavailable ? : !fish ? :
{items.length ? items.map(item => ) : }
}
diff --git a/apps/web/src/pages/spots/[id].astro b/apps/web/src/pages/spots/[id].astro index ad849d9..1a85b37 100644 --- a/apps/web/src/pages/spots/[id].astro +++ b/apps/web/src/pages/spots/[id].astro @@ -1,5 +1,6 @@ --- import Layout from "../../layouts/Layout.astro"; +import AtlasBreadcrumbs from "../../components/AtlasBreadcrumbs.astro"; import SourceBadge from "../../components/SourceBadge.astro"; import CoordinateRadar from "../../components/CoordinateRadar.astro"; import TackleGlyph from "../../components/TackleGlyph.astro"; @@ -31,7 +32,7 @@ const breadcrumbs = spot ? { "@context": "https://schema.org", "@type": "Breadcr ] } : null; --- - ← Все активные точки + {unavailable || !spot ?

Точка недоступна

API не ответил или такой точки нет.

: <>
{spot.waterbody}

Точка {spot.x}:{spot.y}

{spot.description}

{spot.catches_24h}за 24 часа
{spot.catches_3d}за 3 дня
{spot.catches_7d}за 7 дней
diff --git a/apps/web/src/pages/waterbodies/[slug].astro b/apps/web/src/pages/waterbodies/[slug].astro index 38193e0..61620df 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 AtlasBreadcrumbs from "../../components/AtlasBreadcrumbs.astro"; import PageHero from "../../components/PageHero.astro"; import StatePanel from "../../components/StatePanel.astro"; import Layout from "../../layouts/Layout.astro"; @@ -21,7 +22,7 @@ const fishes = [...new Map(items.map(item => [item.fish_slug, item.fish])).entri const schema = water ? { "@context":"https://schema.org", "@type":"CollectionPage", name:`Что ловить на ${water.name_ru} в RF4`, url:`https://rf4spotter.ru/waterbodies/${water.slug}` } : null; --- - ← Все водоёмы + {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 c6991f4..e84dba5 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 AtlasBreadcrumbs from "../../../components/AtlasBreadcrumbs.astro"; import PageHero from "../../../components/PageHero.astro"; import StatePanel from "../../../components/StatePanel.astro"; import Layout from "../../../layouts/Layout.astro"; @@ -21,7 +22,7 @@ const valid = Boolean(water && fish); const schema = valid ? { "@type":"CollectionPage", name:`${fish!.name_ru} на ${water!.name_ru} в RF4`, url:`https://rf4spotter.ru/waterbodies/${water!.slug}/${fish!.slug}` } : null; --- - ← К водоёму + {unavailable ? : !valid ? :
{items.length ? items.map(item => ) : }
}
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 2558ba5..f9f6920 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -32,6 +32,7 @@ - [x] **B10 · Визуальные отпечатки водоёмов.** Для каждого slug воспроизводимо выбираются один из восьми береговых контуров, число волн, положение точки и двухсимвольный индекс. Знак используется в каталоге и detail-hero; это явно абстрактный отпечаток, а не карта или игровая география. - [ ] **B11 · Опциональный media pipeline.** После отдельного письменного разрешения на изображения добавить allowlist официальных asset URL, локальное сохранение без hotlink, content hash, MIME/dimensions, attribution/source URL, дату получения и fallback на SVG. Изменение/удаление оригинала должно отправлять media asset на повторную проверку, а не молча менять публичную карточку. - [x] **B12 · Эмблема сочетания.** Страница «водоём + рыба» получила составной атласный seal: собственный отпечаток водоёма пересекается со смысловым силуэтом рыбы. Так визуальная идентичность сопровождает всю иерархию каталога и не требует внешних изображений. +- [x] **B13 · Навигационная леска атласа.** Разрозненные ссылки назад на detail-страницах заменены доступной breadcrumb-цепочкой с мотивом лески и узлов. Страница точки связывает главную, водоём и координаты; сочетание — каталог, водоём и рыбу. Текущий узел всегда подписан текстом и отмечен `aria-current`. - [ ] **Q01 · Документы источников.** Приложить или дать устойчивые ссылки на первичные разрешения RF4DB, RF4-STAT, RF4MAP и RF4 Posts; для каждого зафиксировать атрибуцию, точный production-лимит, срок хранения и процедуру удаления. - [ ] **Q02 · Управляемое удаление источника.** Добавить обнаружение изменённых/удалённых опубликованных записей без отдельного частого обхода: статус, журнал решения и безопасное исключение из активности после проверки. diff --git a/docs/brand-system.md b/docs/brand-system.md index f77be3f..50810fa 100644 --- a/docs/brand-system.md +++ b/docs/brand-system.md @@ -62,6 +62,8 @@ CTA задаются смыслом через `data-action`: `primary` — ос На странице сочетания два знака образуют одну атласную эмблему: отпечаток водоёма служит нижним слоем, силуэт рыбы — верхним, а маленький знак пересечения подчёркивает смысл «вид на конкретном водоёме». Эмблема декоративна; названия обеих сущностей остаются в текстовой и навигационной иерархии. +Detail-страницы связывает навигационная леска: каждый уровень представлен текстовым узлом, пунктир между узлами показывает путь, а лаймовый конечный узел — текущую страницу. На мобильном цепочка остаётся однострочной и прокручиваемой, не сокращая названия и не превращая мотив в единственный носитель смысла. + ## Brand assets и QA `favicon.svg` — мастер обычных favicon, Apple Touch и PWA `purpose: any`; PNG генерируются из него напрямую в целевом размере, а не масштабируются из малого растра. `icon-maskable.svg` — полнофоновый мастер для отдельных `purpose: maskable` 192/512 px, чтобы системная маска не открывала прозрачные углы. OG имеет ровно 1200×630 px и использует тот же тёмный водный фон, лаймовый поплавок и координатную сетку. Проверены desktop и 390 px каталоги, 32/180/192/512 px assets, светлая поверхность сайта и тёмные бренд-носители.