diff --git a/README.md b/README.md index 7c53c93..ef3a719 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ RF4DB/RF4-STAT/RF4MAP/RF4 Posts сначала принимаются в изо На сайте у каждой записи отображается источник, а у агрегированной активности — все вошедшие в расчёт источники. Неполные community-наблюдения публикуются сразу в отдельной ленте «Полевые сигналы» с предупреждением и перечнем отсутствующих полей; до подтверждения полноты они не влияют на индекс клёва. -Базовый 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-кэширование статических ресурсов. +Базовый 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-кэширование статических ресурсов. Карточки активности показывают единый паспорт данных: источники, свежесть, полноту и уровень доверия. Публичные точки используют постоянные читаемые адреса вида `/spots/kuori-85x92`; старые UUID-адреса остаются совместимыми и перенаправляются на канонический URL. diff --git a/apps/web/src/components/ActivityCard.astro b/apps/web/src/components/ActivityCard.astro index 91cce12..4e1ba95 100644 --- a/apps/web/src/components/ActivityCard.astro +++ b/apps/web/src/components/ActivityCard.astro @@ -2,13 +2,20 @@ import type { Activity } from "../lib/api"; import { activityLevel, ago, kg, plural, spotPath } from "../lib/api"; import FishingIcon from "./FishingIcon.astro"; -import SourceBadge from "./SourceBadge.astro"; +import DataPassport from "./DataPassport.astro"; const { item } = Astro.props as { item: Activity }; const level = activityLevel(item.activity_score); const limited = item.catches < 3; --- {String(item.activity_score).padStart(2,"0")} -
{item.waterbody}{level.short}{limited && Данных мало}

{item.fish}

{item.x}:{item.y} {ago(item.last_confirmed_at)}
{item.sources.map(source => )}

{item.explanation}

Работает сейчас{item.best_bait ?? "не указана"}
+
+
{item.waterbody}{level.short}{limited && Данных мало}
+

{item.fish}

+
{item.x}:{item.y} {ago(item.last_confirmed_at)}
+

{item.explanation}

+ +
Работает сейчас{item.best_bait ?? "не указана"}
+
{item.catches}{plural(item.catches, ["улов", "улова", "уловов"])}
{item.unique_players}{plural(item.unique_players, ["игрок", "игрока", "игроков"])}
{kg(item.average_weight_g)}средний вес
{item.confidence_score}%уверенность
diff --git a/apps/web/src/components/DataPassport.astro b/apps/web/src/components/DataPassport.astro new file mode 100644 index 0000000..b4844db --- /dev/null +++ b/apps/web/src/components/DataPassport.astro @@ -0,0 +1,13 @@ +--- +import SourceBadge from "./SourceBadge.astro"; +import { ago } from "../lib/api"; +type Props = { sources: string[]; sourceUrl?: string | null; observedAt: string; completeness: number; confidence?: number | null; status?: "verified" | "unverified" | "incomplete" }; +const { sources, sourceUrl, observedAt, completeness, confidence = null, status = "verified" } = Astro.props; +const statusLabels = { verified: "Учтено", unverified: "Ждёт проверки", incomplete: "Неполные данные" }; +const completenessLabel = completeness >= 100 ? "Полные" : `${Math.max(0, completeness)}% полей`; +--- +
+
Паспорт данных{statusLabels[status]}
+
{sources.map(source => )}
+
Свежесть
{ago(observedAt)}
Полнота
{completenessLabel}
Доверие
{confidence == null ? "После проверки" : `${confidence}%`}
+
diff --git a/apps/web/src/layouts/Layout.astro b/apps/web/src/layouts/Layout.astro index bb9c996..9b07bee 100644 --- a/apps/web/src/layouts/Layout.astro +++ b/apps/web/src/layouts/Layout.astro @@ -2,6 +2,7 @@ import "../styles/global.css"; import "../styles/catalog.css"; import "../styles/source-status.css"; +import "../styles/data-passport.css"; import FishingIcon from "../components/FishingIcon.astro"; const { title = "RF4 Spotter — свежие точки клёва Russian Fishing 4", diff --git a/apps/web/src/styles/data-passport.css b/apps/web/src/styles/data-passport.css new file mode 100644 index 0000000..c06d2ac --- /dev/null +++ b/apps/web/src/styles/data-passport.css @@ -0,0 +1,5 @@ +.data-passport{margin-top:18px;padding:14px;border:1px solid #cbd7cf;border-radius:13px;background:#f8faf6;color:var(--deep)} +.data-passport header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:11px}.data-passport header>span{font:italic 15px Georgia,serif}.data-passport header strong{padding:4px 8px;border-radius:999px;background:#e2efcf;color:#49651f;font-size:9px;text-transform:uppercase;letter-spacing:.06em}.data-passport header strong[data-passport-status="unverified"]{background:#e8e2f4;color:#59438d}.data-passport header strong[data-passport-status="incomplete"]{background:#f5e6b8;color:#755609} +.data-passport__sources{display:flex;flex-wrap:wrap;gap:5px;margin-bottom:12px}.data-passport dl{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:0}.data-passport dl div{padding-top:8px;border-top:1px solid #d9e1da}.data-passport dt{font-size:8px;text-transform:uppercase;letter-spacing:.08em;color:#72817e}.data-passport dd{margin:3px 0 0;font:400 12px Georgia,serif;color:var(--deep)} +.detail-card .data-passport,.detail-grid aside .data-passport{border-color:#ffffff20;background:#ffffff08;color:#fff}.detail-card .data-passport dd,.detail-grid aside .data-passport dd{color:#fff}.detail-card .data-passport dl div,.detail-grid aside .data-passport dl div{border-color:#ffffff1c}.detail-card .data-passport dt,.detail-grid aside .data-passport dt{color:#a9b8b5}.signal-card .data-passport{position:relative;z-index:1;margin-top:15px;background:#fffdf7;border-color:#ddd3af} +@media(max-width:420px){.data-passport dl{grid-template-columns:1fr}.data-passport dl div{display:flex;justify-content:space-between;gap:10px}} diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 7b94870..0b6508a 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -127,7 +127,7 @@ - [ ] Добавить координатный радар или мини-карту на страницу точки. - [ ] Добавить временную шкалу активности 24/72 часа в рыболовной стилистике. - [ ] Добавить лёгкие силуэты видов рыб, не перегружая карточки результатов. -- [ ] Собрать единый визуальный «паспорт данных»: источник, свежесть, полнота и доверие. +- [x] Собрать единый визуальный «паспорт данных»: источник, свежесть, полнота и доверие; применить к карточкам активности (7 сентября 2026). - [ ] Добавить публичную легенду цветов источников и статусов качества. - [ ] Добавить skeleton-состояния для динамических публичных блоков. - [ ] Добавить ненавязчивый баннер открытой альфы и канал обратной связи.