.category-image-wrap{container-type:inline-size}
/* Szerokość bierzemy z tej samej zmiennej Journala, której używa jego .container,
   więc blok idzie za ustawieniem motywu zamiast mieć własną wartość. */
#product-category.container{--container-max-width:var(--site-max-width,var(--content-max-width,100%))}
/* Etykiety dodatkowe: treść, kolor i ikona pochodzą z admina modułu Product Extra
   Fields i mają tam zostać — to redakcja sklepu, nie nasza. Ustalamy wyłącznie
   TYPOGRAFIĘ i odstępy, żeby etykieta nie górowała nad wierszem (w bazie siedzą
   z `font-size:15px` inline, czyli większym niż cała reszta pozycji).
   ⚠️ `!important` jest tu konieczne i celowe: styl przychodzi atrybutem `style`
   w treści z bazy, którego zwykłą specyficznością nie da się pokonać. */
.category-image-page .part-extra{display:inline-flex;align-items:center;gap:12px;flex-wrap:wrap;margin:0;color:var(--g700)}
.category-image-page .part-extra div,
.category-image-page .part-extra p{margin:0!important;font-size:13px!important;line-height:1.45;
  display:inline-flex!important;align-items:center}
.category-image-page .part-extra span{font-size:inherit!important}
.category-image-page .part-extra a{color:inherit;font-size:inherit!important;text-decoration:underline}
.category-image-page .part-extra svg{width:12px;height:12px;flex:0 0 auto}
.category-image-page .part-extra .fa{margin-right:3px}
/* Plakietki Product Extra Fields mają własny odstęp między ikoną a tekstem
   (`gap` w znaczniku), więc marginesu z reguły wyżej im nie dokładamy — inaczej
   przerwa robi się dwa razy większa niż w pozostałych etykietach. */
.category-image-page .part-extra .pef-pill .fa{margin-right:0}
.category-image-page .ship-icon{display:inline-flex;vertical-align:-2px;margin-right:4px}
.category-image-page .buttons-wrapper,.category-image-page .button-group,
.category-image-page .cart-group{display:contents}

/* Strona kategorii z diagramem — jeden arkusz dla obu układów i trzech szerokości.
   Paleta i konwencje zgodne z przebudowanymi stronami sklepu (order_info, checkout,
   complaint): skala szarości Tailwinda, firmowy #127da8, akcent #c2410c,
   promienie 4/8/12, dwa stopnie cienia.
   Wartości per sklep wchodzą zmiennymi z ustawień modułu (blok .category-image-page). */
.category-image-page{
  --g50:#f9fafb; --g100:#f3f4f6; --g200:#e5e7eb; --g300:#d1d5db;
  --g400:#9ca3af; --g500:#6b7280; --g700:#374151; --g800:#1f2937;
  --brand:#127da8; --brand-dark:#0e6688; --brand-soft:#e8f3f8;
  --action:#c2410c; --ok:#15803d;
  --sh1:0 2px 8px rgba(0,0,0,.06); --sh2:0 4px 12px rgba(0,0,0,.08);
  --mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,monospace;
  /* Krój KWOT — osobny od kroju numerów. Powód: patrz komentarz nad regułami cen. */
  --cena:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  display:grid; gap:18px; grid-template-columns:1fr;
}
.category-image-page *{box-sizing:border-box}

/* --- rysunek --- */
/* Maksymalna wysokość rysunku przychodzi z ustawień OSOBNO dla wariantu A i B:
   w A rysunek stoi obok listy i może być wysoki, w B leży nad nią i za wysoki
   spycha wszystkie pozycje poniżej ekranu. */
.category-image-page .diagram-card{position:relative;border:1px solid var(--g200);border-radius:8px;
  overflow:hidden;background:#fff;--dia-max:var(--ci-dia-max,58vh)}
/* ⛔ KURSOR MÓWI PRAWDĘ O TYM, CO SIĘ DA ZROBIĆ. Rysunek daje się przesuwać dopiero
   po powiększeniu (`panOnlyWhenZoomed`), więc krzyż strzałek na rysunku w skali 1
   obiecywał gest, który nic nie robił. Klasę `is-zoom` przestawia `wirePanzoom`
   z macierzy przekształcenia. */
.category-image-page .diagram-card img.dia{display:block;width:100%;height:auto;
  max-height:var(--dia-max);object-fit:contain;transform-origin:0 0;cursor:default}
.category-image-page .diagram-card.is-zoom img.dia{cursor:grab}
.category-image-page .diagram-card.is-zoom img.dia:active{cursor:grabbing}
/* ⛔ WARSTWA NIE ŁAPIE WSKAŹNIKA. Leży na rysunku, a rysunek ma przeciąganie i zoom —
   znacznik przechwytujący zdarzenia zjadałby te gesty w losowych miejscach. */
.ref-layer{position:absolute;inset:0;pointer-events:none;
  transform-origin:0 0}
/* ⛔ KLASA NAZYWA SIĘ `ref-mark`, NIE `mark`. Bootstrap motywu ma własną regułę
   `.mark, mark {padding:.2em; background-color:#fcf8e3}` — znacznik dostawał od niej
   kremowe tło i wypadanie 3 px paddingu, czyli zamiast obrysować numer, PRZYKRYWAŁ go
   (zgłoszone 19.08: „zaciemnia na biało tego refa"). Nazwa jednowyrazowa w cudzym
   arkuszu to zawsze loteria. */
/* Trzy stany znacznika, celowo różne SIŁĄ, nie barwą — dzięki temu na rysunku pełnym
   zaznaczeń nadal widać, na co właśnie patrzysz:
     (brak)   niewidoczny; rysunek wygląda jak dotąd
     .is-sel  TRWAŁY — ta naklejka jest w koszyku
     .is-on   WSKAZANY — kursor jest nad tym numerem albo nad jego kafelkiem; przebija
              `.is-sel`, więc ma mocniejsze wypełnienie i poświatę

   ⚠️ Barwa podświetlenia jest BŁĘKITNA, bo numery na rysunku są bordowe. Czerwone
   podświetlenie zlewałoby się z samą cyfrą — ta sama zasada rządzi rysunkami na
   motor-stickers.com. `--brand` (#127da8) okazał się na to za ciemny: obwódka ginęła
   przy ramce numeru, więc bierzemy jaśniejszy odcień tej samej rodziny. */
.ref-layer{--ci-mark:14,165,233;--ci-mark-add:31,170,75;--ci-mark-del:229,72,77}
.ref-layer .ref-mark{position:absolute;box-sizing:border-box;
  border:2.5px solid transparent;border-radius:5px;background:none;padding:0;
  /* ⛔ NIEWIDOCZNE, DOPÓKI KLIENT CZEGOŚ NIE WSKAŻE. Pokazywanie wszystkich obwódek
     naraz „żeby było widać, że są" zostało sprawdzone na motor-stickers.com i okazało
     się szkodliwe: skoro niezaznaczone świecą, zaznaczony wygląda tak samo i ginie
     wśród kilkunastu ramek. Afordancją są SAME NUMERY na rysunku.
     ⚠️ `opacity:0` NIE wyłącza łapania wskaźnika — i o to chodzi: numer na rysunku
     ma być klikalny, zanim cokolwiek się zapali. */
  opacity:0;transition:opacity .12s ease-out,background-color .12s,box-shadow .12s;
  cursor:pointer}
/* W koszyku — spokojne, ale widoczne bez wskazywania. */
.ref-layer .ref-mark.is-sel{opacity:1;
  border-color:rgb(var(--ci-mark));background:rgba(var(--ci-mark),.14)}
/* Wskazany kursorem — z obu kierunków wygląda TAK SAMO, bo to ten sam stan. */
.ref-layer .ref-mark.is-on{opacity:1;
  border-color:rgb(var(--ci-mark));background:rgba(var(--ci-mark),.30);
  box-shadow:0 0 0 4px rgba(var(--ci-mark),.26)}
/* ⛔ `:hover` WYŁĄCZNIE tam, gdzie istnieje kursor. Przeglądarki mobilne przypisują
   `:hover` ostatnio dotkniętemu elementowi i trzymają go do następnego dotknięcia —
   bez tego ograniczenia znaczniki zachowywałyby się jak zaznaczenie jednokrotne. */
@media (hover: hover) {
  .ref-layer .ref-mark:hover{opacity:1;
    border-color:rgb(var(--ci-mark));background:rgba(var(--ci-mark),.30);
    box-shadow:0 0 0 4px rgba(var(--ci-mark),.26)}
}

/* Potwierdzenie dodania i zdjęcia — mignięcie barwą i znaczek w rogu. Bez tego klik
   w numer nie zostawia śladu tam, gdzie klient patrzy: plakietka z ilością jest przy
   kafelku, czyli w drugiej części ekranu. Mignięcie przebija oba stany trwałe. */
.ref-layer .ref-mark.flash-add,
.ref-layer .ref-mark.flash-add.is-on,
.ref-layer .ref-mark.flash-add.is-sel{opacity:1;
  border-color:rgb(var(--ci-mark-add));background:rgba(var(--ci-mark-add),.30);
  box-shadow:0 0 0 4px rgba(var(--ci-mark-add),.25)}
.ref-layer .ref-mark.flash-del,
.ref-layer .ref-mark.flash-del.is-on,
.ref-layer .ref-mark.flash-del.is-sel{opacity:1;
  border-color:rgb(var(--ci-mark-del));background:rgba(var(--ci-mark-del),.28);
  box-shadow:0 0 0 4px rgba(var(--ci-mark-del),.25)}

/* ⛔ ZNACZEK RYSOWANY PASKAMI TŁA, NIE LITERĄ. Centrowanie tekstem zależy od metryk
   fontu: interlinia centruje pole wiersza, a nie sam glif, więc „+" siada poniżej środka
   kółka — i przy każdym kroju siada inaczej. `background-position: center` centruje pasek
   względem pola ELEMENTU, bez udziału metryk. Ta sama sztuczka co na motor-stickers.com. */
.ref-layer .ref-mark.flash-add::after,
.ref-layer .ref-mark.flash-del::after{content:"";position:absolute;
  right:-8px;top:-8px;width:15px;height:15px;border-radius:50%;background-repeat:no-repeat;
  pointer-events:none}
.ref-layer .ref-mark.flash-add::after{background-color:rgb(var(--ci-mark-add));
  background-image:linear-gradient(#fff,#fff),linear-gradient(#fff,#fff);
  background-size:7px 2px,2px 7px;background-position:center,center}
.ref-layer .ref-mark.flash-del::after{background-color:rgb(var(--ci-mark-del));
  background-image:linear-gradient(#fff,#fff);
  background-size:7px 2px;background-position:center}
/* ⚠️ Znacznik SCALONY obejmuje całą parę, bo dla tego rysunku nie mamy zmierzonej
   geometrii połówek. Przerywana ramka mówi „gdzieś tutaj", zamiast udawać precyzję,
   której nie ma. */
.ref-layer .ref-mark.is-scalony{border-style:dashed}
/* ⛔ NA DOTYKU ZNACZNIK NICZEGO NIE OBSŁUGUJE. Jest niewidoczny do chwili wskazania,
   a wskazać nie ma czym — łapałby za to dotknięcia przy przewijaniu strony i przy
   przesuwaniu powiększonego rysunku. Ta sama decyzja co na motor-stickers.com. */
@media (hover: none) {
  .ref-layer .ref-mark{pointer-events:none !important}
}
/* ⚠️ `padding:5px` daje grupie DOKŁADNIE 44 px wysokości (1+5+32+5+1) — tyle, ile ma
   pigułka zwijania po drugiej stronie karty. Przy 4 px wychodziły 42 px i przyciski
   stały o dwa piksele niżej niż sąsiad, co przy dwóch kontrolkach na jednej linii
   od razu widać. Przy okazji cała grupa trafia w minimum pola dotyku. */
.category-image-page .dia-tools{position:absolute;right:10px;bottom:10px;display:flex;gap:4px;
  background:rgba(255,255,255,.95);border:1px solid var(--g200);border-radius:8px;padding:5px;box-shadow:var(--sh1)}

/* ⛔ TA REGUŁA MUSI STAĆ POZA ZAPYTANIEM MEDIALNYM. Zwijanie rysunku dotyczy wyłącznie
   telefonu, więc CAŁA jego oprawa siedziała w `@media` — łącznie z ukryciem. Skutkiem
   było to, że na pulpicie przycisk nie miał ŻADNEJ reguły i rysował się domyślnym stylem
   przeglądarki: wąski pasek przyklejony do lewej krawędzi rysunku (zgłoszone 16.08.2026).
   Stan spoczynkowy komponentu deklarujemy więc bezwarunkowo, a zapytanie medialne tylko
   go WŁĄCZA. */
.category-image-page .dia-zwin{display:none}
.category-image-page .dia-tools .btns{width:32px;height:32px;border-radius:4px;border:1px solid transparent;
  background:none;cursor:pointer;color:var(--g700);font-size:15px;display:grid;place-items:center;
  text-decoration:none;min-width:0;padding:0;position:relative}

/* ⛔ Tu ROŚNIE POLE DOTYKU, A NIE PRZYCISK. Cztery lupy po 44 px zajęłyby 190 z 412 px
   szerokości telefonu i przykryłyby rysunek, o który w tym module chodzi. Dlatego
   przycisk zostaje wizualnie 32-pikselowy, a palec łapie 44 px przez niewidoczną
   nakładkę. Pola sąsiadów stykają się (32 + 4 px odstępu < 44), ale się NIE nakładają
   na środki sąsiadów, więc trafienie nadal rozstrzyga bliższy środek.
   ⚠️ `content` musi tu być, inaczej pseudoelement nie powstaje. */
.category-image-page .dia-tools .btns::after{content:"";position:absolute;
  top:50%;left:50%;transform:translate(-50%,-50%);width:44px;height:44px}
.category-image-page .dia-tools .btns:hover{background:var(--g100);border-color:var(--g200)}
.category-image-page .dia-tools .btns:focus-visible{outline:2px solid var(--brand);outline-offset:1px}
.category-image-page .zoom-range{position:absolute;left:10px;bottom:14px;width:120px}

/* --- zdjęcie motocykla: POZA kadrem rysunku, żeby go nie zasłaniało --- */
.category-image-page .bike-card{display:flex;align-items:center;gap:10px;margin:0 0 10px;
  padding:5px 12px 5px 5px;border:1px solid var(--g200);border-radius:8px;background:#fff;
  cursor:zoom-in;width:fit-content;max-width:100%}
.category-image-page .bike-card img{width:58px;height:40px;object-fit:cover;border-radius:4px;flex:0 0 auto}
.category-image-page .bike-card figcaption{min-width:0}
.category-image-page .bike-card .cc{font-family:var(--mono);font-size:12px;font-weight:700;color:var(--g800);display:block}
.category-image-page .bike-card .hint{font-size:11px;color:var(--g500);display:block}
.category-image-page .bike-card:hover{border-color:var(--brand)}
.category-image-page .bike-card:focus-visible{outline:2px solid var(--brand);outline-offset:2px}

/* --- numerki jako kafelki --- */
.category-image-page .ref-card{border:1px solid var(--g200);border-radius:8px;background:#fff;margin-top:10px}
.category-image-page .ref-card .hd{display:flex;gap:10px;align-items:center;flex-wrap:wrap;
  padding:9px 11px;border-bottom:1px solid var(--g100)}
.category-image-page .ref-card .lbl{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--g500)}
.category-image-page .ref-card .cnt{margin-left:auto;font-family:var(--mono);font-size:11px;color:var(--g500)}
/* ⛔ 16 px NIE JEST TU DECYZJĄ ESTETYCZNĄ. iOS Safari przybliża CAŁĄ STRONĘ przy
   wejściu w pole, którego czcionka jest mniejsza niż 16 px — było 12,5 px, więc każde
   dotknięcie wyszukiwarki referencji szarpało widokiem i trzeba było się cofać.
   Nie da się tego obejść stylem; jedyne wyjście to 16 px albo blokada powiększania
   (a tej nie stosujemy, bo odbiera sterowanie osobom słabowidzącym).
   Wysokość 44 px to minimalne pole dotyku wg Apple (44 pt) i Google (48 dp). */
.category-image-page .ref-filter{width:min(230px,100%);font-family:var(--mono);font-size:16px;
  min-height:44px;padding:6px 10px;border:1px solid var(--g300);border-radius:4px;
  color:var(--g800);background:#fff}
.category-image-page .ref-filter:focus-visible{outline:2px solid var(--brand);outline-offset:-1px;border-color:var(--brand)}
.category-image-page .ref-tiles{display:flex;flex-wrap:wrap;gap:8px;padding:12px}
/* ⛔ 44×44 to minimalne pole dotyku (Apple 44 pt, Google 48 dp), a te kafelki są
   GŁÓWNĄ interakcją tej strony i stoją w siatce z odstępem 8 px — chybienie trafia
   w sąsiedni numer, czyli w inną część motocykla. Zmierzony koszt: boks referencji
   rośnie ze 189 na 201 px, bo kafelki układają się w dwa rzędy. Dwanaście pikseli
   na całą stronę za interakcję, która przestaje mylić palec. */
.category-image-page .ref-tile{font-family:var(--mono);font-size:13.5px;font-weight:700;
  font-variant-numeric:tabular-nums;min-width:44px;height:44px;padding:0 10px;border-radius:8px;
  cursor:pointer;display:inline-flex;align-items:center;justify-content:center;background:#fff;
  color:var(--g800);border:1.5px solid var(--ci-ref-border,#c9a86b);box-shadow:var(--sh1)}
/* Numerek, który siedzi w koszyku — zieleń, ta sama, którą wiersz mówi „na stanie".
   ⚠️ Reguła stoi PRZED stanami przejściowymi (:hover, .is-hi, .is-on) celowo: przy
   równej sile decyduje kolejność, a najechanie i wybór mają wygrywać nad stanem
   trwałym — inaczej kafelek w koszyku przestałby reagować na kursor. */
.category-image-page .ref-tile.is-in-cart{border-color:var(--ok);color:#14532d}
.category-image-page .ref-tile:hover{border-color:var(--brand);color:var(--brand-dark);background:var(--brand-soft)}
/* Dwa różne stany, dwa różne wyglądy:
     .is-hi — podpowiedź przy najechaniu na wiersz, taka sama jak zwykłe :hover kafelka
     .is-on — kafelek WYBRANY kliknięciem, wypełniony
   Wcześniej najechanie na wiersz zapalało `is-on`, więc ten sam gest dawał raz jasny,
   raz ciemny kafelek. */
.category-image-page .ref-tile.is-hi{border-color:var(--brand);color:var(--brand-dark);background:var(--brand-soft)}
.category-image-page .ref-tile.is-on{background:var(--brand);border-color:var(--brand);color:#fff}
.category-image-page .ref-tile:focus-visible{outline:2px solid var(--brand);outline-offset:2px}

/* --- lista części --- */
.category-image-page .parts-card{border:1px solid var(--g200);border-radius:8px;overflow:hidden;background:#fff}
.category-image-page .parts-card .hd{display:flex;justify-content:space-between;gap:12px;align-items:center;
  padding:10px 14px;background:var(--g50);border-bottom:1px solid var(--g200);font-family:var(--mono);
  font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--g500)}
.category-image-page .part-row{display:grid;gap:10px 12px;padding:13px 14px;
  border-bottom:1px solid var(--g100);align-items:start;grid-template-columns:auto 64px minmax(0,1fr);
  grid-template-areas:"badge thumb main" "avail avail avail" "buy buy buy" "alt alt alt"}
.category-image-page .part-row:last-child{border-bottom:0}
.category-image-page .part-row:hover{background:var(--g50)}
.category-image-page .part-row.is-target{background:var(--brand-soft);box-shadow:inset 3px 0 0 var(--brand)}
.category-image-page .part-badge{grid-area:badge;font-family:var(--mono);font-size:13px;font-weight:700;
  min-width:34px;height:30px;display:inline-flex;align-items:center;justify-content:center;
  border:1.5px solid var(--ci-ref-border,#c9a86b);border-radius:4px;font-variant-numeric:tabular-nums;background:#fff}
.category-image-page .part-badge.is-empty{border-style:dashed;border-color:var(--g200)}
.category-image-page .part-thumb{grid-area:thumb;width:64px;height:64px;border:1px solid var(--g200);
  border-radius:8px;background:#fff;overflow:hidden;cursor:zoom-in;padding:0;display:block;position:relative}
.category-image-page .part-thumb img{width:100%;height:100%;object-fit:contain;display:block}
.category-image-page .part-thumb:hover{border-color:var(--brand)}
/* Brak zdjęcia — na tej kategorii 12 z 26 pozycji. Zastępniki z kodem kreskowym
   ukrywamy (to grafika-atrapa, nie zdjęcie części), ale kafelek zostaje, żeby wiersze
   trzymały jedną siatkę. Znak informuje, że zdjęcia po prostu nie ma. */
.category-image-page .part-thumb.is-empty,
.category-image-page .repro-row .r-thumb.is-empty{border-style:dashed;cursor:default;background:var(--g50);
  display:grid;place-items:center}
.category-image-page .part-thumb.is-empty::after,
.category-image-page .repro-row .r-thumb.is-empty::after{content:"\f03e";font-family:FontAwesome;
  font-size:17px;color:var(--g300)}
.category-image-page .part-thumb .qv{position:absolute;left:0;right:0;bottom:0;background:rgba(18,125,168,.92);
  color:#fff;font-size:9px;letter-spacing:.05em;text-transform:uppercase;text-align:center;padding:2px 0;
  opacity:0;transition:opacity .12s}
.category-image-page .part-thumb:hover .qv{opacity:1}
.category-image-page .part-main{grid-area:main;min-width:0}
.category-image-page .part-codes{display:flex;flex-wrap:wrap;gap:5px 10px;align-items:baseline;margin-bottom:3px}
.category-image-page .part-mpn{font-family:var(--mono);font-size:16px;font-weight:700;color:var(--g800);
  text-decoration:none;letter-spacing:-.01em}
.category-image-page .part-mpn:hover{color:var(--brand-dark);text-decoration:underline}
.category-image-page .part-oem{font-family:var(--mono);font-size:13.5px;color:var(--g500);
  margin:0 0 3px;display:block}
/* ⚠️ `vertical-align:middle` jest tu NOŚNE, nie kosmetyczne. Domyślnie plakietka stoi
   na wspólnej linii pisma z numerem obok, a że ma 9 px przy jego 13,5 px, jej środek
   wypadał 2 px NIŻEJ niż środek numeru — mała plakietka „opada" do linii pisma, bo
   duży tekst wychodzi nad nią znacznie wyżej, niż schodzi pod spód.
   ⛔ Zostawiamy `display:inline`. Zmierzone: `inline-block` rozpycha plakietkę
   z 14 na 16,8 px (dochodzi do niej interlinia rodzica), a przesuwanie o stałą
   liczbę pikseli podwyższa cały wiersz. Reszta pozostałego przesunięcia to 0,58 px,
   czyli poniżej progu widzialności — `middle` celuje w połowę wysokości x-ów. */
.category-image-page .tag{font-size:9px;letter-spacing:.08em;text-transform:uppercase;color:var(--g500);
  border:1px solid var(--g300);border-radius:4px;padding:1px 4px;margin-right:5px;font-weight:700;
  vertical-align:middle}
/* Opis techniczny producenta i nasza nazwa handlowa mówią to samo dwoma językami,
   więc dostają JEDEN wygląd. Wcześniej różniły się i rozmiarem, i odcieniem, co przy
   dwóch numerach nad nimi dawało cztery poziomy ważności w jednym wierszu. */
.category-image-page .part-loc,
.category-image-page .part-name{font-size:13.5px;color:var(--g500);margin:0}
.category-image-page .part-loc{margin-bottom:1px}
.category-image-page .part-name a{color:var(--g400);text-decoration:none}
.category-image-page .part-name a:hover{text-decoration:underline}
/* Pasek statusów. Zajmuje szerokość sekcji produktu RAZEM z sekcją ceny — leży pod
   nimi i jest od nich niezależny, więc zwężanie go do kolumny opisu było sztuczne.
   Wszystko w nim płynie w jednej linii: stan, termin wysyłki, etykiety modułów. */
/* ⛔ `min-height` jest tu rezerwą, nie ozdobą: 2 px dopełnienia + 24 px pigułki
   z terminem wysyłki. Pigułka należy się WYŁĄCZNIE pozycjom leżącym na półce, czego
   strona w chwili renderowania nie wie (stan przychodzi osobnym żądaniem), a samo
   zdanie o stanie mierzy 20 px — bez rezerwy wiersze „na stanie" rosły o 4 px
   w trakcie czytania. Rezerwa obowiązuje też PO wypełnieniu, więc pozycja bez terminu
   nie kurczy się z powrotem: skok w dół liczy się do wyniku tak samo jak w górę.

   ⚠️ 26 px, nie 34: te brakujące 8 px to była druga linia pod etykiety statusów,
   którą pusty `.part-extra` tworzył zawsze (`flex-basis:100%` zawija go pod spód,
   więc doliczał 6 px odstępu i 2 px marginesu, mimo zerowej wysokości). Etykiety
   są dziś odsiane listą pomijanych, więc ta linia to było czyste puste miejsce —
   najbardziej widoczne na szerokim ekranie, gdzie pasek stanu kończy wiersz. */
.category-image-page .part-avail{grid-area:avail;font-size:12.5px;display:flex;flex-wrap:wrap;
  gap:6px 14px;align-items:center;padding-top:2px;min-height:26px}
/* Znacznik stanu — wybierany w ustawieniach (`stock_icon`), domyślnie znaczek zgody.
   Kropka była zbyt cicha: na tle etykiet ze znaczkami czytała się jak zabrudzenie,
   a nie jak informacja. Znak rysujemy FontAwesome, tym samym, którego używa reszta strony. */
/* Ikona idzie w parze z tekstem statusu — po podniesieniu tekstu do 14 px została
   przy 12 i wyglądała jak resztka po poprzedniej wersji. Kropki (`\f111`) mają własny,
   celowo mniejszy rozmiar niżej i tej reguły nie dziedziczą. */
/* ⚠️ Glif ikony „na stanie" NIE jest już tutaj — klasę FontAwesome wypisuje JS
   z ustawień modułu (`data-stock-icon`), więc każda ikona z zestawu działa bez
   dopisywania jej kodu do arkusza. Zostają tylko te, które niosą znaczenie i nie
   podlegają wyborowi: zegar przy terminie i kropka przy braku. */
.category-image-page .st-ico{font-family:FontAwesome;font-style:normal;margin-right:6px;
  font-size:14px;line-height:1;display:inline-block;vertical-align:0}
/* ⚠️ Termin i stan to zdanie, na które klient najczęściej patrzy po cenie —
   14 px, nie drobny druk. */
.category-image-page .st-slot{font-size:14px}
.category-image-page .st-in{color:var(--ok);font-weight:600}
.category-image-page .st-eta{color:var(--g700)}
.category-image-page .st-eta .st-ico{color:var(--ci-ref-border,#c9a86b)}
.category-image-page .st-out{color:var(--g500)}
.category-image-page .st-out .st-ico{color:var(--g300)}
/* Kropka „brak" jest z natury drobna — rozmiar zostaje po stronie stanu,
   a sam znak wybiera się w panelu. */
.category-image-page .st-out .st-ico{font-size:9px}

/* Warianty znacznika do wyboru w panelu — klasa ląduje na kontenerze strony. */
.category-image-page .ship{color:var(--g700)}
/* Układ bloku zakupu, trzy wiersze:
     cena (z linią brutto)
     krok ilości + lista życzeń
     przycisk zakupu NA PEŁNĄ SZEROKOŚĆ
   Wcześniej wszystko stało w jednej linii i cena z dopiskiem „incl. VAT" dotykała
   pola ilości. Przycisk rozciągnięty równa się z lewą krawędzią ceny — kolumna
   czyta się wtedy jako jeden blok, a nie trzy przypadkowo zderzone elementy. */
/* Blok zakupu, dwa rzędy:
     cena  |  krok ilości
     przycisk zakupu (wypełnia resztę)  |  lista życzeń
   Krok ilości wraca obok ceny, gdzie stał — spychanie go pod cenę robiło z jednej
   kolumny trzypiętrowy stos. Przycisk zostaje w linii z listą życzeń i wypełnia
   całą szerokość, która została. */
/* Odstęp między kolumną ceny a kolumną kroku ilości jest CIASNY (4px), bo każdy
   piksel oddany tutaj zabiera go sekcji produktu — a to ona niesie MPN, opis
   producenta i nazwę, czyli to, po czym klient rozpoznaje część. */
/* ⛔ Przycisk zakupu ma JEDNO wiązanie: lewą krawędź wspólną z ceną. Prawa jest wolna —
   kończy się dopiero przy liście życzeń, więc PODCHODZI pod krok ilości. Wcześniej stał
   w tej samej kolumnie co cena, przez co miał dokładnie jej szerokość; robiło to z nich
   sztuczną parę i marnowało pas między nimi a sercem.
   Trzy kolumny: ostatnia to sama lista życzeń, krok ilości rozpina się na dwie ostatnie
   i jest dosunięty do prawej, a przycisk zajmuje kolumny 1-2 i sięga aż do serca. */
/* ⛔ Kolumna zakupu pozycji zwykłej i reprodukcji to JEDEN układ, opisany raz.
   Reprodukcja miała własny, dwukolumnowy wariant i przez to jej przycisk „do koszyka"
   kończył się w połowie drogi do serduszka — wyglądał na przycięty. Trzecia kolumna
   o stałej szerokości 36 px jest miejscem serduszka; bez niej przycisk i serduszko
   dzielą się szerokością steppera, która jest od nich znacznie większa. */
.category-image-page .part-buy,
.category-image-page .repro-row .r-buy{display:grid;grid-template-columns:1fr auto 36px;
  gap:8px 6px;align-content:start;align-items:center}
.category-image-page .part-buy{grid-area:buy}
.category-image-page .repro-row .r-buy{grid-area:rbuy}
.category-image-page .part-buy .price-group,
.category-image-page .repro-row .r-buy .price-group{grid-column:1;grid-row:1}
.category-image-page .part-buy .stepper,
.category-image-page .repro-row .r-buy .stepper{grid-column:2/-1;grid-row:1;justify-self:end}
.category-image-page .part-buy .btn-cart,
.category-image-page .repro-row .r-buy .btn-cart{grid-column:1/3;grid-row:2;width:100%}
.category-image-page .part-buy .icon-btn,
.category-image-page .repro-row .r-buy .icon-btn{grid-column:3;grid-row:2;justify-self:end}
/* ⚠️ Stopnie pisma ceny siedzą w zmiennych, bo z TYCH SAMYCH wartości liczy się
   rezerwa miejsca na czas doczytywania (szkielet na końcu arkusza). Wpisane drugi raz
   osobną liczbą rozjechałyby się przy pierwszej zmianie rozmiaru, a rezerwa o złej
   wysokości jest GORSZA niż jej brak: zamiast jednego skoku przy wypełnieniu daje
   dwa — przy pojawieniu szkieletu i przy jego zdjęciu. */
/* ⛔ `min-height` trzyma kolumnę nawet wtedy, gdy cena NIE przyjdzie. Przy błędzie
   odpowiedzi skrypt zdejmuje szkielet i dokłada `.is-quiet`, więc bez tej rezerwy
   pudło zapadałoby się do zera — a skok w dół liczy się do wyniku tak samo jak w górę.
   Wartość wprost z interlinii obu wierszy ceny plus odstęp między nimi. */
/* ⛔ 17 px, NIE 19. Kwota siedzi w torze `1fr` obok stepera i przycisku, więc jej
   szerokość zależy od szerokości karty. Przy czterech cyfrach (`1,968.00 PLN`) waluta
   przechodziła do drugiego wiersza (zgłoszone przez usera 17.08.2026).
   Zmierzone dla najszerszej możliwej kwoty `9,999.00 PLN` — ceny nie przekraczają
   9999,00, więc to jest twardy sufit, a nie założenie:
     19 px → 117 px,  18 px → 106 px,  **17 px → 100 px**,  16 px → 95 px.
   17 px zostawia zapas i nie zjada czytelności. Rezerwa miejsca na czas doczytywania
   liczy się z TEJ SAMEJ zmiennej, więc szkielet zmienia się razem z pismem. */
.category-image-page .price-group{--cl-cena:17px;--cl-cena-sub:11px;--cl-lh:1.42857;
  display:flex;flex-direction:column;gap:1px;align-items:flex-start;
  min-height:calc((var(--cl-cena) + var(--cl-cena-sub)) * var(--cl-lh) + 1px)}
/* ── KWOTY: krój z „karty katalogowej" (`produkt-wizje.html`, `.cena-glowna`) ─────
   ⛔ KWOTA NIE JEST NUMEREM KATALOGOWYM i nie idzie krojem maszynowym. Numer klient
      porównuje ZNAK PO ZNAKU z częścią w ręku — tam mono pomaga. Kwotę czyta się jako
      jedną wartość, a mono robi z niej paragon: szerokie, rozstrzelone cyfry.
   ⛔ NIE IDZIE TEŻ Ralewayem z motywu: proza i liczby mają się różnić, a cyfry Ralewaya
      mają różne szerokości, więc kwoty nie stają w kolumnie.
   Zostaje systemowy sans z makiety + figury tabularne: liczby ustawione w kolumnie,
   bez ściągania kolejnego webfontu (strona nie pobiera na to ani bajta).
   ⚠️ TEN SAM krój i te same figury MUSZĄ być na liście części — kwota ma wyglądać
      identycznie tam, gdzie klient wybiera, i tam, gdzie kupuje. */
/* ⛔ `white-space:nowrap` JEST TU WARUNKIEM POPRAWNOŚCI, nie kosmetyką: bez niego
   przeglądarka łamie wiersz w spacji przed walutą i „1,968.00" zostaje w jednej linii,
   a „PLN" spada do drugiej. `.price-sub` miało tę ochronę od początku, sama kwota nie. */
.category-image-page .price-net,.category-image-page .price-new{font-family:var(--cena);font-size:var(--cl-cena);
  font-weight:700;color:var(--g800);font-variant-numeric:tabular-nums;letter-spacing:-.02em;white-space:nowrap}
.category-image-page .price-old{font-family:var(--cena);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:13px;color:var(--g400);text-decoration:line-through}
.category-image-page .price-sub{font-size:var(--cl-cena-sub);color:var(--g500);font-family:var(--cena);font-variant-numeric:tabular-nums;white-space:nowrap}
/* Krok ilości — WŁASNOŚĆ MOTYWU, nie nasza.
   ⛔ Journal renderuje stepper jako pole z dwoma przyciskami ustawionymi absolutnie
   przy prawej krawędzi (`.stepper button{position:absolute;height:50%;right:0}`),
   z własnymi ikonami, szerokościami i obsługą w `stepper.js` wiązaną po klasach
   `fa-angle-*`. Nasze wcześniejsze `width:30px;height:36px;display:grid` nie zdejmowało
   pozycjonowania absolutnego, więc oba przyciski nakładały się na siebie nad polem
   i wychodziły puste — to był objaw, a nie brak glifu.
   Zostawiamy sterowanie motywowi; ustawiamy tylko wysokość, żeby stepper stał w linii
   z przyciskiem zakupu w gęstym wierszu listy. */
.category-image-page .stepper{--stepper-height:36px;flex:0 0 auto}
/* Przycisk zakupu — dociągnięty do tego, co sklep NAPRAWDĘ ma.
   Zmierzone w przeglądarce na trzech powierzchniach:
     strona produktu       #303940, promień 4px, wys. 40px
     siatka (wyszukiwarka) #406A7A, promień 4px, wys. 32px
     ta strona wcześniej   #c2410c, promień 100px, wys. 45px
   Czyli pigułka 100px NIE była tożsamością sklepu, tylko regułą zapasową `.btn`
   z ustawień skinu, która wygrywała tam, gdzie nie ma reguły bardziej szczegółowej.
   Strona kategorii to listing, więc bierzemy wygląd listingu: kolor #406A7A i promień 4px.
   Ikona koszyka zostaje motywu. `!important` wyłącznie na promieniu — reguła skinu
   ma je po swojej stronie i inaczej nie da się jej pokonać. */
.category-image-page .btn-cart{background:#406a7a;color:#fff;border:1px solid transparent;
  border-radius:4px!important;padding:0 16px;height:36px;min-height:0;font-size:13px;
  font-weight:600;line-height:1;text-decoration:none;min-width:0;gap:7px}
.category-image-page .btn-cart:hover,.category-image-page .btn-cart:focus{filter:brightness(1.08);color:#fff}
.category-image-page .btn-cart::before{font-size:14px}
.category-image-page .icon-btn{width:36px;height:36px;border-radius:4px;border:1px solid var(--g300);
  background:#fff;color:var(--g700);display:grid;place-items:center;text-decoration:none;font-size:14px}
.category-image-page .icon-btn:hover{border-color:var(--brand);color:var(--brand-dark)}
/* ⛔ Brak stanu „wyszarzony przy zerowym stanie": na tym sklepie produkt bez sztuk
   na półce JEST do kupienia, z terminem 5-7 albo 7-14 dni. Wyszarzenie odbierałoby
   sprzedaż tam, gdzie dziś działa. */

/* reprodukcja — pełnoprawna pozycja, nie pasek */
/* ⛔ Kolumny MUSZĄ być te same co w wierszu nadrzędnym, inaczej zdjęcie, opis i cena
   reprodukcji stoją o kilkanaście pikseli w lewo względem oryginału tuż nad nią.
   Nie wystarczy powtórzyć `grid-template-columns`: pudełko ma własną ramkę i własne
   dopełnienie, więc jego zawartość zaczyna się o te wartości dalej. Dlatego wychodzimy
   ujemnym marginesem na całą szerokość wiersza i dobieramy dopełnienie tak, żeby
   ramka i padding zsumowały się DOKŁADNIE do dopełnienia wiersza (13 + 1 = 14 px). */
.category-image-page .repro-row{grid-area:alt;display:grid;gap:8px 12px;
  margin:4px -14px 0;padding:11px 13px;
  background:#fbf7ef;border:1px solid var(--g200);border-radius:8px;
  grid-template-columns:auto 64px minmax(0,1fr);
  grid-template-areas:"rbadge rthumb rmain" "ravail ravail ravail" "rbuy rbuy rbuy"}
.category-image-page .repro-row .r-badge{grid-area:rbadge}
.category-image-page .repro-row .r-avail{grid-area:ravail}
.category-image-page .repro-row .r-thumb{grid-area:rthumb;width:64px;height:64px;border:1px solid var(--g200);
  border-radius:4px;background:#fff;overflow:hidden;padding:0;cursor:zoom-in;display:block}
.category-image-page .repro-row .r-thumb img{width:100%;height:100%;object-fit:contain;display:block}
.category-image-page .repro-row .r-main{grid-area:rmain;min-width:0}
/* ⛔ Barwy dobrane pod próg kontrastu, nie „na oko". Poprzednia obwódka #c9a86b na
   kremowym tle wiersza dawała 2,1:1 przy wymaganych 3:1 dla obrysu elementu — na jasnym
   ekranie plakietka po prostu znikała. Ciemniejszy brąz trzyma 3,7:1, a tekst 7,2:1
   przy wymaganych 4,5:1. Rozmiar 9 → 10,5 px: to etykieta do przeczytania, nie znaczek. */
.category-image-page .repro-badge{display:inline-flex;align-self:start;margin-bottom:5px;
  font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;font-weight:700;
  color:#6b5420;background:#fff;border:1px solid #9a7b3f;border-radius:4px;padding:2px 7px}

/* nakładka powiększenia zdjęcia */
.ci-lightbox{position:fixed;inset:0;z-index:9999;background:rgba(31,41,55,.85);display:grid;place-items:center;padding:24px}
.ci-lightbox[hidden]{display:none}
.ci-lightbox figure{margin:0;background:#fff;border-radius:12px;overflow:hidden;max-width:min(880px,92vw)}
.ci-lightbox img{display:block;width:100%;height:auto;max-height:76vh;object-fit:contain}
.ci-lightbox figcaption{display:flex;justify-content:space-between;gap:12px;align-items:center;padding:10px 12px;
  border-top:1px solid #f3f4f6;font-family:ui-monospace,monospace;font-size:12px;color:#374151}
.ci-lightbox .x{background:#fff;border:1px solid #d1d5db;border-radius:4px;padding:3px 12px;cursor:pointer;color:#374151;font-size:18px;line-height:1}

/* ---- TELEFON (<720) — jedyny stopień, w którym stos jest uzasadniony ---- */
@container (max-width:719px){
  .category-image-page .diagram-card{--dia-max:46vh}
  .category-image-page .part-row{padding:12px 10px;grid-template-columns:auto 56px minmax(0,1fr)}
  .category-image-page .part-thumb{width:56px;height:56px}
  /* Wiersz ma tu dopełnienie 10 px, więc pudełko reprodukcji wychodzi o tyle samo,
     a jego 9 px + 1 px ramki znów daje równo 10. */
  .category-image-page .repro-row{margin-left:-10px;margin-right:-10px;padding-left:9px;padding-right:9px;
    grid-template-columns:auto 56px minmax(0,1fr)}
  .category-image-page .repro-row .r-thumb{width:56px;height:56px}
  .category-image-page .part-buy{justify-content:space-between;width:100%}

  /* ---- rysunek zostaje w polu widzenia ----
     ⛔ Przyklejenie na telefonie MUSI mieć własny przełącznik i własne granice: panel
     ma tu ponad połowę wysokości okna, więc bez ograniczenia zasłoniłby listę, do której
     przewijania właśnie służy.
     ⚠️ `top` NIE jest zerem ani stałą liczbą — nagłówek motywu też jest przyklejony
     i sam wybiera swoją wysokość (u nas zostaje z niego 75 px). Liczy to skrypt i wpisuje
     w `--ci-mob-top`; wartość zapasowa działa, gdyby skrypt nie zdążył.
     ⚠️ `overflow:auto` jest tu potrzebne, bo panel bywa wyższy od przydzielonego mu
     miejsca — wtedy przewija się w środku, zamiast rozpychać przyklejony blok
     na cały ekran. Ten sam zabieg stosuje wariant B na szerokim ekranie. */
  /* ⛔ Przykleja się SAM RYSUNEK — numerki i wyszukiwarka mają odjeżdżać razem z listą.
     Żeby rysunek mógł zostać na ekranie, gdy lista części przewija się pod nim, jego blok
     zawierający musi obejmować CAŁĄ stronę modułu. Dlatego znosimy pudełko panelu
     (`display:contents`) i jego dzieci stają się bezpośrednimi elementami siatki strony;
     przyklejony panel obejmowałby też numerki, a te mają jechać z treścią. */
  .category-image-page.is-sticky-mob .ci-pane{display:contents}

  /* Odstępy przejmuje `gap` siatki strony — własne marginesy dzieci dołożyłyby się do niego
     (zmierzone: 10 px + 18 px = 28 px zamiast 18). */
  .category-image-page.is-sticky-mob .bike-card,
  .category-image-page.is-sticky-mob .ref-card{margin-top:0;margin-bottom:0}

  /* ---- zwijanie przyklejonego rysunku ----
     Pojawia się dopiero, gdy rysunek się przykleił — albo gdy jest już zwinięty,
     bo wtedy jest jedyną drogą powrotu.

     ⚠️ DWA WARIANTY, ROZŁĄCZNE:
      • rysunek widoczny → PIGUŁKA w polu rysunku, po lewej, naprzeciw lup. Nie zabiera
        ani piksela wysokości, a przyklejony rysunek zabiera trzecią część ekranu —
        wcześniej pasek pod rysunkiem dokładał do tego kolejne 46 px.
      • rysunek zwinięty → PASEK na całą szerokość. Karta nie ma już czego pokazywać,
        więc pigułka pływałaby w pustym prostokącie; pasek jest wtedy i mniejszy,
        i czytelniejszy jako „kliknij, żeby wrócić".
     `:not(.is-zwiniety)` jest konieczne, bo obie klasy współistnieją: zwinięta karta
     nadal bywa przyklejona. */
  .category-image-page.is-sticky-mob .diagram-card.is-przyklejony .dia-zwin,
  .category-image-page.is-sticky-mob .diagram-card.is-zwiniety .dia-zwin{display:inline-flex}

  .category-image-page.is-sticky-mob .dia-zwin{align-items:center;justify-content:center;
    gap:8px;min-height:44px;padding:0 12px;border:0;background:var(--g50);
    font-family:inherit;font-size:13px;font-weight:600;color:var(--g700);cursor:pointer;
    -webkit-tap-highlight-color:transparent}

  /* ⛔ `z-index` jest tu NOŚNY, nie kosmetyczny. Powiększanie nakłada na rysunek
     `transform`, a element z transformacją maluje się w warstwie elementów
     pozycjonowanych — bez tego powiększony rysunek wychodził na przycisk. */
  .category-image-page.is-sticky-mob .diagram-card.is-przyklejony:not(.is-zwiniety) .dia-zwin{
    position:absolute;left:10px;bottom:10px;z-index:2;
    /* ⚠️ `width:auto` JAWNIE. Wariant zwinięty rozciąga przycisk na całą szerokość,
       a reguły stanów dzielą ten sam element — bez tego pigułka dziedziczyłaby
       rozciągnięcie i nachodziła na przyciski powiększania. */
    width:auto;
    border-radius:8px;background:rgba(255,255,255,.95);
    border:1px solid var(--g200);box-shadow:var(--sh1)}

  .category-image-page.is-sticky-mob .diagram-card.is-zwiniety .dia-zwin{
    position:static;width:100%;border-radius:0;background:var(--g50);box-shadow:none}

  .category-image-page.is-sticky-mob .dia-zwin:active{background:var(--g100)}
  .category-image-page.is-sticky-mob .dia-zwin-z{width:18px;height:18px;fill:none;
    stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;
    transition:transform .16s}

  /* Zwinięty rysunek zostawia SAM pasek — karta kurczy się i dalej stoi przyklejona,
     więc powrót jest zawsze pod ręką. */
  .category-image-page.is-sticky-mob .diagram-card.is-zwiniety .dia,
  .category-image-page.is-sticky-mob .diagram-card.is-zwiniety .ref-layer,
  .category-image-page.is-sticky-mob .diagram-card.is-zwiniety .dia-tools,
  .category-image-page.is-sticky-mob .diagram-card.is-zwiniety .zoom-range{display:none}
  .category-image-page.is-sticky-mob .diagram-card.is-zwiniety .dia-zwin-z{transform:rotate(180deg)}

  .category-image-page.is-sticky-mob .diagram-card{
    position:sticky;top:var(--ci-mob-top,72px);z-index:10;
    /* Sufit rysunku niższy niż domyślne 46vh: przyklejony zabiera miejsce liście,
       pod którą właśnie ma pomagać się poruszać. */
    --dia-max:32vh;
    /* ⛔ Cień jest NOŚNY, nie ozdobny: przyklejony biały blok stoi nad białą listą,
       więc bez niego wiersze wjeżdżające pod spód wyglądają jak ucięte przez usterkę,
       a nie jak przesuwane pod warstwą. */
    box-shadow:0 6px 12px -6px rgba(0,0,0,.18)}
}

/* ⛔ Bez tej reguły PRZYKLEJANIE NA TELEFONIE NIE DZIAŁA W OGÓLE — i nie da się tego
   zobaczyć w przeglądarce zwężonej do 390 px.
   Journal nadaje `<html>` klasę `phone` dopiero po rozpoznaniu URZĄDZENIA, a wtedy
   wchodzi jego reguła `.phone .site-wrapper{overflow:hidden}`. `overflow:hidden` czyni
   z elementu KONTENER PRZEWIJANIA, więc `position:sticky` w środku przykleja się do
   NIEGO, a nie do okna — czyli nie przykleja się wcale. Zmierzone: panel odjeżdżał
   na −712 px mimo `position: sticky`.
   `overflow:clip` przycina dokładnie tak samo, ale kontenera przewijania NIE tworzy.
   ⚠️ `:has()` zawęża to do stron, na których nasz przyklejony panel naprawdę jest —
   nie ruszamy zachowania menu wysuwanego na pozostałych stronach sklepu. */
.phone .site-wrapper:has(.category-image-page.is-sticky-mob){overflow:clip}
/* ---- TABLET (720–1079) — NIE stos: opis obok zakupu ---- */
@container (min-width:720px) and (max-width:1079px){
  .category-image-page .diagram-card{--dia-max:42vh}
  .category-image-page .part-row{grid-template-columns:auto 64px minmax(0,1fr) 250px;
    grid-template-areas:"badge thumb main buy" "badge thumb avail avail" "alt alt alt alt"}
  .category-image-page .part-buy{justify-content:flex-end;align-content:flex-start}
  .category-image-page .repro-row{grid-template-columns:auto 64px minmax(0,1fr) 250px;
    grid-template-areas:"rbadge rthumb rmain rbuy" "rbadge rthumb ravail ravail"}
  .category-image-page .repro-row .r-buy{justify-content:flex-end}
}
/* ---- DESKTOP (≥1080) ---- */
@container (min-width:1080px){
  .category-image-page.layout-a{grid-template-columns:minmax(0,1.02fr) minmax(0,1fr);align-items:start}
  .category-image-page.layout-a.is-sticky .ci-pane{position:sticky;top:14px}
  .category-image-page.layout-a .refine-categories,
  .category-image-page.layout-a .parts-card{grid-column:2}
  .category-image-page .part-row{grid-template-columns:auto 64px minmax(0,1fr) 228px;
    grid-template-areas:"badge thumb main buy" "badge thumb avail avail" "alt alt alt alt"}
  .category-image-page .part-buy{justify-content:flex-end;align-content:flex-start}
  .category-image-page .repro-row{grid-template-columns:auto 64px minmax(0,1fr) 228px;
    grid-template-areas:"rbadge rthumb rmain rbuy" "rbadge rthumb ravail ravail"}
  /* ⛔ W wariancie B strona MUSI być blokiem, nie siatką: element przyklejony
     wewnątrz komórki siatki trzyma się tylko własnego wiersza, więc `position:sticky`
     na panelu nic nie dawało — rysunek odjeżdżał razem z resztą. */
  .category-image-page.layout-b{display:block}
  .category-image-page.layout-b > * + *{margin-top:18px}
  /* ⚠️ z-index musi przebić Journalowy stepper: jego przyciski są `position:absolute`
     z `z-index:3`, więc przy równym indeksie decydowała kolejność w dokumencie i strzałki
     ilości przebijały przez przyklejony rysunek. Zmierzone elementFromPoint. */
  .category-image-page.layout-b.is-sticky .ci-pane{position:sticky;top:8px;z-index:10;
    background:#fff;padding-bottom:10px;max-height:calc(100vh - 16px);overflow:auto}
  .category-image-page.layout-b .part-row{grid-template-columns:auto 64px minmax(0,1fr) 276px;
    grid-template-areas:"badge thumb main buy" "badge thumb avail avail" "alt alt alt alt";
    align-items:start}
}
@media (prefers-reduced-motion:reduce){.category-image-page *{transition:none!important;animation:none!important}}

/* ---- plakietka ilości w koszyku na kafelku numerka ----
   Duża powierzchnia kafelka dodaje, mała plakietka zdejmuje jedną sztukę.
   Asymetria celowa: przypadkowe trafienie ma dokładać, nie kasować. */
.category-image-page .ref-tiles.tiles-cart .ref-tile{position:relative;overflow:visible}
.category-image-page .tile-qty{position:absolute;top:-7px;right:-7px;min-width:19px;height:19px;
  padding:0 5px;border-radius:19px;background:var(--ok);color:#fff;font-size:11px;font-weight:700;
  line-height:19px;text-align:center;box-shadow:0 1px 3px rgba(0,0,0,.25);cursor:pointer;
  transition:transform .12s,background .12s}
.category-image-page .tile-qty.is-zero{display:none}
.category-image-page .tile-qty:hover{transform:scale(1.12);background:#ef4444}
.category-image-page .ref-tile.is-added .tile-qty,
.category-image-page .ref-tile.is-removed .tile-qty{display:block}
.category-image-page .ref-tile.is-added .tile-qty{background:var(--ok);transform:scale(1.25)}
.category-image-page .ref-tile.is-added .tile-qty::after{content:"+";position:absolute;inset:0;
  background:var(--ok);border-radius:19px;line-height:19px}
.category-image-page .ref-tile.is-removed .tile-qty{background:#ef4444;transform:scale(1.25)}
.category-image-page .ref-tile.is-removed .tile-qty::after{content:"\2212";position:absolute;inset:0;
  background:#ef4444;border-radius:19px;line-height:19px}

/* ---- pasek statusów: co stoi w linii, a co pod spodem ----
   Stan magazynowy i termin wysyłki mówią o TEJ SAMEJ rzeczy — kiedy część wyjedzie —
   więc czytają się jako jedno zdanie w pierwszej linii, rozdzielone kropką.
   Etykiety statusów mówią o czymś innym (zamiennik, uszkodzenie, niekompletność),
   więc schodzą niżej i KAŻDA dostaje własny wiersz: wciśnięte w jedną linię zlewały
   się w ciąg, w którym nie było widać, gdzie kończy się jedna, a zaczyna druga. */
/* Termin wysyłki — plakietka w tej samej rodzinie co reszta statusów, nie doklejone
   zdanie. Wcześniej wyglądał dokładnie tak, jak wypluwał go poprzedni moduł: goły tekst
   obok stanu, bez własnej tożsamości. */
/* ⛔ Atrybut `hidden` sam z siebie NIE ukryje elementu, któremu daliśmy `display`.
   Szablon oddaje pigułkę terminu z `hidden` i liczy na to, że przeglądarka jej nie
   narysuje — ale `[hidden]{display:none}` z arkusza przeglądarki ma siłę selektora
   typu i przegrywa z regułą po klasie. Trzeba to napisać samemu, z `[hidden]`
   w selektorze, żeby wygrało specyficznością. Skutek zaniechania: przez cały czas
   doczytywania przy KAŻDEJ pozycji wisiała pigułka „Dispatch:" bez daty, po czym
   znikała z pozycji, których nie ma na półce. Dokładnie to, czemu atrybut miał
   zapobiec — i czego szablon nie mógł wymusić sam. */
.category-image-page .ship[hidden]{display:none}
/* ⛔ TA SAMA FORMA, CO PLAKIETKI OZNACZEŃ. Do 18.08.2026 wysyłka była szarą pigułką
   (promień 12 px, tło #f9fafb, pismo 12 px zwykłymi literami), a plakietki obok — prostokątne,
   10,5 px, wersalikami, z ciemną obwódką. Dwa różne języki wizualne w jednym wierszu:
   zgłoszone przez użytkownika („nie pasuje ani kolorem, ani kształtem do reszty").
   ⚠️ BARWY SUKCESU dla wszystkich odmian, nie tylko `today`. Plakietka pojawia się
   WYŁĄCZNIE przy towarze z półki (skrypt usuwa ją, gdy `qty <= 0`), więc każdy jej wariant
   jest dobrą wiadomością — „jutro" tak samo jak „dziś". Rozróżnianie ich barwą sugerowało,
   że jedno jest gorsze.
   ⚠️ Ikona ciężarówki ZOSTAJE — to jedyna rzecz, która odróżnia wysyłkę od oznaczeń
   produktu, gdy oba stoją obok siebie w tej samej formie. */
.category-image-page .ship{display:inline-flex;align-items:center;gap:5px;
  font-size:10.5px;line-height:1.5;letter-spacing:.08em;text-transform:uppercase;font-weight:700;
  color:#166534;background:#f0fdf4;border:1px solid var(--ok);
  border-radius:4px;padding:2px 7px}
.category-image-page .ship::before{content:"\f0d1";font-family:FontAwesome;font-size:11px;
  color:var(--ok);margin-right:1px;letter-spacing:0}
/* ⛔ BEZ `opacity` — zmierzone: .75 dawało 3,91 na tle plakietki, przy progu 4,5 dla tekstu.
   Przygaszenie etykiety kusi, żeby zbudować hierarchię, ale przy 10,5 px i tak jej nie widać,
   a kontrast spada poniżej czytelności. Hierarchię niesie tu dwukropek i waga wartości. */
.category-image-page .ship-label{color:#166534}
.category-image-page .ship-label::after{content:":"}
.category-image-page .ship-value{font-weight:700;color:#166534}

/* Znak zapytania przy terminie dostawy */
.category-image-page .st-why{width:15px;height:15px;border-radius:50%;border:1px solid var(--g300);
  background:#fff;color:var(--g500);font-size:10px;font-weight:700;line-height:1;cursor:pointer;
  margin-left:6px;padding:0;display:inline-flex;align-items:center;justify-content:center;
  vertical-align:1px}
.category-image-page .st-why:hover{border-color:var(--brand);color:var(--brand-dark);background:var(--brand-soft)}

/* Puste miejsce zanim przyjdzie odpowiedź — bez migotania i bez skakania układu.
   ⚠️ 20 px, nie 18: tyle mierzy gotowe zdanie o stanie, bo ikona przed nim (14 px,
   `inline-block`) rozpycha wiersz ponad interlinię. Przy 18 px każda pozycja
   podskakiwała o 2 px w chwili wypełnienia. */
.category-image-page .st-slot:empty{display:flex;align-items:center;min-height:20px}
.category-image-page .part-avail.is-quiet .st-slot{display:none}

.category-image-page .part-extra{flex-basis:100%;display:flex;flex-direction:column;
  align-items:flex-start;gap:4px;margin:2px 0 0;color:var(--g700)}
/* ⛔ Pusty pasek etykiet MUSI zniknąć z układu, a nie tylko nie mieć wysokości.
   `flex-basis:100%` zawija go do własnej linii, więc nawet przy zerowej wysokości
   doliczał 6 px odstępu i 2 px marginesu — 8 px pustego miejsca pod zdaniem o stanie,
   przy każdej pozycji. Etykiety odsiewa lista pomijanych w ustawieniach, więc w praktyce
   ten pasek jest pusty ZAWSZE i te 8 px nigdy niczego nie mieściło.
   ⚠️ Gdy etykieta jednak przyjdzie (dziś: uszkodzenie i niekompletność, razem 16
   produktów w sklepie), linia pojawi się i pozycja urośnie. Świadomie nie rezerwujemy
   pod to miejsca: 8 px na każdej pozycji za przypadek jeden na kilkadziesiąt tysięcy
   to zła zamiana — patrz „Czego świadomie NIE rezerwujemy" w liście wdrożeniowej. */
.category-image-page .part-extra:empty{display:none}
.category-image-page .part-extra div,
.category-image-page .part-extra p{margin:0!important;font-size:13px!important;line-height:1.45;
  display:inline-flex!important;align-items:center}
.category-image-page .part-extra span{font-size:inherit!important}
.category-image-page .part-extra a{color:inherit;font-size:inherit!important;text-decoration:underline}
.category-image-page .part-extra svg{width:12px;height:12px;flex:0 0 auto}
.category-image-page .part-extra .fa{margin-right:3px}
/* Plakietki Product Extra Fields mają własny odstęp między ikoną a tekstem
   (`gap` w znaczniku), więc marginesu z reguły wyżej im nie dokładamy — inaczej
   przerwa robi się dwa razy większa niż w pozostałych etykietach. */
.category-image-page .part-extra .pef-pill .fa{margin-right:0}

/* ⛔ Bez kropki rozdzielającej. Miała rozdzielać stan magazynowy od terminu wysyłki,
   ale w tej skali czyta się jak zabrudzenie ekranu, a nie jak znak interpunkcyjny —
   sam odstęp rozdziela je czytelniej i bez pytań. */

/* ---- telefon: zdjęcie motocykla równa się z rysunkiem pod spodem ---- */
@container (max-width:719px){
  .category-image-page .bike-card{width:100%;max-width:none}
  .category-image-page .bike-card img{width:74px;height:52px}
  .category-image-page .part-buy{grid-template-columns:1fr auto 36px}
}

/* ---- suma cen numerków w koszyku, w nagłówku panelu ---- */
.category-image-page .ref-total{margin-left:12px;font-family:var(--mono);font-size:12.5px;
  font-weight:700;color:var(--g800);font-variant-numeric:tabular-nums}
.category-image-page .ref-total.is-zero{display:none}
.category-image-page .ref-total .lbl{font-weight:400;color:var(--g500);margin-right:5px;
  font-size:10.5px;letter-spacing:.08em;text-transform:uppercase}

/* ---- okno z wyjaśnieniem terminu dostawy ---- */
/* Okno z wyjaśnieniem terminu — językiem wizualnym reszty przebudowanych stron:
   skala szarości Tailwinda, firmowy #127da8, promień 12, stopka z jednym przyciskiem.
   ⚠️ Rozmiar treści 14 px, nie 13 — to jest zdanie do przeczytania, nie przypis. */
.ci-modal{position:fixed;inset:0;z-index:10000;background:rgba(31,41,55,.6);display:grid;
  place-items:center;padding:20px}
.ci-modal[hidden]{display:none}
.ci-modal-box{background:#fff;border-radius:12px;max-width:min(560px,94vw);
  box-shadow:0 20px 50px rgba(0,0,0,.25);overflow:hidden}
.ci-modal-hd{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:15px 18px;border-bottom:1px solid #e5e7eb;color:#1f2937}
.ci-modal-hd strong{font-size:16px;font-weight:600;display:flex;align-items:center;gap:9px}
/* Ikona zegara ta sama, którą wiersz oznacza pozycję z terminem — okno ma być
   rozpoznawalne jako rozwinięcie TEGO znaczka, a nie osobny komunikat. */
.ci-modal-hd strong::before{content:"\f017";font-family:FontAwesome;font-size:15px;
  color:var(--ci-ref-border,#c9a86b);font-weight:400}
.ci-modal-hd .x{background:none;border:0;font-size:22px;line-height:1;color:#9ca3af;
  cursor:pointer;padding:0 2px}
.ci-modal-hd .x:hover{color:#374151}
.ci-modal-body{padding:16px 18px;line-height:1.65;color:#374151}
/* ⛔ Rozmiar MUSI stać na samym akapicie. Journal ma gołą regułę `p{font-size:16px}`,
   a dziedziczenie po rodzicu przegrywa z KAŻDĄ regułą trafiającą w element — nawet
   najsłabszą. Ustawiony na `.ci-modal-body` był ignorowany i tekst okna wychodził
   tej samej wielkości co nagłówek. */
/* ⚠️ 15 px, nie 14: to samo okno stoi teraz takze na stronie produktu, a tamta karta
   trzyma 15 px dla tresci. Dwa rozmiary tego samego okna w dwoch miejscach sklepu
   czytalyby sie jak dwa rozne komunikaty (podniesione 22.08.2026). */
.ci-modal-body p{margin:0 0 11px;font-size:15px;line-height:1.65}
.ci-modal-body p:last-child{margin-bottom:0}
.ci-modal-ft{display:flex;justify-content:flex-end;padding:13px 18px;
  border-top:1px solid #e5e7eb;background:#f9fafb}
.ci-modal-ft .btn-ok{border:0;border-radius:6px;background:#127da8;color:#fff;
  font-family:inherit;font-size:14px;font-weight:600;line-height:1;
  padding:10px 20px;cursor:pointer}
.ci-modal-ft .btn-ok:hover{background:#0e6688}
.ci-modal-ft .btn-ok:focus-visible{outline:2px solid #127da8;outline-offset:2px}

/* ==========================================================================
   SZKIELETY — miejsce trzymane pod treść, która przychodzi osobnym żądaniem
   ==========================================================================
   Cena, stan i termin nie mogą być wypieczone w stronie, bo strony są cache'owane
   (patrz komentarz przy `.part-avail` w szablonie). Dociąga je skrypt, a do tej chwili
   miejsce po nich stało puste i układ osiadał w trakcie czytania.

   ⛔ Rezerwa liczy się ze zmiennych `--cl-cena` / `--cl-cena-sub`, z których liczy się
   też gotowa cena. Dopisanie tu drugiej, własnej liczby znaczyłoby, że przy pierwszej
   zmianie stopnia pisma rezerwa przestaje pasować — a szkielet o złej wysokości daje
   DWA skoki zamiast jednego.

   ⚠️ Wszystko poniżej jest jednakowe dla każdego gościa i nie niesie ani ceny, ani
   stanu, ani koszyka — może więc bezpiecznie leżeć w cache'u strony. */

@keyframes cl-szkielet{from{background-position:100% 0}to{background-position:-100% 0}}

/* Wspólne tło pasków. Barwy z siatki szarości modułu — pasek ma być widoczny,
   ale nie ma udawać treści. */
.category-image-page .price-check::before,
.category-image-page .price-check::after,
.category-image-page .st-slot:empty::before{
  content:"";display:block;border-radius:3px;
  background:linear-gradient(90deg,var(--g100) 30%,var(--g200) 50%,var(--g100) 70%);
  background-size:200% 100%;
  /* Skończona liczba przebiegów: gdy odpowiedź nigdy nie dojdzie, pasek przestaje
     migotać zamiast pulsować w nieskończoność nad martwym miejscem. */
  animation:cl-szkielet 1.4s ease-in-out 12}

/* ---- kolumna ceny ----
   Dwa paski w miejsce dwóch wierszy: ceny brutto i linii o podatku. Wysokość każdego
   z nich to wysokość paska + dopełnienie do pełnej interlinii, więc pudło zajmuje
   DOKŁADNIE tyle, ile zajmie gotowa cena (19 px × 1,42857 + 11 px × 1,42857 + 1 px
   odstępu = 43,9 px; zmierzona cena: 43,8 px).
   ⛔ `display:flex` jest konieczne, nie kosmetyczne: przy blokach marginesy pionowe
   sąsiadów zlewają się w jeden i suma przestaje się zgadzać. */
.category-image-page .price-check{display:flex;flex-direction:column;gap:1px;align-items:flex-start}
.category-image-page .price-check::before{
  height:calc(var(--cl-cena) * .9);
  margin:calc(var(--cl-cena) * .2643) 0;
  width:calc(var(--cl-cena) * 4.6)}
.category-image-page .price-check::after{
  height:calc(var(--cl-cena-sub) * .9);
  margin:calc(var(--cl-cena-sub) * .2643) 0;
  width:calc(var(--cl-cena-sub) * 7.8)}

/* Napis „sprawdź cenę" zostaje w treści dla czytników ekranu — mówi, czemu w tym
   miejscu jeszcze nic nie ma. Rysować go nie ma po co, skoro stoi tam pasek. */
.category-image-page .cl-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* ---- zdanie o stanie ----
   Jeden pasek w linii o wysokości gotowego zdania (20 px). Szerokość mniej więcej
   na „Delivery in 7 days (ETA)", czyli najdłuższy z trzech wariantów. */
.category-image-page .st-slot:empty::before{height:12px;width:132px}

/* ⛔ Ruch wyłączony dla tych, którzy sobie tego życzą w ustawieniach systemu.
   Miejsce zostaje zarezerwowane tak samo — znika samo migotanie. */
@media (prefers-reduced-motion:reduce){
  .category-image-page .price-check::before,
  .category-image-page .price-check::after,
  .category-image-page .st-slot:empty::before{animation:none}
}

/* ==========================================================================
   WARIANT PODANIA POZYCJI: osobne karty zamiast spójnej tabeli
   ==========================================================================
   Wybierane w panelu (Układ → Podanie pozycji). Ta sama siatka kolumn i te same
   progi szerokości co w tabeli — zmienia się WYŁĄCZNIE oprawa pojedynczej pozycji,
   więc wszystko, co niżej dostraja kolumny na trzech szerokościach, obowiązuje tu
   bez zmian i nie trzeba tego powielać.

   Wygląd wzięty z tego, czym sklep posługuje się już na koncie klienta (`.order-row`)
   i w checkoucie: biała karta, obwódka `--g200`, promień 10 px, a przy najechaniu
   cień `--sh1` i ciemniejsza obwódka. Te same wartości siedzą już w zmiennych
   modułu, więc wariant nie wprowadza ani jednej nowej barwy. */

.category-image-page.rows-cards .parts-card{
  border:0;border-radius:0;background:none;
  /* ⛔ `visible`, nie `hidden`: cień przy najechaniu wychodzi poza obrys karty
     i przy przycinaniu byłby ucięty na krawędzi listy. */
  overflow:visible;
  display:flex;flex-direction:column;gap:10px}

/* Nagłówek przestaje być paskiem tabeli i staje się podpisem sekcji.
   ⚠️ Dopełnienie 15 px, nie 14: karta ma własną obwódkę, więc jej treść zaczyna się
   o tę jedną kreskę dalej niż treść wiersza w tabeli. Bez tego podpis stoi o piksel
   w lewo względem numerów pod nim. */
.category-image-page.rows-cards .parts-card .hd{
  background:none;border-bottom:0;padding:2px 15px 0}

.category-image-page.rows-cards .part-row{
  background:#fff;border:1px solid var(--g200);border-radius:10px;
  transition:border-color .16s,box-shadow .16s}
/* ⛔ Reguła `.part-row:last-child{border-bottom:0}` z wariantu tabelowego ma tę samą
   specyficzność co powyższa i zdejmowałaby ostatniej karcie dolną krawędź. */
.category-image-page.rows-cards .part-row:last-child{border-bottom:1px solid var(--g200)}

/* Najechanie: karta się podnosi, zamiast zmieniać barwę tła. Szare tło działa
   w tabeli, gdzie wiersz nie ma własnej krawędzi — na karcie czyta się jak stan
   wyłączenia.

   Złoto BIERZEMY ZE ZMIENNEJ, nie z nowej stałej: to ta sama barwa, którą mają
   kafelki z numerkami, i steruje nią ustawienie „kolor obwódki kafelka". Wpisana
   tu drugi raz rozjechałaby się z kafelkami przy pierwszej zmianie w panelu.

   ⚠️ Obwódka + poświata to ta sama konstrukcja, którą niżej ma stan `is-target`
   (pozycja wskazana kliknięciem numerka) — tam firmowo niebieska. Dzięki temu
   najechanie i wybór czytają się jako dwa stopnie jednego języka, a nie dwa
   osobne pomysły.

   ⛔ Cień podany DWA RAZY celowo: `color-mix` wyprowadza poświatę z ustawionego
   złota, ale przeglądarka, która go nie zna, odrzuca CAŁĄ deklarację — bez wersji
   rezerwowej wyżej karta zostałaby wtedy zupełnie bez cienia. */
.category-image-page.rows-cards .part-row:hover{
  background:#fff;border-color:var(--ci-ref-border,#c9a86b);
  box-shadow:0 0 0 3px rgba(201,168,107,.12),var(--sh1);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--ci-ref-border,#c9a86b) 12%,transparent),var(--sh1)}

/* Pozycja wskazana kliknięciem w numerek. W tabeli rolę wskaźnika pełni pionowa
   belka przy krawędzi; na karcie z zaokrąglonymi rogami belka się rozjeżdża,
   więc wskazuje cała obwódka. */
.category-image-page.rows-cards .part-row.is-target{
  background:var(--brand-soft);border-color:var(--brand);
  box-shadow:0 0 0 3px var(--brand-soft)}

/* Reprodukcja zostaje wewnątrz karty macierzystej — to wariant TEJ pozycji, nie
   osobna pozycja. ⛔ Bez tego jej ujemny margines (pełna szerokość wiersza tabeli)
   dosunąłby ją wprost do obwódki karty i dwie kreski stanęłyby jedna na drugiej. */
.category-image-page.rows-cards .repro-row{margin:8px 0 0;padding:11px 12px}

/* ⛔ Pusty pas pod reprodukcję na dole karty.
   Siatka wiersza ma zarezerwowany ostatni pas `alt` na wypadek reprodukcji. Przy
   pozycji, która jej nie ma, pas mierzy 0 px — ale odstęp 10 px PRZED nim obowiązuje
   tak samo jak między każdą inną parą pasów. Zmierzone: pod przyciskiem zostawało
   23 px zamiast 12 px dopełnienia.

   W TABELI to czyta się jak oddech między pozycjami i tak zostaje — dlatego reguła
   jest zawężona do kart, gdzie ten sam pas leży tuż nad krawędzią ramki i jest po
   prostu pustym miejscem w środku karty.

   ⚠️ Mapy obszarów trzeba wypisać w całości: `grid-template-areas` nie da się skrócić
   „o jeden wiersz" inaczej niż podając pozostałe. Dwie mapy wystarczą — wąska (stos)
   i szeroka, tę drugą dzielą tablet, desktop i wariant B. */
.category-image-page.rows-cards .part-row:not(:has(.repro-row)){
  grid-template-areas:"badge thumb main" "avail avail avail" "buy buy buy"}

@container (min-width:720px){
  .category-image-page.rows-cards .part-row:not(:has(.repro-row)){
    grid-template-areas:"badge thumb main buy" "badge thumb avail avail"}
}

/* ---------- warstwa podświetlania w POWIĘKSZENIU ----------
   Rysunek w lightboksie jest znacznie większy niż na stronie, więc numery da się trafić
   palcem — na telefonie to jedyny sposób wyboru, bo hover nie istnieje, a rysunku i listy
   nie widać naraz.

   ⚠️ `position: relative` na scenie, nie na <figure>: podpis pod obrazkiem jest częścią
   figury, więc warstwa rozciągnięta na figurę sięgałaby i na niego. */
.ci-lightbox .lb-scena{position:relative;display:block}
.ci-lightbox .ref-layer-lb[hidden]{display:none}
/* Znacznik w powiększeniu jest odrobinę grubszy — przy obrazku dwa razy większym
   ta sama kreska wygląda na cieńszą, niż jest. */
.ci-lightbox .ref-mark{border-width:3px}

/* ---------- dymek z nazwą części przy numerze na rysunku ----------
   ⛔ WŁASNY, NIE `title=`. Systemowy dymek pojawia się z opóźnieniem narzuconym przez
   przeglądarkę (rzędu sekundy) i nie da się go skrócić — przy wskazywaniu numerów na
   rysunku klient zdąży przejechać kursorem po kilku i nie dowiedzieć się o żadnym.

   ⚠️ `position: fixed` w <body>, poza warstwą znaczników: warstwa dostaje transformatę
   panzooma (dymek skalowałby się razem z rysunkiem), a w powiększeniu leży pod
   lightboksem z własnym kontekstem stosu. Stąd też `z-index` ponad lightboksem (9999). */
.ref-tip{position:fixed;z-index:10050;max-width:260px;
  padding:5px 9px;border-radius:5px;
  background:#1f2937;color:#fff;
  font-size:12.5px;line-height:1.35;font-weight:500;
  box-shadow:0 4px 12px rgba(0,0,0,.22);
  /* Dymek nigdy nie łapie wskaźnika: stoi tuż nad numerem, więc przechwytywałby
     kliknięcia w miejscu, w które klient celuje. */
  pointer-events:none}
.ref-tip[hidden]{display:none}

/* ⛔ NUMER I WSKAŹNIK W JEDNEJ KOLUMNIE, jeden pod drugim. Ta reguła zginęła przy
   przepisywaniu bloku niżej i wskaźnik ustawił się OBOK numeru zamiast pod nim - bo bez
   niej opakowanie dziedziczy `flex-direction: row`. Obszar siatki (`badge`) bierze
   opakowanie, nie sam numer: dwa elementy z tym samym `grid-area` nachodziłyby na siebie. */
.category-image-page .part-ref{grid-area:badge;display:flex;flex-direction:column;
  align-items:center;gap:5px}

/* ---------- wskaźnik „jest w koszyku" pod numerem pozycji ----------
   ⛔ WSKAŹNIK, NIE PRZYCISK: nie da się w niego kliknąć i nie ma być można. Dodać część
   do koszyka można z rysunku, z kafelka numerka i zwykłym przyciskiem w wierszu - czwarta
   droga do tego samego tylko mnożyłaby miejsca, w które klient klika przypadkiem.

   ⛔ WIDOCZNY WYŁĄCZNIE, GDY CZĘŚĆ JEST W KOSZYKU. Pusty koszyczek przy każdej z kilkunastu
   pozycji byłby szumem - plakietka ma nieść stan, nie ozdabiać wiersz.

   ⚠️ Barwa ZIELONA, nie firmowa: to potwierdzenie („masz to"), a nie element nawigacji.
   Ten sam zielony, którym miga dodanie na rysunku, więc jedno przechodzi w drugie.

   ⚠️ Numer i ikona dzielą JEDNĄ komórkę siatki, więc obszar `badge` bierze opakowanie
   `.part-ref` - dwa elementy z tym samym `grid-area` nachodziłyby na siebie. */
/* ⚠️ BARWA `--ok` (#15803d) - dokładnie ta, którą oznaczany jest kafelek numerka
   stojącej w koszyku części. Jedna rzecz („mam to") ma wyglądać tak samo wszędzie,
   niezależnie od tego, w którym miejscu strony klient na nią patrzy. */
.category-image-page .part-ref-cart{position:relative;
  width:34px;height:26px;
  display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--ok);border-radius:4px;
  background:rgba(21,128,61,.08);color:var(--ok);
  font-size:12.5px;
  /* Wskaźnik niczego nie obsługuje - kliknięcie ma trafić w wiersz pod spodem. */
  pointer-events:none}
.category-image-page .part-ref-cart.is-zero{display:none}

.category-image-page .part-ref-cart-qty{position:absolute;top:-6px;right:-6px;
  min-width:15px;height:15px;padding:0 3px;
  display:inline-flex;align-items:center;justify-content:center;
  border-radius:8px;background:var(--ok);color:#fff;
  font-size:9.5px;font-weight:700;line-height:1;font-variant-numeric:tabular-nums}

/* Przycisk koszyka dla części, której nie da się kupić (filtr blokady).
   ⛔ Wygląda jak wyłączony, bo JEST wyłączony - to `<span>`, nie odnośnik. Wyszarzony
   wygląd przy działającym kliknięciu byłby kłamstwem. Ten sam wygląd co na stronie
   produktu, żeby ta sama informacja wyglądała tak samo w obu miejscach. */
.category-image-page .btn-cart.is-blocked{background:#e5e7eb;border-color:#d1d5db;
  color:#6b7280;cursor:not-allowed;box-shadow:none;white-space:nowrap}
.category-image-page .btn-cart.is-blocked:hover{background:#e5e7eb;border-color:#d1d5db}

/* Kafelek numerka części, której nie da się kupić. Wygląda jak wyłączony, bo jest -
   skrypt przechwytuje kliknięcie, zanim dojdzie do obsługi koszyka.
   ⚠️ Numer zostaje czytelny: kafelek nadal służy do wskazania pozycji na rysunku
   i przewinięcia do wiersza, tylko nie do zakupu. */
.category-image-page .ref-tile.is-blocked{opacity:.55;cursor:default;
  border-style:dashed}
.category-image-page .ref-tile.is-blocked .tile-qty{display:none}
