feat: add spot coordinate radar
This commit is contained in:
@@ -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. На странице точки координаты дополнительно показаны фирменным радаром, который не имитирует отсутствующую географию водоёма.
|
||||
|
||||
Все пять community-парсеров подключены к отдельному scheduler-процессу. Состояние запусков и ошибок хранится в PostgreSQL, параллельный запуск одного источника блокируется, минимальный интервал жёстко ограничен 1800 секундами. Локально процесс включается профилем `docker compose --profile scheduler up -d`; detail-URL RF4MAP/RF4 Posts задаются переменными окружения.
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
const { x, y } = Astro.props as { x: number; y: number };
|
||||
---
|
||||
<div class="coordinate-radar" role="img" aria-label={`Координатный радар точки X ${x}, Y ${y}`}>
|
||||
<svg viewBox="0 0 180 180" aria-hidden="true">
|
||||
<circle cx="90" cy="90" r="72"/><circle cx="90" cy="90" r="48"/><circle cx="90" cy="90" r="24"/>
|
||||
<path d="M90 10v160M10 90h160M34 34l112 112M146 34 34 146"/>
|
||||
<path class="radar-sweep" d="M90 90 90 18A72 72 0 0 1 157 64Z"/>
|
||||
<circle class="radar-mark" cx="90" cy="90" r="7"/><circle class="radar-pulse" cx="90" cy="90" r="13"/>
|
||||
</svg>
|
||||
<span class="radar-north">N</span>
|
||||
<strong><small>X</small>{x}<i></i><small>Y</small>{y}</strong>
|
||||
<span class="radar-caption">Координаты в игре</span>
|
||||
</div>
|
||||
@@ -4,6 +4,7 @@ import "../styles/catalog.css";
|
||||
import "../styles/source-status.css";
|
||||
import "../styles/data-passport.css";
|
||||
import "../styles/data-legend.css";
|
||||
import "../styles/coordinate-radar.css";
|
||||
import FishingIcon from "../components/FishingIcon.astro";
|
||||
const {
|
||||
title = "RF4 Spotter — свежие точки клёва Russian Fishing 4",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
import SourceBadge from "../../components/SourceBadge.astro";
|
||||
import CoordinateRadar from "../../components/CoordinateRadar.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;
|
||||
@@ -23,7 +24,7 @@ const breadcrumbs = spot ? { "@context": "https://schema.org", "@type": "Breadcr
|
||||
<Layout title={spot ? `Точка ${spot.x}:${spot.y}, ${spot.waterbody} — RF4 Spotter` : "Точка не найдена — RF4 Spotter"} description={spotDescription} noindex={!spot} structuredData={breadcrumbs}>
|
||||
<a class="back" href="/">← Все активные точки</a>
|
||||
{unavailable || !spot ? <div class="state"><h1>Точка недоступна</h1><p>API не ответил или такой точки нет.</p></div> : <>
|
||||
<section class="spot-hero"><div><span class="eyebrow">{spot.waterbody}</span><h1>Точка {spot.x}:{spot.y}</h1><p>{spot.description}</p></div><div class="pin">{spot.x}<span>:</span>{spot.y}</div></section>
|
||||
<section class="spot-hero"><div><span class="eyebrow">{spot.waterbody}</span><h1>Точка {spot.x}:{spot.y}</h1><p>{spot.description}</p></div><CoordinateRadar x={spot.x} y={spot.y}/></section>
|
||||
<div class="periods"><div><strong>{spot.catches_24h}</strong><span>за 24 часа</span></div><div><strong>{spot.catches_3d}</strong><span>за 3 дня</span></div><div><strong>{spot.catches_7d}</strong><span>за 7 дней</span></div></div>
|
||||
<section class="detail-grid"><div><div class="section-heading"><h2>Последние уловы</h2></div>{catches.length ? <div class="catch-list">{catches.map(item => <article><div><strong>{item.fish}</strong><span>{item.bait ?? "Приманка не указана"}</span><SourceBadge source={item.source_system} href={item.source_url}/></div><div><strong>{kg(item.weight_g)}</strong><span>{item.player_name ?? "Анонимно"}</span></div></article>)}</div> : <div class="state compact-state"><h2>Уловов пока нет</h2><p>Для этой точки нет одобренных наблюдений.</p></div>}</div><aside><span class="eyebrow">Оценка за 24 часа</span>{activity && level ? <><h2>{activity.activity_score} / 100</h2><strong data-activity-level={level.short}>{level.description}</strong><div class="source-strip">{activity.sources.map(source => <SourceBadge source={source}/>)}</div><p>{activity.explanation}</p><p class="note">Основано на {activity.catches} {plural(activity.catches, ["наблюдении", "наблюдениях", "наблюдениях"])} от {activity.unique_players} {plural(activity.unique_players, ["игрока", "игроков", "игроков"])}. Уверенность: {activity.confidence_score}%.</p></> : <p>За последние 24 часа данных для расчёта нет.</p>}<span class="eyebrow">Лучшие приманки</span>{spot.top_baits.length ? <ol>{spot.top_baits.map(name => <li>{name}</li>)}</ol> : <p>Недостаточно данных.</p>}<p class="note">Учитываются только одобренные наблюдения.</p></aside></section>
|
||||
</>}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
.coordinate-radar{position:relative;flex:0 0 180px;width:180px;height:180px;display:grid;place-items:center;color:var(--lime)}.coordinate-radar svg{position:absolute;inset:0;width:100%;height:100%;fill:none;stroke:#ffffff20;stroke-width:1}.coordinate-radar .radar-sweep{fill:#c9f45b0d;stroke:#c9f45b38}.coordinate-radar .radar-mark{fill:var(--lime);stroke:none}.coordinate-radar .radar-pulse{stroke:var(--lime);stroke-width:2;opacity:.55}.coordinate-radar strong{position:relative;display:grid;grid-template-columns:auto auto 1px auto auto;align-items:baseline;gap:4px;padding:7px 10px;border-radius:7px;background:#082226d9;font:400 20px Georgia,serif}.coordinate-radar strong small{font:700 8px Inter,sans-serif;color:#9eb0ad}.coordinate-radar strong i{width:1px;height:14px;background:#ffffff35}.radar-north{position:absolute;top:3px;font-size:8px;letter-spacing:.15em}.radar-caption{position:absolute;bottom:1px;color:#91a5a1;font-size:8px;text-transform:uppercase;letter-spacing:.12em}@media(max-width:720px){.coordinate-radar{display:none}}
|
||||
+1
-1
@@ -124,7 +124,7 @@
|
||||
### Графические элементы и объяснение данных
|
||||
|
||||
- [x] Подготовить фирменный знак «крючок + поплавок + координатная сетка» и согласованный набор favicon/app icons (7 сентября 2026).
|
||||
- [ ] Добавить координатный радар или мини-карту на страницу точки.
|
||||
- [x] Добавить честный координатный радар без имитации отсутствующей карты на страницу точки (7 сентября 2026).
|
||||
- [ ] Добавить временную шкалу активности 24/72 часа в рыболовной стилистике.
|
||||
- [ ] Добавить лёгкие силуэты видов рыб, не перегружая карточки результатов.
|
||||
- [x] Собрать единый визуальный «паспорт данных»: источник, свежесть, полнота и доверие; применить к карточкам активности (7 сентября 2026).
|
||||
|
||||
Reference in New Issue
Block a user