Design systém · v1
Grafy, které vyprávějí léčbu
Editorská teze celého systému zní: „Léčba obezity nikdy nekončí.“ Každá kazuistika obsahuje návraty hmotnosti — a právě ty jsou sdělením, ne chybou. Tento systém překládá tezi do vizuálního jazyka: pokles, návrat, nový zásah, otevřený konec. Publikem je pacient v 11 večer na telefonu, ne lékař. Má odejít s pocitem „to bych mohl být já“, ne „prohrávám soutěž“.
- Next.js (App Router)
- TypeScript
- Tailwind v4
- d3-scale/shape/array
- motion
- vlastní SVG — žádná chart knihovna
Graf je věta, nakreslená
Nestavíme „graf váhy v čase“. Stavíme argument. Každá vrstva grafu existuje jen proto, aby nesla část věty:
| Část věty | Vizuální prostředek |
|---|---|
| „Léčili jsme…“ | pruhy intervencí pod křivkou, sdílená časová osa |
| „…a fungovalo to…“ | křivka se ohne až za začátkem pruhu — kauzalita je vidět |
| „…pak se váha vrátila…“ | návrat vlastní barvou — teplá, ne alarmující; nikdy skrytý |
| „…tak jsme zasáhli znovu…“ | další pruh, další ohyb — rytmus příběhu |
| „…a nekončíme.“ | otevřený kroužek na konci křivky, běžící pruh se šipkou |
Pět pilířů storytellingu
01
Tvar křivky je teze
Zubatost není šum k uhlazení — je to samotné sdělení. Regainy se nevyhlazují, neschovávají a nebarví jako selhání.
02
Kauzalita je pointa
Čtenář musí vidět, že se křivka ohne po začátku intervence. Hover propojení a animace existují jen kvůli tomuhle.
03
Minimum není konec
Každý případ po minimu regainuje. Emoční vrchol nikdy nesmí ležet v nejnižším bodě — příběh končí otevřeně, „léčba pokračuje“.
04
Identifikace, ne obdiv
Default osy je % od výchozí váhy: pacient s 95 kg se najde v „−15 %“, ne v křivce ze 142 kg. Absolutní kg jsou přepínač.
05
Skupina, ne žebříček
Srovnání = kohortní obálka (medián + rozmezí), nikdy pojmenované linky přes sebe. Nikde neexistuje řazení podle výsledku.
Zamčená rozhodnutí (L1–L10)
Tahle rozhodnutí jsou závazná. Pokud si myslíš, že je některé špatně, otevři diskusi před implementací — neodchyluj se potichu.
- L1Veškerý pohyb řídí jeden skalár 0→1. Každá animovaná vlastnost je jeho čistou funkcí.
- L2Trend je hrdina; syrové denní body jsou jen slabý rozptyl za ním.
- L3Default osy Y je % změny od výchozí váhy; kg jsou přepínač.
- L4Osa Y nikdy nezačíná nulou.
- L5Regain úseky jsou viditelné a barevně odlišené — ne červeně, ne skrytě.
- L6Žádný případ se nikdy neřadí proti jinému. Žádný „nejlepší výsledek“.
- L7Srovnání = kohorta (grid + obálka), nikdy překryté pojmenované linky.
- L8Každá intervence nese dva popisky: lidský primární, klinický sekundární.
- L9Mobile-first. Desktop je rozšíření.
- L10Statický stav musí být úplný a správný dřív, než se přidá pohyb.
Barvy
Jediný zdroj pravdy je blok @theme v globals.css. V komponentách nikdy nesmí být hex — jen sémantické třídy (stroke-data, fill-card…). Rebrand pro klienta je pak změna jednoho řádku.
--color-data
#12b2a2
křivka, akcenty, kohortní obálka
--color-chrome
#3d0653
nadpisy, UI, zvýraznění, operace
--color-regain
#d98e32
návraty hmotnosti — teplá, ne alarm
--color-ink
#241f2b
hlavní text
--color-ink-muted
#6f6a78
sekundární text, osy
--color-grid
#eae6ee
mřížka, rámečky
--color-card
#ffffff
karty, chips, pozadí grafu
--color-surface
#fdfcfb
pozadí stránky — světlé (O1)
✓ AnoRegain kreslit --color-regain: teplý apricot čte se „kapitola“, ne „poplach“.
✕ NeČervená pro regain, tmavý režim jako default, nový hex přímo v komponentě.
Typografie a čísla
- Písmo Geist (latin + latin-ext kvůli češtině); mono jen pro kódy.
- Všechna čísla s
tabular-nums— countery při animaci nesmí poskakovat: −20,5 kg · −14,6 % - Formát
cs-CZpřeslib/format.ts: desetinná čárka, delty vždy se znaménkem (+2,1 / −20,5), osy bez desetin. - Datumy: v pruzích a stepperu „11/2017 – 12/2018“, v readoutu měsíc slovem („listopad 2019“), na ose jen roky.
Anatomie grafu — živá reference
Tohle je skutečná produkční komponenta CaseChart s referenčním případem (KAZ-001). Všechno níž je interaktivní: přehrávání, scrub, kapitoly, hover na pruhy, přepínač jednotek.
- trend hmotnosti
- období návratu hmotnosti
- domácí vážení
- měření v ambulanci
- léčba pokračuje
- 2/2019 · plikace žaludku
- 6/2020 · návrat hmotnosti během pandemie
- 12/2022 · postupný návrat hmotnosti – běžná součást léčby
-40,3 kg
celková změna hmotnosti za 8,3 roku (-28,4 %) — a léčba pokračuje
- 1Syrová domácí vážení — rozptyl, 15 % opacita, nikdy v popředí (L2)
- 2Trend — LOESS přes 45denní časové okno, tyrkysově
- 3Úseky návratu — stejná linka, barva regain (L5)
- 4Ambulantní měření — kroužky s fialovým okrajem
- 5BMI osa vpravo — lineární přeškálování kg, jedna křivka, dvě osy
- 6Pruhy intervencí — řádek na kategorii, sdílená osa X
- 7Beat chips — delty, BMI, poznámky; kolizní layout s vodicími linkami
- 8Moment operace — svislá čárkovaná; křivka se ohýbá za ní
- 9Otevřený konec — kroužek „léčba pokračuje“; nikdy plná tečka
- 10Transport — play/pauza, scrub, kapitoly odvozené z intervencí
Trend a vyhlazení
Zdrojová data jsou ~2 000 denních vážení — syrově vykreslená jsou čmáranice. Trend počítá LOESS: lokální lineární regrese s tricube váhami přes časové okno (ne okno po indexech — data mají mezery). Lokálně-lineární fit drží koncové body a zlomy bez zpoždění klouzavého průměru, a zlomy jsou přesně to, kde se vypráví příběh.
Default systému: trajektorie s čitelnými zlomy — návrat má vlastní barvu.
Spojené zvýrazněné body místo falešně hladké křivky. Fallback je automatický — komponenta se rozhodne sama podle počtu měření.
- Default šířka pásma 45 dní — trajektorie, ne vlnovka.
- Pod 20 bodů se vyhlazení automaticky vypne a kreslí se spojené zvýrazněné body (O3). Případ KAZ-005 je toho živou ukázkou.
- Detekce návratů: hystereze nad vyhlazeným trendem — vzestup ≥ 2,5 kg od lokálního minima otevírá úsek, pokles ≥ ½ prahu ho zavírá. Drobné vlnky úsek nerozbijí.
Anotační systém
- Dvojí popisky (L8): primárně lidsky („injekce na hubnutí“), klinicky na hover/tap („Liraglutid 3,0 mg · 3,0 mg 1× denně“). Pacient nesmí potřebovat slovník.
- Hover propojení: najetí na pruh zvýrazní jeho úsek křivky fialovou a ztlumí zbytek. Tohle je jádro layoutu — ohyb za pruhem.
- Kolize chips: preferovaná pozice nad kotvou na trendu; při překryvu se chip posouvá po 22px krocích nahoru, pak pod bod. Posunuté chips dostanou vodicí linku. Překryv není nikdy přípustný — na žádném breakpointu.
- Mobil (L9): pruhy se skládají do svislého stepperu pod grafem — chronologické akty s tečkami podle kategorie, NOTE beats vložené mezi ně. Nikdy jen zmenšený Gantt.
- Bodové zákroky: operace je diamant + čárkovaná svislice; nemá interval, takže nikdy neukazuje „0 kg“ — píše se „jednorázový zákrok“ a efekt nese DELTA beat za ním.
Motion — jeden skalár řídí všechno
usePlayhead vrací jediný MotionValue 0→1 (L1). Neexistuje timeline orchestrace: každá animovaná vlastnost je čistá funkce skaláru, takže scrub vzad nemůže nic rozbít a reduced-motion je prostě „skalár = 1“.
Mapa aktů
| Vlastnost | Čistá funkce času playheadu t |
|---|---|
| odkrytí vrstev dat | clipRect.width = x(t) |
| tečka + counter | x(t), y(trend(t)), text z téže interpolace |
| růst pruhu | width = clamp(x(min(t, end)) − x(start)) |
| puls operace | scale = 1 + 0,5·e^(−(t−t₀)/10 dní) |
| chip | fade končí přesně v momentu beatu |
| souhrn | opacity v aktu 3 (p > 0,96) |
- Autoplay jednou při scrollu do view. Nikdy loop.
prefers-reduced-motion: okamžitě kompletní finální stav, transport se vůbec nevykreslí. Žádná „rychlejší animace“.- Vrchol není minimum. Časování je lineární a příběh pokračuje za minimem k otevřenému konci. Pokud animace nedokáže ukázat „ohyb za intervencí“, škrtá se — jiné ospravedlnění nemá.
Kohorta a browse
Filtry (pohlaví, věk, vstupní BMI, druh léčby) vedou na grid karet + souhrnnou obálku: medián a mezikvartilové rozmezí v % od výchozí váhy, na ose „čas od začátku léčby“. Obálka se kreslí jen tam, kde má data ≥ 3 případy — nikdy nedegraduje na jednotlivé linky.
Jak probíhá léčba u lidí jako vy (8 příběhů)
- střední průběh (medián)
- rozmezí poloviny případů
Karta případu — sparkline je příběh, ne skóre:
Osm let léčby: léky, dvě operace a cesta, která pokračuje
muž · 40 let · 8,3 roku léčby · pokračuje
- Karta nikdy nenese číslo výsledku — grid s deltami je žebříček (L6). Tvar křivky mluví sám: pokles, návraty, otevřený kroužek.
- Neexistuje řazení podle výsledku; pořadí je ediční (dle PID).
- Na klienta jdou jen štíhlé souhrny (~150 bodů trendu na případ), nikdy plná měření.
Datový kontrakt
Zdroj pravdy je zod schéma v data/schema.ts. Reálná měření budou přicházet z platformy Čas pro zdraví; případ je JSON:
{
"id": "stefurak", "pid": "KAZ-001",
"title": "Osm let léčby: …",
"narrative": "… **léčba nikdy nekončí** …", // markdown
"patient": { "sex": "M", "ageAtStart": 40, "heightCm": 169,
"diagnoses": ["DIA", "KARDIO", "OSA"] },
"measurements": [ { "date": "2016-03-01", "weightKg": 142.0,
"source": "CLINIC" }, … ], // CLINIC | HOME
"interventions": [ { "category": "PHARMA", "code": "LIRA-1",
"labelPlain": "injekce na hubnutí", // ← primární (L8)
"labelClinical": "Liraglutid 3,0 mg", // ← sekundární
"start": "2017-11-01", "end": "2018-12-31" }, … ], // end: null = běží
"beats": [ { "type": "DELTA", "from": "…", "to": "…", "text": "−20,5 kg …" },
{ "type": "BMI", "at": "…", "text": "BMI 49,7 – …" },
{ "type": "NOTE", "at": "…", "text": "návrat během pandemie" } ]
}- Odvozené hodnoty se nikdy neukládají. BMI, pásma, delty, kapitoly, regainy — všechno počítá
lib/derive.tsa vyhlazení. Uložená pásma jsou přesně to, co rozbilo klientův Excel. parseCase()vyhazuje čitelné české chyby — špatný JSON se dozví co a kde má špatně.- Beats jsou autorská vrstva (editor rozhoduje, co je úder příběhu); delty per intervence jsou odvozená vrstva (hover). Nemíchat.
Nová kazuistika v 5 krocích: ① JSON podle schématu (měření seřazená, intervence s oběma popisky) → ② 3–8 beats → ③ registrace v data/cases/index.ts → ④ npm test (validace + vlastnosti) → ⑤ vizuální kontrola na 360 px. Stránka i karta vzniknou samy.
Co nikdy nestavíme
✕Žebříček, „nejlepší výsledek“, řazení podle zhubnutí
✕Pojmenované křivky více pacientů v jednom grafu
✕Konfety, trofeje, oslavné efekty
✕Tmavý režim jako default — data o vlastním zdraví nemají číst zlověstně
✕Smyčkující animace
✕Osa Y od nuly — zničí veškeré rozlišení
✕Obalování chart knihovny — vlastní SVG je celý design
✕Ukládání odvozených hodnot do dat
Checklist před merge
- ✓Čitelné na 360 px; žádný překryv anotací na žádném breakpointu
- ✓Viditelný keyboard focus na každém ovládacím prvku; scrub ovladatelný šipkami
- ✓prefers-reduced-motion = kompletní finální stav okamžitě
- ✓Žádný hex mimo @theme; žádné localStorage/sessionStorage
- ✓Všechna čísla tabular-nums, formáty přes lib/format.ts
- ✓Regain viditelný a klidný; konec příběhu otevřený, ne minimum
- ✓npm test zelený (schéma, derive, vyhlazení, vlastnosti seed dat)
- ✓Graf vykreslitelný ve fixních rozměrech bez interaktivního chrome (budoucí PNG export, O5)
Otevřené body do dalších verzí: sekundární metriky (glykémie, tlak — layout slot rezervován, O4) · PNG export pro PowerPoint (O5) · napojení na reálná data z platformy Čas pro zdraví.