70 lines
8.1 KiB
Plaintext
70 lines
8.1 KiB
Plaintext
---
|
|
import { spotPath, type Activity } from "../lib/api";
|
|
|
|
const { items } = Astro.props as { items: Activity[] };
|
|
const groups = [...items.reduce((result, item) => {
|
|
const group = result.get(item.waterbody_slug) ?? [];
|
|
group.push(item);
|
|
result.set(item.waterbody_slug, group);
|
|
return result;
|
|
}, new Map<string, Activity[]>()).values()];
|
|
const plotPoints = (group: Activity[]) => {
|
|
const minX = Math.min(...group.map(item => item.x), 0);
|
|
const maxX = Math.max(...group.map(item => item.x), 1);
|
|
const minY = Math.min(...group.map(item => item.y), 0);
|
|
const maxY = Math.max(...group.map(item => item.y), 1);
|
|
const xRange = Math.max(1, maxX - minX);
|
|
const yRange = Math.max(1, maxY - minY);
|
|
const occupied = new Set<string>();
|
|
return group.map(item => {
|
|
const targetColumn = Math.min(19, Math.max(2, Math.round(((item.x - minX) / xRange) * 16) + 2));
|
|
const targetRow = Math.min(19, Math.max(2, Math.round((1 - (item.y - minY) / yRange) * 16) + 2));
|
|
let column = targetColumn;
|
|
let row = targetRow;
|
|
for (let distance = 0; occupied.has(`${column}:${row}`) && distance < 20; distance += 1) {
|
|
column = ((targetColumn - 2 + distance + 1) % 18) + 2;
|
|
row = ((targetRow - 2 + Math.floor((distance + 1) / 18)) % 18) + 2;
|
|
}
|
|
occupied.add(`${column}:${row}`);
|
|
return { item, column, row };
|
|
});
|
|
};
|
|
---
|
|
<section class="activity-map" aria-label="Схема координат горячих точек">
|
|
<header class="activity-map__header">
|
|
<div><span class="overline">Режим схемы</span><h3>Горячие точки по водоёмам</h3></div>
|
|
<p>Для каждого водоёма своя координатная сетка `x:y`. Совпадающие точки разнесены, чтобы каждая оставалась доступной.</p>
|
|
</header>
|
|
{groups.map(group => <section class="activity-map__group" aria-labelledby={`activity-map-${group[0].waterbody_slug}`}>
|
|
<h4 id={`activity-map-${group[0].waterbody_slug}`}>{group[0].waterbody} <small>{group.length} {group.length === 1 ? "точка" : "точки"}</small></h4>
|
|
<div class="activity-map__plot" role="group" aria-label={`Координатная схема водоёма ${group[0].waterbody}, точек: ${group.length}`}>
|
|
<span class="activity-map__axis activity-map__axis--x" aria-hidden="true">x →</span>
|
|
<span class="activity-map__axis activity-map__axis--y" aria-hidden="true">y ↑</span>
|
|
{plotPoints(group).map(({ item, column, row }) => <a class:list={["activity-map__point", `activity-map__point--col-${column}`, `activity-map__point--row-${row}`]} href={spotPath(item)} aria-label={`${item.waterbody}, ${item.x}:${item.y}, ${item.fish}`} title={`${item.waterbody} · ${item.x}:${item.y} · ${item.fish}`}><span>{item.x}:{item.y}</span><strong>{item.fish}</strong></a>)}
|
|
</div>
|
|
</section>)}
|
|
</section>
|
|
|
|
<style>
|
|
.activity-map { display: grid; gap: 18px; }
|
|
.activity-map__header { display: flex; justify-content: space-between; gap: 22px; align-items: end; }
|
|
.activity-map__header h3 { margin: 6px 0 0; font: 400 27px Georgia, serif; letter-spacing: -.03em; }
|
|
.activity-map__header p { max-width: 330px; margin: 0; color: var(--text-muted); font-size: 12px; line-height: 1.5; }
|
|
.activity-map__group { display: grid; gap: 10px; }
|
|
.activity-map__group h4 { margin: 0; font: 400 21px Georgia, serif; }
|
|
.activity-map__group h4 small { margin-left: 8px; color: var(--text-muted); font: 700 10px Inter, sans-serif; text-transform: uppercase; letter-spacing: .08em; }
|
|
.activity-map__plot { position: relative; display: grid; grid-template-columns: repeat(20, minmax(0, 1fr)); grid-template-rows: repeat(20, minmax(0, 1fr)); min-height: 470px; overflow: hidden; border: 1px solid var(--border); border-radius: 20px; background: linear-gradient(90deg, color-mix(in srgb, var(--accent-muted) 8%, transparent) 1px, transparent 1px), linear-gradient(color-mix(in srgb, var(--accent-muted) 8%, transparent) 1px, transparent 1px), var(--surface-soft); background-size: 10% 10%; }
|
|
.activity-map__plot::after { content: ""; position: absolute; inset: 8%; border: 1px dashed color-mix(in srgb, var(--accent-muted) 40%, transparent); border-radius: 12px; pointer-events: none; }
|
|
.activity-map__axis { position: absolute; z-index: 1; color: var(--text-subtle); font-size: 10px; font-weight: 750; letter-spacing: .08em; text-transform: uppercase; }
|
|
.activity-map__axis--x { right: 5%; bottom: 3%; }
|
|
.activity-map__axis--y { left: 2%; top: 4%; writing-mode: vertical-rl; transform: rotate(180deg); }
|
|
.activity-map__point { position: relative; z-index: 2; display: grid; align-self: center; justify-self: center; gap: 3px; min-width: 74px; margin: 5px; padding: 8px 10px; border: 1px solid color-mix(in srgb, var(--accent-muted) 45%, var(--border)); border-radius: 12px; background: var(--surface); color: var(--text); text-decoration: none; box-shadow: 0 8px 18px color-mix(in srgb, var(--deep) 11%, transparent); }
|
|
.activity-map__point::before { content: ""; position: absolute; left: 50%; bottom: -6px; width: 10px; height: 10px; border-right: 1px solid color-mix(in srgb, var(--accent-muted) 45%, var(--border)); border-bottom: 1px solid color-mix(in srgb, var(--accent-muted) 45%, var(--border)); background: var(--surface); transform: translateX(-50%) rotate(45deg); }
|
|
.activity-map__point:hover, .activity-map__point:focus-visible { border-color: var(--focus); transform: scale(1.03); }
|
|
.activity-map__point span { color: var(--text-muted); font-size: 10px; font-weight: 750; }
|
|
.activity-map__point strong { max-width: 180px; overflow-wrap: anywhere; font-size: 12px; line-height: 1.2; }
|
|
:global(.activity-map__point--col-1) { grid-column: 1; } :global(.activity-map__point--col-2) { grid-column: 2; } :global(.activity-map__point--col-3) { grid-column: 3; } :global(.activity-map__point--col-4) { grid-column: 4; } :global(.activity-map__point--col-5) { grid-column: 5; } :global(.activity-map__point--col-6) { grid-column: 6; } :global(.activity-map__point--col-7) { grid-column: 7; } :global(.activity-map__point--col-8) { grid-column: 8; } :global(.activity-map__point--col-9) { grid-column: 9; } :global(.activity-map__point--col-10) { grid-column: 10; } :global(.activity-map__point--col-11) { grid-column: 11; } :global(.activity-map__point--col-12) { grid-column: 12; } :global(.activity-map__point--col-13) { grid-column: 13; } :global(.activity-map__point--col-14) { grid-column: 14; } :global(.activity-map__point--col-15) { grid-column: 15; } :global(.activity-map__point--col-16) { grid-column: 16; } :global(.activity-map__point--col-17) { grid-column: 17; } :global(.activity-map__point--col-18) { grid-column: 18; } :global(.activity-map__point--col-19) { grid-column: 19; } :global(.activity-map__point--col-20) { grid-column: 20; }
|
|
:global(.activity-map__point--row-1) { grid-row: 1; } :global(.activity-map__point--row-2) { grid-row: 2; } :global(.activity-map__point--row-3) { grid-row: 3; } :global(.activity-map__point--row-4) { grid-row: 4; } :global(.activity-map__point--row-5) { grid-row: 5; } :global(.activity-map__point--row-6) { grid-row: 6; } :global(.activity-map__point--row-7) { grid-row: 7; } :global(.activity-map__point--row-8) { grid-row: 8; } :global(.activity-map__point--row-9) { grid-row: 9; } :global(.activity-map__point--row-10) { grid-row: 10; } :global(.activity-map__point--row-11) { grid-row: 11; } :global(.activity-map__point--row-12) { grid-row: 12; } :global(.activity-map__point--row-13) { grid-row: 13; } :global(.activity-map__point--row-14) { grid-row: 14; } :global(.activity-map__point--row-15) { grid-row: 15; } :global(.activity-map__point--row-16) { grid-row: 16; } :global(.activity-map__point--row-17) { grid-row: 17; } :global(.activity-map__point--row-18) { grid-row: 18; } :global(.activity-map__point--row-19) { grid-row: 19; } :global(.activity-map__point--row-20) { grid-row: 20; }
|
|
@media (max-width: 720px) { .activity-map__header { display: grid; gap: 8px; align-items: start; } .activity-map__plot { min-height: 390px; } .activity-map__point { min-width: 62px; padding: 7px 8px; } .activity-map__point strong { max-width: 90px; } }
|
|
</style>
|