<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Zápisky – Jakub Kubík</title><description>Technické texty Jakuba Kubíka k jeho projektům: weby pro malé organizace.</description><link>https://jakubkubik.com/cs/zapisky/</link><language>cs</language><lastBuildDate>Sat, 03 Oct 2026 00:00:00 GMT</lastBuildDate><atom:link href="https://jakubkubik.com/cs/rss.xml" rel="self" type="application/rss+xml"/><item><title>Jak vznikl web OZ Juliána: redesign bez WordPressu a s vlastní správou aktualit</title><link>https://jakubkubik.com/cs/zapisky/ozjuliana-bez-wordpressu/</link><guid isPermaLink="true">https://jakubkubik.com/cs/zapisky/ozjuliana-bez-wordpressu/</guid><description>Proč web malého sdružení běží bez WordPressu a databáze, jak funguje správa aktualit s bezpečným přihlášením z telefonu a jak se ověřoval redesign.</description><pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;V administraci hostingu je tlačítko „nastavit DNS“. Kdo na něj klikne, potichu přepíše záznamy, podle kterých
prohlížeč hledá server domény: najednou míří přímo na server hostingu, mimo CDN (síť serverů, která
web doručuje a chrání). Web běží dál, takže si změny nikdo nevšimne. Na ozjuliana.sk se přesně tohle
stalo a změnu neodhalil člověk, ale skript, který po každém nasazení živý web pouze čte. Takových pojistek má web
malého sdružení víc, než by člověk čekal, a tenhle zápisek je o tom, proč tam jsou.&lt;/p&gt;
&lt;p&gt;Občanské sdružení Juliána (slovenská obdoba spolku) celoročně sbírá oblečení, obuv, hračky a potřeby do domácnosti
a rozváží je lidem v nouzi. Web &lt;a href=&quot;https://ozjuliana.sk/&quot;&gt;ozjuliana.sk&lt;/a&gt; pro něj dobrovolnicky dělám a spravuji
od dubna 2026. Zadání zní jednoduše: říct, co sdružení dělá a jak se dá pomoct, a umožnit člověku
bez technického zázemí přidat z telefonu zprávu o sbírce i s fotkami. Čísla ve zkratce jsou
v &lt;a href=&quot;https://jakubkubik.com/cs/projekty/ozjuliana/&quot;&gt;případové studii&lt;/a&gt;; tady je delší příběh.&lt;/p&gt;
&lt;h2 id=&quot;proc-ne-wordpress-a-proc-soubory-json-bez-databaze&quot;&gt;Proč ne WordPress a proč soubory JSON bez databáze&lt;/h2&gt;
&lt;p&gt;Web běží na levném sdíleném webhostingu za CDN. Na serveru je jen PHP: žádné SSH (vzdálená příkazová
řádka), žádný Git, nasazuje se přes FTPS (přenos souborů šifrovaným spojením). WordPress by tam běžel,
ale přinesl by s sebou pravidelné aktualizace jádra a pluginů, databázi a nekonečné pokusy
o přihlášení na všeobecně známé adrese. Sdružení přitom přidá článek s fotkami jednou za pár týdnů.
Pro takovou potřebu se nevyplatí udržovat celý redakční systém.&lt;/p&gt;
&lt;p&gt;Výsledkem je vlastní PHP kód bez frameworku a bez Composeru. Veřejné stránky jsou šablony, které fungují
i bez JavaScriptu. Tři malé skripty jen přidávají animace, lištu pro souhlas s cookies, úvodní clonu s logem
a prohlížeč fotek.&lt;/p&gt;
&lt;p&gt;Databáze není ani pro články. Každý článek je jeden soubor JSON (textový formát pro strukturovaná data),
fotky leží vedle ve složce a při nahrání je zmenší knihovna GD. Dala by se použít i SQLite, jenže
při desítkách článků nic nezrychlí a soubory mají dvě praktické výhody: celý obsah webu se zálohuje
zkopírováním dvou složek a v nouzi se dá článek opravit přes FTP v obyčejném textovém editoru.&lt;/p&gt;
&lt;p&gt;Zápis jde vždy do dočasného souboru, který se teprve potom přejmenuje na cílový, takže přerušené uložení
nenechá napůl zapsaný článek.&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;{
    &quot;slug&quot;: &quot;zbierka-satstva-2026&quot;,
    &quot;title&quot;: &quot;Zbierka šatstva 2026&quot;,
    &quot;status&quot;: &quot;published&quot;,
    &quot;published_at&quot;: &quot;2026-09-18T12:00:00+02:00&quot;,
    &quot;text&quot;: &quot;Ďakujeme všetkým, ktorí prispeli.\n\nZbierka pokračuje každý piatok.&quot;,
    &quot;cover&quot;: { &quot;big&quot;: &quot;uploads/…-big.jpg&quot;, &quot;thumb&quot;: &quot;uploads/…-thumb.jpg&quot;, &quot;alt&quot;: &quot;Triedenie darovaného oblečenia&quot; },
    &quot;gallery&quot;: []
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Článek je vymyšlený, skutečné soubory mají navíc čas vytvoření a poslední úpravy. Obsah ze serveru
každý večer stáhne záloha, která přes FTPS pouze čte a uchovává 30 denních snímků. Při chybě přijde e‑mail.&lt;/p&gt;
&lt;h2 id=&quot;dvoufaktorove-prihlaseni-v-cistem-php&quot;&gt;Dvoufaktorové přihlášení v čistém PHP&lt;/h2&gt;
&lt;p&gt;Administrace článků je jediná část webu, kde se dá jeho obsah změnit, proto má povinné dvoufaktorové
přihlášení: heslo a šestimístný kód z aplikace Google Authenticator. Bez druhého faktoru účet ani nevznikne.&lt;/p&gt;
&lt;p&gt;TOTP, tedy jednorázový kód odvozený od času (norma RFC 6238), je překvapivě malý algoritmus: HMAC
(kryptografický otisk) z tajného klíče a pořadového čísla 30sekundového okna, z výsledku se vybere
šest číslic. Na hostingu bez Composeru je jednodušší napsat ho v čistém PHP ze standardních funkcí,
než ručně nahrávat knihovnu se závislostmi. Implementace se ověřuje proti testovacím hodnotám přímo
z normy a testovací sada bez závislostí běží před každým nasazením.&lt;/p&gt;
&lt;p&gt;Kolem toho je běžná výbava: hesla uložená jako otisk bcrypt, ochranný token proti podvrženému
odeslání (CSRF) v každém formuláři, session s omezenou platností, počítadlo neúspěšných
pokusů a první účet, který se nedá založit bez přístupu k serveru. Konkrétní nastavení tu záměrně
neuvádím; útočníkovi by pomohla víc než čtenáři.&lt;/p&gt;
&lt;h2 id=&quot;administrace-pro-telefon-a-cloveka-bez-technickeho-zazemi&quot;&gt;Administrace pro telefon a člověka bez technického zázemí&lt;/h2&gt;
&lt;p&gt;Obsah se spravuje téměř výhradně z telefonu. Administrace je proto navržená od šířky 320 px: větší
tlačítka a zaškrtávací pole, dotykové cíle alespoň 44 px a vstupní pole s písmem alespoň 16 px, aby iPhone
při ťuknutí stránku nepřibližoval. Funguje bez JavaScriptu, takže ji nerozbije ani starší prohlížeč.&lt;/p&gt;
&lt;p&gt;Formulář má jen to, co je potřeba: nadpis, datum, text, úvodní fotku, galerii, popisky fotek a přepínač mezi
konceptem a zveřejněním. Text je obyčejný text, prázdný řádek znamená nový odstavec. Žádný editor
s tlačítky, který by do článku vkládal skryté HTML. Fotky z telefonu se otočí podle údajů EXIF,
které do nich zapíše fotoaparát, zmenší a uloží bez metadat. Při auditu se ověřilo všech 84 fotek
na živém webu a ani jedna nemá souřadnice GPS.&lt;/p&gt;
&lt;p&gt;Jedno rozhodnutí vzešlo přímo z toho, kdo web obsluhuje. Prohlížeč, ve kterém se už někdo úspěšně
přihlásil, má vlastní počítadlo pokusů. Cizí pokusy o přihlášení tak mohou zablokovat neznámá
zařízení, ale ne telefon, ze kterého se obsah opravdu přidává.&lt;/p&gt;
&lt;p&gt;K administraci patří i krátký návod ve slovenštině. Nejužitečnější rada v něm není technická: první věta
článku má říct kdy, kde, co a kolik, protože z ní vzniká úryvek na kartách i popis pro vyhledávače.&lt;/p&gt;
&lt;h2 id=&quot;lista-pro-souhlas-podle-zakona-452-2021&quot;&gt;Lišta pro souhlas podle zákona 452/2021&lt;/h2&gt;
&lt;p&gt;Slovenský zákon o elektronických komunikacích v § 109 odst. 8 vyžaduje souhlas s ukládáním údajů
v zařízení návštěvníka, pokud nejsou nezbytné pro fungování služby. Web je postavený tak, aby bez souhlasu
nic takového nepotřeboval: veřejné stránky nenastavují žádnou cookie ani nespouštějí session a písma se
načítají z vlastního serveru, ne z Google Fonts.&lt;/p&gt;
&lt;p&gt;Google Analytics se načte až po kliknutí na „Prijať“ (Přijmout). „Prijať“ a „Odmietnuť“ jsou na první vrstvě
rovnocenná tlačítka, volbu lze kdykoli změnit odkazem v patičce a odvolání smaže analytické cookies. Každá
volba se zapíše na server jako doklad o souhlasu: náhodný identifikátor, čas, verze a volba, bez IP adresy.
Výjimkou je vložená mapa Google v kontaktu, která se načte hned; stránka o ochraně osobních údajů
to otevřeně uvádí.&lt;/p&gt;
&lt;h2 id=&quot;redesign-ktery-se-da-zmerit&quot;&gt;Redesign, který se dá změřit&lt;/h2&gt;
&lt;p&gt;V září přišel redesign s pracovním názvem „Pokojná elegancia“ (klidná elegance). Předcházel mu audit staré
verze a výsledky byly střízlivé: 29 různých velikostí písma, hranice vstupních polí s kontrastem 1,24 : 1,
čtyři nálezy nástroje axe pro automatickou kontrolu přístupnosti a navigace, která na některých šířkách
telefonu o pár pixelů přetékala.&lt;/p&gt;
&lt;p&gt;Práce měla dvě fáze. První byl refaktoring s jediným pravidlem: žádná viditelná změna. Společná šablona,
jeden modul kontaktního formuláře, barvy a velikosti jako proměnné.&lt;/p&gt;
&lt;p&gt;Po každém commitu se nová verze porovnala se starou: HTML výstupy 41 scénářů bajt po bajtu na PHP 8.4 i 8.5 a screenshoty pixel po pixelu. Na konci fáze byly výstupy shodné, kromě jedné předem schválené
opravy, a 450 screenshotů (15 stránek × 10 šířek × 3 režimy: běžný, bez JavaScriptu a se sníženým
pohybem) mělo rozdíl 0 pixelů. Celá sada běžela bez administrátorských práv: PHP ve WebAssembly
a Chrome bez okna, ovládaný přes Playwright.&lt;/p&gt;
&lt;p&gt;Teprve potom následoval samotný redesign. Každá zamýšlená odchylka se zapsala předem a porovnání ji
normalizovalo; všechno ostatní muselo zůstat shodné. Takhle se nedá omylem změnit stránka, která se měnit
neměla.&lt;/p&gt;
&lt;p&gt;Výsledek: typografická škála s 12 kroky (audit před redesignem napočítal 29 hodnot), hranice polí s kontrastem
nad 3 : 1, nula porušení podle axe (pravidla přístupnosti WCAG 2, úrovně A a AA) ve 30 bězích na 10 stránkách
a žádné vodorovné přetečení v rozsahu 320–1440 px, v nejrizikovějších pásmech kontrolované po jednom pixelu.
Sekce o poslání sdružení se na mobilu zkrátila z přibližně 1 000 na 450 px a logo v hlavičce telefonu
narostlo z 20 × 20 na 36 × 32 px, takže je konečně čitelné.&lt;/p&gt;
&lt;h2 id=&quot;co-se-pokazilo-a-jak-se-to-opravilo&quot;&gt;Co se pokazilo a jak se to opravilo&lt;/h2&gt;
&lt;p&gt;Audit před redesignem našel v původní administraci chyby, které běžné klikání neodhalí. Apostrof
v nadpisu rozbil potvrzovací okno při mazání, takže se článek smazal bez zeptání. Neplatný znak UTF-8 ve jméně účtu vedl k zápisu prázdného souboru s účty. Na PHP 8.5 navíc zastaralá funkce pro práci
s obrázky vypsala po uložení článku varování a přesměrování se neprovedlo. Všechny tři chyby byly opraveny
a ověřeny ještě před spuštěním.&lt;/p&gt;
&lt;p&gt;Hned po spuštění přišel bezpečnostní audit živého webu ve dvou kolech, druhé z nich se dvěma nezávislými
útočnými revizemi kódu. Přinesl 38 zjištění, ale cestu do administrace bez přihlášení nenašlo ani jedno
kolo. Nejpoučnější zjištění: ze 16 souběžných pokusů se špatným heslem se nezablokoval ani jeden, protože pokus se
započítal až po kontrole hesla. Dnes se pokus započítá pod zámkem ještě před ní. Opravy kódu vyšly
v pěti pull requestech ještě tentýž den.&lt;/p&gt;
&lt;p&gt;Poslední chyba, ta s tlačítkem z úvodu, nebyla v kódu. Doména po jednom kliknutí mířila přímo na server
hostingu a web fungoval dál, jen bez CDN a její ochrany. Prozradil to skript, který po každém nasazení
kontroluje živý web pouze čtením: neprošla mu kontrola certifikátu. Záznamy se vrátily hned následující
ráno. Bez toho skriptu mohl web takhle běžet celé měsíce a nikdo by si toho nevšiml.&lt;/p&gt;
&lt;h2 id=&quot;co-si-z-toho-beru&quot;&gt;Co si z toho beru&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Malé sdružení nepotřebuje redakční systém, potřebuje jeden dobrý formulář. Při desítkách článků mu
stačí i soubory bez databáze, pokud je zápis atomický a záloha automatická.&lt;/li&gt;
&lt;li&gt;Návrh administrace začíná otázkou, kdo ji obsluhuje a na jakém zařízení. Od toho se odvíjí i bezpečnost, například důvěryhodný prohlížeč s vlastním počítadlem.&lt;/li&gt;
&lt;li&gt;Refaktoring bez měření stojí na dojmu. Bajtová a pixelová parita mění větu „myslím, že se nic nezměnilo“
v důkaz.&lt;/li&gt;
&lt;li&gt;Audit se vyplatí dvakrát: na starém kódu před změnami a na živém webu po spuštění. Každý z nich
našel něco jiného.&lt;/li&gt;
&lt;li&gt;Kontrola nasazení, která živý web pouze čte, zachytí i chyby mimo kód.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tlačítko „nastavit DNS“ je v administraci hostingu dodnes. Skript, který na to při příštím
nasazení přijde, taky.&lt;/p&gt;
</content:encoded><category>Weby pro malé organizace</category></item></channel></rss>