refactor: unify editorial section headings
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
---
|
||||
const { eyebrow, title, description, count, id, editorial = false } = Astro.props as {
|
||||
eyebrow?: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
count?: string;
|
||||
id?: string;
|
||||
editorial?: boolean;
|
||||
};
|
||||
---
|
||||
<div class:list={["section-heading", { "section-heading--editorial": editorial }]}>
|
||||
<div>
|
||||
{eyebrow && <span class="overline">{eyebrow}</span>}
|
||||
<h2 id={id}>{title}</h2>
|
||||
{description && <p>{description}</p>}
|
||||
</div>
|
||||
{count && <span class="result-count">{count}</span>}
|
||||
</div>
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
import SourceBadge from "./SourceBadge.astro";
|
||||
import SectionHeading from "./SectionHeading.astro";
|
||||
import { ago, kg, type PublicObservation } from "../lib/api";
|
||||
const { signals } = Astro.props as { signals: PublicObservation[] };
|
||||
const groups = [...signals.reduce((map, signal) => {
|
||||
@@ -17,7 +18,7 @@ const groups = [...signals.reduce((map, signal) => {
|
||||
}, new Map<string, PublicObservation & { observations: PublicObservation[] }>()).values()];
|
||||
---
|
||||
<section class="signal-section content-grid" aria-labelledby="signals-title">
|
||||
<div class="signal-heading"><div><span class="overline">Сырые данные источников</span><h2 id="signals-title">Полевые сигналы</h2><p>Показываем сразу, даже если часть полей отсутствует. Эти карточки не участвуют в расчёте активности до полного подтверждения.</p></div><span class="signal-count">{signals.length} сигналов · {groups.length} сюжетов</span></div>
|
||||
<SectionHeading editorial eyebrow="Сырые данные источников" title="Полевые сигналы" id="signals-title" description="Показываем сразу, даже если часть полей отсутствует. Эти карточки не участвуют в расчёте активности до полного подтверждения." count={`${signals.length} сигналов · ${groups.length} сюжетов`} />
|
||||
<div class="signal-grid">{groups.map(signal => {
|
||||
const provenance = [...new Map(signal.observations.map(item => [`${item.source_system}|${item.source_url}`, item])).values()];
|
||||
return <article class="signal-card" data-quality={signal.quality}>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import SourceBadge from "../components/SourceBadge.astro";
|
||||
import SectionHeading from "../components/SectionHeading.astro";
|
||||
import { api, kg, type DictionaryItem, type ImportRun, type OfficialRecord, type PaginatedOfficialRecord } from "../lib/api";
|
||||
const params = Astro.url.searchParams;
|
||||
const fish = params.get("fish") ?? "";
|
||||
@@ -21,7 +22,7 @@ const last = runs[0];
|
||||
<Layout title="Официальные рекорды Russian Fishing 4 — RF4 Spotter" description="Последние официальные рекорды RF4 по рыбам и водоёмам: вес, приманка, игрок, дата и прямая ссылка на источник." noindex={showNoIndex} errorPage={unavailable}>
|
||||
<section class="records-hero"><div><span class="eyebrow">Публичные данные RF4</span><h1>Официальные<br/><em>рекорды</em></h1></div><div class="source-status"><span class:list={["status-dot", last?.status]}></span><strong>{last ? `Импорт: ${last.status}` : "Импорт ещё не запускался"}</strong>{last?.finished_at && <small>{new Date(last.finished_at).toLocaleString("ru-RU")} · {last.rows_seen} строк</small>}</div></section>
|
||||
<form class="record-filters" method="get"><label>Рыба<select name="fish"><option value="">Любая рыба</option>{fishes.map(item => <option value={item.slug} selected={fish === item.slug}>{item.name_ru}</option>)}</select></label><label>Водоём<select name="waterbody"><option value="">Все водоёмы</option>{waterbodies.map(item => <option value={item.slug} selected={waterbody === item.slug}>{item.name_ru}</option>)}</select></label><button>Фильтровать</button>{(fish || waterbody) && <a href="/records">Сбросить</a>}</form>
|
||||
<div class="section-heading content-grid"><div><span class="overline">Официальный источник</span><h2>Последние записи</h2></div><span class="result-count">{items.length} из {totalRecords} записей</span></div>
|
||||
<div class="content-grid"><SectionHeading eyebrow="Официальный источник" title="Последние записи" count={`${items.length} из ${totalRecords} записей`} /></div>
|
||||
{unavailable && <div class="state content-grid"><h2>Источник временно недоступен</h2></div>}
|
||||
{!unavailable && items.length && <div class="record-table"><div class="record-row record-head"><span>Рыба</span><span>Вес</span><span>Водоём</span><span>Приманка</span><span>Игрок</span><span>Дата и источник</span></div>{items.map(record => <article class="record-row"><strong data-label="Рыба">{record.fish}</strong><strong data-label="Вес">{kg(record.weight_g)}</strong><span data-label="Водоём">{record.waterbody}</span><span data-label="Приманка">{record.bait ?? "—"}</span><span data-label="Игрок">{record.player_name ?? "—"}</span><span data-label="Дата и источник" class="record-provenance"><time>{record.record_date ? new Date(record.record_date).toLocaleDateString("ru-RU") : "—"}</time><SourceBadge source={record.source_system} href={record.source_url}/></span></article>)}</div>}
|
||||
{!unavailable && items.length && offset + items.length < totalRecords && <a class="load-more" href={`/records?${(() => { const p = new URLSearchParams(params); p.delete("offset"); p.set("offset", String(offset + items.length)); return p.toString(); })()}`}>Показать ещё <span>{offset + items.length} из {totalRecords}</span> ↓</a>}
|
||||
|
||||
@@ -44,3 +44,6 @@ footer{min-height:118px;background:var(--deep);color:#dbe4df;padding:28px max(32
|
||||
|
||||
/* Brand grammar: radar belongs to coordinates; incomplete signals use a field-note edge, not decorative ripples. */
|
||||
.detail-card:before,.signal-card:after{content:none}.signal-card{border-top:3px solid #c9ad55}
|
||||
|
||||
/* Shared editorial signature for data sections. */
|
||||
.section-heading{gap:24px}.section-heading>div{min-width:0}.section-heading p{max-width:700px;margin:10px 0 0;color:var(--text-muted);line-height:1.55}.section-heading--editorial{padding-top:68px;border-top:1px solid var(--border);margin-bottom:25px}.section-heading--editorial h2{font-size:46px;line-height:.95}.section-heading--editorial .result-count{align-self:end}@media(max-width:720px){.section-heading--editorial{display:block;padding-top:45px}.section-heading--editorial .result-count{display:inline-block;margin-top:14px}}
|
||||
|
||||
Reference in New Issue
Block a user