From 758d5d58fb0f31ca866677b611464c7975648c00 Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Tue, 18 Aug 2026 21:27:09 +0000 Subject: [PATCH 1/2] docs: add deep audit of SLASHED vs Automatic.css v4.0.0 --- docs/audit-slashed-vs-acss-v4.md | 651 +++++++++++++++++++++++++++++++ 1 file changed, 651 insertions(+) create mode 100644 docs/audit-slashed-vs-acss-v4.md diff --git a/docs/audit-slashed-vs-acss-v4.md b/docs/audit-slashed-vs-acss-v4.md new file mode 100644 index 00000000..4429daf8 --- /dev/null +++ b/docs/audit-slashed-vs-acss-v4.md @@ -0,0 +1,651 @@ +# SLASHED — dogłębny audyt frameworka i konfiguratora, porównanie z Automatic.css v4.0.0 + +> Dokument roboczy z sesji przeglądu. Źródła: kod frameworka (`core/`, `optional/`, +> `scripts/`), konfigurator (`configurator/src/**`), dokumentacja projektu +> (`docs/*.md`, `docs/*.json`), oraz oficjalna dokumentacja ACSS 4.x +> (docs.automaticcss.com). Wszystkie liczby zostały przeliczone z rzeczywistego +> kodu (grep/node/jq), nie z deklaracji w README. + +## 0. Skrócona ocena + +SLASHED jest technicznie bardzo solidny w warstwie *procesu* — 15 nazwanych +`@layer`ów bez ani jednej reguły poza warstwą, ~20 skryptów CI-gate, rejestr +tokenów ze stabilnymi ID i tombstone'ami usunięć. To jest rzadkie i dobrze +wykonane. Największe realne braki nie leżą w dyscyplinie, ale w **powierzchni +funkcjonalnej** (czego ACSS v4 ma, a SLASHED nie ma) i w **głębi +konfiguratora** (część kontrolek jest płytsza niż tokeny, które reprezentują, +a część głębsza niż to, co framework faktycznie robi). + +Poniżej: (1) czego brakuje funkcjonalnie względem ACSS v4, (2) błędy/konflikty +w samym frameworku CSS, (3) błędy/konflikty w konfiguratorze, (4) konkretna +lista poprawek w kolejności priorytetu. + +--- + +## 1. Czego SLASHED nie ma, a ACSS 4.0 ma (realny gap funkcjonalny) + +Poniższa lista to funkcje ACSS 4.x, które są rzeczywiście używane w produkcji +i nie mają odpowiednika w SLASHED. Pozycje pogrubione = najbardziej wartościowe +do rozważenia; nie oznacza to "skopiuj 1:1", tylko że pokrywają realną potrzebę. + +### 1.1 Kolor i tło + +- **Auto Color Relationships** — w ACSS użycie klasy `.bg--ultra-dark` na + sekcji automatycznie przełącza kolor tekstu, nagłówków, linków i styl + przycisków wewnątrz niej. SLASHED ma odpowiednik częściowy: auto-contrast + `--sf-color-text--on-*` (bardzo dobra, "branchless" implementacja przez + `sign()`), ale **działa tylko dla tekstu na konkretnym kolorze**, nie ma + mechanizmu "zmiana tła sekcji → automatyczna zmiana stylu przycisku/linku + wewnątrz". To jest różnica jakościowa: ACSS wiąże to z klasą kontekstową + (`.bg--*`), SLASHED nie ma klas `.sf-bg--*` w ogóle — tło ustawia się tylko + przez zmienne, bez żadnego mechanizmu propagacji na potomków. +- **Surfaces (tła złożone)** — ACSS ma osobny system "Surface": nazwane, + wielokrotnie użyte tła złożone z obrazu/gradientu/wzoru + pozycja + rozmiar + + overlay + animacja + integracja z color-relationship, do 5 zdefiniowanych + slotów. SLASHED ma `--sf-color-surface` (alias `--sf-color-base`, **tylko + kolor**) i osobno `.sf-surface--*` (tonalne warianty kolorystyczne w + `core/macros.css`). Nie ma **żadnego** mechanizmu tła obrazkowego/gradientowego + jako reużywalnego, nazwanego presetu z overlay. To jest realna dziura — + strony marketingowe (hero sections) potrzebują tego bardzo często. +- **Custom Overlays** — ACSS ma do 5 konfigurowalnych overlayów (gradient/obraz/ + blur/blend-mode/animacja/inset), generujące `.overlay-{name}`. SLASHED ma + tylko `.sf-overlay` — jedna klasa w `core/layout.css:146`, brak wariantów, + brak blend-mode, brak animacji, brak per-instance konfiguracji poza jednym + kolorem/opacity. Scrim (`core/tokens.macros.css`) to inny, równoległy, + jednopoziomowy, zahardkodowany na czarno mechanizm — nie zintegrowany z + systemem kolorów OKLCH. +- **Unified Lightness** — przełącznik "wszystkie kolory bazowe mają tę samą + jasność OKLCH" (spójność percepcyjna palety). SLASHED nie ma tego jako + przełącznika — użytkownik musi ręcznie dopasować L każdego `-source-light`. + Łatwe do dodania: jeden dodatkowy token `--sf-palette-unify-l` + `oklch(from + var(--sf-color-x-source-light) var(--sf-palette-unify-l) c h)`. +- **Transparencies przez `color-mix()`** — ACSS 4 świadomie *usunął* predefiniowane + tokeny transparencji na rzecz `color-mix()`/relative color w locie. SLASHED + robi to *odwrotnie*: ma 30 zahardkodowanych tokenów `-a5/-a10/-a30/-a50/-a80` + (tylko dla 6 rodzin, nie dla statusów) i **zero** użyć `color-mix()` w całym + kodzie. To nie jest "brak funkcji" per se, ale jest niekonsekwentne: SLASHED + chwali się w README podejściem "runtime, formulaic", a tu robi to samo co + ACSS 3.x robił i co ACSS 4 uznał za przestarzałe podejście. + +### 1.2 Layout + +- **Boxed Layout** — kompletny, jednoprzełącznikowy system "canvas + boxed body + + border + shadow + top margin" do layoutów w stylu oldschoolowych, ramkowanych + stron. SLASHED nie ma nic podobnego — trzeba by to złożyć ręcznie z tokenów + layoutu. Niski priorytet (niszowa funkcja), ale zero-effort do dodania jako + jedna klasa `.sf-boxed` + kilka tokenów. +- **Variable Grid** — grid, który *nie* zależy od breakpointów, tylko wypełnia + wiersz tak długo, aż dzieci osiągną `--min`. SLASHED ma to! — `.sf-grid` + (`auto-fit`/`auto-fill`, `minmax(min(var(--sf-grid-min),100%),1fr)`, + `core/layout.css:333-356`) to funkcjonalny odpowiednik Variable Grid. **Brak + gapu** w tym miejscu — to nie jest gap funkcjonalny, tylko wart odnotowania: + SLASHED ma tu realną parytet. +- **Masonry (CSS `columns`)** — ACSS ma `?columns` recipe z `column-count`/ + `column-width`, ruled columns. SLASHED **nie ma żadnego** mechanizmu CSS + Columns / masonry — `grep -n "column-count\|columns:"` w `core/`+`optional/` + zwraca zero trafień poza `grid-template-columns`. To jest prawdziwa dziura: + masonry to popularny wzorzec (galerie, karty o różnych wysokościach) i + natywny CSS `columns` jest tani do zaimplementowania (kilka tokenów + + 1 klasa `.sf-columns` + modyfikator `--fit`). +- **Content Width Safe** (`min(var(--content-width), calc(100% - + var(--gutter)*2))`) — pojedynczy, "bezpieczny" token łączący content-width z + gutterem dla elementów **poza** sekcją/kontenerem. SLASHED ma + `--sf-content-width` i `--sf-gutter` jako osobne prymitywy oraz `.sf-center`, + ale **nie ma** jednego tokenu/klasy odpowiadającej `content-width--safe` — + użytkownik poza `.sf-container`/`.sf-content-grid` nie ma gotowego sposobu na + "content width + gwarantowany gutter" bez ręcznego `calc()`. Łatwy do + dodania alias. +- **Inverted Radius Framework** — nietrywialny, ale unikalny w ACSS: technika + "wyciętego rogu" (radius, który zagina się w stronę rodzica) realizowana + przez pseudo-elementy + box-shadow, konfigurowalna przez atrybuty `data-*`. + SLASHED nie ma tego wcale. Wysoki koszt implementacji, niski/średni priorytet + — rzadko potrzebne, ale gdy jest potrzebne, jest bardzo trudne do zrobienia + ręcznie; framework, który by to miał, wygrywałby konkretne case'y designerskie. +- **Header Height / Scroll Offsets „breakpoint-less"** — SLASHED **ma** to + (`--sf-header-height` fluid, `--sf-sticky-offset`, `scroll-padding-block-start` + w `core/reset.css:16`) — to jest realny parytet, wręcz SLASHED robi to + "fluid" (ACSS 4 też przeszedł na to podejście, "based on fluid heading + height" — dokładnie ten sam kierunek). + +### 1.3 Typografia + +- **Text/Heading Line Length caps (`ch`)** — ACSS pozwala ustawić `max-width` + w `ch` per rozmiar tekstu/nagłówka z panelu dashboardu. **SLASHED to już ma** + w CSS (`--sf-text-*-max-width`, `--sf-h*-max-width`, `core/tokens.css:1066+`, + `:1589+`) i **konfigurator to eksponuje** (`TypographyPanel.svelte:869+`, + `876+`). To jest realny parytet — dobra wiadomość, nie trzeba nic dodawać. +- **Custom self-hosted fonts z generowanym `@font-face`** — ACSS ma UI do + wgrywania plików fontów i generowania `@font-face` (w tym fallbacki, + variable fonts, `font-display`). SLASHED **nie generuje żadnego CSS fontów** + — to naturalna konsekwencja bycia frameworkiem bez build-time/dashboardu + (font-face trzeba pisać osobno), ale jest to prawdziwy brak funkcjonalny + wobec ACSS, który *ma* dashboard. Nie da się tego dodać bez panelu + administracyjnego — więc to nie jest coś do "naprawienia" w CSS, ale warto + rozważyć **prostą sekcję w configuratorze**, która generuje boilerplate + `@font-face` do skopiowania (czysto generatywna funkcja UI, nie wymaga + zmiany frameworka). +- **Auto-BEM / t-shirt-size do numeric width conversion** — nieistotne dla + SLASHED (BEM-first z założenia). + +### 1.4 Efekty / interaktywność + +- **Effects — pełny system `.on-hover--*` / `.on-enter--*` / `.on-exit--*` / + `.on-visible--*`** — ACSS ma jednolitą taksonomię efektów z kompozycyjnością + (`on-enter--fade on-enter--float on-enter--grow` na jednym elemencie) i + stagger przez `sibling-index()`. SLASHED ma **fragmenty** tego rozproszone + po plikach: `.sf-hover-*` (utilities, 6 klas), `.sf-entrance--*`/`.sf-exit--*` + (motion, ~11 klas), `.sf-stagger` (motion). Braki wobec ACSS: + - **brak kompozycyjności udokumentowanej explicite** — SLASHED *technicznie* + pozwala dać dwie klasy `.sf-entrance--fade` + coś innego, ale nie ma + drugiego wymiaru efektu do złożenia (np. "fade + grow" jednocześnie) — + `.sf-entrance--*` to jedna klasa na jedną transformację, nie da się + złożyć grow+fade bez własnego CSS. + - **brak `.on-visible` (IntersectionObserver-backed)** — wszystko w SLASHED + jest CSS-only (`animation-timeline: view()`), co jest "bardziej + deterministyczne", ale traci możliwość "animuj raz i zostań" (one-shot) + bez JS, które niektóre providery CMS wymagają dla starszych silników. + Utrata pokrycia w Firefox jest jawnie dokumentowana w kodzie + (`core/motion.css:76-80` — brak fallbacku na Firefox to świadoma decyzja). + - **brak Exit Effects na scroll dla shadow/glow/filter** — ACSS ma warianty + filter/shadow/opacity w hover; SLASHED `.sf-hover-*` ma tylko transform + (grow/shrink/float/sink/slide) — **nie ma `.sf-hover-shadow`, + `.sf-hover-glow`, `.sf-hover-brighten`, `.sf-hover-fade`**. To jest + konkretna, łatwa do domknięcia dziura (tokeny box-shadow i filter już + istnieją w systemie, potrzeba tylko klas `:hover`). +- **Ribbons** — narożne "ribbon" etykiety (np. "Sale"), z tokenami offset/ + width/padding/bg/tekst/shadow. SLASHED nie ma tego wcale. Niska częstość + użycia ogólnie, ale zerowy koszt implementacji (to jest dosłownie jedna mała + klasa + zestaw tokenów, podobna technicznie do już istniejącego `.sf-scrim`). +- **Gradient Fades (`.fade--{axis}`)** — ACSS ma uniwersalny mixin/klasę do + zanikania krawędzi elementu w tło (top/right/bottom/left/block/inline). + SLASHED ma **coś podobnego, ale gorzej pokryte**: `.sf-overflow-fade(--top/ + --bottom/--left/--right/--block/--inline)` w `core/macros.css` robi + identyczną rzecz przez `mask-image`. To jest w praktyce parytet + funkcjonalny — SLASHED go już ma, tylko nazwany inaczej (`overflow-fade` vs + `fade`). Dobra wiadomość, nie trzeba dodawać. +- **External Link Indication** — automatyczne oznaczanie linków `target=_blank` + wizualnie i dla czytników ekranu, z `:has()`-based wykluczeniami. SLASHED ma + `.sf-link-external` (manualna klasa) — **ale nie ma automatycznego + wykrywania `target="_blank"` lub linku do innej domeny**; użytkownik musi + ręcznie dodać klasę. To jest różnica: ACSS robi to "automatycznie" (opt-in + w dashboardzie, ale bez ręcznego oznaczania każdego linku), SLASHED wymaga + ręcznej klasy na każdym linku. Dla frameworka bez JS/build-time trudno to + zautomatyzować bez atrybutowego selektora `a[target="_blank"]::after` — co + jest *technicznie* możliwe do dodania w czystym CSS i nie wymaga JS. +- **Clickable Parent / Focus Parent** — SLASHED **ma** to + (`.sf-clickable-parent`, `.sf-focus-parent`, `core/accessibility.css:204+`) — + parytet potwierdzony, nawet lepiej zaimplementowany bez potrzeby SCSS + mixinów (ACSS wymaga SCSS dla wersji poza recipe). + +### 1.5 Cienie / filtry + +- SLASHED ma `--sf-shadow-{xs..xl}`, `--sf-text-shadow-*`, `--sf-drop-shadow-*` + — trzy typy cieni, analogiczne do ACSS (Box/Text/Drop Shadows). **Parytet + koncepcyjny potwierdzony.** Różnica: ACSS pozwala **nazwać** dowolny slot + (`--box-shadow-subtle` zamiast `--box-shadow-1`); SLASHED trzyma się t-shirt + sizes bez możliwości przemianowania — drobna różnica UX w dashboardzie ACSS, + nieistotna dla frameworka bez JS. + +### 1.6 Ikony + +- **Icon Framework** — kompletny system (boxed/naked, dwa motywy jasny/ciemny, + listy ikon, rozmiary, hover) sterowany atrybutem `data-icon`. SLASHED ma + **tylko rozmiary** (`--sf-icon-{xs..2xl}`, `.sf-icon--*`, `core/layout.css: + 378-399`) — **brak "boxed" wariantu** (padding+border+bg+radius jako + jedna przełączana forma), **brak systemu list ikon** (`icon-list`), **brak + motywu light/dark per-ikona** niezależnego od theme strony. To jest + realna, średniej wagi dziura — strony z listami "feature + ikona" są + bardzo częste, a SLASHED nie ma gotowego wzorca na "ikona w okrągłym tle + z kolorem statusu", trzeba komponować ręcznie z `.sf-box` + `.sf-icon`. + +### 1.7 Formularze i dostępność + +- **Smart Spacing** — automatyczne zerowanie marginesów w elementach rich-text + i re-aplikowanie inteligentnego odstępu tylko między sąsiadującymi + elementami (`:not(:first-child)`), z osobnymi tokenami per typ elementu + (nagłówki, paragrafy, listy, zagnieżdżone listy, figury, blockquote). SLASHED + ma częściowy odpowiednik w `.sf-flow`/`.sf-prose` (`core/macros.css`, + `core/tokens.macros.css`) — flow-spacing owszem istnieje + (`--sf-flow-space`), ale **nie ma granularnych tokenów per typ elementu** + (nagłówek h2 vs h3, zagnieżdżone listy, figury/figcaption) — `.sf-prose` ma + jeden wspólny zestaw, nie 16 tokenów jak ACSS. To jest realny, średni gap: + blogi/CMS-content chcą precyzyjnej kontroli spacingu każdego typu elementu. +- **Automatic Spacing z zero-specificity auto-container/content/grid-gap** — + ACSS potrafi *automatycznie* dodać gap do wszystkich sekcji/kontenerów/ + gridów bez klas, z zerową specyficznością (łatwo nadpisywalne). SLASHED nie + ma nic podobnego — spacing jest zawsze explicite przez klasę (`.sf-stack`, + `.sf-section`). To jest filozoficzna różnica (SLASHED = "Explicit" w samej + nazwie), więc **nie polecam kopiowania tej funkcji** — byłaby niekonsystentna + z deklarowaną filozofią frameworka. Odnotowuję jako świadomą różnicę, nie + jako brak. + +### 1.8 Podsumowanie sekcji 1 — priorytetowa lista realnych braków + +Uporządkowane według (wartość dla typowego use case) × (koszt implementacji +w czystym CSS, bez JS/build): + +1. **Surfaces / tła złożone z overlay** (obraz+gradient+overlay+color relationship) + — brak jest odczuwalny na stronach marketingowych; średni koszt. +2. **Rozszerzenie `.sf-hover-*` o shadow/glow/brighten/fade** — tokeny box-shadow + i filter już istnieją, brakuje tylko klas `:hover`; niski koszt, wysoka wartość. +3. **CSS Columns / masonry** (`.sf-columns`, `--sf-col-count`, `--sf-col-gap`, + `--sf-col-rule-*`) — zero pokrycia dziś; niski koszt. +4. **Icon "boxed" wariant + icon-list** — częsty wzorzec UI (feature lists); + średni koszt. +5. **Granularne tokeny spacing per typ elementu w `.sf-prose`/`.sf-flow`** + (h2..h6, listy zagnieżdżone, figure/figcaption, blockquote osobno) — + średni koszt, wysoka wartość dla treści CMS/blog. +6. **Unified Lightness** przełącznik percepcyjny — niski koszt (jeden token + + `oklch(from ...)`), średnia wartość. +7. **Ribbons** — bardzo niski koszt, niska/średnia wartość. +8. **Content-width-safe alias** — trywialny koszt, drobna wartość. +9. **Boxed layout preset** — niski koszt, niska wartość (niszowe). +10. **Inverted Radius Framework** — wysoki koszt, niska/średnia wartość — nie + priorytetowe teraz. + +--- + +## 2. Błędy i konflikty w samym frameworku CSS (core/optional) + +Zweryfikowane bezpośrednio w kodzie (grep/node, nie tylko dokumentacja): + +### 2.1 Tokeny konsumowane, ale nigdzie niezadeklarowane — **realne bugi** + +``` +core/base.css:118 background: var(--sf-color-code-block-bg, var(--sf-color-code-bg)); +core/base.css:119 color: var(--sf-color-code-block-text, inherit); +``` +`--sf-color-code-block-bg` i `--sf-color-code-block-text` nie są deklarowane +nigdzie w źródle. Framework je "udaje" jako fallback-only hook tokens +(`scripts/hook-tokens.js`), co jest świadomym wzorcem — ale sam mechanizm +gate'u (`check:hook-tokens`) tylko **weryfikuje**, że te dwa są rzeczywiście +takie, nie **wykrywa** nowych. To jest bezpieczne dziś, fragile na przyszłość. + +Podobnie `--sf-icon-size` (`core/layout.css:380`) nie jest deklarowany jako +token domyślny — istnieje tylko jako fallback w `var(--sf-icon-size, +var(--sf-icon-m))`, ustawiany lokalnie przez `.sf-icon--{size}`. To jest +zamierzony wzorzec (lokalna zmienna), ale nie jest wpisany do +`hook-tokens.js` — asymetria w traktowaniu identycznego wzorca. + +### 2.2 Podwójne/konfliktowe deklaracje najważniejszych tokenów kolorów + +`--sf-color-primary` (i analogicznie 9 innych rodzin) jest deklarowany +**sześć razy** w dwóch plikach z **czterema różnymi wartościami** w zależności +od kontekstu (`core/tokens.css:92,390`; `core/themes.css:66,72,104,162` — +LumLocker dark/light + source-dark + source-light). Wszystkie 10 tokenów +`--sf-color-text--on-*` są deklarowane **cztery razy** (dwa pliki, ta sama +formuła `sign()` powtórzona bajt-w-bajt). Framework to wie i gate'uje +(`check:mirrors` — "10 SL-001 dark derivations verified"), ale to wciąż +oznacza: **dwa źródła prawdy dla najważniejszych tokenów w systemie**, a gate +tylko potwierdza, że kopie się dziś zgadzają — nie zapobiega przyszłej +desynchronizacji przy edycji tylko jednego miejsca. + +**Rekomendacja:** scalić `core/themes.css`'s re-declarations do jednego +miejsca (np. `@layer slashed.tokens` z pojedynczą definicją per selektor +`:root`/`[data-theme]`/`[data-lumlocker]`), albo wygenerować `themes.css` +automatycznie ze wspólnego szablonu w buildzie, żeby zniknęła potrzeba gate'u +"czy kopie się zgadzają". + +### 2.3 161 nieużywanych tokenów (21% z 751) — gate informacyjny, nie blokujący + +`node scripts/audit.js --unused` (odtworzone lokalnie) zwraca listę m.in.: +cały `--sf-z-*` (10 tokenów — martwe, bo klasy `.sf-z-*` są **zakomentowane** +w `optional/utilities.css:91-100`), `--sf-safe-*` (4, `env(safe-area-inset-*)` +— brak konsumenta), `--sf-radius-2xl/2xs/3xl/4xl/l/none/outer/pill/xl` (9), +`--sf-text-display-{s,m,l}` (3 — **uwaga: to są główne tokeny "display" skali +typografii, martwe bo core nie ma jeszcze klasy `.sf-display-*`**, tylko +konfigurator je odczytuje przez All-tokens/ClampField), `--sf-tracking-wider/ +-widest` (2), `--sf-transition-{enter,exit,fast,slow,opacity}` (5), +`--sf-gradient-fade--{t,r,b,l}` + `--sf-gradient-surface` (5), `--sf-opacity- +muted`, `--sf-optical-sizing`, `--sf-space-none/px` (2). + +To jest największy pojedynczy problem "sprzątania" we frameworku: **21% +publicznego, semver-zamrożonego API nie robi nic w dostawianym CSS**. +Konsekwencje praktyczne: +- Konfigurator wystawia kontrolki do tokenów, które nie mają żadnego efektu + wizualnego (`--sf-text-display-s/m/l` mają panel w TypographyPanel — user + przesunie slider i **nic się nie zmieni**, bo core nie konsumuje display + scale scale bez klasy `.sf-display-*`, która nie istnieje). +- `--sf-gradient-fade--*` istnieją w tokens.css, ale nic w `core/` ich nie + używa — a `.sf-overflow-fade` (podobna koncepcyjnie funkcja) używa + zupełnie innego mechanizmu (`mask-image` + `--sf-mask-scrim-end`, token + osobny). To wygląda na **dwie niezależne, częściowo zaimplementowane wersje + tej samej funkcji** (jedna martwa, jedna żywa). + +**Rekomendacja:** rozdzielić listę 161 na (a) legalne "public knobs bez +lokalnego konsumenta, bo są przeznaczone do użycia we własnym CSS" (np. +`--sf-safe-*`) i (b) faktyczne sieroty generatora/duplikaty (`--sf-gradient- +fade--*`, `--sf-radius-2xl` itd.). Dla (b): albo podłączyć konsumenta (np. +dokończyć `.sf-display-*` klasy dla `--sf-text-display-*`, odkomentować +`.sf-z-*` w utilities), albo usunąć token i zapisać w `token-renames.json` +jako `removal`. + +### 2.4 `optional/legacy.css` obiecuje funkcję, której nie ma + +Nagłówek pliku: *"Fallbacks for older browser gaps: dynamic viewport units, +focus-visible, scrollbar gutter, **has()**, dvh."* Treść pliku ma **trzy** +`@supports not (...)` bloki (dvh, focus-visible, scrollbar-gutter) — **żadnego +fallbacku dla `:has()`**, choć `core/layout.css` twardo zależy od `:has()` +(`.sf-section--guttered:has(...)`, `core/layout.css:63`). To jest +dokumentacyjny bug: obiecana funkcja nie istnieje w pliku. Do naprawy: albo +dopisać fallback, albo poprawić komentarz nagłówkowy (usunąć "has()"). + +Dodatkowo `optional/legacy.css` **nie jest w żadnym z 4 bundli** +(`bundle.config.json`) — trzeba go linkować ręcznie, co jest udokumentowane w +README, ale sprawia że jest praktycznie niewidoczny/łatwy do przeoczenia. + +### 2.5 `:has()`-based reguła strukturalna w `.sf-section--guttered` jest fragile + +```css +.sf-section--guttered:has(> .sf-content-grid):not(:has(> :not(.sf-content-grid))) +``` +(`core/layout.css:63`) — dodanie **jakiegokolwiek** drugiego dziecka do sekcji +guttered (nawet komentarza-elementu typu `