diff --git a/README.md b/README.md index 19d4f22..e87e62a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,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, web manifest и production-кэширование статических ресурсов. Карточки активности показывают единый паспорт данных: источники, свежесть, полноту и уровень доверия. На `/status` опубликована легенда цветов всех источников и статусов качества. -Публичные точки используют постоянные читаемые адреса вида `/spots/kuori-85x92`; старые UUID-адреса остаются совместимыми и перенаправляются на канонический URL. На странице точки координаты дополнительно показаны фирменным радаром, который не имитирует отсутствующую географию водоёма. +Публичные точки используют постоянные читаемые адреса вида `/spots/kuori-85x92`; старые UUID-адреса остаются совместимыми и перенаправляются на канонический URL. На странице точки координаты дополнительно показаны фирменным радаром, который не имитирует отсутствующую географию водоёма, а уловы за 72 часа — шкалой-леской с 12-часовым шагом. Все пять community-парсеров подключены к отдельному scheduler-процессу. Состояние запусков и ошибок хранится в PostgreSQL, параллельный запуск одного источника блокируется, минимальный интервал жёстко ограничен 1800 секундами. Локально процесс включается профилем `docker compose --profile scheduler up -d`; detail-URL RF4MAP/RF4 Posts задаются переменными окружения. diff --git a/apps/web/src/components/ActivityTimeline.astro b/apps/web/src/components/ActivityTimeline.astro new file mode 100644 index 0000000..2f3d4aa --- /dev/null +++ b/apps/web/src/components/ActivityTimeline.astro @@ -0,0 +1,19 @@ +--- +import type { Catch } from "../lib/api"; +const { catches } = Astro.props as { catches: Catch[] }; +const now = Date.now(); +const step = 12 * 60 * 60 * 1000; +const buckets = Array.from({ length: 6 }, (_, index) => ({ index, count: 0 })); +for (const item of catches) { + const timestamp = new Date(item.caught_at ?? item.reported_at).getTime(); + const age = Math.floor((now - timestamp) / step); + if (age >= 0 && age < buckets.length) buckets[buckets.length - 1 - age].count += 1; +} +const max = Math.max(1, ...buckets.map(bucket => bucket.count)); +--- +
+
Последние 72 часа

Леска активности

По показанным ниже уловам · шаг 12 часов

+ +
diff --git a/apps/web/src/layouts/Layout.astro b/apps/web/src/layouts/Layout.astro index 004a393..eff75b0 100644 --- a/apps/web/src/layouts/Layout.astro +++ b/apps/web/src/layouts/Layout.astro @@ -5,6 +5,7 @@ import "../styles/source-status.css"; import "../styles/data-passport.css"; import "../styles/data-legend.css"; import "../styles/coordinate-radar.css"; +import "../styles/activity-timeline.css"; import FishingIcon from "../components/FishingIcon.astro"; const { title = "RF4 Spotter — свежие точки клёва Russian Fishing 4", diff --git a/apps/web/src/pages/spots/[id].astro b/apps/web/src/pages/spots/[id].astro index 73064dd..9f458dc 100644 --- a/apps/web/src/pages/spots/[id].astro +++ b/apps/web/src/pages/spots/[id].astro @@ -2,6 +2,7 @@ import Layout from "../../layouts/Layout.astro"; import SourceBadge from "../../components/SourceBadge.astro"; import CoordinateRadar from "../../components/CoordinateRadar.astro"; +import ActivityTimeline from "../../components/ActivityTimeline.astro"; import { activityLevel, api, kg, plural, type Activity, type Catch, type Spot } from "../../lib/api"; const { id } = Astro.params; let spot: Spot | null = null, catches: Catch[] = [], activity: Activity | null = null, unavailable = false; @@ -26,6 +27,7 @@ const breadcrumbs = spot ? { "@context": "https://schema.org", "@type": "Breadcr {unavailable || !spot ?

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

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

: <>
{spot.waterbody}

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

{spot.description}

{spot.catches_24h}за 24 часа
{spot.catches_3d}за 3 дня
{spot.catches_7d}за 7 дней
+

Последние уловы

{catches.length ?
{catches.map(item =>
{item.fish}{item.bait ?? "Приманка не указана"}
{kg(item.weight_g)}{item.player_name ?? "Анонимно"}
)}
:

Уловов пока нет

Для этой точки нет одобренных наблюдений.

}
} diff --git a/apps/web/src/styles/activity-timeline.css b/apps/web/src/styles/activity-timeline.css new file mode 100644 index 0000000..2b4aaa9 --- /dev/null +++ b/apps/web/src/styles/activity-timeline.css @@ -0,0 +1,3 @@ +.activity-timeline{width:min(1180px,calc(100% - 64px));margin:20px auto 0;padding:25px 28px;border-radius:14px;background:#fff;border:1px solid #d9e1da}.activity-timeline header{display:flex;align-items:end;justify-content:space-between;gap:20px}.activity-timeline h2{font:400 29px Georgia,serif;margin:5px 0 0}.activity-timeline header p{margin:0;color:#71817e;font-size:10px} +.timeline-chart{height:145px;display:grid;grid-template-columns:repeat(6,1fr);gap:8px;margin-top:18px;padding-top:8px;border-bottom:1px solid #a9bbb1;background:repeating-linear-gradient(to top,transparent 0,transparent 35px,#dce5de 36px)}.timeline-slot{position:relative;display:grid;grid-template-rows:1fr auto;place-items:center}.timeline-line{position:relative;align-self:end;width:2px;height:var(--height);min-height:10px;background:#3f7779}.timeline-line i{position:absolute;left:0;top:0;width:12px;height:18px;margin:-4px 0 0 -5px;border-radius:7px 7px 9px 9px;background:linear-gradient(#ff785a 0 35%,var(--lime) 35%);box-shadow:0 0 0 4px #c9f45b18}.timeline-slot strong{position:absolute;top:2px;font:400 14px Georgia,serif;color:#365c5e}.timeline-slot small{position:absolute;bottom:-20px;color:#71817e;font-size:8px;white-space:nowrap}.activity-timeline+*{margin-top:42px} +@media(max-width:720px){.activity-timeline{width:calc(100% - 28px);padding:20px 15px}.activity-timeline header{display:block}.activity-timeline header p{margin-top:6px}.timeline-slot small{font-size:7px}} diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 1ffc4a9..f53ecea 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -125,7 +125,7 @@ - [x] Подготовить фирменный знак «крючок + поплавок + координатная сетка» и согласованный набор favicon/app icons (7 сентября 2026). - [x] Добавить честный координатный радар без имитации отсутствующей карты на страницу точки (7 сентября 2026). -- [ ] Добавить временную шкалу активности 24/72 часа в рыболовной стилистике. +- [x] Добавить временную шкалу активности 72 часа с 12-часовым шагом в виде лески с поплавками (7 сентября 2026). - [ ] Добавить лёгкие силуэты видов рыб, не перегружая карточки результатов. - [x] Собрать единый визуальный «паспорт данных»: источник, свежесть, полнота и доверие; применить к карточкам активности (7 сентября 2026). - [x] Добавить публичную легенду цветов источников и статусов качества на странице `/status` (7 сентября 2026).