Relaunch www.carecom.de jako aplikacji Blazor z wykorzystaniem AI

Krótka relacja z doświadczeń dotycząca przebudowy leciwej już strony carecom.de. Przeniesienie zostało przy tym – oczywiście pod moim nadzorem – w dużej części wykonane przez Claude Code.

Harald Mühlhoff 7 min czytania

Niespełna dziesięć lat temu raz już całkowicie postawiłem tę stronę od nowa – wtedy z React, TypeScript, Redux i backendem ASP.NET Core (por. Tworzenie www.carecom.de). Koncepcja stacku była w 2017 świeża i ekscytująca, ale jak wszystko w świecie webu wyraźnie pokryła się patyną. Czas więc na relaunch – ze wszystkim, co C# i .NET mają do zaoferowania w 2026 roku.

Nowa carecom.de działa teraz jako Blazor Web App na .NET 10, z pre-renderowaniem po stronie serwera i celowo interaktywnymi komponentami tam, gdzie ma to sens (formularz kontaktowy, informacja o plikach cookie, slider zdjęć, mini-Space-Invaders). W tle nie ma już żadnego frameworka JavaScript ani potoku Redux – zamiast tego bezpiecznie typowane C# aż do komponentów UI oraz własny, kompaktowy CSS bez Bootstrapa.

Tyle rzeczy zwykłych. Ekscytujące jest właściwie nie to, co tam stoi, lecz kto to napisał: znacznie ponad 99% kodu – komponenty Razor, CSS, klasy serwisów, konfiguracja – pochodzi od Claude Code, agentowego narzędzia CLI firmy Anthropic. W szeregu sesji wyznaczałem cele i iteracje; agent planował, pisał, testował, korygował i z kolei proponował.

Sposób działania

Zaczęliśmy od krótkiego uzgodnienia wymagań – model hostingu, lokalizacja, framework UI. Bezpośrednio po tym Claude przescrapował starą stronę strona po stronie, wyodrębnił teksty dosłownie i zapisał lokalnie obrazy wraz ze strukturą folderów. W ciągu kilku iteracji powstał z tego kompletny klon, który zarówno wizualnie, jak i treściowo zbliża się do oryginału – a w toku kolejnych sesji zyskał własne ulepszenia.

Komponenty i powłoka układu

W architekturze punkt ciężkości spoczął na komponentach wielokrotnego użytku: SiteHeader, SiteFooter, PageHeaderRow, LanguageSwitcher, RightSidebar, PersonCard, BlogTeaser, ReferenceList, QuoteBlock, Testimonial, PhotoSlider, TextCarousel, CookieConsent, SeoTags, SocialIcons, LatestPosts, SpaceInvaders – wszędzie tam, gdzie ten sam wzorzec pojawiał się w wielu miejscach, został zagęszczony do osobnego komponentu Razor.

W poprzek wszystkich stron treściowych leży powłoka SitePage z nazwanymi slotami: BeforeMain dla elementów pełnej szerokości przed kontenerem (karuzela na stronie głównej, slider Photography), ChildContent jako kolumna główna, Sidebar jako opcjonalne rozszerzenie automatycznego menu podstron oraz Aside jako kompletne zastąpienie standardowego paska bocznego (strona kontaktowa z dwoma PersonCards). Każda strona dziedziczy po PageBase, które centralnie przechowuje procedurę Lang/Effective i tym samym całkowicie oszczędza per-stronowy boilerplate.

Wielojęzyczność – deklaratywnie

Lokalizacja przez prefiks URL (/de/…, /en/…) z przełącznikiem języka w prawym górnym rogu SiteHeadera. Faktycznie dwujęzyczne teksty (ta sama forma DOM, inne słowa) leżą jako bezpiecznie typowane rekordy L(De, En) w centralnym pliku SiteContent.cs, generowane przez generator źródeł z plików XML dla poszczególnych sekcji. SiteContent.Contact.Phone[Effective] dostarcza właściwy wariant – bez balastu IStringLocalizer i bez generowania .resx.

Dla strukturalnych rozgałęzień językowych w znacznikach – całych sekcji tylko-DE, jak blok doradztwa IT na stronie Consulting, albo różnych form DOM dla poszczególnych języków – dostępne są dwa malutkie komponenty Razor: <De> i <En>. Odczytują one aktywną wartość języka przez [CascadingParameter(Name = "Lang")] z kaskady MainLayout, która z kolei zasilana jest bezpośrednio ze ścieżki URL. Zastępuje to dziesiątki bloków @if (Effective == "de") deklaratywnym znacznikiem, który czyta się jak HTML.

Photography

Na stronie Photography działa slider 42 zdjęć, który zajmuje całą szerokość przeglądarki – ale ze stałą proporcją 3:2 pasującą do oryginalnych obrazów 1920×1280 i object-fit: contain. Na zwykłych ekranach widać każde zdjęcie perfekcyjnie dopasowane; na bardzo dużych ekranach max-height: 70vh ogranicza wysokość, a wąskie letterboxowanie po bokach to świadomie zaakceptowany kompromis w zamian za pionowe przycięcie. Podpisy i strzałki nawigacji pojawiają się tylko przy najechaniu kursorem, by zdjęcie stało się rzeczą najważniejszą. Na urządzeniach dotykowych bez możliwości najechania sterowanie pozostaje stale widoczne.

Animacja marki: caredef jako choreografowane SVG

Favicon nie został zaprojektowany ręcznie, lecz wygenerowany bezpośrednio z wordmarku: Claude na podstawie profilu gęstości pikseli wyznaczył obwiednię pierwszego „C" w carecom.png, próbkował dominujący kolor (#10AE5B – zieleń CARECOM) i wyrenderował z tego favikony 32×32, 64×64 i 192×192.

caredef.svg – skojarzenia związane z marką CARECOM

Stary caredef.jpg na stronie marki (Marka CARECOM) jest tymczasem całkowicie przełożony na animowane SVG: wordmark CARECOM® jako osadzony w base64 PNG (forma liter co do piksela, bo żaden zainstalowany font jej nie odtwarza), siedem satelickich pojęć (Competence, Commitment, Communication, Computer, Complete, Competitive, Common Sense) oraz fonetyczny podpis jako prawdziwe elementy tekstowe SVG na oryginalnych pozycjach. Przy wywołaniu logo sprężyście wskakuje do środka (easing sprężynowy z przeregulowaniem), następnie siedem słów sekwencyjnie w takcie 150 ms pojawia się z lekko przesuniętych na zewnątrz pozycji na swoje miejsce, na koniec podpis przylatuje z dołu. prefers-reduced-motion jest respektowane.

Easter egg: Space Invaders w tradycji 6502

Na stronie Informatyk / Computer Scientist poprosiłem Claude'a o zintegrowanie mini-gry Space Invaders – jako nawiązanie do mojego pierwszego projektu w asemblerze 6502 na Apple ][. Rezultatem jest własna gierka na canvasie w czystym JS przeglądarki, w schemacie kolorów strony (niebiescy akcentowo najeźdźcy na czerni stopki, stopniowani według wartości punktowej od jasnych do ciemnych) i z dwujęzycznymi etykietami na canvasie.

Trzy drogi prowadzą tam: kotwica „6502" w tekście wyliczenia języków programowania, własna trasa /pl/play dla udostępnialnego linku bezpośredniego oraz klasyczny kod Konami (↑ ↑ ↓ ↓ ← → ← → B A), który z każdej strony nawiguje do gry.

Izolacja CSS

CSS specyficzny dla komponentów jest konsekwentnie wydzielony: dla każdego Foo.razor istnieje Foo.razor.css, który Blazor opatruje jednoznacznym oznaczeniem zakresu [b-xxxxx]. Dawniej liczący 860 wierszy site.css skurczył się do niespełna 150 wierszy – tokeny projektowe, podstawowa typografia i prawdziwe wzorce przekrojowe. W tych nielicznych miejscach, gdzie reguły muszą przekraczać granice komponentów (kolory kotwic SiteFooter na zagnieżdżonych kotwicach SocialIcons, obramowanie <li> w slocie RightSidebar, reguła szerokości dla renderowanego przez InputText pola wejściowego w ContactForm), zadanie wykonuje ::deep.

Ochrona danych i analityka

Google Analytics 4 jest wbudowany jako prawdziwy opt-in: bez kliknięcia „Akceptuję" w banerze cookie gtag.js nigdy nie jest ładowany, nie są ustawiane żadne pliki cookie, nie są wysyłane żadne dane do Google. Dopiero po wyraźnej zgodzie (art. 6 ust. 1 lit. a RODO) startuje bootstrap, a komponent GoogleAnalyticsTracker przy każdej wewnętrznej nawigacji Blazor wysyła dodatkowe page_view – inaczej przy zmianach tras SPA w Analytics lądowałyby tylko początkowe odsłony strony. Polityka prywatności opisuje ten mechanizm w osobnym rozdziale ze wskazówką o odwołaniu zgody. Komponent SeoTags ustawia dla każdej strony tytuł, opis, canonical, tagi OpenGraph i Twitter Card oraz alternatywy hreflang.

Tryby renderowania i kamienie potknięcia

Ważna lekcja techniczna, która spotkała nas od razu dwukrotnie: CascadingValue ze statycznie renderowanego rodzica nie przekracza automatycznie granicy trybu renderowania do poddrzewa @rendermode InteractiveServer. Skutek: w formularzu kontaktowym etykiety po interaktywnym przejściu nagle pojawiały się po niemiecku, mimo że strona była pod /en/contact; a w mini-grze Space Invaders blok wprowadzający uparcie pokazywał wariant DE, bo konsumenci <De>/<En> wewnątrz interaktywnego komponentu widzieli tylko wartość domyślną. Czysta poprawka w obu przypadkach: Lang jako jawny [Parameter] przekazany z Razor strony, a komponent wewnętrznie ponownie publikuje go dla swoich dzieci przez <CascadingValue Value="@Lang" Name="Lang" IsFixed="true">.

Podsumowanie

To, co dawniej kosztowałoby tygodnie pracy ręcznej – migracja treści, podział na komponenty, wierność co do piksela wobec oryginału, teksty RODO, układ poczty, slider zdjęć, favikony, lokalizacja, animowane SVG marki, gierka z trzema ścieżkami odnajdywalności, pełna izolacja CSS – powstało w przejrzystej liczbie sesji z Claude Code. Mój udział sprowadzał się zasadniczo do wyznaczania celów, przeglądu i sporadycznej korekty kursu; resztę załatwił agent: planowanie, pisanie, budowanie, smoke-testy, dokumentowanie.

Jeśli rozważają Państwo podobny relaunch lub agentowe narzędzia do kodu we własnej firmie: proszę śmiało się ze mną skontaktować!

Harald Mühlhoff

Wystąpił błąd. Załaduj ponownie 🗙

Ponowne łączenie z serwerem …

Ponowne połączenie nie powiodło się – następna próba za s.

Ponowne połączenie nie powiodło się.
Spróbuj ponownie lub załaduj stronę od nowa.

Serwer wstrzymał sesję.

Nie udało się wznowić sesji.
Spróbuj ponownie lub załaduj stronę od nowa.