# Pipeline Sport tracker

Jeden SVG dokument je zdroj pro náhled, digitál i tisk.

## Tok

```text
routes/<id>.json  |  GPX / TCX  |  hash k= polyline
        │              │               │
        ▼              ▼               ▼
   activity.mjs → analyzeTrack() (track.mjs)
        │   cumKm, výškopis, zisk, splity, čas (značky nebo model)
        ▼
   poster-svg.mjs renderPoster({ activity, template, format, layers, accent, photos, mapImage })
        │   1000 × H jednotek, data-role hooky
        ├──▶ studio: inline SVG + twin.mjs (hover, přehrání, kurzory)
        ├──▶ share/embed: inline SVG + twin.mjs, autoplay
        ├──▶ export.mjs svgToPng(): Blob → Image → canvas 300 dpi → PNG
        ├──▶ export.mjs printPoster(): okno s @page → PDF
        └──▶ ukazky/: renderPoster v Node při buildu
```

## Projekce a mapa

`fitToBox` promítá body Web Mercatorem do boxu šablony a vrací kameru
(střed, měřítko). `mapLibreCamera` z ní spočítá `center` a `zoom` pro
MapLibre, takže rastr mapy a vektorová trasa sdílí jednu projekci. `map.mjs`
vykreslí OpenFreeMap positron offscreen, u tmavých šablon invertuje pixely,
a vrátí JPEG data URI, který `renderPoster` vloží jako `<image>` do route
boxu. Export renderuje mapu znovu na 2600 px.

## Designový systém

`makeGrid` (okraj 6,4 %, 12 sloupců, mezera 2,4 %) a škála `TYPE` jsou
společné všem šablonám. `headline` zalamuje titul vyváženě (`wrapLines`
minimalizuje rozdíl šířek řádků a trestá osamocené slovo) a zmenšuje písmo,
dokud se nejdelší řádek nevejde; šířky odhaduje `textWidth` po znacích.
Data mají tři formy: `dataLine` (mono řádek), `dataColumn` (svislý sloupec),
`dataTable` (vlasová tabulka). `photoLayout` vybere podle poměru stran fotky
buď blok přes celou šířku s minimální výškou 60 % (72 % u 9:16), nebo blok na
8 sloupců vedle podpisu trasy. `coverImage` ořezává fotku se zachováním
ohniska `fx, fy`; bez známých rozměrů padá na `preserveAspectRatio`
zarovnání 3×3. `creditEdge` tiskne credit fotky svisle na pravé hraně.

`rotatedHeadline` sází titul do měřeného svislého pole. `captionBlock`
zalamuje mono popisky včetně prostrkání. `editionFooter` vrací horní hranici
dat a tiráže. `diagonalField` kreslí barevný klín, `routeSeal` umístí trasu
do kruhového pole a `elevationOverprint` skládá dva otisky jednoho profilu
s posunem; digitální kurzor sleduje neodsunutý profil. Průhledná druhá barva
vytvoří třetí tón v překryvu. Výchozí řádkování verzálek v Klínu a Soutisku
je 1,1 násobku velikosti písma, aby se česká diakritika nedotýkala řádku nad ní.

## Šablony

Každá šablona je funkce `tpl*(ctx)` v `poster-svg.mjs` vracející SVG
fragment, `routeBox`, `route.camera` a `defs`. Sdílené vrstvy: `routeLayer`
(trasa, značky, start/cíl, kurzor, hit area), `elevationLayer`, `dataLine`,
`dataColumn`, `dataTable`, `splitsTable`, `colophon`, `photoBlock`,
`photoBox` (maska s přechodem), `duotoneFilter`,
`monoFilter`, `dimFilter`. Text se přizpůsobuje šířce přes `fitFontSize`
(odhad šířky znaku podle rodiny).

Registrace má 12 šablon: Linie, Noc, Vrstevnice, Reliéf, Fotka, Galerie,
Číslo, Telemetrie, Klín, Osa, Pečeť a Soutisk. Poslední čtyři mají
`photoMode: optional` a nepoužívají mapu. Při vypnuté vrstvě profilu nebo
chybějícím výškopisu Soutisk bez fotky zvětší trasu do hlavního pole.
Klín, Osa, Pečeť a Soutisk upravují vlastní akcent přes `ensureContrast`.

`SHOWCASE` obsahuje 26 plakátů. Každá ze čtyř kompozic má ukázku s fotkou
i bez ní. První čtyři položky tvoří náhled pod studiem a odkazují hotové PNG
ve 150 dpi z `public/experiments/sport-tracker/showcase/`. Soubor
`docs/concepts.md` v repozitáři popisuje výběr a vizuální kontrolu.

## Digitální dvojče

`twin.mjs` najde v SVG `data-role="route"`, `route-progress`, `cursor`,
`route-hit`, `elev-box`, `elev-cursor`, `elev-hit` a `data-split`. Hover
nad trasou hledá nejbližší bod po délce path (hrubý krok + ternární
zjemnění), hover nad profilem bere poměr X. `apply(ratio)` posune kurzor,
odhalí `route-progress` přes `stroke-dashoffset`, zvýrazní splity a zavolá
`onProgress` s km, výškou a časem. `play()` animuje 0 → 1 s easingem.

## Stav a sdílení

`state.mjs` kóduje stav do hashe: `a` aktivita, `t` šablona, `f` formát,
`l` bitová maska vrstev, `c` akcent (hex nebo `auto` = z hlavní fotky),
`i` počet, `o` pořadí a `fp` ohniska fotek presetu, `n s h` texty. Vlastní trasa se
zjednoduší na 240 bodů a jede jako `k` (Google polyline, přesnost 5) a `e`
(delta výškopis), plus `m x p n s h d q`. Fotky do hashe nejdou. Studio
ukládá poslední hash do `localStorage`.

## Export

`inlineFontCss()` stáhne `fonts/fonts.css` a nahradí každé `url()` data URI,
`renderPoster` ho vloží do `<style>`. PNG: `exportPixels(format, 300)`
s limitem 10 000 px na delší hranu. PDF: nové okno s `@page { size: W H;
margin: 0 }` a `window.print()` po `document.fonts.ready`.

Požadavek na CSS posílá `Accept: text/css`. Vite bez této hlavičky vrací
javascriptový modul pro vložení stylů; takový obsah v exportním `<style>`
neplatí a PNG použije náhradní písmo. `tests/fonts.test.mjs` ověřuje typ
odpovědi, vložení všech osmi fontových souborů a opakované použití CSS.

## Fotky presetů

`scripts/find-photos.mjs` hledá na Wikimedia Commons kandidáty s volnou
licencí (CC0, CC BY, CC BY-SA, public domain) a krajinným poměrem stran.
`scripts/photo-sources.mjs` drží vybrané soubory, `scripts/fetch-photos.mjs`
je stáhne, ImageMagick je převede na sRGB JPEG 1600 px bez metadat a zapíše
`photos/manifest.json` s autorem, licencí a zdrojem plus `photos/NOTICE.md`.
`presets.mjs` odkazuje soubory, `activity.mjs` z manifestu skládá credit do
patičky. Studio při exportu převádí URL fotek na data URI, aby PNG obsahoval
i fotky.

## Fixture tratí

`scripts/build-routes.mjs` čte `scripts/preset-sources.mjs` (waypointy,
profil), volá OSRM (`bike`, `foot`), BRouter (`hiking-mountain`) nebo
interpoluje vodní waypointy, zjednoduší RDP na 420 bodů, doplní EU-DEM
výškopis po 100 bodech s pauzou 1,1 s a zapíše `routes/<id>.json` a
`manifest.json`. OSRM demo nezná horské pěšiny, proto Sněžka, Pravčická,
Ještěd, Lysá hora a Radhošť jedou přes BRouter.
