feat: add unified data passports
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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;
|
||||
---
|
||||
<a class="spot-card" data-testid={`spot-${item.x}-${item.y}`} href={spotPath(item)}>
|
||||
<span class="spot-rank">{String(item.activity_score).padStart(2,"0")}</span>
|
||||
<div class="spot-main"><div class="spot-topline"><span>{item.waterbody}</span><span class="activity-pill" data-activity-level={level.short}><i></i>{level.short}</span>{limited && <span class="data-quality">Данных мало</span>}</div><h3>{item.fish}</h3><div class="spot-meta"><span><FishingIcon name="pin" size={14}/> {item.x}:{item.y}</span><span><FishingIcon name="clock" size={14}/> {ago(item.last_confirmed_at)}</span></div><div class="source-strip" aria-label="Источники данных">{item.sources.map(source => <SourceBadge source={source}/>)}</div><p class="data-note">{item.explanation}</p><div class="bait-line"><FishingIcon name="lure" size={25}/><div><span>Работает сейчас</span><strong>{item.best_bait ?? "не указана"}</strong></div></div></div>
|
||||
<div class="spot-main">
|
||||
<div class="spot-topline"><span>{item.waterbody}</span><span class="activity-pill" data-activity-level={level.short}><i></i>{level.short}</span>{limited && <span class="data-quality">Данных мало</span>}</div>
|
||||
<h3>{item.fish}</h3>
|
||||
<div class="spot-meta"><span><FishingIcon name="pin" size={14}/> {item.x}:{item.y}</span><span><FishingIcon name="clock" size={14}/> {ago(item.last_confirmed_at)}</span></div>
|
||||
<p class="data-note">{item.explanation}</p>
|
||||
<DataPassport sources={item.sources} observedAt={item.last_confirmed_at} completeness={limited ? 75 : 100} confidence={item.confidence_score}/>
|
||||
<div class="bait-line"><FishingIcon name="lure" size={25}/><div><span>Работает сейчас</span><strong>{item.best_bait ?? "не указана"}</strong></div></div>
|
||||
</div>
|
||||
<div class="spot-stats"><div><strong>{item.catches}</strong><span>{plural(item.catches, ["улов", "улова", "уловов"])}</span></div><div><strong>{item.unique_players}</strong><span>{plural(item.unique_players, ["игрок", "игрока", "игроков"])}</span></div><div><strong>{kg(item.average_weight_g)}</strong><span>средний вес</span></div><div><strong>{item.confidence_score}%</strong><span>уверенность</span></div></div><span class="card-arrow"><FishingIcon name="arrow" size={22}/></span>
|
||||
</a>
|
||||
|
||||
@@ -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)}% полей`;
|
||||
---
|
||||
<section class="data-passport" aria-label="Паспорт данных">
|
||||
<header><span>Паспорт данных</span><strong data-passport-status={status}>{statusLabels[status]}</strong></header>
|
||||
<div class="data-passport__sources">{sources.map(source => <SourceBadge source={source} href={sources.length === 1 ? sourceUrl : null}/>)}</div>
|
||||
<dl><div><dt>Свежесть</dt><dd>{ago(observedAt)}</dd></div><div><dt>Полнота</dt><dd>{completenessLabel}</dd></div><div><dt>Доверие</dt><dd>{confidence == null ? "После проверки" : `${confidence}%`}</dd></div></dl>
|
||||
</section>
|
||||
@@ -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",
|
||||
|
||||
@@ -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}}
|
||||
+1
-1
@@ -127,7 +127,7 @@
|
||||
- [ ] Добавить координатный радар или мини-карту на страницу точки.
|
||||
- [ ] Добавить временную шкалу активности 24/72 часа в рыболовной стилистике.
|
||||
- [ ] Добавить лёгкие силуэты видов рыб, не перегружая карточки результатов.
|
||||
- [ ] Собрать единый визуальный «паспорт данных»: источник, свежесть, полнота и доверие.
|
||||
- [x] Собрать единый визуальный «паспорт данных»: источник, свежесть, полнота и доверие; применить к карточкам активности (7 сентября 2026).
|
||||
- [ ] Добавить публичную легенду цветов источников и статусов качества.
|
||||
- [ ] Добавить skeleton-состояния для динамических публичных блоков.
|
||||
- [ ] Добавить ненавязчивый баннер открытой альфы и канал обратной связи.
|
||||
|
||||
Reference in New Issue
Block a user