Nie pomyłka, tylko data ważności. Sprawdziłem własny tekst o CSS i dostałem 8/20 kontra 20/20

przez Łukasz | sie 20, 2026

W grudniu 2025 napisałem, że modele AI nie rozwiązują konfliktów specyficzności i nie wyliczają końcowych wartości CSS. W sierpniu 2026 sprawdziłem to eksperymentem, w którym sędzią jest silnik przeglądarki. Cztery współczesne modele: 79 trafień na 80. Claude 3 Opus, którego w tamtym tekście wymieniłem z nazwy: 8 na 20.


Tekst „Jak AI czyta CSS” jest najczęściej czytaną rzeczą, jaką opublikowałem na webflux.pl. Angielska wersja poszła szeroko. I to właśnie dlatego postanowiłem go sprawdzić — bo im lepiej cytowany jest tekst, tym gorzej działa, jeśli zawiera nieprawdziwe zdania.

Postawiłem tam trzy twierdzenia o tym, czego modele językowe nie potrafią zrobić z arkuszem stylów:

Model AI jednak: nie rozwiązuje konfliktów specyficzności, nie wylicza końcowych wartości, nie renderuje layoutu.

Po ośmiu miesiącach dwa z tych trzech twierdzeń nie bronią się już wobec żadnego z testowanych modeli. Poniżej jest dokładnie to, co zrobiłem, żeby się o tym przekonać — łącznie z narzędziem, którym możesz powtórzyć pomiar na własnych modelach w kilka minut.

Dlaczego sędzią jest przeglądarka, a nie druga opinia

Największy problem z pisaniem o możliwościach modeli polega na tym, że weryfikacja zwykle sprowadza się do „zapytałem i wygląda nieźle”. To nie jest pomiar. To jest wrażenie.

CSS ma tę wyjątkową właściwość, że pozwala tego uniknąć. Dla dowolnego fragmentu HTML ze stylami istnieje dokładnie jedna poprawna odpowiedź na pytanie „jaki jest computed color tego elementu” — i wylicza ją silnik przeglądarki, deterministycznie, w milisekundę. Nie ma miejsca na interpretację, na „w sumie prawie”, na życzliwe czytanie odpowiedzi.

Zbudowałem więc zestaw dwudziestu prób, w którym:

  • model odpowiada pierwszy, bez dostępu do przeglądarki i bez możliwości uruchomienia kodu,
  • odpowiedzi są zamrożone, zanim ktokolwiek pozna prawidłowy wynik,
  • dopiero potem ta sama strona renderuje każdą próbę w ramce 1000×800 i odpytuje silnik: getComputedStyle, getBoundingClientRect, elementFromPoint, checkVisibility,
  • ocena jest mechaniczna — hex zamienia się na rgb, 24 px przechodzi jak 24px, ale brakująca pozycja na liście albo inna liczba to błąd, bez taryfy ulgowej.

Kolejność jest wymuszona przez interfejs, nie przez dobre chęci: wynik przeglądarki jest zakryty do momentu wciśnięcia przycisku, a kolumna dodana po tym momencie dostaje etykietę „po fakcie” i wypada z punktacji. Nie da się dopasować oceny do wyniku, nawet gdyby się bardzo chciało.

Co jest w dwudziestu próbach

Cztery kategorie. Kilka przykładów, żeby było jasne, o jakim poziomie trudności mówimy.

Kaskada. Siedem prób o rozstrzyganie konfliktów. Najciekawsza para to P02 i P03, które różnią się jedną literą:

css
:where(#box) .t{color:teal}  .t{color:orange}   /* wygrywa orange */
:is(#box)    .t{color:teal}  .t{color:orange}   /* wygrywa teal   */

:where() zeruje specyficzność swojego argumentu, więc #box nie liczy się wcale i remis rozstrzyga kolejność. :is() bierze specyficzność najwyższego argumentu, czyli id, i wygrywa mimo wcześniejszej pozycji. Identyczna struktura dokumentu, przeciwny wynik.

Dalej robi się gorzej. Reguła spoza warstw bije każdą warstwę — ale po dodaniu !important po obu stronach porządek się odwraca i to warstwa wygrywa. Wśród samych warstw !important też odwraca kolejność: wygrywa ta zadeklarowana wcześniej, nie później. Trzy próby, ta sama para reguł, trzy różne wyniki.

Wartości wyliczone. Łańcuch em przez trzy poziomy zagnieżdżenia. line-height: 2em u rodzica o rozmiarze 10px, dziecko o rozmiarze 30px — dziedziczy się gotowe 20px, nie 60px. Procent w padding-top liczony od szerokości bloku zawierającego, nie od wysokości. clamp(1rem, 5vw, 2rem) przy oknie 1000px.

Layout. position: fixed wewnątrz przodka z transform — element przestaje być zaczepiony o okno. Element o z-index: 999uwięziony w kontekście stackingu utworzonym przez opacity: .99, przegrywający z sąsiadem o z-index: 1. Ważony flex-shrink, gdzie trzeba podać dwie szerokości co do piksela.

Agent. Jedna próba, ale w praktyce najważniejsza: sześć przycisków ukrytych na sześć różnych sposobów. Które zostają w drzewie dostępności, czyli które agent AI nadal widzi i może kliknąć.

Wyniki

model kaskada (7) wartości (7) layout (5) agent (1) razem
Claude Opus 5 7 7 5 1 20/20
GPT-5.6 7 7 5 1 20/20
Gemini 3.1 Pro 7 7 5 1 20/20
Gemini 3.6 Flash 7 7 4 1 19/20
Claude 3 Opus 2 3 3 0 8/20

Cztery współczesne modele od trzech różnych firm odpowiadają identycznie na dziewiętnaście z dwudziestu prób. Ta jednomyślność jest ważniejsza od samych wyników: moje grudniowe twierdzenia nie przegrywają na styk, tylko rozjeżdżają się z każdym testowanym modelem naraz i w tę samą stronę.

Zdanie o specyficzności i wyliczaniu wartości pada na wyniku 28/28. Zdanie o layoucie pada na 19/20.

I wtedy sprawdziłem model, o którym pisałem

W grudniowym tekście wymieniłem z nazwy Claude 3 Opus. Odpaliłem na nim ten sam zestaw.

8/20.

I to nie jest ogólne „wypadł słabiej”. Wykłada się dokładnie tam, gdzie mój tekst mówił, że modele się wykładają: dwa trafienia na siedem prób ze specyficznością, trzy na siedem z wyliczaniem wartości. Cztery odpowiedzi zasługują na osobną uwagę.

P02 i P03 wyszły zamienione miejscami. Tam, gdzie prawidłowo jest orange, odpowiedział teal. Tam, gdzie jest tealorange. Czyli wie, że :is() i :where() różnią się specyficznością, i ma to dokładnie odwrotnie. To nie jest zgadywanie. To jest odwrócona wiedza — najczystszy możliwy dowód, że model sięgnął po zapamiętane skojarzenie zamiast przejść algorytm kaskady.

W próbie z kontekstem stackingu odpowiedział, że na wierzchu jest element z z-index: 999. Poszedł za liczbą zamiast za regułą tworzenia kontekstu. To jest dosłownie mechanizm, który opisywałem jako „czyta CSS jak dokument”.

W próbie z niepoprawnym var() odpowiedział kolorem czerwonym. W tym dokumencie nie ma czerwonego. Nigdzie. To już nie jest pomyłka w rachunku — to jest wartość wymyślona. Inna klasa błędu niż wszystko, co robią w tym zestawie modele współczesne.

W próbie o drzewo dostępności wskazał dwa przyciski zamiast czterech. Zgubił element ukryty przez clip-path i element wyniesiony poza ekran. Uznał, że skoro czegoś nie widać, to tego nie ma.

Co z mojego tekstu upadło, a co przetrwało

Upadło:

Nie wykonują layout engine, nie budują box modelu, nie wyliczają realnych pikseli.

Dwa pierwsze człony są nadal prawdziwe. Trzeci nie: cztery modele podały co do piksela szerokość elementu po ważonym kurczeniu flexa, wysokość z procentowego paddingu i rozmiar czcionki po trzech mnożeniach em.

AI czyta CSS jak dokument — i rozumie hierarchię poprzez strukturę kodu.

To zdanie nie jest za słabe — wskazuje zły mechanizm. Próby z @layer i !important mają identyczną strukturę dokumentu i odwrotne wyniki. Ze struktury nie da się tego odczytać. Trzeba przejść algorytm kaskady krok po kroku, a modele go przechodzą.

Model nie policzy rozmiaru w pikselach. Ale „zrozumie”, że cena jest ważna, powiększona, wyeksponowana.

Policzy rozmiar w pikselach. Ten przykład był sercem tamtego tekstu i dziś działa przeciwko niemu.

Przetrwało — i to jest najważniejsza część tej całej historii:

AI nie wykonuje CSS. AI rozumie CSS.

Teza tytułowa stoi. Żaden z testowanych modeli nie uruchomił silnika layoutu. Zmieniło się nie to, jak to działa, tylko dokąd sięga.

Przetrwały też w całości sekcje o agentach i o widoczności — i dostały potwierdzenie eksperymentalne, którego wtedy nie miały. opacity: 0, clip-path: inset(100%) i wyniesienie poza ekran nie usuwają elementu z drzewa dostępności. Agent go widzi i może w niego kliknąć. Usuwa tylko display: none i visibility: hidden. Wszystkie cztery współczesne modele odpowiedziały tu poprawnie, przeglądarka to potwierdza, a Claude 3 Opus był jedynym, który się pomylił.

To jest praktyczny wniosek, który przeżył swój tekst: różnica między „ukryte dla oka” a „nieobecne dla agenta” jest realna, mierzalna i większość stron ją myli.

Jedno pudło wśród współczesnych i to, czego nie wygładzam

Gemini 3.6 Flash pomylił się raz, na próbie z position: fixed wewnątrz przodka z transform. Odpowiedział 0, czyli podał regułę z podręcznika: fixed jest zaczepiony o okno. Przeglądarka mówi 150, bo transform tworzy blok zawierający także dla elementów fixed. Ta sama pomyłka wróciła w trzech kolejnych przebiegach.

Ciekawe jest to, kto tę samą próbę trafił: Gemini 3.1 Pro. Ten sam dostawca, ta sama rodzina, wyższa półka. Gdyby chodziło o lukę w danych treningowych Google, Pro poległby razem z Flashem. Granica nie przebiega między firmami, tylko między półkami.

Ale nie jest to równa drabinka i nie zamierzam udawać, że jest. Claude 3 Opus — model, który w całości zrobił 8/20 — trafił tę próbę. Tę samą, na której wykłada się Gemini 3.6 Flash. Postęp modeli nie jest jednostajnym podnoszeniem wszystkich umiejętności naraz i ten jeden wiersz w tabeli mówi o tym więcej niż cała reszta.

Czego ten eksperyment nie udowadnia

Cztery rzeczy, zanim ktoś je wypunktuje w komentarzu.

Claude 3 Opus jest z marca 2024, a mój tekst z grudnia 2025. Udowodniłem, że poprawnie opisałem modele, do których się odwoływałem. Nie udowodniłem, że poprawnie opisałem czołówkę z grudnia 2025 — między jednym a drugim był GPT-4o, o1, Claude 3.7, Gemini 2.0, i żadnego z nich nie sprawdziłem. Uczciwa wersja wniosku brzmi: tekst trafnie opisywał modele, które wymieniał.

N = 20. To jest sonda, nie benchmark. Dwadzieścia prób wystarczy, żeby obalić zdanie „model tego nie potrafi”, bo do tego wystarczy jeden kontrprzykład powtórzony u kilku dostawców. Nie wystarczy, żeby zmierzyć, jak dobre te modele naprawdę są.

Próby układał jeden z testowanych modeli — Claude Opus 5, ten sam, który zdobył 20/20. Broni się przed tym zarzutem tylko jedno: gdyby zestaw był skrojony pod jego wiedzę, dwa konkurencyjne modele od dwóch innych firm powinny wyłożyć się w kilku miejscach. Nie wyłożyły się.

Warunki nie były w pełni jednakowe. Claude 3 Opus dostał wszystkie dwadzieścia prób naraz w jednym oknie czatu, więc widział wzorzec zestawu — to raczej pomaga niż przeszkadza. Mimo tej przewagi zrobił 8/20. Nie sprawdzałem też, jak wyniki zmieniają się przy różnych ustawieniach losowości.

Co z tego wynika dla pisania o AI

Mój grudniowy tekst nie był bzdurą. Był pomiarem bez podanej daty ważności.

To jest, jak sądzę, ogólniejszy problem całego pisania o modelach. Formułujemy zdania w czasie teraźniejszym ciągłym — „AI nie potrafi”, „modele nie rozumieją” — o rzeczach, które zmieniają się co kilka miesięcy. Zdanie prawdziwe w momencie pisania zostaje w sieci na zawsze, jest cytowane, trafia do kolejnych tekstów i do kolejnych modeli, długo po tym jak przestało opisywać cokolwiek istniejącego.

Nie ma na to lepszego lekarstwa niż wracanie i mierzenie ponownie. To nie jest kara doklejona do właściwej pracy — w dziedzinie, która przesuwa się co osiem tygodni, to jest ta praca, tylko widziana z drugiej strony.

Mam teraz trzy punkty pomiarowe: 8, 19, 20. Grudniowy tekst nie jest już pomyłką do odszczekania. Jest pierwszym punktem szeregu czasowego.

Czego ten zestaw jeszcze nie sprawdza

Najciekawszej rzeczy: co robi model, gdy nazwa klasy kłamie o regule. .btn-primary z display: none. .sidebar zajmujący główną kolumnę. .hero-title mniejszy od przypisu.

Mój grudniowy tekst twierdził, że nazwy klas są dla modelu sygnałami semantycznymi i że to na nich opiera zrozumienie strony. Ten eksperyment pokazał, że model potrafi policzyć reguły. Nie pokazał, po co sięgnie, kiedy nazwa i reguła mówią co innego — a to jest dokładnie ta sytuacja, która decyduje o bezpieczeństwie strony przygotowanej dla agentów.

To będzie następny odcinek.

Sprawdź to sam

Narzędzie jest jednym plikiem HTML. Nie wymaga instalacji niczego — otwierasz w przeglądarce, kopiujesz dwadzieścia promptów, wklejasz w okno dowolnego modelu, wracasz z odpowiedziami i klikasz „Rozstrzygnij”. Twoja przeglądarka policzy prawdę tym samym silnikiem, którym policzyłem ją ja.

Jeśli masz dostęp do modelu, którego tu nie ma — starszego, tańszego, lokalnego — jego wynik jest ciekawszy niż wszystko, co opisałem powyżej. Napisz, ile wyszło.

Inference — jak powstaje odpowiedź, token po tokenie

Inference — jak powstaje odpowiedź, token po tokenie

W pierwszym artykule kod z aneksu pokazywał rozkład prawdopodobieństwa: pięciu kandydatów na jeden token, każdy ze swoją liczbą. Zostawiliśmy go tam bez ciągu dalszego. Teraz pytanie, które się wtedy nasuwało: skoro model zwraca rozkład, to kto właściwie wybiera jedno...

Attention — jak model decyduje, na co patrzeć

Attention — jak model decyduje, na co patrzeć

„Zamek był zardzewiały." Przeczytałeś to i już wiesz, że nie chodzi o budowlę. Wiesz dzięki jednemu słowu, które stoi obok. Gdyby zamiast „zardzewiały" było „otoczony fosą", to samo słowo znaczyłoby coś zupełnie innego. W poprzednim artykule zostawiliśmy model...

Embeddingi — czym jest znaczenie dla maszyny

Embeddingi — czym jest znaczenie dla maszyny

Wpisz w dowolną wyszukiwarkę semantyczną „auto", a znajdzie ci dokumenty o samochodach. Nie dlatego, że ktoś ręcznie wpisał synonimy. Te dwa słowa nie mają wspólnego rdzenia, nie mają wspólnej odmiany, a mimo to maszyna traktuje je jako bliskie. To jest jedyne miejsce...