/* ==========================================================================
   Arkusz główny serwisu
   Przeniesiony 1:1 z zatwierdzonej makiety `wizytowka-A1-redakcja.html`
   (wariant A1 „Redakcja", po zmianach klienta 1–10).
   Jeden plik dla całego serwisu — bez stylów inline w <head>.
   ========================================================================== */

/* Kolory marki (--brand/--brand-d/--gold/--green) mieszkają w jednym pliku
   motywu razem z panel.css, nie tutaj dwa razy — patrz tam. */
@import url("../theme/klient/zmienne.css");

:root {
  --ink: #231a17;
  --muted: #6e635b;
  --line: #e7ded2;
  --paper: #f6f1ea;
  --card: #fffdf9;
  --err: #d23b3b;
  /* Kolory formatów. W `:root`, a nie w `body.shop`, bo plakietki Druk /
     E-book / Audiobook stoją w dwóch miejscach o różnej oprawie: na karcie
     w katalogu księgarni i na karcie tytułu (styl A1). Ta sama książka ma
     mieć tę samą kropkę w obu — druga kopia wartości rozjechałaby się przy
     pierwszej poprawce palety.
     Nazwy idą za wartością z `BookFormat` („audiobook"), a nie za skrótem
     z makiety („audio"): szablon wstawia `fmt.value` do klasy, więc rozjazd
     nazw oznaczałby brak koloru przy pierwszym audiobooku. */
  --druk: #16415c;
  --ebook: #1f7a5e;
  --audiobook: #b1561c;
}

* { box-sizing: border-box; margin: 0; padding: 0 }
html { scroll-behavior: smooth }
body {
  font-family: 'Lora', Georgia, serif;
  background: var(--paper);
  color: var(--ink);
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; text-decoration: none }
.wrap { max-width: 1180px; margin: 0 auto; padding: 0 28px }
h1, h2, h3, .osw {
  font-family: 'Oswald', sans-serif;
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: .01em;
  /* Paczka 7, pkt 4: nagłówki łamią się równo między wierszami, nie
     „jedno słowo osierocone na końcu". Przeglądarki bez wsparcia po
     prostu ignorują właściwość — zero ryzyka regresji. */
  text-wrap: balance;
}
p {
  /* To samo dla akapitów — `pretty` unika krótkiej „sierotki” w
     ostatnim wierszu, gdzie `balance` byłby za drogi (długie teksty). */
  text-wrap: pretty;
}
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  border-radius: 100px; padding: 12px 24px;
  font-family: 'Oswald'; font-weight: 500; font-size: 13.5px;
  letter-spacing: .06em; text-transform: uppercase;
  cursor: pointer; border: 1px solid transparent; transition: .2s;
}
.btn-primary { background: var(--brand); color: #fff }
.btn-primary:hover { background: var(--brand-d) }
.btn-ghost { border-color: var(--ink); color: var(--ink) }
.btn-ghost:hover { background: var(--ink); color: #fff }
.eyebrow {
  font-family: 'Oswald'; font-size: 12px; letter-spacing: .24em;
  text-transform: uppercase; color: var(--brand); font-weight: 600;
}

/* --- pasek z hasłem (ZMIANA 10) --- */
.ribbon {
  background: var(--brand); color: #f6e9e4; text-align: center;
  font-family: 'Lora', serif; font-style: italic; font-size: 15px;
  letter-spacing: .01em; padding: 11px 16px;
}

/* --- nagłówek --- */
header {
  position: sticky; top: 0; z-index: 50;
  background: rgba(246, 241, 234, .92);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
/* Poziomy odstęp powtórzony z `.wrap` — nie jest nadmiarowy.
   Nagłówek nosi oba przydomki (`class="wrap nav"`), a `.wrap` i `.nav` mają
   tę samą wagę, więc wygrywa ten zapisany niżej. Samo `padding: 14px 0`
   zerowało tu odstęp z `.wrap` i logo wychodziło 28 px przed kolumnę
   treści — widać to było na desktopie i na telefonie (PYTANIA D28,
   klient: prostujemy). Pionowy odstęp jest inny niż w `.wrap`, więc
   skrótu „padding-block" nie da się tu użyć bez powtórzenia obu wartości. */
.nav {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 28px; position: relative;
}
/* Wysokość znaku jest ustawieniem panelu (KROK 2) i przyjeżdża zmienną
   z `/motyw.css`. Czterdzieści sześć pikseli zostaje jako wartość awaryjna
   na wypadek, gdyby ten arkusz nie dojechał — bez niej logo stanęłoby
   w nagłówku w rozmiarze własnym pliku, czyli na przykład 300 px wysokie. */
.logo img, .logo svg { height: var(--logo-h, 46px); width: auto; display: block }
/* Logo i blok koszyka nie mają się nigdy ściskać — to one muszą zostać
   czytelne, a jedynym elementem, który ma ustąpić miejsca przy ciasnym
   nagłówku, jest menu (patrz `.menu-toggle` niżej). */
.nav > .logo, .nav > .nav-actions { flex-shrink: 0 }
nav.menu { display: flex; gap: 26px }
nav.menu a {
  font-family: 'Oswald'; font-weight: 400; font-size: 14px;
  letter-spacing: .05em; text-transform: uppercase; color: #3d342e;
  /* Bez tego najdłuższa pozycja łamie się w pionie, gdy pasek robi się
     ciasny — a „AKTUAL-/NOŚCI" w dwóch wierszach rozpycha cały nagłówek. */
  white-space: nowrap;
}
nav.menu a:hover { color: var(--brand) }
/* Szósta pozycja w menu („Publikacje", B3b) bywała ucinana przy wąskim
   oknie — a po dołożeniu serduszka do bloku koszyka (A12/ulubione) próg,
   przy którym to się dzieje, przesunął się i zależy też od tego, które
   czcionki akurat się wczytały. Stałej wartości w pikselach nie da się
   tu dłużej ufać (A14), więc odstęp został węższy od 1080 px w dół jako
   pierwsza, drobna poprawka, a decyzję „mieści się / nie mieści się"
   podejmuje teraz `main.js` na podstawie realnego pomiaru przepełnienia
   rzędu — patrz `.menu-toggle` i `.nav-tight` niżej. */
@media (max-width: 1080px) {
  nav.menu { gap: 16px }
}

/* Trzy strefy na szerokim ekranie: logo po lewej — menu wyśrodkowane w
   wolnym miejscu między logo a ikonami — ikony i „Księgarnia" po prawej.
   Zgłoszenie: menu przyklejało się do logo (`justify-content: space-between`
   na `.nav` wyżej nie dawało żadnego odstępu tam, gdzie `.nav-actions`
   zaraz obok, z `margin-left: auto`, zabierała sobie całą wolną
   przestrzeń — logo i menu zostawały złożone razem po lewej).

   Grid, nie flex: `column-gap: 40px` to stały, gwarantowany odstęp —
   logo i ikony mają min. 40 px prześwitu od menu zawsze, a gdy środkowa
   kolumna (`1fr`) jest szersza niż samo menu, odstęp tylko rośnie
   (`justify-self: center`), nigdy nie maleje.

   Scoped do `min-width: 1081px` — próg zgodny z istniejącym podziałem
   powyżej (poniżej 1080 menu i tak robi się ciasne, a `main.js` w końcu
   chowa je za hamburgerem, A14). Poniżej tego progu `.nav` zostaje przy
   zwykłym flexie: bez JS menu nigdy nie trafia za hamburger, a stary,
   przetestowany `flex-wrap` (poprawki 7, pkt 8) gwarantuje, że wtedy
   blok ikon zejdzie na własny wiersz zamiast wypychać telefon w poziomie
   — trójstrefowy grid z twardym minimum 40 px tej gwarancji by nie dawał.

   To też naprawia próg chowania menu za hamburgerem dla szerokich okien:
   kolumna `1fr` ma automatyczne minimum równe szerokości treści (menu),
   więc gdy logo + 40 + menu + 40 + ikony nie mieszczą się w rzędzie,
   `.nav` faktycznie się rozpycha (`scrollWidth` rośnie) zamiast ściskać
   odstępy — `main.js` (`sprawdzSzerokosc`) już to mierzy, nic w nim nie
   trzeba zmieniać. */
@media (min-width: 1081px) {
  .nav {
    display: grid; grid-template-columns: auto 1fr auto;
    column-gap: 40px;
  }
  .nav > .logo { grid-column: 1 }
  nav.menu, .menu-toggle { grid-column: 2; justify-self: center }
  .nav > .nav-actions { grid-column: 3; justify-self: end; margin-left: 0 }
}

/* --- menu za hamburgerem, gdy się nie mieści (A14) ---
   `main.js` mierzy `nav.scrollWidth` kontra `nav.clientWidth` i dopisuje
   `.nav-tight`, gdy rząd faktycznie przepełnia się — zero zgadywania
   progu w pikselach, bo ten próg zależy od czcionki, liczby pozycji menu
   i tego, czy sklep dokłada swoje ikony. Bez JavaScriptu `.nav-tight`
   nigdy się nie pojawia i menu stoi otwarte jak przed tą poprawką. */
.menu-toggle {
  display: none; width: 44px; height: 44px; flex-shrink: 0;
  align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 10px; background: var(--card);
  cursor: pointer; padding: 0;
}
.menu-toggle span,
.menu-toggle span::before,
.menu-toggle span::after {
  content: ''; display: block; width: 20px; height: 2px;
  background: #3d342e; transition: .2s;
}
.menu-toggle span::before { transform: translateY(-6px) }
.menu-toggle span::after { transform: translateY(4px) }
.nav.nav-tight .menu-toggle:not([hidden]) { display: flex }
.nav.nav-tight nav.menu { display: none }
.nav.nav-tight.menu-open nav.menu {
  display: flex; flex-direction: column; gap: 2px;
  position: absolute; top: 100%; left: 0; right: 0;
  background: var(--card); border: 1px solid var(--line); border-top: none;
  border-radius: 0 0 14px 14px; padding: 10px 28px 16px;
  box-shadow: 0 18px 32px rgba(40, 18, 18, .14); z-index: 60;
}
.nav.nav-tight.menu-open nav.menu a { padding: 10px 0 }

/* Poprawki 7, pkt 8: skrypt wyżej (`main.js`, A14) mierzy, czy się mieści
   CAŁY rząd z rozwiniętym menu, i dopiero wtedy chowa menu za hamburgerem —
   ale NIE sprawdza drugi raz, czy to, co zostaje (logo + hamburger + blok
   koszyka z przyciskiem „Księgarnia →"), zmieściło się po zwinięciu. Na
   szerokim telefonie to niewidoczne, bo zostaje mimo wszystko za wąskie
   dla trójki ikon-i-przycisku. Przy 320–430 px logo+hamburger+blok razem
   to ok. 350–440 px — więcej, niż ma do dyspozycji ekran.

   Naprawa stoi w CSS, nie w skrypcie, żeby działała też bez JavaScriptu
   (ten sam kompromis, co przy samym hamburgerze): `flex-wrap` nie robi
   nic, dopóki rząd się mieści (czyli przez większość ekranów — próg leży
   w praktyce dużo niżej niż 1080 px, od którego `main.js` w ogóle zaczyna
   chować menu), a gdy faktycznie nie mieści się, blok koszyka schodzi na
   własny wiersz miast wypychać stronę w poziomie. */
.nav { flex-wrap: wrap; row-gap: 10px }

/* --- koszyk w nagłówku (ZMIANA 1) --- */
.nav-actions {
  display: flex; align-items: center; gap: 14px;
  /* Działa w obu ułożeniach: w jednym rzędzie (`justify-content:
     space-between` na `.nav` już dosuwa ten blok do prawej) i po
     zawinięciu na własny wiersz, gdzie jest jedynym elementem tego
     wiersza — tam `space-between` nie ma czego rozpychać i bez tego
     `margin-left: auto` blok wylądowałby przy lewej krawędzi. */
  margin-left: auto;
}
.cart {
  position: relative; width: 46px; height: 46px; border-radius: 12px;
  border: 1px solid var(--line); background: var(--card);
  display: grid; place-items: center; transition: .2s;
}
.cart:hover { border-color: var(--brand) }
.cart svg { stroke: var(--ink) }
.cart .badge {
  position: absolute; top: -8px; right: -8px;
  background: var(--brand); color: #fff;
  font-family: 'Oswald'; font-weight: 600; font-size: 11px;
  min-width: 20px; height: 20px; border-radius: 100px;
  display: grid; place-items: center; padding: 0 5px;
}

/* --- karuzela hero (ZMIANA 2) ---
   Przycinanie siedzi na `.hc-viewport`, a nie na `.hero-carousel`, i to jest
   istota naprawy zgłoszonej jako „strzałki są przycięte przy krawędziach".
   Strzałka stoi przy samej krawędzi karuzeli; dopóki przycinał ją ten sam
   element, co slajdy, wystający kawałek znikał. Rozdzielenie na dwa pudełka
   — zewnętrzne pozycjonujące i wewnętrzne przycinające — załatwia to bez
   `overflow: visible`, które wypuściłoby slajdy na całą szerokość okna. */
.hero-carousel { position: relative }
.hc-viewport { overflow: hidden }

/* Baner zamiast pustej karuzeli. Wysokość w `min-height`, nie w `height`:
   hasło z panelu bywa jednym słowem i bywa dwoma wierszami, a baner, który
   przy dłuższym napisie przycina tekst, wygląda na zepsuty bardziej niż
   baner o zmiennej wysokości. */
.hero-baner {
  min-height: 320px; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 22px; text-align: center; padding: 48px 20px;
}
.hero-baner-logo { width: 220px; height: auto; max-width: 70vw }
.hero-baner p { font-family: 'Lora', Georgia, serif; font-size: clamp(19px, 2.4vw, 26px); color: #5a504a; max-width: 22em; margin: 0 }
.hc-track { display: flex; transition: transform .6s cubic-bezier(.5, 0, .2, 1) }
/* Podskok bez animacji przy zapętleniu (4A, 1.8) — patrz komentarz w
   `main.js` przy klonach pierwszego/ostatniego slajdu. */
.hc-track.hc-skok { transition: none }
.hc-slide {
  min-width: 100%; display: grid; grid-template-columns: .85fr 1.15fr;
  gap: 54px; align-items: center;
  /* Boczne odstępy robią rynnę pod strzałki. Bez nich przycisk kładłby się
     na okładce po lewej i na tytule po prawej — a tytuł jest tu wielkości
     64 px i sięga do samej krawędzi kolumny. */
  padding: 52px 58px 30px;
}
.hc-slide .cov { display: flex; justify-content: center }
/* Poprawki 20.10, pkt 4: ramką jest odnośnik, nie sam `<img>` — wymiar
   i obrót siedzą na `<a>`, okładka w środku wypełnia go przez `.ok-obraz`.

   21.10: tło, zaokrąglenie, cień i `overflow` zjechały z ramki na obrazek.
   Tło żadne — przez przezroczystą ramkę widać `--paper` strony, na którym
   karuzela stoi. Cień i promień idą zmiennymi, bo niesie je `.ok-obraz`;
   `overflow: hidden` musiało odpaść, bo obcinało jego cień. Nic poza samą
   okładką w tej ramce nie leży, więc nie ma czego trzymać w karbach. */
.hc-slide .cov a {
  display: block; position: relative;
  width: 300px; aspect-ratio: 2/3;
  --ok-promien: 5px;
  --ok-cien: drop-shadow(0 30px 60px rgba(40, 18, 18, .32));
  transform: rotate(-3deg);
}
.hc-slide h2 { font-size: clamp(40px, 5.6vw, 64px); text-transform: uppercase; margin: 10px 0 4px }
.hc-slide .auth { font-style: italic; color: var(--brand); font-size: 19px; margin-bottom: 14px }
.hc-slide p { font-size: 18px; color: var(--muted); max-width: 520px; margin-bottom: 16px }
.hc-price { font-family: 'Oswald'; font-weight: 600; font-size: 25px; color: var(--brand); margin-bottom: 18px }
.hero-cta { display: flex; gap: 14px; flex-wrap: wrap }

/* Wejście slajdu: okładka lekko wjeżdża, a tekst pojawia się po kolei.
   Wszystko pod `.hc-ready`, które dokłada skrypt — bez JavaScriptu nie ma
   komu zdjąć stanu początkowego i slajd zostałby przezroczysty na zawsze.
   To ta sama zasada co przy lupie w księgarni i banerze ciasteczek:
   strona ma działać bez skryptu, skrypt tylko dokłada ruch.

   Poprawki 20.10, pkt 4: animacja jedzie teraz na `<a>`, nie na `<img>`,
   bo przechył `rotate(-3deg)` przeniósł się na ramkę razem z wymiarem
   i cieniem. Zostawiona na obrazku obróciłaby go drugi raz, w sumie
   o -6°, i przesuwałaby okładkę wewnątrz nieruchomej ramki zamiast
   wjeżdżać nią całą. Wszystkie cztery reguły wejścia muszą wskazywać
   ten sam element — stan początkowy, docelowy i oba warianty przy
   wyłączonych animacjach. */
.hc-ready .hc-slide .cov a,
.hc-ready .hc-slide .hc-text > * {
  opacity: 0; transition: opacity .5s ease, transform .5s cubic-bezier(.2, .7, .3, 1);
}
.hc-ready .hc-slide .cov a { transform: rotate(-3deg) translateX(34px) scale(.97) }
.hc-ready .hc-slide .hc-text > * { transform: translateY(14px) }
.hc-ready .hc-slide.is-on .cov a { opacity: 1; transform: rotate(-3deg) translateX(0) scale(1) }
.hc-ready .hc-slide.is-on .hc-text > * { opacity: 1; transform: translateY(0) }
/* Kaskada: nadkreślenie, tytuł, autor, opis, cena, przyciski. Krok 90 ms —
   przy 5 s na slajd ostatni element dojeżdża po ~0,95 s i zostaje prawie
   cztery sekundy na przeczytanie. */
.hc-ready .hc-slide.is-on .hc-text > :nth-child(1) { transition-delay: .10s }
.hc-ready .hc-slide.is-on .hc-text > :nth-child(2) { transition-delay: .19s }
.hc-ready .hc-slide.is-on .hc-text > :nth-child(3) { transition-delay: .28s }
.hc-ready .hc-slide.is-on .hc-text > :nth-child(4) { transition-delay: .37s }
.hc-ready .hc-slide.is-on .hc-text > :nth-child(5) { transition-delay: .46s }
.hc-ready .hc-slide.is-on .hc-text > :nth-child(6) { transition-delay: .55s }

.hc-arrow {
  position: absolute; top: 44%; transform: translateY(-50%); z-index: 5;
  width: 46px; height: 46px; border-radius: 50%;
  background: var(--card); border: 1px solid var(--line);
  display: grid; place-items: center; cursor: pointer;
  color: var(--brand); transition: .2s; padding: 0;
}
.hc-arrow:hover { background: var(--brand); color: #fff; border-color: var(--brand) }
.hc-prev { left: 0 }
.hc-next { right: 0 }
.hc-dots { display: flex; gap: 8px; justify-content: center; padding-bottom: 10px }
/* Kropka jest paskiem postępu: tło to tor, `.f` w środku to wypełnienie
   jadące od 0 do 100 % w rytmie `--hc-czas` (ustawia go skrypt z wartości
   podanej w panelu). Szerokość toru rośnie na aktywnej kropce, żeby było
   widać, który slajd się liczy, także gdy ruch jest wyłączony. */
.hc-dots .d {
  width: 26px; height: 4px; border-radius: 4px; background: #e2d6c8;
  cursor: pointer; transition: width .2s; padding: 0; border: 0;
  overflow: hidden; display: block;
}
.hc-dots .d .f { display: block; width: 0; height: 100%; background: var(--brand) }
.hc-dots .d.on { width: 42px }
.hc-dots .d.on .f { width: 100%; transition: width var(--hc-czas, 5s) linear }
/* Cofnięcie przez strzałkę albo kliknięcie kropki zeruje pasek bez
   animacji — inaczej wypełnienie jechałoby wstecz przez całą sekundę. */
.hc-dots .d.on.hc-zeruj .f { width: 0; transition: none }

/* Ruch jest ozdobą, nie treścią: kto prosi system o mniej animacji, dostaje
   tę samą karuzelę bez jazdy i bez automatycznej zmiany slajdu (to drugie
   robi skrypt). Jedno zapytanie medialne na trzy miejsca, w których coś się
   tu rusza — taśma, wejście slajdu i pasek postępu. */
@media (prefers-reduced-motion: reduce) {
  .hc-track { transition: none }
  .hc-ready .hc-slide .cov a,
  .hc-ready .hc-slide .hc-text > * { opacity: 1; transition: none }
  /* Przechył okładki zostaje — to nie ruch, tylko jej docelowy wygląd. */
  .hc-ready .hc-slide .cov a { transform: rotate(-3deg) }
  .hc-ready .hc-slide .hc-text > * { transform: none }
  .hc-dots .d.on .f { transition: none }
}

section { padding: 60px 0 }
.shead {
  display: flex; align-items: flex-end; justify-content: space-between;
  margin-bottom: 32px; gap: 20px; flex-wrap: wrap;
}
.shead h2 { font-size: clamp(28px, 3.6vw, 40px); text-transform: uppercase }
/* Nagłówek szeryfowy — dziś tylko sekcja zespołu (A8). Bez wersalików
   i bez rozstrzelenia: Lora w kapitalikach traci to, po co ją tu
   wzięliśmy, czyli cieplejszy ton tam, gdzie mowa o ludziach. */
.shead h2.serif {
  font-family: 'Lora', Georgia, serif; font-weight: 600;
  text-transform: none; letter-spacing: 0; line-height: 1.15;
}
.shead a {
  font-family: 'Oswald'; font-size: 13px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--brand);
}

/* ==========================================================================
   OKŁADKA W RAMCE 2:3 — jedna zasada dla całego silnika
   (poprawki 20.10, pkt 4, zasada zmieniona 21.10;
    markup w `app/templates/_okladka_macros.html`)
   ==========================================================================

   Ramka okładki ma wszędzie proporcję 2:3. Okładka o innej proporcji
   (kwadratowa książeczka dla dzieci, wizualizacja z białym tłem) idzie
   w CAŁOŚCI, wyśrodkowana w poziomie i WYRÓWNANA DO DOŁU ramki, a wolne
   miejsce wokół niej zostaje PUSTE — czyli ma kolor tej sekcji, w której
   kafelek leży.

   ZMIANA ZASADY (21.10). Pierwsze podejście (pkt 4, cz. 1) wypełniało to
   wolne miejsce rozmytym i przyciemnionym kadrem tej samej okładki.
   Właściciel to odwołał i ma rację: rozmyta kopia jest w tej ramce
   drugim, mocniejszym obrazem niż sama okładka — oko idzie najpierw do
   niej. Zasada jest teraz odwrotna i prostsza: pod okładką nie ma
   NICZEGO. Żadnej białej ani kremowej karty, żadnej obwódki, żadnego
   rozmycia.

   „KOLOR TŁA SEKCJI" ROBI SIĘ SAM, przez przezroczystość — i to jest
   cała decyzja tej zmiany. Ramka nie ustawia własnego tła, więc widać
   przez nią dokładnie to, co rysuje sekcja: gradient półki nowości
   (`.feat-wrap`), biel karty w księgarni (`body.shop .card`), `--paper`
   na karcie książki i na stronie wydarzenia. Wariant „token na każdą
   sekcję" odpadł, bo sekcji z okładkami jest osiem i trzeba by trzymać
   osiem wartości zgodnych z tłem, które rysuje ktoś inny — a pod półką
   nowości leży GRADIENT, czyli wartości tam po prostu nie ma. Przy
   przezroczystości nie ma czego rozjechać i nie ma czego pilnować.
   Tryb ciemny: w silniku go dziś nie ma (ani `prefers-color-scheme`,
   ani przełącznika), ale ta zasada nie wpisuje ani jednego koloru na
   sztywno, więc zadziała w dniu, w którym powstanie.

   CIEŃ IDZIE ZA OKŁADKĄ, nie za ramką. `box-shadow` na ramce obrysowywał
   prostokąt 2:3, czyli przy kwadratowej książeczce rysował cień wokół
   pustego miejsca — dokładnie tę „kartę pod okładką", której ma nie być.
   Teraz niesie go `filter: drop-shadow` na samym obrazku: ten idzie za
   kanałem alfa i za kadrem po `contain`, więc kładzie się pod tym, co
   widać. Wartość przychodzi ramce przez dziedziczoną zmienną `--ok-cien`
   (zmienne własne dziedziczą się przez `<a>`, który w kilku miejscach
   stoi między ramką a obrazkiem), bo każde miejsce ma inną wysokość
   cienia: 60 px w karuzeli, 14 px przy kafelku autora. Brak zmiennej
   znaczy „bez cienia" i jest poprawnym stanem — tak stoi dziś siatka
   księgarni, koszyk i półka e-booków.

   Z tego samego powodu zaokrąglenie przeszło na obrazek (`--ok-promien`).
   Na ramce działało tylko razem z `overflow: hidden`, a `overflow: hidden`
   obcina `drop-shadow` dziecka. Ramki tracą więc `overflow: hidden`
   wszędzie poza siatką księgarni, gdzie nadal trzyma w karbach
   powiększenie okładki przy najechaniu.

   Te reguły zastępują siedem osobnych `object-fit: contain` rozsianych
   po arkuszu (decyzja 4A, 1.9). Nowych nie dopisujemy: ramka woła makro
   i dostaje wszystko. */
.ok-obraz, .ok-pusta {
  width: 100%; height: 100%; display: block;
  border-radius: var(--ok-promien, 0);
  /* `filter: none` jest poprawną wartością, a `drop-shadow(none)` nie —
     dlatego cień wchodzi tu całym filtrem, a nie samą jego wartością. */
  filter: var(--ok-cien, none);
}
.ok-obraz {
  object-fit: contain;
  /* Wyśrodkowana w poziomie, do dołu w pionie. Dół, a nie środek, bo
     o to prosi zlecenie i ma w tym rację: w rzędzie kafelków okładki
     o różnej wysokości stoją wtedy na jednej linii, jak książki na
     półce. Wyśrodkowane pionowo wisiałyby każda na swojej wysokości
     i rząd przestawał wyglądać na rząd.
     Wyjątek „prawie 2:3" (`.ok-przytnij`) nadpisuje to z atrybutu
     `style`, bo tam `object-position` jest punktem skupienia z panelu. */
  object-position: 50% 100%;
  /* Cień ma się pogłębiać przy najechaniu na kafelek tak samo płynnie,
     jak robił to `box-shadow` na ramce. Przejście musi stać TUTAJ, na
     `filter`: zmienna `--ok-cien` sama się nie animuje (zmienne własne
     bez `@property` są nieinterpolowalne), ale wyliczony z niej filtr
     już tak. Czas ten sam co `.nbook .cv`. */
  transition: filter .28s;
}
/* Tytuł bez okładki w ramce, która nie ma własnej zaślepki — czyli
   kafelek katalogu i półki (dorobek sprzed czasów zdjęć, B3). Księgarnia,
   koszyk, ulubione i półka e-booków mają swoje `div.nocover` i tu nie
   trafiają: tam szablon rozstrzyga `{% if cover %}` przed wołaniem makra.
   Kolor karty zostaje właśnie TU i tylko tu. Zakaz „białej karty pod
   okładką" mówi o pustym miejscu WOKÓŁ okładki; tytuł bez okładki nie ma
   wokół czego zostawiać pustego miejsca, a niewidoczna dziura w rzędzie
   byłaby czytana jako błąd składu, nie jako brak zdjęcia. */
.ok-pusta { background: var(--card) }
/* Jedyny wyjątek od „nigdy nie przycinamy" (pkt 4, cz. 2). Okładka
   odbiegająca od 2:3 o mniej niż pięć procent — 600×880 zamiast 600×900,
   czyli margines składu, a nie inny format — wchodzi w ramkę przez
   przycięcie, bo cały obraz zostawiłby jej dwa paski po trzy piksele:
   wygląda to jak błąd renderowania, nie jak zamierzone tło.

   Klasę zakłada makro (`_okladka_macros.html`), a próg liczy serwer
   (`app/covers.py::kadr`) — tutaj nie ma żadnej liczby do przestawienia.
   `object-position` idzie obok, w atrybucie `style`, bo to wartość
   z bazy: punkt skupienia ustawiony przy książce w panelu.

   Reguła musi być JEDYNA, która mówi o `object-fit` na okładce. Przy
   pierwszym podejściu nie była: po każdym miejscu z okładką został
   z czasów przed częścią pierwszą własny wiersz w rodzaju
   `.nbook .cv img { object-fit: contain }`. Dopóki wszędzie było
   `contain`, te duplikaty niczego nie psuły — ale mają wyższą swoistość
   (dwie klasy i element kontra dwie klasy), więc wyjątek przegrywał
   z nimi w ciszy: w HTML-u stała klasa `ok-przytnij` i `object-position`
   z panelu, a przeglądarka dalej robiła `contain`. Wyszło to dopiero na
   zrzucie — porównanie „przed częścią drugą" i „po" było identyczne
   co do piksela. Pięć takich wierszy (półka i katalog, karta książki,
   katalog księgarni, koszyk, półki konta) zostało usuniętych; jeśli
   dopisujesz nowe miejsce z okładką, nie dopisuj `object-fit` — makro
   i te dwie reguły załatwiają wszystko. */
.ok-obraz.ok-przytnij { object-fit: cover }
/* Ramka dla miejsc, w których okładka nie miała własnego kontenera —
   kafelek autora na stronie głównej (`.author-book`) i okładki przy
   wydarzeniu (`.wyd-okladki`).

   Bez `overflow: hidden` od 21.10: obcinało `drop-shadow` z obrazka
   dokładnie tam, gdzie cień ma być widoczny, czyli pod dolną krawędzią
   okładki. Zaokrąglenie, dla którego ono tu stało, przeszło na obrazek
   (`--ok-promien`), a w tych dwóch ramkach nie leży nic, co mogłoby
   z niej wystać. */
.ok-ramka { position: relative; aspect-ratio: 2/3 }

/* --- Wizualizacja 3D (poprawki 20.10, pkt 5) -------------------------
   Książka jako przedmiot: PNG bez tła, w trzech miejscach, w których
   okładka stoi duża i sama (karuzela, karta książki, list). Nigdy
   w siatce kafelków i to jest wymóg ze zlecenia, nie oszczędność:
   kafelki w rzędzie mają wyglądać na równe, a render ma inną sylwetkę
   niż płaski prostokąt — jedna książka „w 3D" między pięcioma płaskimi
   wygląda jak błąd składu. Pkt 3 tej samej paczki równał te kafelki
   i byłoby to cofnięcie jego roboty.

   Cień z `drop-shadow`, nie z `box-shadow`: idzie za kanałem alfa,
   więc kładzie się pod sylwetką książki. `box-shadow` obrysowałby
   prostokąt obrazka, czyli narysował ramkę wokół przezroczystego tła —
   dokładnie to, czego ten obrazek ma nie mieć. */
.ok-3d {
  position: relative; display: block;
  width: 100%; height: 100%; object-fit: contain;
  filter: drop-shadow(0 22px 26px rgba(40, 18, 18, .34));
}
/* Ramka, która niesie render, przestaje być ramką. Od 21.10 zostało z tego
   jedno: zdjęcie przekrzywienia. Trzy pozostałe rzeczy, które trzeba tu
   było odkręcać — kartę pod spodem, prostokątny cień i zaokrąglenie —
   ramki straciły już same, razem ze zmianą zasady okładki, bo każda z nich
   była widoczna jako obcy prostokąt za książką nie tylko przy renderze 3D,
   ale i przy kwadratowej książeczce dla dzieci.

   Przechył zostaje po stronie wyjątku, bo w karuzeli jest zamierzony:
   płaska okładka ma leżeć lekko ukosem, jak wyjęta z pudła, a render
   książki stojącej w perspektywie przekrzywiony o kolejne trzy stopnie
   wygląda na przewrócony.

   `aspect-ratio` zostaje 2:3, choć render bywa szerszy. Celowo: ramka
   trzyma wtedy dokładnie to samo miejsce co płaska okładka, więc
   wstawienie i zdjęcie wizualizacji nie przestawia układu strony ani
   nie rusza przyklejonej kolumny na karcie książki. Render wchodzi
   w nią przez `contain`, czyli w całości. */
.hc-slide .cov a.ok-rama3d, .kb-cover-link.ok-rama3d { transform: none }

/* --- półka nowości (ZMIANA 4 + poprawka: 6 w rzędzie, mniejsze karty) --- */
.feat-wrap { background: linear-gradient(180deg, #fffdf9, #f6f1ea) }
.news-books { display: grid; grid-template-columns: repeat(6, 1fr); gap: 18px }
.nbook { display: flex; flex-direction: column }
/* Ramka kafelka półki i katalogu — najliczniejsza w silniku (półka
   nowości, `/publikacje`, bibliografia autora, „Wkrótce", dwie półki
   powiązanych na karcie książki).

   21.10: bez tła i bez `overflow: hidden`. Pod kafelkiem leży gradient
   `.feat-wrap` albo `--paper` i to on ma być widoczny wokół okładki
   o innej proporcji. Cień i promień niosą zmienne, bo rysuje je
   `.ok-obraz`; gdyby zostały na ramce, obrysowywałyby prostokąt 2:3
   także tam, gdzie okładka go nie wypełnia. Plakietki i serduszko stoją
   w tej ramce jako `position: absolute` i `overflow` ich nie dotyczy. */
.nbook .cv {
  position: relative; aspect-ratio: 2/3; transition: .28s;
  --ok-promien: 5px;
  --ok-cien: drop-shadow(0 14px 30px rgba(40, 18, 18, .18));
}
/* Okładki w `.cv` nie ma tu już ani słowa — rozmiar, `object-fit`
   i `display` niesie `.ok-obraz` (patrz wyżej). W tym pojemniku nie stoi
   żaden inny obrazek: zaślepka bez okładki to `.ok-pusta` z makra. */
.nbook:hover .cv {
  transform: translateY(-6px) rotate(-1deg);
  --ok-cien: drop-shadow(0 24px 44px rgba(40, 18, 18, .28));
}
/* `z-index: 2` jak u sąsiadów w tej samej ramce — `.frag` ma 2,
   serduszko 3. Ta jedna plakietka go nie miała i uchodziło jej to na
   sucho, bo nic pozycjonowanego nie stało za nią w dokumencie. Punkt 4
   to zmienił: okładka dostała `position: relative` (żeby nie zakryło
   jej rozmyte tło) i zaczęła się malować nad plakietką — „NOWOŚĆ"
   i „ZAPOWIEDŹ" zniknęły z kafelków katalogu i półki. Wyszło to na
   zrzutach „po", przed zamknięciem punktu.

   Zmiana zasady z 21.10 zdjęła z okładki `position: relative` (nie ma
   już czego przebijać, bo nie ma warstwy tła), ale `z-index` zostaje
   i ma zostać: obrazek niesie teraz `filter`, czyli zakłada własny
   kontekst nakładania. Pozycjonowana plakietka z dodatnim `z-index`
   maluje się nad nim, plakietka bez niego — nie na każdej przeglądarce
   tak samo. Jedno przeoczenie na tej samej plakietce wystarczy. */
.nbook .tag2 {
  position: absolute; top: 12px; left: 12px; z-index: 2;
  background: var(--brand); color: #fff;
  font-family: 'Oswald'; font-size: 10px; text-transform: uppercase;
  letter-spacing: .05em; padding: 3px 9px; border-radius: 100px;
}
.nbook .tag2.z { background: var(--gold) }
.nbook .tag2.b { background: #2f6d4f }
/* „Wyczerpany" / „Na zamówienie" (B3) — wygaszony grafit. Status to
   zastrzeżenie, nie wyróżnienie: ma być czytelny, ale nie ma prawa
   krzyczeć głośniej od bordowej nowości obok. */
.nbook .tag2.ns { background: #585049 }
.nbook .k {
  font-family: 'Oswald'; font-size: 10px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted); margin-top: 12px;
}
/* `h4` razem z `h3`, bo w bloku „O autorze" na karcie książki ten sam
   kafelek stoi o poziom niżej (nazwisko w `h2`, „Inne książki" w `h3`).
   Tytuł książki ma wtedy wyglądać tak samo — zmienia się tylko miejsce
   w spisie nagłówków, nie waga wizualna. */
.nbook h3, .nbook h4 {
  font-family: 'Oswald'; font-weight: 500; font-size: 16px;
  text-transform: uppercase; margin: 3px 0 1px;
}
.nbook .au { font-size: 12.5px; color: var(--muted); font-style: italic }
.nbook .price { display: flex; align-items: baseline; gap: 6px; margin-top: 8px; flex-wrap: wrap }
.nbook .price b { font-family: 'Oswald'; font-weight: 600; font-size: 17px; color: var(--brand) }
.nbook .price s { font-size: 12px; color: var(--muted) }
.nbook .price .okl { font-size: 10px; color: var(--muted); font-style: italic; width: 100% }
/* `margin-top: auto` dosuwa przycisk do dołu kafelka, a nie do tekstu nad
   nim. W makiecie nie było to widać, bo wszystkie tytuły mieściły się
   w jednym wierszu; w bazie stoi „Magdalena Samozwaniec. 91 wspomnień
   o pierwszej damie polskiej satyry" na trzy wiersze i bez tego jego
   przycisk zjeżdżał o 30 px niżej od sąsiadów. Działa, bo `.nbook` jest
   kolumną flex (wyżej), a komórki siatki rozciągają się do wysokości
   najwyższej w rzędzie. */
.nbook .buy { margin-top: auto; padding-top: 12px }
/* `.morebtn` („Więcej" → karta książki) dzieli z `.addbtn` cały kształt,
   bo oba stoją w tym samym miejscu kafelka i zamieniają się miejscami
   zależnie od flagi sklepu (DECYZJE poz. 90). Rozjechane wysokości
   dawałyby półkę skaczącą po włączeniu sklepu. Różni je sam kolor
   obwódki: zakup jest bordowy, doczytanie — grafitowe, jak `.btn-ghost`
   w hero. */
.addbtn, .morebtn {
  width: 100%; border: 1px solid var(--brand); color: var(--brand);
  background: none; border-radius: 100px; padding: 9px 10px;
  font-family: 'Oswald'; font-size: 11px; font-weight: 500;
  letter-spacing: .05em; text-transform: uppercase; cursor: pointer;
  transition: .2s; display: inline-flex; align-items: center;
  justify-content: center; gap: 7px;
}
.addbtn:hover { background: var(--brand); color: #fff }
.morebtn { border-color: var(--ink); color: var(--ink); text-decoration: none }
.morebtn:hover { background: var(--ink); color: #fff }

/* --- zakup: wspólne dla karuzeli, półki, katalogu i karty (A2) ------------
   Formularz „dodaj do koszyka" opakowuje przycisk, bo dodanie musi być
   POST-em, a nie linkiem — link do akcji zmieniającej stan dałby się
   wywołać z cudzej strony i byłby pobierany przez roboty.

   `display: contents` sprawia, że to opakowanie znika z układu i przycisk
   trafia bezpośrednio w siatkę rodzica. Inaczej każde miejsce, w którym
   przycisk dotąd był bezpośrednim dzieckiem (`.nbook .buy`, `.hero-cta`,
   `.kb-fmt-buy`), musiałoby dostać własną regułę na nowy element pośredni —
   cztery reguły zamiast jednej, i cztery okazje, żeby któraś się rozjechała. */
.zakup { display: contents }

/* Przedsprzedaż to też zakup, więc kształt zostaje ten sam — zmienia się
   tylko kolor, na złoty: „kupisz, ale nie dziś". Osobny kolor jest
   potrzebny, bo przy identycznym przycisku jedyną różnicą byłby napis,
   a ten na kafelku czyta się dopiero po tytule i cenie. */
.addbtn.przedsprzedaz { border-color: var(--gold); color: #7a5c22 }
.addbtn.przedsprzedaz:hover { background: var(--gold); color: #fff }
/* W hero przycisk jest wypełniony (`.btn-primary`), więc złoto wchodzi tłem,
   nie obwódką — inaczej karuzela dostałaby biały napis na białym. */
.btn.przedsprzedaz { background: var(--gold); border-color: var(--gold); color: #fff }
.btn.przedsprzedaz:hover { background: #96702f; border-color: #96702f }

/* „Zapytaj o dostępność" nie jest zakupem — nic nie ląduje w koszyku, więc
   nie dostaje bordowej obwódki zarezerwowanej dla kasy. Wygląda jak
   „Więcej": ta sama pigułka, grafit zamiast bordo. */
.addbtn.pytaj, .btn.pytaj {
  border-color: var(--ink); color: var(--ink);
  background: none; text-decoration: none;
}
.addbtn.pytaj:hover, .btn.pytaj:hover { background: var(--ink); color: #fff }

/* Data premiery NAD przyciskiem przedsprzedaży (poprawki 7, pkt 7) —
   obok się nie mieści: przy 360 px „Zamów w przedsprzedaży" i „Wysyłka
   po premierze: 23.10.2026" w jednym wierszu nie mieszczą się ani razem,
   ani osobno. Nad, a nie pod: przycisk musi zostać OSTATNIM elementem
   kafelka (`.buy`/`.buy-act` ma `margin-top: auto` i rząd w siatce
   rozciąga wszystkie karty do wysokości najwyższej) — inaczej ten
   dopisek, obecny tylko przy przedsprzedaży, odsuwałby przycisk od dołu
   kafelka dokładnie o swoją wysokość, i w rzędzie z choć jedną
   przedsprzedażą przyciski przestawały się równać z resztą.
   Margines z obu stron (nie tylko `margin-top`), bo ten sam dopisek
   stoi raz jako ostatnie dziecko `.buy-act` (księgarnia, ulubione — tam
   liczy się odstęp od ceny nad nim), a raz jako osobny element przed
   `.buy` (półka na stronie głównej — tam liczy się odstęp do przycisku
   pod nim). */
.premiera-info {
  margin: 7px 0; font-size: 11.5px; line-height: 1.45;
  color: var(--muted);
}
.premiera-info b { color: #7a5c22; font-weight: 600 }
@media (max-width: 1180px) { .news-books { grid-template-columns: repeat(4, 1fr) } }

/* --- plakietka „fragment" na kafelkach (A5) -------------------------------
   Jedna reguła dla wszystkich miejsc, w których stoi kafelek książki:
   półka na stronie głównej (`.nbook .cv`), katalog księgarni
   (`.card .ph`), strona autora, „Czytelnicy wybrali także" i „Wkrótce".
   Wszystkie te pojemniki są już `position: relative` z `overflow: hidden`,
   bo trzymają plakietkę „Nowość" — więc nowa nie potrzebuje niczego
   dokładać do układu.

   Siada w LEWYM DOLNYM rogu okładki, choć lewy górny wyglądałby
   naturalniej. Powód: lewy górny jest zajęty przez `.tag2` / `.pin`
   („Nowość", „Zapowiedź"), a te dwie rzeczy występują razem — zapowiedź
   z fragmentem to sytuacja najzwyklejsza z możliwych. W jednym rogu
   jedna zasłoniłaby drugą.

   Tło jest półprzezroczystą czernią, nie kolorem marki. Plakietka leży na
   okładce, której koloru nie znamy z góry, a w katalogu jest 79 różnych:
   bordowy napis na bordowej okładce nie istnieje. Czerń z rozmyciem
   działa na każdym tle, bo sama sobie robi kontrast. */
.frag {
  position: absolute; bottom: 10px; left: 10px; z-index: 2;
  background: rgba(35, 26, 23, .82); color: #fff;
  font-family: 'Oswald', sans-serif; font-size: 10px; font-weight: 500;
  text-transform: uppercase; letter-spacing: .06em;
  padding: 3px 9px; border-radius: 100px;
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}
/* W karuzeli nie ma rogu, do którego dałoby się przykleić plakietkę:
   okładka jest obrócona o -3°, ma 300 px i stoi wyśrodkowana w szerokiej
   kolumnie, więc „lewy dolny róg pojemnika" wypadłby obok okładki, a nie
   na niej. Dlatego w hero plakietka dosiada się do nadkreślenia.

   Musi przy tym zostać DZIECKIEM `.eyebrow`, a nie kolejnym dzieckiem
   `.hc-text`: kaskada wejścia slajdu rozdaje opóźnienia przez
   `:nth-child`, więc nowy element w tym miejscu przesunąłby wszystkie
   następne o jedno i tytuł wjeżdżałby z opóźnieniem autora. */
.eyebrow .frag { position: static; margin-left: 10px; letter-spacing: .06em }

/* --- serduszko „ulubione" (B1) --------------------------------------------
   Jeden wygląd przycisku, trzy miejsca osadzenia. Przycisk opakowuje
   formularz, bo odłożenie tytułu zmienia stan konta i musi być POST-em
   (patrz `_ulubione_macros.html`) — opakowanie znika z układu tak samo
   jak przy `.zakup`, żeby nie mnożyć reguł na element pośredni.

   Serce jest obwiedzione, nie wypełnione, dopóki tytuł nie jest na
   liście. Dwa stany na jednej ścieżce SVG: `fill` zmienia arkusz, a nie
   drugi rysunek — dwa pliki rozjechałyby się kształtem przy pierwszej
   poprawce. */
.serce-form { display: contents }
.serce {
  border: none; background: none; padding: 0; cursor: pointer;
  color: inherit; display: inline-flex; align-items: center; gap: 8px;
  transition: transform .18s, color .18s;
}
.serce svg { fill: none; transition: fill .18s, stroke .18s }
.serce:hover { transform: scale(1.08) }
.serce.on svg { fill: var(--brand); stroke: var(--brand) }

/* W rogu okładki. Prawy GÓRNY, nie lewy: lewy górny trzyma plakietkę
   „Nowość" / „Zapowiedź" (`.tag2`, `.pin`), a lewy dolny — „fragment".
   Trzy rzeczy w dwóch rogach znaczyłyby, że zapowiedź z fragmentem
   zasłania serduszko dokładnie tam, gdzie najczęściej się przydaje.

   Tło jest półprzezroczystą czernią z rozmyciem, tak samo jak przy
   `.frag` i z tego samego powodu: ikona leży na okładce, której koloru
   nie znamy, a w katalogu jest ich 79. Bordowe serce na bordowej
   okładce nie istnieje. */
.serce-rog {
  position: absolute; top: 10px; right: 10px; z-index: 3;
  width: 32px; height: 32px; border-radius: 100px;
  justify-content: center; gap: 0;
  background: rgba(35, 26, 23, .72); color: #fff;
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}
.serce-rog.on svg { fill: #fff; stroke: #fff }
.serce-rog:hover { background: rgba(35, 26, 23, .9) }

/* W wierszu przycisków karty książki — tu serce ma podpis, więc wygląda
   jak trzeci przycisk, nie jak ikonka. Kształt pigułki ten sam co
   `.morebtn`, żeby wiersz „Dodaj do koszyka / Przeczytaj fragment /
   Dodaj do ulubionych" nie składał się z trzech różnych brył. */
.kb-ulub { margin-top: 14px }
.serce-wiersz {
  border: 1px solid var(--line); border-radius: 100px;
  padding: 9px 16px; color: var(--muted);
}
.serce-wiersz:hover { border-color: var(--brand); color: var(--brand); transform: none }
.serce-wiersz.on { border-color: var(--brand); color: var(--brand) }
.serce-napis {
  font-family: 'Oswald', sans-serif; font-size: 11px; font-weight: 500;
  letter-spacing: .05em; text-transform: uppercase;
}

/* W karuzeli — obok „Poznaj książkę", bo obrócona okładka slajdu nie ma
   rogu, do którego dałoby się cokolwiek przykleić (patrz `.eyebrow .frag`
   wyżej). Sama ikona bez podpisu: wiersz hero ma już dwa przyciski
   z napisami i trzeci zrobiłby z niego spis treści. */
.serce-hero {
  width: 44px; height: 44px; border-radius: 100px; justify-content: center;
  gap: 0; border: 1px solid var(--line); background: var(--card);
}
.serce-hero:hover { border-color: var(--brand); color: var(--brand) }

/* Ikona z licznikiem w nagłówku. Kształtu nie ustawia — bierze go
   z `.cart` (wizytówka) albo `.iconbtn` (księgarnia), bo makro dostaje
   tę klasę argumentem. Zostaje samo podbarwienie przy najechaniu:
   pusty obrys w pasku ikon czyta się jako „nieaktywne", a ta ikona
   prowadzi na listę zawsze, także gdy lista jest pusta. */
.ulub-ikona svg { fill: none; transition: fill .18s }
.ulub-ikona:hover svg { fill: rgba(123, 30, 42, .14) }

/* --- aktualności --- */
.acts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px }
.act {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; overflow: hidden; transition: .25s;
}
.act:hover { transform: translateY(-5px); box-shadow: 0 18px 36px rgba(40, 18, 18, .12) }
.act .ph { aspect-ratio: 16/10; overflow: hidden; position: relative }
.act .ph img { width: 100%; height: 100%; object-fit: cover }
/* Poprawki 3 (A12): zdjęcie, którego proporcje mocno odbiegają od
   kafelka (plakat, grafika z tekstem na całą wysokość) — pokazujemy je
   w całości, a wolne miejsce wypełnia rozmyte tło z tego samego obrazu,
   zamiast obcinać `object-fit: cover`. `.ph-tlo` niesie tło (osadzone
   przez szablon jako `style="background-image: …"`, bo CSS nie umie
   przepisać `src` z `<img>` na tło bez duplikowania adresu w arkuszu),
   `img` idzie na wierch z `object-fit: contain`. */
.act .ph.ph-cale .ph-tlo {
  position: absolute; inset: 0; background-size: cover;
  background-position: center; filter: blur(22px) brightness(.92);
  transform: scale(1.15);
}
.act .ph.ph-cale img { object-fit: contain; position: relative }
.act .ab { padding: 22px }
.act .date {
  font-family: 'Oswald'; font-size: 11.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--brand);
}
.act h3 { font-family: 'Oswald'; font-weight: 500; font-size: 21px; text-transform: uppercase; margin: 8px 0 }
.act p { font-size: 14.5px; color: var(--muted) }
/* Kafelek z bazy jest linkiem, kafelek z makiety zostaje `<div>`-em —
   oba muszą wyglądać tak samo, więc `<a>` traci podkreślenie i kolor
   linku. `display: block`, bo inline-owy `<a>` nie przyjmie `aspect-ratio`
   zdjęcia w środku. */
a.act { display: block; color: inherit; text-decoration: none }
.act .miejsce {
  font-size: 13.5px; color: var(--ink); font-weight: 600;
  margin-bottom: 4px;
}

/* --- „Co u nas słychać": taśma z trzech źródeł (B2e) ----------------------
   Taśma, nie siatka: pozycji jest do pięciu, a w rzędzie mieszczą się trzy.
   Przewijanie jest natywne — `overflow-x: auto` ze `scroll-snap` działa
   palcem, gładzikiem i kółkiem bez ani jednej linijki skryptu, a strzałki
   są dokładką dla myszy i klawiatury (odsłania je `main.js`, i tylko wtedy,
   gdy jest co przewijać).

   Automatycznej zmiany tu nie ma, inaczej niż w karuzeli premier. Hero
   dostało automat na wyraźne życzenie zlecenia (A1) i wozi jeden tytuł na
   slajd; tu stoi lista do czytania, a lista, która ucieka spod oka
   w połowie zdania, jest wadą, nie ozdobą. */
.sly { position: relative }
.sly-tasma {
  display: grid; grid-auto-flow: column;
  grid-auto-columns: calc((100% - 48px) / 3);
  gap: 24px; overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory;
  /* Oddech na cień i na podniesienie kafelka przy najechaniu: `overflow`
     przycina do krawędzi pudełka, więc bez tego marginesu `translateY(-5px)`
     ucina kartę u góry. */
  padding: 6px 2px 16px;
  scrollbar-width: thin;
}
.sly-tasma > .act { scroll-snap-align: start }
/* Kafelek jest kolumną, która rośnie do pełnej wysokości taśmy. Bez tego
   karta bez zajawki (odcinek bez opisu) byłaby niższa od sąsiadek i rząd
   miałby ząbki — przy siatce `.acts` wyrównywał je sam `grid`. */
.sly-tasma > a.act { display: flex; flex-direction: column }
.sly-tasma > .act .ab { flex: 1 }
/* Widoczna obwódka kursora klawiatury: taśma jest `tabindex="0"`, bo bez
   tego nie da się jej przewinąć strzałkami bez myszy. */
.sly-tasma:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px }

/* Plakietka rodzaju. Bez niej data nad tytułem znaczy raz dzień publikacji,
   raz termin targów, raz dzień emisji nagrania — i nie da się tego
   rozróżnić. Kolory z palety serwisu, po jednym na rodzaj: zieleń jest
   kolorem „jest w sprzedaży" przy książkach, więc nadaje się do terminu,
   na który można zdążyć; złoto należy do cyklu. */
.rodzaj {
  display: inline-block; font-family: 'Oswald'; font-weight: 500;
  font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
  color: #fff; background: var(--brand); border-radius: 3px;
  padding: 3px 8px; margin-bottom: 10px;
}
.rodzaj-wydarzenie { background: #2f6d4f }
.rodzaj-odcinek { background: var(--gold) }

/* Strzałki leżą na krawędziach taśmy, a nie obok niej: `.wrap` nie ma po
   bokach marginesu, w który dałoby się je wysunąć bez rozpychania strony
   na wąskim ekranie. Wygląd ten sam, co w karuzeli hero. */
.sly-arrow {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 5;
  width: 46px; height: 46px; border-radius: 50%;
  background: var(--card); border: 1px solid var(--line);
  display: grid; place-items: center; cursor: pointer;
  color: var(--brand); transition: .2s; padding: 0;
  box-shadow: 0 6px 18px rgba(40, 18, 18, .14);
}
.sly-arrow:hover { background: var(--brand); color: #fff; border-color: var(--brand) }
/* Koniec taśmy: strzałka zostaje na miejscu, ale przygasa. Zniknięcie
   przestawiłoby układ pod kursorem w chwili, w której się w nią celuje. */
.sly-arrow:disabled { opacity: .3; cursor: default; box-shadow: none }
.sly-arrow:disabled:hover {
  background: var(--card); color: var(--brand); border-color: var(--line);
}
.sly-prev { left: 2px }
.sly-next { right: 2px }

/* Dwie karty, potem jedna z zajrzeniem w następną. Ostatni próg nie jest
   pełną szerokością celowo: wystający brzeg sąsiada jest jedyną
   informacją, że taśma ma ciąg dalszy — strzałki na telefonie przykrywa
   się kciukiem, a paska przewijania nie widać. */
@media (max-width: 980px) {
  .sly-tasma { grid-auto-columns: calc((100% - 24px) / 2) }
}
@media (max-width: 680px) {
  .sly-tasma { grid-auto-columns: 86% }
  .sly-arrow { width: 40px; height: 40px }
}

/* --- pasek „ostatnio na Instagramie" (B6c) ---
   Sześć kwadratów w rzędzie, na tej samej siatce co półka z tytułami.
   Bez strzałek i bez skryptu: kafelki są kwadratowe i małe, więc cały
   pasek mieści się w rzędzie do samego telefonu, gdzie schodzi do
   trzech na dwa. Taśma przewijana jak „Co u nas słychać" dawałaby
   przewijanie w poziomie dla zawartości, która się mieści. */
.iga {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 12px;
}
.ig-pole {
  position: relative; display: block; aspect-ratio: 1 / 1;
  border-radius: 10px; overflow: hidden; background: var(--line);
  box-shadow: 0 8px 20px rgba(40, 18, 18, .10); transition: .25s;
}
.ig-pole:hover { transform: translateY(-4px); box-shadow: 0 14px 30px rgba(40, 18, 18, .2) }
.ig-pole img { width: 100%; height: 100%; object-fit: cover; display: block }
.ig-pole:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px }

/* Ikona nagrania. Bez niej kafelek rolki jest nieruchomą klatką i nic
   nie mówi o tym, że pod spodem jest film — a rolki są na firmowym
   profilu połową treści. */
.ig-play {
  position: absolute; top: 8px; right: 8px; width: 26px; height: 26px;
  display: grid; place-items: center; border-radius: 50%;
  background: rgba(0, 0, 0, .55); color: #fff;
}

/* Podpis wjeżdża od dołu przy najechaniu. Na stałe przykryłby szóstą
   część każdego zdjęcia — a zdjęcie jest tu treścią, podpis dodatkiem.
   Na dotyku nie ma „najechania", więc tam zostaje sam obrazek; podpis
   jedzie i tak w `alt`, czyli tam, gdzie go przeczyta czytnik ekranu. */
.ig-cien {
  position: absolute; inset: auto 0 0 0; padding: 22px 10px 9px;
  background: linear-gradient(to top, rgba(26, 14, 14, .88), rgba(26, 14, 14, 0));
  color: #fff; opacity: 0; transition: opacity .25s;
  display: flex; flex-direction: column; gap: 2px;
}
.ig-pole:hover .ig-cien, .ig-pole:focus-visible .ig-cien { opacity: 1 }
.ig-txt {
  font-size: 11.5px; line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden;
}
.ig-data { font-size: 10px; letter-spacing: .06em; text-transform: uppercase; opacity: .8 }

/* Trzy w rzędzie, potem dwa na trzy. Nie jedna kolumna: pasek
   z Instagrama jest dodatkiem na dole strony i nie ma prawa zająć
   sześciu ekranów przewijania na telefonie. */
@media (max-width: 980px) {
  .iga { grid-template-columns: repeat(3, 1fr) }
}
@media (max-width: 560px) {
  .iga { grid-template-columns: repeat(2, 1fr); gap: 9px }
  /* Przy dwóch kafelkach w rzędzie zdjęcie jest na tyle duże, że
     podpis ma sens od razu — na dotyku nikt na nie nie najedzie. */
  .ig-cien { opacity: 1; padding: 18px 9px 8px }
}

/* --- o wydawnictwie --- */
.about { display: grid; grid-template-columns: 1fr 1fr; gap: 54px; align-items: center }
/* Zdjęcie sekcji wgrywa się w panelu i bywa go nie ma. Druga kolumna
   bez obrazka zostawiłaby tekst na połowie szerokości, obok pustki. */
.about--bez-zdjecia { grid-template-columns: 1fr }
.about-art { aspect-ratio: 4/3; border-radius: 16px; overflow: hidden; box-shadow: 0 24px 50px rgba(40, 18, 18, .22) }
.about-art img { width: 100%; height: 100%; object-fit: cover }
/* `h1` obok `h2`: na stronie głównej ten nagłówek jest jedną z sekcji,
   na podstronie „O nas" (A14) tytułem całości. Wygląda tak samo, bo to
   ten sam napis z panelu — różni się tylko rangą w dokumencie. */
.about h1, .about h2 { font-size: clamp(28px, 3.6vw, 40px); text-transform: uppercase; margin: 12px 0 16px }
.about p { color: #5a504a; font-size: 16.5px; margin-bottom: 14px }
.about .meta { display: flex; gap: 30px; margin-top: 22px }
.about .meta b { font-family: 'Oswald'; color: var(--brand); font-size: 30px; display: block; line-height: 1 }
.about .meta span { font-size: 13px; color: var(--muted) }
.about .about-cta { margin: 26px 0 0 }

/* --- profil wydawniczy na „O nas" (A14) ---
   Działy z katalogu, nie wypisane w szablonie. Siatka zawija się sama,
   bo działów jest dziś sześć, a po pierwszym nowym temacie siedem. */
.dzialy {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 14px;
}
.dzialy li {
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  padding: 18px 20px; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
.dzialy li > a, .dzialy li > span:first-child {
  font-family: 'Oswald'; font-size: 15px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink); flex: 1 1 100%;
}
.dzialy li > a { color: var(--brand) }
.dzialy li b { font-family: 'Oswald'; color: var(--brand); font-size: 26px; line-height: 1 }
.dzialy .dzial-ile { font-size: 13px; color: var(--muted) }

/* --- Ludzie wydawnictwa (ZMIANA 5) --- */
.ornament { width: 60px; height: 3px; background: var(--brand); border-radius: 3px; margin: 0 0 18px }
.ludzie { display: grid; grid-template-columns: repeat(3, 1fr); gap: 26px; margin-top: 6px }
.osoba {
  text-align: center; background: var(--card); border: 1px solid var(--line);
  border-radius: 16px; padding: 32px 24px; transition: .25s;
}
.osoba:hover { transform: translateY(-5px); box-shadow: 0 18px 38px rgba(40, 18, 18, .12) }
/* Od B4 karta bywa odnośnikiem do podstrony osoby, a bywa `div`-em —
   osoby z makiety nie mają adresu. Te trzy reguły zrównują jedno
   z drugim, żeby siatka wyglądała tak samo w obu przypadkach. */
a.osoba { display: block; color: inherit; text-decoration: none }
/* Poprawki 3, A3: samo uniesienie karty (reguła wyżej) nie mówi „to
   link" tak wyraźnie jak podkreślenie nazwiska — stąd dopisane tutaj,
   przy `a.osoba`, nie przy `.osoba h3` ogólnie, żeby karta bez adresu
   (osoby z makiety, `div.osoba`) go nie dostała. */
a.osoba:hover h3, a.osoba:focus-visible h3 { color: var(--brand); text-decoration: underline }
a.osoba:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px }
.osoba .ava {
  width: 112px; height: 112px; border-radius: 50%; margin: 0 auto 16px;
  display: grid; place-items: center;
  font-family: 'Oswald'; font-weight: 600; font-size: 34px; color: #fff;
  background: linear-gradient(145deg, #9a4440, #5e1320);
  border: 3px solid #fff; box-shadow: 0 14px 30px rgba(40, 18, 18, .2);
}
/* Zdjęcie wchodzi w to samo kółko co inicjały — bez `padding`, żeby
   twarz wypełniła krążek, i bez gradientu, który prześwitywałby na
   zdjęciach z przezroczystością. */
.osoba .ava-foto { padding: 0; background: #efe7e2; overflow: hidden }
.osoba .ava-foto img { width: 100%; height: 100%; object-fit: cover; display: block }
.osoba h3 { font-family: 'Lora'; font-weight: 600; font-size: 22px; color: var(--ink) }
.osoba .rola {
  font-family: 'Oswald'; font-size: 11.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--brand); margin: 7px 0 10px;
}
.osoba p { font-size: 15px; color: #5a504a; font-style: italic }
.zespol-cta {
  margin-top: 28px; text-align: center;
  font-family: 'Oswald'; font-size: 13px; letter-spacing: .06em;
  text-transform: uppercase;
}
.zespol-cta a { color: var(--brand) }

/* --- autorzy ---
   Od A9 kafelek to nie samo nazwisko: zdjęcie urosło ze 120 do 170 px,
   pod nazwiskiem stoi jedno zdanie biogramu, a niżej okładka książki
   z linkiem do jej karty. Dwa wyjścia z jednego kafelka — na stronę
   autora (zdjęcie i nazwisko) i na kartę tytułu (okładka) — bo to dwa
   różne pytania, które czytelnik może mieć po zobaczeniu twarzy.

   Kafelek jest kolumną `flex`, żeby okładka mogła zejść na sam dół
   (`margin-top: auto`): w rzędzie stoją wtedy w jednej linii, choćby
   biogramy miały różną długość. */
.authors {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 30px 24px;
  /* Płynne przejście między rzędami. Klasę zakłada i zdejmuje skrypt
     (`main.js`); bez JavaScriptu nikt jej nie zakłada i rząd stoi.
     Czas musi zgadzać się ze stałą `PRZEJSCIE` w skrypcie. */
  transition: opacity .32s ease;
}
.authors.authors-znika { opacity: 0 }
.author { text-align: center; display: flex; flex-direction: column }
.author .av {
  width: 170px; height: 170px; border-radius: 50%; margin: 0 auto 16px;
  display: grid; place-items: center;
  font-family: 'Oswald'; font-weight: 600; font-size: 42px; color: #fff;
  background: linear-gradient(145deg, #9a4440, #5e1320);
  box-shadow: 0 16px 34px rgba(40, 18, 18, .2); border: 4px solid #fff;
  overflow: hidden;
}
.author .av img { width: 100%; height: 100%; object-fit: cover; display: block }
.author h3 { font-family: 'Oswald'; font-weight: 500; font-size: 19px; text-transform: uppercase }
/* Odnośnik obejmuje zdjęcie i nazwisko naraz, więc musi być blokiem —
   inaczej kółko liczyłoby marginesy od linii tekstu. */
.author-kto { display: block; color: inherit }
.author-kto:hover h3 { color: var(--brand) }
.author-kto .av { transition: transform .25s ease, box-shadow .25s ease }
.author-kto:hover .av { transform: translateY(-3px); box-shadow: 0 20px 38px rgba(40, 18, 18, .26) }
/* Okładka z tytułem: miniatura obok kursywy, jak podpis pod zdjęciem.
   Bez okładki zostaje sam tytuł — kafelek wygląda wtedy jak przed A9. */
.author-book {
  margin-top: auto; padding-top: 14px;
  display: flex; align-items: center; justify-content: center; gap: 10px;
  color: inherit;
}
/* Poprawki 20.10, pkt 4. Tu decyzja 4A nie zadziałała w ogóle — i to
   było jedyne takie miejsce w silniku. Okładka miała `object-fit: cover`,
   czyli **przycinała**, w ramce `44×62 px`, która nawet nie była 2:3
   (0,710 zamiast 0,667). Kwadratowa książeczka dla dzieci traciła tu
   boki. Lista siedmiu selektorów z 4A po prostu pominęła ten jeden.

   Rozmiar zostaje ten sam co od paczki 7 (szerokość 44 px — tak, żeby
   tytuł obok nie wyglądał na przypadkową ikonkę, a wciąż jak podpis pod
   nazwiskiem, nie jak druga półka z książkami), tylko wysokość bierze
   się teraz z proporcji, nie z ręcznie wpisanych 62 px: 44 × 3/2 = 66.
   Cztery piksele różnicy, bo poprawna proporcja jest ważniejsza od
   zachowania dawnej liczby.

   Okładka dostała też ramkę (`.ok-ramka` w szablonie), bo wcześniej jej
   nie miała — `<img>` stał wprost w odnośniku, a proporcji 2:3 nie ma
   gdzie powiesić bez elementu, który ją uniesie. */
.author-book .ok-ramka {
  width: 44px; flex-shrink: 0;
  --ok-promien: 3px;
  --ok-cien: drop-shadow(0 6px 14px rgba(40, 18, 18, .22));
}
.author-book span { font-size: 13.5px; color: var(--muted); font-style: italic }
a.author-book:hover span { color: var(--brand) }
@media (prefers-reduced-motion: reduce) {
  .authors, .author-kto .av { transition: none }
}

/* --- newsletter --- */
.news { background: #241a17; color: #fff; border-radius: 22px; padding: 50px; text-align: center }
.news h2 { color: #fff; font-size: clamp(26px, 3.4vw, 38px); text-transform: uppercase }
.news p { color: #cdbfb8; max-width: 520px; margin: 12px auto 22px; font-size: 16px }

/* Formularz zapisu (B5b), makro `_newsletter_macros.html`. Jeden zestaw
   reguł na trzy miejsca: ciemna karta strony głównej, pasek w stopce
   i jasna strona „odsyłacz wygasł". Do B5b stała tu atrapa — pole bez
   nazwy i przycisk bez formularza — i jej reguły (`.news .form`,
   `.news input`) zeszły razem z nią. Osobny prefiks, bo samo `.news input`
   trafiałoby dziś także w okienko zgody i rysowało je jako pigułkę
   szerokości 220 px. */
.nlform { max-width: 520px; margin: 0 auto; text-align: left }
/* Na stronie „odsyłacz wygasł" formularz stoi pod zdaniem wyjaśniającym
   i ma się z nim równać do lewej — wyśrodkowanie ma sens tylko w karcie
   strony głównej, gdzie wyśrodkowane jest wszystko. */
.legal .nlform { margin-left: 0; margin-top: 18px }
/* Akapity formularza trzymają się jego kolumny. Bez tego `.news p`
   wyśrodkowuje klauzulę w karcie i rozjeżdża ją z okienkiem zgody. */
.nlform p { max-width: none; margin-left: 0; margin-right: 0 }
.nlform-pola { display: flex; gap: 10px; flex-wrap: wrap }
.nlform-email {
  flex: 1 1 220px; min-width: 0;
  border: 1px solid var(--line); background: #fff; color: var(--ink);
  border-radius: 100px; padding: 13px 20px;
  font-family: 'Lora', serif; font-size: 15px;
}
.nlform-email:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(124, 29, 44, .1);
}
.nlform-pola .btn { flex-shrink: 0 }
/* Zgoda i klauzula wchodzą pod pasek już bez ujemnego marginesu, który
   `.form-note` ma pod etykietę pola — tutaj nad nimi stoi przycisk. */
.nlform .kt-chk { margin: 14px 0 0; font-size: 12.5px; line-height: 1.5 }
.nlform .form-note { margin: 9px 0 0; font-size: 12px }
.nlform .form-note-todo { margin-top: 9px }

/* Odpowiedź po wysłaniu formularza (PRG, `?nl=…` → `templating`).
   Zielona tylko przy powodzeniu; najczęstsza odpowiedź brzmi „sprawdź
   skrzynkę", czyli następny krok, a nie usterka. */
.nlform-info {
  margin: 0 0 12px; padding: 11px 15px; border-radius: 12px;
  font-size: 13.5px; line-height: 1.5;
  background: #fdf4f4; color: var(--err); border: 1px solid rgba(210, 59, 59, .25);
}
.nlform-ok { background: #e7f6ec; color: #1a6b38; border-color: rgba(26, 107, 56, .25) }

/* Wariant na ciemnym tle: karta strony głównej i pasek w stopce. Zmienia
   wyłącznie kolory — układ jest ten sam, bo to ten sam formularz. */
.nlform-ciemny .nlform-email {
  border-color: rgba(255, 255, 255, .2); background: rgba(255, 255, 255, .06); color: #fff;
}
.nlform-ciemny .nlform-email::placeholder { color: rgba(255, 255, 255, .5) }
.nlform-ciemny .nlform-email:focus {
  border-color: var(--gold); box-shadow: 0 0 0 3px rgba(176, 135, 65, .22);
}
.nlform-ciemny .btn-primary { background: var(--gold); border-color: var(--gold); color: #241a17 }
.nlform-ciemny .btn-primary:hover { background: #c79a4c; border-color: #c79a4c }
.nlform-ciemny .kt-chk, .nlform-ciemny .form-note { color: #cdbfb8 }
.nlform-ciemny .kt-chk input { accent-color: var(--gold) }
/* `display` i `font-size` jawnie, bo w stopce stoi wyżej `footer a`
   z regułą dla pozycji menu: blok własnej wielkości. Odsyłacz w zdaniu
   klauzuli jest odsyłaczem w zdaniu, nie pozycją listy. */
.nlform-ciemny a {
  display: inline; font-size: inherit; margin: 0;
  color: var(--gold); text-decoration: underline;
}
/* Żółte tło znacznika „[DO WERYFIKACJI]" na ciemnym tle zostaje — ono ma
   kłuć w oczy, bo to rusztowanie na staging, nie element projektu. */

/* Prośba o odsyłacz wypisujący (`newsletter_wypisanie.html`). Stoi na
   jasnej stronie `.legal`, więc pola biorą wygląd z `.kt-fld`; tutaj
   zostaje tylko przycisk obok pola zamiast pod nim. */
.nl-adres { max-width: 520px; margin: 22px 0 6px }
.nl-adres-pola { display: flex; gap: 10px; flex-wrap: wrap; align-items: stretch }
.nl-adres-pola input { flex: 1 1 240px; min-width: 0; width: auto }
.nl-adres-pola .btn { flex-shrink: 0 }

/* Przycisk cofający pod potwierdzeniem wypisania. W ramce i bez koloru
   firmowego: to wyjście awaryjne dla kogoś, kto kliknął przez pomyłkę
   albo komu odsyłacz otworzył skaner w bramce pocztowej — ma być
   widoczne, ale nie ma zapraszać z powrotem na listę. */
.nl-cofnij {
  margin: 26px 0 8px; padding: 20px 22px;
  border: 1px solid var(--line); border-radius: 16px; background: #fff;
}
.nl-cofnij p { margin: 0 0 14px !important; font-size: 14.5px !important; color: var(--muted) }

@media (max-width: 560px) {
  /* Pole i przycisk jedno pod drugim. Zawijają się same (`flex-wrap`),
     ale przycisk szerokości napisu wygląda pod polem jak doklejony
     z lewej — dopiero cała szerokość czyta się jak druga połowa pary. */
  .nlform-pola .btn, .nl-adres-pola .btn { width: 100%; justify-content: center }
  .nl-cofnij { padding: 18px 16px }
  .nl-cofnij .btn { width: 100%; justify-content: center }
}

/* --- formularz kontaktowy (ZMIANA 8) --- */
.kt-head { text-align: center; max-width: 560px; margin: 0 auto 34px }
.kt-head h2 { font-size: clamp(28px, 3.6vw, 40px); text-transform: uppercase }
.kt-head p { color: var(--muted); margin-top: 8px }
.kt-grid { display: grid; grid-template-columns: 1fr 1.3fr; gap: 34px; align-items: start }
.kt-info {
  background: var(--brand); color: #f3e7e2; border-radius: 18px;
  padding: 34px; position: relative; overflow: hidden;
}
.kt-info::after {
  content: ""; position: absolute; right: -70px; bottom: -70px;
  width: 220px; height: 220px; border-radius: 50%;
  background: rgba(176, 135, 65, .18);
}
/* Odstęp pod nazwą jest tutaj, a nie pod podtytułem z adresem: od P6
   poprawek 2 adres jest pozycją na liście („Adres" z pinezką), a nie
   podtytułem, więc nagłówek sąsiaduje wprost z pierwszym `.kt-item`. */
.kt-info h3 { color: #fff; font-size: 22px; margin-bottom: 22px; font-family: 'Oswald'; text-transform: uppercase }
.kt-item { display: flex; gap: 14px; align-items: flex-start; margin-bottom: 16px; position: relative; z-index: 2 }
.kt-item .ic {
  width: 40px; height: 40px; border-radius: 11px;
  background: rgba(255, 255, 255, .12);
  display: grid; place-items: center; flex-shrink: 0;
}
.kt-item .lb {
  font-family: 'Oswald'; font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--gold);
}
.kt-item .vl { font-size: 15px; color: #fff }
/* Dane kontaktowe są linkami (A10), ale mają wyglądać tak, jak wyglądały
   — podkreślenie dopiero pod kursorem. Kolor zostaje biały, bo niebieski
   link na bordowym tle czyta się gorzej niż sam numer. */
.kt-item .vl a, .kt-adres { color: inherit; text-decoration: none }
.kt-item .vl a:hover, .kt-adres:hover { text-decoration: underline }
.kt-adres { display: inline-block }
/* Kolejne numery w jednej pozycji „Telefony" (P6). Odstęp jest mniejszy
   niż między pozycjami listy, bo to jedna pozycja, a nie trzy. Sam numer
   nie łamie się nigdy: „601 492 / 277" w dwóch wierszach przestaje
   wyglądać jak jeden numer. Kropka jest w CSS, nie w szablonie, bo na
   wąskim ekranie nazwisko idzie nad numer i wtedy ma jej nie być —
   wisząca kropka na końcu wiersza wygląda jak literówka. */
.kt-numer + .kt-numer { margin-top: 5px }
.kt-numer .nr { white-space: nowrap }
.kt-numer .os::after { content: " · " }
@media (max-width: 560px) {
  .kt-numer .os { display: block }
  .kt-numer .os::after { content: none }
}
/* Zaślepka mapy — do kliknięcia nie ma tu nic z Google, tylko nasz napis. */
.kt-mapa {
  display: flex; gap: 12px; align-items: center; margin-top: 18px;
  padding: 14px 16px; border-radius: 12px; position: relative; z-index: 2;
  background: rgba(255, 255, 255, .12); color: #fff; text-decoration: none;
}
.kt-mapa:hover { background: rgba(255, 255, 255, .2) }
.kt-mapa svg { flex-shrink: 0 }
.kt-mapa-napis { display: block; font-size: 15px }
.kt-mapa-nota { display: block; font-size: 12px; color: #e7cfca; margin-top: 2px }
/* Mapa po wstawieniu. Wysokość stała, bo `iframe` bez niej ma 150 px
   z przeglądarki i wychodzi pasek zamiast mapy. */
.kt-mapa-ramka { margin-top: 18px; position: relative; z-index: 2 }
.kt-mapa-ramka iframe {
  width: 100%; height: 220px; border: 0; border-radius: 12px; display: block;
}
.kt-mapa-ramka a {
  display: inline-block; margin-top: 8px; font-size: 13px; color: #e7cfca;
}
.kt-soc { display: flex; gap: 10px; margin-top: 18px; position: relative; z-index: 2 }
.kt-soc a {
  width: 38px; height: 38px; border-radius: 50%;
  background: rgba(255, 255, 255, .12); display: grid; place-items: center;
}
.kt-soc a:hover { background: var(--gold) }
.kt-card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 18px; padding: 34px;
  box-shadow: 0 18px 40px rgba(40, 20, 20, .06);
}
.kt-g2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px }
.kt-fld { margin-bottom: 16px }
.kt-fld label {
  display: block; font-family: 'Oswald'; font-weight: 500; font-size: 12px;
  letter-spacing: .06em; text-transform: uppercase; color: #4a3f38; margin-bottom: 7px;
}
.kt-fld input, .kt-fld select, .kt-fld textarea {
  width: 100%; border: 1px solid var(--line); background: #fff;
  border-radius: 11px; padding: 13px 14px; font-size: 15px;
  font-family: 'Lora', serif; color: var(--ink);
}
.kt-fld input:focus, .kt-fld select:focus, .kt-fld textarea:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(124, 29, 44, .1);
}
.kt-fld textarea { resize: vertical; min-height: 110px }
.kt-fld.bad input, .kt-fld.bad select, .kt-fld.bad textarea { border-color: var(--err); background: #fdf4f4 }
.kt-fld .msg { font-size: 12.5px; color: var(--err); margin-top: 6px; display: none }
.kt-fld.bad .msg { display: block }
/* Komunikat zapory antyspamowej — jedno zdanie nad przyciskiem.
   Nie siedzi przy żadnym polu, bo nie dotyczy żadnego pola: mówi
   „tego nie przyjęliśmy i oto dlaczego". Kolorem błędu, ale na
   spokojnym tle, bo w większości przypadków czyta to automat, a gdy
   trafi na człowieka — ma brzmieć jak informacja, nie jak zarzut. */
.kt-blokada {
  font-size: 13.5px; line-height: 1.6; color: var(--err);
  background: #fdf4f4; border: 1px solid var(--err);
  border-radius: 11px; padding: 11px 13px; margin: 0 0 16px;
}
/* „(opcjonalnie)" albo „*" przy telefonie — napis zmienia się razem
   z wybranym tematem (A11). Drobniej i bez wersalików, żeby było widać,
   że to dopisek do etykiety, a nie jej część. */
.kt-opc { font-size: 11px; letter-spacing: 0; text-transform: none; color: var(--muted) }
/* Pole-pułapka formularza kontaktowego (24.10, `app/antyspam.py`).
   Chowamy je **poza ekranem**, a nie przez `display: none`: pole
   wyjęte z układu strony zostaje w drzewie dokumentu, więc automat,
   który czyta surowy HTML, dalej je widzi i dalej wypełnia — a to jest
   cała idea pułapki. `display: none` odsiewałby tylko te automaty,
   które stylów nie czytają.

   Za niewidoczność dla człowieka odpowiadają trzy rzeczy razem, bo
   każda z nich osobno zostawia dziurę: pozycja poza ekranem (oko),
   `tabindex="-1"` w szablonie (klawisz Tab) i `aria-hidden` (czytnik
   ekranu). Formularz jest jedyną drogą do wydawnictwa i nie wolno
   mu odrzucać zgłoszenia osoby, która trafiła w pole przez pomyłkę. */
.kt-pulapka {
  position: absolute; left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}
.kt-chk { display: flex; gap: 10px; align-items: flex-start; font-size: 13.5px; color: var(--muted); margin-bottom: 18px }
.kt-chk input { margin-top: 3px; width: 17px; height: 17px; accent-color: var(--brand); flex-shrink: 0 }
.kt-chk.bad { color: var(--err) }
/* Obowiązek informacyjny pod formularzem — treść z content/legal/.
   Drobniej od etykiety zgody i bez kursywy mimo kursywy w źródle:
   trzy wiersze pochyłego Lory czyta się źle, a wyróżnienie i tak
   należy się tu zgodzie, nie informacji. */
.form-note {
  font-size: 12.5px; line-height: 1.65; color: var(--muted);
  margin: -8px 0 18px;
}
.form-note em { font-style: normal }
.form-note-todo { margin-top: -10px }
/* Rozwijana reszta obowiązku informacyjnego pod newsletterem (A7) —
   domyślnie widać jedno zdanie, `<summary>` dopisuje resztę tą samą
   czcionką, żeby rozwinięcie nie wyglądało jak inny komponent. */
.form-note-wiecej { margin-top: 4px }
.form-note-wiecej > summary {
  cursor: pointer; color: inherit; list-style: none;
  text-decoration: underline; text-decoration-style: dotted;
}
.form-note-wiecej > summary::-webkit-details-marker { display: none }
.form-note-wiecej[open] > summary { margin-bottom: 4px }
.kt-send {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  width: 100%; border: none; border-radius: 11px; padding: 15px;
  font-family: 'Oswald'; font-weight: 500; font-size: 14.5px;
  letter-spacing: .06em; text-transform: uppercase; cursor: pointer;
  background: var(--brand); color: #fff; transition: .2s;
}
.kt-send:hover { background: var(--brand-d) }
/* Przycisk „Pobierz dane z GUS" (poprawki 4A, 2.6) — w jednym rzędzie
   z polem NIP, nie pod nim: to akcja do tego pola, nie osobny krok. */
.kasa-nip-wrap { display: flex; gap: 8px; align-items: center }
.kasa-nip-wrap input { flex: 1 }
.kasa-gus-btn {
  flex-shrink: 0; border: 1px solid var(--brand); background: #fff;
  color: var(--brand); border-radius: 11px; padding: 0 14px; height: 48px;
  font-family: 'Oswald'; font-weight: 500; font-size: 12px;
  letter-spacing: .04em; text-transform: uppercase; cursor: pointer;
  transition: .2s;
}
.kasa-gus-btn:hover { background: var(--brand); color: #fff }
.kt-success { display: none; text-align: center; padding: 26px 10px }
.kt-success.on { display: block }
.kt-success .ck {
  width: 72px; height: 72px; border-radius: 50%; background: #e7f6ec;
  display: grid; place-items: center; margin: 0 auto 18px;
}
.kt-success h3 { font-size: 24px; margin-bottom: 8px; font-family: 'Oswald'; text-transform: uppercase }
.kt-success p { color: var(--muted); font-size: 15px; max-width: 380px; margin: 0 auto }
.kt-success p b { color: var(--ink) }

/* --- stopka (ZMIANA 9) --- */
footer { background: #1e1512; color: #c7bbb2; padding: 60px 0 28px; margin-top: 30px }

/* Pasek newslettera nad kolumnami stopki (B5b). Zdanie po lewej,
   formularz po prawej — dwie równe kolumny, bo formularz ma zgodę
   i klauzulę pod spodem i zajmuje tyle samo miejsca co akapit obok.
   Sam formularz rysuje `.nlform` wyżej; tutaj zostaje układ paska. */
.fnews { display: grid; grid-template-columns: 1fr 1fr; gap: 26px 44px; align-items: start }
.fnews-txt p { font-size: 14.5px; margin: 0; max-width: 340px }
/* W pasku formularz zaczyna się od lewej krawędzi swojej kolumny —
   `margin: 0 auto` z `.nlform` jest po to, żeby wyśrodkować go w karcie
   strony głównej, a tu środkowałby go względem połowy stopki. */
.fnews .nlform { margin: 0; max-width: 440px }
/* Kreska między paskiem a kolumnami: bliżej niż domyślne 42 px u góry,
   bo nad nią kończy się formularz, a nie treść strony. */
.forn-nad-stopka { margin: 34px 0 30px }

/* Liczba kolumn stoi w nazwie klasy, bo zmienia się razem z zawartością
   serwisu: bez sklepu wypadają „Księgarnia" i „Zakupy" naraz, czyli pół
   stopki (A13). Pierwsza kolumna to zawsze marka, ostatnia zawsze
   kontakt — obie mieszczą dłuższe wiersze niż kolumna jednowyrazowych
   pozycji, stąd proporcje zamiast równych `1fr`.

   Od 1.8 kolumna kontaktu też potrafi wypaść: świeża instancja nie ma
   wpisanego ani adresu, ani e-maila, ani numeru, a nagłówek „KONTAKT"
   nad pustym miejscem to obietnica, nie widoczny brak. Stąd warianty
   parzyste — 2 (wizytówka bez kontaktu) i 4 (sklep bez kontaktu). */
.fgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 40px; align-items: start }
.fgrid-2 { grid-template-columns: 1.7fr 1fr }
.fgrid-3 { grid-template-columns: 1.7fr 1fr 1.3fr }
.fgrid-4 { grid-template-columns: 1.5fr 1fr 1fr 1fr; gap: 30px }
.fgrid-5 { grid-template-columns: 1.5fr 1fr 1fr 1fr 1.2fr; gap: 30px }
/* Pięć kolumn nie mieści się na laptopie: „Dostawa i płatności" łamie się
   wtedy na trzy wiersze i kolumny przestają być równej wysokości. */
@media (max-width: 1180px) {
  .fgrid-5 { grid-template-columns: repeat(3, 1fr) }
  .fgrid-4 { grid-template-columns: repeat(3, 1fr) }
}
/* Biała podkładka pod ZNAK, nie pod nazwę: selektor celowo wymaga `img`.
   Makro `brand.logo` oddaje tę samą klasę także zastępczemu `<span>`
   z nazwą wydawnictwa (gdy znaku w panelu nie ma), a `.fbrand .flogo`
   malowało wtedy biały prostokąt 42 px pod białym tekstem — na zrzucie
   startowej instancji wychodziła z tego biała plama z nazwą wystającą
   spod spodu. Napis ma własną regułę niżej i podkładki nie potrzebuje. */
.fbrand img.flogo { height: 42px; width: auto; background: #fff; padding: 6px 10px; border-radius: 8px; display: inline-block }
/* Nazwa wydawnictwa tekstem — co widać tam, gdzie znaku w panelu nie ma
   (KROK 2). Nie jest to „brakujące logo": to poprawna treść odnośnika do
   strony głównej, czytelna i dla człowieka, i dla czytnika ekranu. Rozmiar
   bierze się z wysokości znaku, żeby pasek nagłówka nie zmieniał wysokości
   w zależności od tego, czy ktoś już logo wgrał. */
.logo-napis {
  display: inline-flex; align-items: center;
  min-height: var(--logo-h, 46px);
  font-family: 'Lora', Georgia, serif;
  font-size: clamp(17px, calc(var(--logo-h, 46px) * .42), 26px);
  font-weight: 600; letter-spacing: .01em; line-height: 1.1;
  color: currentColor; text-decoration: none;
}
.fbrand .logo-napis { color: #fff; min-height: 42px }
.fbrand p { font-size: 14.5px; margin: 16px 0 0; max-width: 300px }
.fsoc { display: flex; gap: 10px; margin-top: 16px }
.fsoc a {
  width: 38px; height: 38px; border-radius: 50%;
  background: rgba(255, 255, 255, .08); display: grid; place-items: center;
}
.fsoc a:hover { background: var(--brand) }
footer h4 {
  font-family: 'Oswald'; color: #fff; font-size: 15px;
  letter-spacing: .08em; text-transform: uppercase; margin-bottom: 16px;
}
footer a { display: block; color: #c7bbb2; font-size: 14px; line-height: 1.45; margin-bottom: 9px }
footer a:hover { color: var(--gold) }
/* Wiersz kolumny kontaktu, który linkiem nie jest — dziś tylko adres bez
   mapy (A10; godziny biura zeszły w P6 poprawek 2). Wygląda jak sąsiednie
   linki, bo stoi w tej samej kolumnie i w tej samej kolejce — różni się
   tylko tym, że nie reaguje na kliknięcie, i to jest cała różnica,
   o którą chodzi. */
.fkontakt p { color: #c7bbb2; font-size: 14px; margin: 0 0 9px }
.forn { height: 2px; background: linear-gradient(90deg, transparent, var(--brand), transparent); margin: 42px 0 18px }
.flegal { text-align: center; font-size: 12.5px; color: #9a8e85; line-height: 1.9 }
.flegal a { display: inline; color: #9a8e85 }
.flegal a:hover { color: var(--gold) }

/* --------------------------------------------------------------------------
   KARTA KSIĄŻKI (/ksiazka/{slug})
   Oprawa A1 — Lora, Oswald, papier i bordo tej samej palety co wizytówka.
   Świadomie NIE `body.shop`: karta działa także przy `SHOP_ENABLED=false`
   i wtedy jest po prostu kolejną podstroną wizytówki. Układ powtarza rytm
   hero (okładka po lewej, tekst po prawej), ale bez obrotu okładki —
   na slajdzie to ozdoba, tutaj czytelnik ogląda konkretny egzemplarz.
   -------------------------------------------------------------------------- */
.karta { background: var(--paper); padding: 28px 0 70px }

.kb-crumbs {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-family: 'Oswald', sans-serif; font-size: 11.5px;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 26px;
}
.kb-crumbs a { color: var(--brand) }
.kb-crumbs a:hover { text-decoration: underline }
/* Tytuł w okruszkach bywa długi — na telefonie zabrałby trzy wiersze. */
.kb-here { color: var(--muted); max-width: 100%; overflow-wrap: anywhere }

.kb-top { display: grid; grid-template-columns: 340px 1fr; gap: 52px; align-items: start }

/* --- okładka --- */
.kb-cover { position: sticky; top: 92px }
/* 21.10: ramka okładki i zaślepka „bez okładki" rozchodzą się, bo jedna
   przestała mieć co rysować, a druga rysuje wszystko. Przy okładce zostaje
   sam wymiar — tło (`--paper` sekcji `.karta`), cień i promień przeszły
   na obrazek. Zaślepka trzyma kartę, cień i zaokrąglenie po staremu: nie
   ma w niej okładki, wokół której miałoby być puste miejsce. */
.kb-cover-link, .kb-cover-none {
  position: relative; display: block; aspect-ratio: 2/3;
}
.kb-cover-link {
  --ok-promien: 6px;
  --ok-cien: drop-shadow(0 26px 52px rgba(40, 18, 18, .26));
}
.kb-cover-none {
  border-radius: 6px; overflow: hidden; background: var(--card);
  box-shadow: 0 26px 52px rgba(40, 18, 18, .26);
}
/* Obrazek w środku opisuje `.ok-obraz` — patrz komentarz przy
   `.ok-obraz.ok-przytnij`. */
/* Plakietka ta sama co na półce — `.tag2` jest już zdefiniowana wyżej
   w `.nbook`, więc tu zostaje samo jej umieszczenie w rogu okładki. */
.kb-cover .tag2 {
  position: absolute; top: 12px; left: 12px; z-index: 2;
  background: var(--brand); color: #fff;
  font-family: 'Oswald', sans-serif; font-size: 10px; text-transform: uppercase;
  letter-spacing: .05em; padding: 3px 9px; border-radius: 100px;
}
.kb-cover .tag2.z { background: var(--gold) }
.kb-cover .tag2.b { background: #2f6d4f }
/* Podpowiedź „Powiększ" pojawia się dopiero przy najechaniu, ale na
   urządzeniu dotykowym (brak hovera) stoi widoczna od razu — inaczej na
   telefonie nic by nie sugerowało, że okładkę da się otworzyć.

   21.10: przenika i podnosi się o sześć pikseli, zamiast wjeżdżać od dołu
   spod krawędzi. Wjazd był schowany w `overflow: hidden` ramki, a ta go
   straciła — bez tego pasek wisiałby pod okładką widoczny na stałe.
   Przenikanie trzyma to samo w jednym elemencie i nie jest ustępstwem:
   przy okładce wyrównanej do dołu ramki pasek ląduje teraz dokładnie na
   jej dolnej krawędzi, więc „skąd" wjeżdża, nie ma już czego pokazać.
   Narożniki bierze z `--ok-promien`, bo to krawędź okładki, nie ramki. */
.kb-zoom {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; align-items: center; justify-content: center; gap: 7px;
  padding: 10px 12px; background: rgba(35, 26, 23, .82); color: #f6f1ea;
  font-family: 'Oswald', sans-serif; font-size: 11.5px;
  letter-spacing: .1em; text-transform: uppercase;
  border-bottom-left-radius: var(--ok-promien, 0);
  border-bottom-right-radius: var(--ok-promien, 0);
  opacity: 0; transform: translateY(6px);
  transition: opacity .22s, transform .22s;
  /* Przezroczysty pasek nie może zjadać kliknięcia w okładkę ani
     kursora: bez tego `:hover` na odnośniku gasł w chwili, w której
     mysz weszła w jego obszar, i podpowiedź migała. */
  pointer-events: none;
}
.kb-cover-link:hover .kb-zoom, .kb-cover-link:focus-visible .kb-zoom {
  opacity: 1; transform: none;
}
@media (hover: none) { .kb-zoom { opacity: 1; transform: none } }
.kb-cover-none {
  display: grid; place-items: center; padding: 28px;
  border: 1px dashed var(--line); box-shadow: none; text-align: center;
  font-family: 'Oswald', sans-serif; font-size: 19px; font-weight: 500;
  text-transform: uppercase; color: var(--muted); line-height: 1.25;
}

/* --- kolumna tekstu --- */
.kb-main h1 {
  font-size: clamp(30px, 4.2vw, 46px); text-transform: uppercase;
  margin: 10px 0 6px;
}
.kb-sub {
  font-family: 'Lora', Georgia, serif; font-style: italic;
  font-size: 19px; color: var(--muted); margin-bottom: 10px;
}
/* Autorzy w nagłówku: krążek ze zdjęciem i nazwisko (A4). Siatka zamiast
   jednego akapitu, bo przy dwóch autorach wiersz musi się złamać CAŁYMI
   parami — zdjęcie oderwane od nazwiska wygląda jak cudze. `gap: 6px 18px`
   daje ciaśniej w pionie niż w poziomie: złamane pary mają stać blisko
   siebie, a sąsiednie w jednym wierszu — rozdzielone. */
.kb-authors {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px;
  margin-bottom: 22px;
}
.kb-au { display: inline-flex; align-items: center; gap: 10px }
/* Ten sam krążek co `.author .av` na wizytówce, tylko mniejszy i bez
   cienia: przy nazwisku jest znacznikiem, nie portretem. Gradient zostaje,
   bo pod nim stoją inicjały — zdjęcie zakrywa go w całości. */
.kb-au-av {
  width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0;
  display: grid; place-items: center; overflow: hidden;
  background: linear-gradient(145deg, #9a4440, #5e1320);
  font-family: 'Oswald', sans-serif; font-weight: 600; font-size: 14px;
  color: #fff; letter-spacing: .02em;
}
.kb-au-av img { width: 100%; height: 100%; object-fit: cover; display: block }
.kb-au a, .kb-au > span:last-child {
  font-family: 'Lora', Georgia, serif; font-style: italic;
  font-size: 19px; color: var(--brand);
}
.kb-au a { text-decoration: underline }

/* Ilustracje (poprawki 4B, oferta sezonowa) — osobny wiersz pod krążkami
   autorów, tą samą czcionką co nazwisko autora wyżej (`.kb-au a`), żeby
   nie wyglądał jak metryczka, a jak ciąg dalszy podpisu pod tytułem. */
.kb-ilustracje {
  font-family: 'Lora', Georgia, serif; font-style: italic;
  font-size: 17px; color: var(--muted); margin: -10px 0 22px;
}
.kb-ilustracje a { color: var(--brand); text-decoration: underline }

/* --- formaty i ceny --- */
.kb-formats { display: grid; gap: 12px; margin-bottom: 30px }
.kb-fmt {
  display: flex; align-items: center; justify-content: space-between;
  gap: 18px; flex-wrap: wrap;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 10px; padding: 14px 18px;
}
.kb-fmt-head {
  display: flex; align-items: center; gap: 9px;
  font-family: 'Oswald', sans-serif; font-size: 14px;
  letter-spacing: .05em; text-transform: uppercase;
}
.kb-fmt .dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0 }
.kb-fmt.druk .dot { background: var(--druk) }
.kb-fmt.ebook .dot { background: var(--ebook) }
.kb-fmt.audiobook .dot { background: var(--audiobook) }
/* Nakład wyczerpany: format zostaje widoczny (to informacja o książce),
   ale przygasa, żeby nie konkurował z tymi, które da się kupić. */
.kb-fmt.out { opacity: .62 }
.kb-fmt-stock {
  font-family: 'Lora', Georgia, serif; font-style: italic;
  font-size: 13px; letter-spacing: 0; text-transform: none; color: var(--muted);
}
.kb-fmt-buy { display: flex; align-items: center; gap: 16px; flex-wrap: wrap }
.kb-price { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap }
.kb-price b { font-family: 'Oswald', sans-serif; font-weight: 600; font-size: 22px; color: var(--brand) }
.kb-price b.soon { font-size: 15px; font-weight: 500; color: var(--muted) }
.kb-price s { font-size: 13px; color: var(--muted) }
.kb-off {
  font-family: 'Oswald', sans-serif; font-size: 11px; font-weight: 600;
  letter-spacing: .05em; color: #fff; background: var(--brand);
  border-radius: 100px; padding: 2px 8px;
}
/* Omnibus zajmuje cały wiersz pod kwotą — doklejony z boku ginie, a to
   jest zdanie, które prawo każe pokazać, nie przypis. */
.kb-omni { flex-basis: 100%; font-size: 11.5px; color: var(--muted); line-height: 1.45 }
/* `.addbtn` jest wspólna z półką; na karcie przycisk nie ma rozciągać się
   na całą szerokość wiersza. */
.kb-fmt-buy .addbtn { width: auto; padding: 10px 20px; font-size: 12px }

/* --- tytuł poza sprzedażą (B3c) ---
   Stoi w miejscu cen, więc ma ciężar pudełka z ceną — ramkę i papier
   karty — ale nie jej kolor: bordo w serwisie znaczy „kup to", a tu jest
   dokładnie odwrotna wiadomość. Zostaje szarobrązowa krawędź z lewej,
   ten sam znak co na plakietce `.tag2.ns` i w kolumnie `.kw-status`. */
.kb-stan {
  background: var(--card); border: 1px solid var(--line);
  border-left: 3px solid #585049; border-radius: 10px;
  padding: 16px 20px; margin-bottom: 30px;
}
.kb-stan-co {
  font-family: 'Oswald', sans-serif; font-size: 15px; font-weight: 500;
  letter-spacing: .05em; text-transform: uppercase; color: #3d342e;
}
/* Wyjaśnienie zwężone jak opis książki niżej, bo czyta się je tak samo —
   zdaniami, nie rzutem oka. */
.kb-stan-opis {
  font-size: 15px; color: #5a504a; line-height: 1.6;
  max-width: 620px; margin-top: 7px;
}
.kb-stan-pytaj { margin-top: 14px }

/* Opis ma własną szerokość, choć kolumna jest szersza. Przy 1440 px
   prawa kolumna ma ~740 px, a wiersz Lory 16,5 px robi się wtedy na tyle
   długi, że oko gubi początek następnego. Ta sama zasada co w makiecie A1,
   gdzie akapit slajdu stoi w `max-width: 520px`. */
.kb-desc p { font-size: 16.5px; color: #5a504a; margin-bottom: 14px; max-width: 660px }
.kb-desc p:last-child { margin-bottom: 0 }
/* Zaślepka brakującego opisu (B3c). Kursywa i wyblakły kolor mówią, że to
   nie jest treść książki, tylko zdanie od wydawnictwa — tak samo jak przy
   „nakład wyczerpany" w wierszu formatu (`.kb-fmt-stock`). Gdyby stała
   tym samym krojem co opisy innych tytułów, czytałaby się jak ich jedno
   zdanie streszczenia. */
.kb-desc-brak p { font-style: italic; color: var(--muted) }
.kb-desc-brak .btn { margin-top: 14px }

/* --- „O autorze" (A4) ---
   Ramka w tym samym papierze co metryka niżej (`--card` + `--line`), bo to
   druga sekcja dopisana pod opisem — gdyby miała własne tło, karta
   rozpadłaby się na trzy różne kartki. Dwie sekcje pod sobą (dwóch autorów
   z biogramami) rozdziela własny `margin-top`. */
.kb-author {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 28px 30px; margin-top: 44px;
}
.kb-author + .kb-author { margin-top: 18px }
/* Zdjęcie w stałej kolumnie, tekst w reszcie. `align-items: start` trzyma
   krążek przy górnej krawędzi — wyśrodkowany pionowo odjeżdżałby w dół
   przy dłuższej zajawce. */
.kb-author-top { display: grid; grid-template-columns: 132px 1fr; gap: 26px; align-items: start }
.kb-author-av {
  width: 132px; height: 132px; border-radius: 50%; overflow: hidden;
  display: grid; place-items: center;
  background: linear-gradient(145deg, #9a4440, #5e1320);
  font-family: 'Oswald', sans-serif; font-weight: 600; font-size: 34px;
  color: #fff; border: 3px solid #fff;
  box-shadow: 0 14px 30px rgba(40, 18, 18, .18);
}
.kb-author-av img { width: 100%; height: 100%; object-fit: cover; display: block }
.kb-author-txt h2 {
  font-family: 'Oswald', sans-serif; font-size: 24px; font-weight: 500;
  text-transform: uppercase; margin: 6px 0 10px;
}
.kb-author-bio {
  font-family: 'Lora', Georgia, serif; font-size: 16px; line-height: 1.7;
  color: #5a504a; max-width: 620px;
}
/* Ikony profili na JASNYM tle — trzeci wariant obok `.fsoc` (stopka)
   i `.kt-soc` (sekcja kontaktu), oba na ciemnym. Nazwa bez przedrostka
   miejsca, bo tego samego zestawu używa blok „O autorze" na karcie książki
   i karta autora (`autor.html`): ta sama osoba, te same profile, ten sam
   papier pod spodem. Klasę i kolor rysunku podaje wywołanie makra. */
.soc-light { display: flex; gap: 10px; margin-top: 16px }
.soc-light a {
  width: 38px; height: 38px; border-radius: 50%;
  background: var(--paper); border: 1px solid var(--line);
  display: grid; place-items: center; transition: .2s;
}
.soc-light a:hover { background: var(--brand); border-color: var(--brand) }
/* Rysunek ma `fill`/`stroke` w kolorze marki — na czerwonym tle po
   najechaniu zniknąłby. `currentColor` by to załatwiło, ale makro wpisuje
   kolor na sztywno (rysuje też na ciemnym tle, gdzie dziedziczenie koloru
   tekstu dałoby inny odcień). Dlatego rozjaśniamy sam rysunek filtrem:
   działa i dla `fill`, i dla `stroke`, bez dublowania pięciu ścieżek. */
.soc-light a:hover svg { filter: brightness(0) invert(1) }
.kb-author-more {
  display: inline-block; margin-top: 16px;
  font-family: 'Oswald', sans-serif; font-size: 13px; font-weight: 500;
  letter-spacing: .08em; text-transform: uppercase; color: var(--brand);
}
.kb-author-more:hover { text-decoration: underline }
/* Półka innych tytułów pod kreską — to osobna rzecz niż notka, a bez
   kreski wyglądałaby na jej ciąg dalszy. */
.kb-author-books { margin-top: 26px; padding-top: 24px; border-top: 1px solid var(--line) }
.kb-author-books h3 {
  font-family: 'Oswald', sans-serif; font-size: 13px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 16px;
}
/* Cztery kafelki, nie sześć jak na wizytówce — tyle najwyżej oddaje
   `book_page.INNE_KSIAZKI_SLOTS`, a w szóstkowym rastrze zostałaby po nich
   pusta jedna trzecia rzędu. */
.kb-author-books .news-books { grid-template-columns: repeat(4, 1fr) }

/* --- metryka wydania i producent --- */
.kb-facts {
  display: grid; grid-template-columns: 1.35fr 1fr; gap: 26px;
  margin-top: 52px;
}
.kb-specs, .kb-gpsr {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 26px 28px;
}
.kb-specs h2, .kb-gpsr h2 {
  font-family: 'Oswald', sans-serif; font-size: 15px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--brand); margin-bottom: 16px;
}
/* Etykieta i wartość w dwóch kolumnach, każdy wiersz podkreślony —
   tabelka parametrów czyta się po lewej krawędzi, nie po tekście. */
.kb-specs dl { display: grid; gap: 0 }
.kb-specs dl > div {
  display: grid; grid-template-columns: 150px 1fr; gap: 14px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.kb-specs dl > div:last-child { border-bottom: 0; padding-bottom: 0 }
.kb-specs dt {
  font-family: 'Oswald', sans-serif; font-size: 11.5px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
  padding-top: 3px;
}
.kb-specs dd { font-size: 15px; overflow-wrap: anywhere }

.kb-gpsr p { font-size: 14.5px; line-height: 1.65; margin-bottom: 10px; color: #5a504a }
.kb-gpsr-name { font-weight: 600; color: var(--ink) !important }
.kb-gpsr a { color: var(--brand); text-decoration: underline }
.kb-gpsr-note {
  font-size: 12px !important; font-style: italic;
  color: var(--muted) !important; margin: 16px 0 0 !important;
}

/* --- „Czytelnicy wybrali także" ---
   Ta sama półka co na stronie głównej, tylko cztery kafelki zamiast
   ośmiu — więc i siatka jest czterokolumnowa, inaczej kafelki zostałyby
   w szóstkowym rastrze i zajęły dwie trzecie rzędu. */
.kb-related { margin-top: 64px }
.kb-related .news-books { grid-template-columns: repeat(4, 1fr) }

/* --- „Inne z tej serii" ---
   Ta sama półka co wyżej — sekcja nosi obie klasy, więc tutaj zostaje
   tylko to, czym się różni: w miejscu kategorii stoi podpis „tom N",
   a to numer porządkowy, nie nazwa działu. Kapitaliki zamiast wersalików,
   żeby dwie sąsiadujące półki nie wyglądały na tę samą listę. */
.kb-seria .nbook .k { font-variant: small-caps; letter-spacing: .02em }

/* --- powiększenie okładki ---
   Bez JavaScriptu odnośnik otwiera obrazek normalnie; `main.js` tylko
   dokłada ten podgląd. Dlatego warstwa startuje ukryta atrybutem `hidden`
   i nic jej nie pokazuje, póki skrypt się nie wczyta. */
.kb-lightbox {
  position: fixed; inset: 0; z-index: 200;
  display: grid; place-items: center; padding: 32px;
  background: rgba(25, 16, 14, .9);
}
.kb-lightbox[hidden] { display: none }
.kb-lightbox img {
  max-width: min(92vw, 560px); max-height: 90vh;
  border-radius: 6px; box-shadow: 0 30px 70px rgba(0, 0, 0, .5);
}
.kb-lightbox-close {
  position: absolute; top: 18px; right: 20px;
  width: 44px; height: 44px; border-radius: 50%;
  background: rgba(246, 241, 234, .14); border: 1px solid rgba(246, 241, 234, .34);
  color: #f6f1ea; cursor: pointer; display: grid; place-items: center;
  font-size: 22px; line-height: 1; transition: .2s;
}
.kb-lightbox-close:hover { background: var(--brand); border-color: var(--brand) }

/* --- fragment do czytania (A5) --------------------------------------------
   Przycisk stoi pod formatami, czyli przy cenach i „Dodaj do koszyka".
   Wygląda jak „Poznaj książkę" (obwódka, nie wypełnienie), bo nie jest
   zakupem — a dwa jednakowo mocne przyciski obok siebie kazałyby wybierać
   między „kup" i „przeczytaj", zamiast prowadzić od drugiego do pierwszego. */
.kb-sample { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 22px }
.kb-sample-ile {
  font-family: 'Oswald', sans-serif; font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted);
}

/* Sam fragment. Dwa stany z jednego kawałka HTML-u:

   1. `:target` — bez JavaScriptu. Kliknięcie w „Przeczytaj fragment"
      dopisuje `#fragment` do adresu i sekcja rozwija się W STRONIE, pod
      kartą. Nie jest to okienko, ale jest to fragment do przeczytania —
      a tego dotyczy zlecenie.
   2. `.is-okno` — klasę dokłada `main.js` i wtedy ta sama sekcja staje się
      warstwą na wierzchu. Tak to widzi prawie każdy czytelnik.

   `display: none` w stanie trzecim (schowanym) robi jeszcze jedną rzecz
   poza schowaniem: obrazki stron mają `loading="lazy"`, a przeglądarka nie
   ściąga leniwych obrazków z gałęzi, której nie rysuje. Czyli dopóki nikt
   nie kliknie, karta książki nie waży ani kilobajta więcej. */
.kb-frag { display: none }
.kb-frag:target { display: block; margin: 36px 0 10px; scroll-margin-top: 90px }
.kb-frag.is-okno {
  display: block; position: fixed; inset: 0; z-index: 210; margin: 0;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  background: rgba(25, 16, 14, .92); padding: 0;
}
.kb-frag-box {
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  max-width: 920px; margin: 0 auto; overflow: hidden;
}
.kb-frag.is-okno .kb-frag-box {
  border-radius: 0; border: 0; min-height: 100%;
  max-width: 980px; box-shadow: 0 0 80px rgba(0, 0, 0, .4);
}

/* Główka zostaje przyklejona u góry przy przewijaniu: po trzeciej stronie
   fragmentu krzyżyk byłby daleko za ekranem, a na telefonie nie ma klawisza
   Esc. Wyjście z podglądu musi być widoczne przez cały czas. */
.kb-frag-head {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
  padding: 18px 22px; background: var(--card); border-bottom: 1px solid var(--line);
}
.kb-frag-head h2 { font-size: 21px; text-transform: uppercase; line-height: 1.25 }
.kb-frag-close {
  flex-shrink: 0; width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center; font-size: 26px; line-height: 1;
  color: var(--ink); text-decoration: none; border: 1px solid var(--line);
  background: var(--paper); transition: .2s;
}
.kb-frag-close:hover { background: var(--brand); border-color: var(--brand); color: #fff }

/* Strony jedna pod drugą, nie obok siebie i nie w karuzeli. Fragment czyta
   się od góry, a przewijanie w pionie jest jedynym ruchem, który na
   telefonie działa bez instrukcji.

   `aspect-ratio` na obrazku jest rezerwacją miejsca: strony dojeżdżają
   leniwie, a bez niej tekst pod spodem skakałby przy każdej doczytanej. */
.kb-frag-strony { padding: 20px; display: grid; gap: 18px; background: #efe9e1 }
.kb-frag-strony img {
  width: 100%; height: auto; aspect-ratio: 1 / 1.414; object-fit: contain;
  display: block; background: #fff; border-radius: 4px;
  box-shadow: 0 6px 18px rgba(40, 18, 18, .14);
}
.kb-frag-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; padding: 18px 22px;
  border-top: 1px solid var(--line); background: var(--card);
}
.kb-frag-foot > span { font-size: 13.5px; color: var(--muted) }

@media (max-width: 620px) {
  .kb-frag-head { padding: 14px 16px }
  .kb-frag-head h2 { font-size: 17px }
  .kb-frag-strony { padding: 12px; gap: 12px }
  .kb-frag-foot { padding: 14px 16px }
  /* Na telefonie „Pobierz fragment (PDF)" bierze całą szerokość — przycisk
     w rzędzie z liczbą stron zostawał ścięty do dwóch słów w linijce. */
  .kb-frag-foot .btn { width: 100%; justify-content: center }
}

/* Na wąskim ekranie etykieta i wartość w dwóch kolumnach zostawiają na
   wartość jakieś 110 px — czyli ISBN łamany w połowie. Metryka schodzi
   wtedy na jeden słupek: etykieta nad wartością. Próg niżej niż główne
   900 px, bo do tej szerokości dwie kolumny jeszcze się mieszczą. */
@media (max-width: 620px) {
  .kb-specs, .kb-gpsr { padding: 20px 20px }
  .kb-specs dl > div { grid-template-columns: 1fr; gap: 2px; padding: 11px 0 }
  .kb-fmt { align-items: flex-start; flex-direction: column; gap: 10px }
  .kb-fmt-buy .addbtn { width: 100% }
  .karta { padding: 20px 0 50px }
}

@media (max-width: 900px) {
  /* Menu już nie chowa tu nic na sztywno (A14) — o tym, czy się mieści,
     decyduje `.nav-tight` dopisywane przez `main.js` po realnym pomiarze
     przepełnienia rzędu, patrz reguły przy `.menu-toggle` wyżej. */
  /* Jedna kolumna i węższa rynna: na telefonie strzałki schodzą na dół
     slajdu (reguła niżej), więc boki nie muszą ich mieścić. */
  .hc-slide { grid-template-columns: 1fr; gap: 24px; text-align: center; padding: 40px 10px 26px }
  .hc-slide p { margin-left: auto; margin-right: auto }
  /* Strzałek NIE chowamy — zlecenie mówi „popraw, desktop i telefon",
     a `display: none` to nie poprawka, tylko usunięcie sterowania.
     Po bokach nie ma ich jednak gdzie postawić: okładka ma 300 px i na
     375-pikselowym ekranie zajmuje całą szerokość slajdu, więc przycisk
     przy krawędzi leżałby na niej albo na tytule. Dlatego schodzą do
     własnego paska pod slajdem, a kropki zostają pod nimi. */
  .hero-carousel { padding-bottom: 54px }
  .hc-arrow { top: auto; bottom: 0; transform: none; width: 42px; height: 42px }
  .news-books { grid-template-columns: 1fr 1fr }
  /* Poprawki 4A, 1.10: przy dwóch kolumnach kafelek ma ok. 150 px na
     przycisk, a „Zamów w przedsprzedaży" przy odstępach liter z desktopu
     (.05em) łamie się na dwa wiersze — jedyny napis na kafelku, który
     tego nie mieścił. Mniejszy font i ciaśniejsze odstępy mieszczą go
     w jednym wierszu bez zmiany treści napisu. */
  .addbtn, .morebtn { font-size: 10px; letter-spacing: .02em; padding: 9px 6px; white-space: nowrap }
  .acts { grid-template-columns: 1fr }
  .about { grid-template-columns: 1fr }
  .ludzie { grid-template-columns: 1fr }
  .authors { grid-template-columns: 1fr 1fr }
  /* Przy dwóch kolumnach na ekranie 375 px na kolumnę zostaje jakieś
     155 px, a kółko z A9 ma 170 — bez tego zdjęcie wychodzi poza kolumnę
     i rozpycha stronę w bok. Napisy zostają bez zmian, bo mieszczą się. */
  .author .av { width: 140px; height: 140px; font-size: 34px; border-width: 3px }
  .kt-grid { grid-template-columns: 1fr }
  .kt-g2 { grid-template-columns: 1fr }
  .fgrid { grid-template-columns: 1fr 1fr }
  /* Pasek newslettera: zdanie nad formularzem. Obok siebie zostaje na
     kolumnę jakieś 170 px, czyli pole adresu węższe od przycisku obok. */
  .fnews { grid-template-columns: 1fr; gap: 18px }
  .fnews .nlform { max-width: none }
  .fnews-txt p { max-width: none }
  /* Ciemna karta na stronie głównej: 50 px marginesu wewnętrznego zjada
     na telefonie jedną trzecią szerokości pola adresu. */
  .news { padding: 34px 22px }
  /* Karta książki: okładka nad tekstem, jak slajd hero wyżej. Przyklejenie
     okładki (`position: sticky`) ma sens tylko obok przewijanej kolumny —
     nad tekstem przykleiłaby się do nagłówka i zasłoniła opis. */
  .kb-top { grid-template-columns: 1fr; gap: 26px }
  .kb-cover { position: static; width: 240px; margin: 0 auto }
  .kb-facts { grid-template-columns: 1fr; margin-top: 38px }
  /* „O autorze": zdjęcie nad tekstem i wyśrodkowane. Obok tekstu zostawia
     na telefonie jakieś 180 px na zajawkę, czyli trzy słowa w wierszu.
     Wyśrodkowany jest tylko krążek — notka zostaje do lewej, bo wyśrodkowany
     akapit na cztery wiersze czyta się gorzej niż chorągiewka. */
  .kb-author { padding: 24px 22px; margin-top: 34px }
  .kb-author-top { grid-template-columns: 1fr; gap: 18px }
  .kb-author-av { margin: 0 auto }
  .kb-author-txt { text-align: center }
  .kb-author-bio { text-align: left }
  .kb-author .soc-light { justify-content: center }
  .kb-author-books .news-books { grid-template-columns: 1fr 1fr }
  /* Półka pokrewnych schodzi na dwa kafelki razem z półką na stronie
     głównej — własna reguła, bo `.kb-related .news-books` jest wyżej
     w specyficzności niż samo `.news-books` z wiersza wyżej. */
  .kb-related { margin-top: 44px }
  .kb-related .news-books { grid-template-columns: 1fr 1fr }
}

/* Poprawki 7, pkt 8: krążek autora (140 px, ustawiony wyżej dla dwóch
   kolumn od 900 px) mieści się w kolumnie dopiero od ok. 360 px wzwyż —
   `.wrap` ma 28 px marginesu z każdej strony i 24 px odstępu między
   kolumnami, więc przy 320–359 px na kolumnę zostaje mniej niż 140 px
   i siatka (`.authors`, siatka CSS) rozpycha się razem z krążkiem, który
   się w niej nie mieści — to jedyne miejsce w serwisie, gdzie element
   siatki miał SZTYWNY rozmiar większy niż własna kolumna na najwęższych
   telefonach z zakresu zlecenia (320–430 px). Mniejszy krążek tylko
   w tym wąskim przedziale, bez zmiany układu dwóch kolumn. */
@media (max-width: 359px) {
  .author .av { width: 108px; height: 108px; font-size: 26px; border-width: 3px }
}

/* ==========================================================================
   KSIĘGARNIA
   Przeniesione z zatwierdzonej makiety `ksiegarnia.html`. Księgarnia ma inną
   paletę, inny krój (Inter zamiast Lory) i szerszy kontener niż wizytówka,
   więc KAŻDA reguła poniżej jest zawężona do `body.shop`. Dzięki temu dodanie
   sklepu nie rusza ani jednego piksela strony głównej — a dopisek do klasy
   `<body>` wystarczy, żeby włączyć cały zestaw.
   Stopka jest wspólna (ZMIANA 9) i stoi w sekcji wyżej — tu jej nie powtarzamy.
   ========================================================================== */

body.shop {
  /* Zmienne nadpisane na <body> spływają kaskadą do reguł wizytówki, więc
     nie trzeba ich przepisywać. --brand, --brand-d i --green mają w obu
     makietach te same wartości, --gold dziedziczy się z :root. */
  --ink: #1a1614;
  --muted: #6b6258;
  --line: #e8e3da;
  --paper: #f6f3ee;   /* w makiecie księgarni nazwana --bg */
  --card: #fff;
  /* --druk, --ebook i --audiobook dziedziczą się z `:root`: te same kolory
     formatów nosi karta tytułu, która nie jest `body.shop`. */

  font-family: 'Inter', system-ui, sans-serif;
  line-height: 1.6;
}
body.shop h1, body.shop h2, body.shop h3, body.shop .osw {
  line-height: 1.1; letter-spacing: .005em;
}
/* Wizytówka daje każdej <section> 60px pionu — księgarnia rozkłada odstępy
   sama, inaczej siatka kart odjechałaby od filtrów. */
body.shop section { padding: 0 }
body.shop .wrap { max-width: 1280px; padding: 0 26px }

/* --- pasek zapowiedzi (zamiast .ribbon z wizytówki) --- */
body.shop .announce {
  background: var(--brand); color: #f3e3e3; text-align: center;
  font-size: 13px; padding: 9px;
}
body.shop .announce b { color: #fff }

/* --- nagłówek: logo, wyszukiwarka, koszyk, podnawigacja --- */
body.shop header {
  z-index: 60; background: #fff; backdrop-filter: none;
  border-bottom: 1px solid var(--line);
}
body.shop .nav {
  display: flex; align-items: center; justify-content: flex-start;
  /* Pion zeruje `.nav` wizytówki (14 px) — wysokość daje tu `height`.
     Poziom musi zostać taki jak w `.wrap`, bo to jeden i ten sam element
     (`class="wrap nav"`): samo `padding: 0` dosuwało logo do krawędzi
     ekranu, czego makieta nie robi. Widać to dopiero na telefonie, gdzie
     kontener nie ma marginesu od `max-width`. */
  gap: 24px; height: 74px; padding: 0 26px;
}
/* Nagłówek księgarni jest niższy od wydawniczego, więc znak schodzi o cztery
   piksele — proporcjonalnie do wysokości z panelu, a nie na stałe. */
body.shop .logo img, body.shop .logo svg {
  height: calc(var(--logo-h, 46px) - 4px);
}
body.shop .search {
  flex: 1; max-width: 540px; display: flex; align-items: center; gap: 10px;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: 12px; padding: 11px 16px;
}
body.shop .search input {
  border: none; background: none; outline: none; flex: 1;
  font-size: 14.5px; font-family: inherit; color: var(--ink);
}
body.shop .nav-actions { gap: 8px; margin-left: auto }
body.shop .iconbtn {
  position: relative; width: 44px; height: 44px; border-radius: 11px;
  display: grid; place-items: center; transition: .2s; cursor: pointer;
  border: 1px solid transparent; color: var(--ink);
}
body.shop .iconbtn:hover { background: var(--paper); border-color: var(--line) }
/* Lupa żyje tylko na wąskim ekranie — rozwinięciem zajmuje się media query
   niżej. `.iconbtn` ustawia `display: grid`, co samo wygrałoby z atrybutem
   `hidden`, więc wyłączenie musi stać osobno. */
body.shop .searchtoggle { display: none }
body.shop .iconbtn .badge {
  position: absolute; top: 2px; right: 2px;
  background: var(--brand); color: #fff;
  font-family: 'Oswald'; font-weight: 700; font-size: 10.5px;
  min-width: 18px; height: 18px; border-radius: 9px;
  display: grid; place-items: center; padding: 0 4px;
}
body.shop .subnav {
  border-top: 1px solid var(--line); display: flex; gap: 22px;
  height: 48px; align-items: center; overflow-x: auto;
}
body.shop .subnav a {
  font-family: 'Oswald'; font-weight: 500; font-size: 13px;
  letter-spacing: .04em; text-transform: uppercase; color: #3a352f;
  white-space: nowrap;
}
body.shop .subnav a:hover { color: var(--brand) }
body.shop .subnav a.home { color: var(--brand) }

/* --- trzy wejścia po formacie --- */
body.shop .formats {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin: 24px 0;
}
body.shop .fcard {
  border-radius: 18px; padding: 30px; color: #fff; position: relative;
  overflow: hidden; min-height: 150px; display: flex; flex-direction: column;
  justify-content: space-between; transition: .25s;
}
body.shop .fcard:hover { transform: translateY(-4px); box-shadow: 0 18px 38px rgba(0, 0, 0, .16) }
body.shop .fcard.druk { background: linear-gradient(135deg, #1b4c6b, var(--druk)) }
body.shop .fcard.ebook { background: linear-gradient(135deg, #249070, var(--ebook)) }
body.shop .fcard.audiobook { background: linear-gradient(135deg, #c4631f, var(--audiobook)) }
body.shop .fcard .ic {
  width: 46px; height: 46px; border-radius: 12px;
  background: rgba(255, 255, 255, .18); display: grid; place-items: center;
  margin-bottom: auto;
}
body.shop .fcard h3 { font-size: 24px; text-transform: uppercase; margin-top: 14px }
body.shop .fcard p { font-size: 13.5px; opacity: .9 }
body.shop .fcard .cnt {
  position: absolute; top: 24px; right: 26px;
  font-family: 'Oswald'; font-weight: 600; font-size: 30px; opacity: .5;
}

/* --- pasek zaufania --- */
body.shop .info-band {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; margin: 8px 0 0;
}
body.shop .ib {
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  padding: 20px; display: flex; gap: 14px; align-items: center;
}
body.shop .ib .ic {
  width: 46px; height: 46px; border-radius: 12px; background: #f3e3e3;
  color: var(--brand); display: grid; place-items: center; flex-shrink: 0;
}
body.shop .ib h4 {
  font-family: 'Oswald'; font-weight: 500; font-size: 15px;
  text-transform: uppercase; margin-bottom: 2px;
}
body.shop .ib p { font-size: 13px; color: var(--muted) }

/* --- układ: filtry z boku, wyniki obok --- */
body.shop .shoplayout {
  display: grid; grid-template-columns: 248px 1fr; gap: 28px;
  padding: 14px 0 70px; align-items: start;
}
body.shop .filters {
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  padding: 24px; position: sticky; top: 150px;
}
body.shop .filters h4 {
  font-family: 'Oswald'; font-weight: 500; font-size: 13px;
  text-transform: uppercase; letter-spacing: .08em; color: var(--muted);
  margin: 22px 0 12px;
}
body.shop .filters form > h4:first-child { margin-top: 0 }
body.shop .fopt {
  display: flex; align-items: center; gap: 10px; padding: 7px 0;
  font-size: 14.5px; cursor: pointer; color: #3a352f;
}
body.shop .fopt input { accent-color: var(--brand); width: 16px; height: 16px }
body.shop .fopt .cnt {
  margin-left: auto; font-size: 12px; color: var(--muted);
  background: var(--paper); padding: 1px 8px; border-radius: 100px;
}
body.shop .fopt .dot { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0 }
body.shop .prange { display: flex; gap: 10px; margin-top: 6px }
body.shop .prange input {
  width: 100%; border: 1px solid var(--line); border-radius: 9px;
  padding: 9px; font-size: 13px; font-family: inherit;
}
/* „Wyczyść filtry" pojawia się tylko wtedy, gdy jest co czyścić. */
body.shop .clearf {
  display: block; margin-top: 18px; font-family: 'Oswald'; font-size: 12px;
  letter-spacing: .05em; text-transform: uppercase; color: var(--brand);
}
body.shop .clearf:hover { text-decoration: underline }

/* --- główka wyników i sortowanie --- */
body.shop .rhead {
  display: flex; justify-content: space-between; align-items: center;
  margin-bottom: 20px; flex-wrap: wrap; gap: 12px;
}
body.shop .rhead h2 { font-size: 24px; text-transform: uppercase }
body.shop .rhead .sub { color: var(--muted); font-size: 14px; margin-top: 2px }
body.shop .sortsel {
  border: 1px solid var(--line); background: #fff; border-radius: 10px;
  padding: 11px 14px; font-size: 14px; font-family: inherit; cursor: pointer;
}

/* --- siatka kart --- */
body.shop .grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 20px;
}
body.shop .card {
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  overflow: hidden; display: flex; flex-direction: column; transition: .22s;
}
body.shop .card:hover {
  box-shadow: 0 16px 36px rgba(40, 20, 20, .10);
  transform: translateY(-4px); border-color: #ddd3c8;
}
/* 21.10: szare `#eee` wypada. Było jedynym miejscem w silniku, gdzie puste
   miejsce wokół okładki miało własny, obcy kolor — i jedynym, w którym
   zakaz „białej karty pod okładką" dawał się zobaczyć bez okładki
   o innej proporcji: szara plama odcinała się od bieli karty.

   `overflow: hidden` ZOSTAJE i to jedyna taka ramka. Powiększenie okładki
   przy najechaniu (`scale(1.04)` wiersz niżej) bez niego wylazłoby na
   tytuł i cenę pod spodem. Cienia ta ramka nie ma i nie miała — cień
   rysuje tu cała karta, przy najechaniu — więc nie ma czego obcinać. */
body.shop .card .ph { position: relative; aspect-ratio: 2/3; overflow: hidden }
/* Zostaje samo przejście pod powiększenie na najechanie (wiersz niżej).
   Rozmiar i `object-fit` niesie `.ok-obraz`; własny `object-fit: contain`
   w tym miejscu wygrywał swoistością z wyjątkiem „prawie 2:3". */
body.shop .card .ph img { transition: .4s }
body.shop .card:hover .ph img { transform: scale(1.04) }
body.shop .card .ph .pin {
  position: absolute; top: 12px; left: 12px;
  font-family: 'Oswald'; font-size: 11px; font-weight: 500;
  letter-spacing: .04em; text-transform: uppercase;
  padding: 4px 10px; border-radius: 100px; z-index: 2;
  background: var(--brand); color: #fff;
}
/* Zastępnik dla tytułów bez okładki — lepszy niż puste szare pole. */
body.shop .card .ph .nocover {
  width: 100%; height: 100%; display: grid; place-items: center;
  background: linear-gradient(160deg, #efe9e0, #ddd3c8);
  color: #9a8e85; font-size: 22px; letter-spacing: .1em;
  text-transform: uppercase;
}
body.shop .card .info { padding: 16px 16px 18px; display: flex; flex-direction: column; flex: 1 }
body.shop .card .info .k {
  font-family: 'Oswald'; font-size: 11px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}
body.shop .card .info h3 {
  font-family: 'Oswald'; font-weight: 500; font-size: 17px;
  text-transform: uppercase; margin: 5px 0 2px;
}
body.shop .card .info .au { font-size: 13px; color: var(--muted) }
body.shop .formats-av { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px }
body.shop .fmt {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: 'Oswald'; font-weight: 500; font-size: 11px;
  letter-spacing: .03em; text-transform: uppercase;
  padding: 5px 9px; border-radius: 8px; border: 1px solid var(--line);
  background: var(--paper); color: #46403a;
}
body.shop .fmt .dot { width: 8px; height: 8px; border-radius: 50% }
body.shop .fmt.druk .dot { background: var(--druk) }
body.shop .fmt.ebook .dot { background: var(--ebook) }
body.shop .fmt.audiobook .dot { background: var(--audiobook) }
body.shop .stock {
  font-size: 12px; color: var(--green); margin-top: 9px;
  display: flex; align-items: center; gap: 6px;
}
body.shop .stock .d { width: 7px; height: 7px; border-radius: 50%; background: var(--green) }
/* Nakład wyczerpany — makieta nie miała takiego stanu, a stara strona ma
   dwa takie tytuły. Zamiast je ukrywać, mówimy prawdę. */
body.shop .stock.out { color: var(--muted) }
body.shop .stock.out .d { background: #c3b9ae }
/* Zapowiedź — trzeci stan obok „dostępna" i „wyczerpany" (A2). Złoto, to
   samo co na przycisku przedsprzedaży: kropka i przycisk mówią o tej samej
   rzeczy, więc mają mówić tym samym kolorem. */
body.shop .stock.soon { color: #7a5c22 }
body.shop .stock.soon .d { background: var(--gold) }

/* --- cena i przycisk „do koszyka" --- */
body.shop .buy {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 14px; gap: 10px; flex-wrap: wrap;
}
body.shop .price { display: flex; flex-direction: column }
body.shop .price .from { font-size: 11px; color: var(--muted) }
body.shop .price b { font-family: 'Oswald'; font-size: 19px; font-weight: 600 }
/* Cena przed obniżką stoi w jednym wierszu z aktualną, nie pod nią —
   a podpis „cena okładkowa" zawija się pod obie. */
body.shop .price .amt { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap }
body.shop .price s { font-size: 12.5px; color: var(--muted) }
/* Ten sam podpis, co na półce wizytówki (A6): sama kreska nie mówi, co
   przekreślamy, a w katalogu stoi obok trzech innych kwot. */
body.shop .price .okl { font-size: 10px; color: var(--muted); font-style: italic; width: 100% }
body.shop .price b.soon { font-size: 15px; color: var(--muted) }
/* Omnibus (dyrektywa 2019/2161) — przy obniżce musi być widoczny. */
body.shop .omni { flex-basis: 100%; font-size: 11px; color: var(--muted); line-height: 1.4 }
/* Przycisk pod ceną, na całą szerokość karty — a nie kwadrat z samym plusem
   po prawej od kwoty, jak było do A2.

   Kwadrat pochodził z makiety i wyglądał dobrze, dopóki stał sam. Gdy obok
   niego na tej samej siatce pojawiły się „Zamów w przedsprzedaży"
   i „Zapytaj o dostępność" — napisy, których w kwadrat się nie da włożyć —
   katalog zaczął pokazywać trzy różne kształty na jedną czynność. Klient
   zgłosił to jako niespójność i stąd punkt A2. Plus zostaje jako ikona
   wiodąca, bo to on niósł sens „dorzuć", a napis go tylko dopowiada. */
/* `margin-top: auto` — przycisk dosunięty do dołu karty, nie do ceny nad
   nim (A6: „przycisk zawsze na tej samej wysokości"). Karty w jednym
   rzędzie mają różną liczbę wierszy: dwa formaty zamiast jednego, tytuł
   na trzy linijki, zdanie o Omnibusie przy obniżce, podpis „cena
   okładkowa" — i bez tego każdy z tych wierszy przesuwał przycisk
   o kilkanaście pikseli względem sąsiada. Działa, bo `.card .info` jest
   kolumną flex z `flex: 1`, a komórki siatki rozciągają się do wysokości
   najwyższej w rzędzie. */
body.shop .buy-act { margin-top: auto; padding-top: 10px }
body.shop .buy-act .addbtn {
  width: 100%; background: var(--brand); color: #fff;
  border-color: var(--brand); padding: 11px 14px;
}
body.shop .buy-act .addbtn:hover { background: var(--brand-d); border-color: var(--brand-d) }
body.shop .buy-act .addbtn svg { flex-shrink: 0 }
/* Przedsprzedaż i pytanie są w katalogu takie same jak wszędzie — wypełnione,
   bo stoją w tym samym miejscu karty co „Dodaj do koszyka" i muszą mieć
   tę samą wagę. Reguły wyżej nadpisują sam kolor. */
body.shop .buy-act .addbtn.przedsprzedaz {
  background: var(--gold); border-color: var(--gold); color: #fff;
}
body.shop .buy-act .addbtn.przedsprzedaz:hover { background: #96702f; border-color: #96702f }
body.shop .buy-act .addbtn.pytaj {
  background: none; border-color: var(--ink); color: var(--ink);
}
body.shop .buy-act .addbtn.pytaj:hover { background: var(--ink); color: #fff }

/* --- stronicowanie (makieta go nie miała, przy 63 tytułach jest potrzebne) --- */
body.shop .pager {
  display: flex; align-items: center; justify-content: center; gap: 16px;
  margin-top: 36px;
}
body.shop .pager a {
  border: 1px solid var(--line); background: #fff; border-radius: 10px;
  padding: 11px 18px; font-family: 'Oswald'; font-size: 12.5px;
  letter-spacing: .04em; text-transform: uppercase; transition: .2s;
}
body.shop .pager a:hover { border-color: var(--brand); color: var(--brand) }
body.shop .pager .pinfo { font-family: 'Oswald'; font-size: 13px; color: var(--muted) }

/* --- brak wyników --- */
body.shop .empty {
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  padding: 44px 32px; text-align: center;
}
body.shop .empty h3 { font-size: 20px; text-transform: uppercase; margin-bottom: 10px }
body.shop .empty p {
  font-size: 14.5px; color: var(--muted); max-width: 460px; margin: 0 auto 6px;
}
body.shop .empty a { color: var(--brand); text-decoration: underline }

/* --- „Przygotowujemy…" na podstronie z siatką (zlecenie 18.10.2026, 1.3) ---
   Katalog i spis autorów to szerokie siatki kafelków. Samo zdanie w tym
   miejscu stoi na pół ekranu pustki i czyta się jak resztka po czymś, co
   się nie wczytało — dlatego dostaje własną, wyśrodkowaną oprawę.
   Podstrony tekstowe („O nas", „Wydarzenia") tego nie potrzebują: zdanie
   stoi tam w wąskiej kolumnie pod nagłówkiem, czyli dokładnie tam, gdzie
   normalnie stoi tekst. */
.pusto-strona {
  text-align: center; padding: 72px 20px 96px;
  font-family: 'Lora', Georgia, serif;
  font-size: clamp(16px, 1.6vw, 19px); color: var(--muted);
}
.pusto-strona p { max-width: 30em; margin: 0 auto }

/* --- przyciski dla przeglądarek bez JS ---
   Filtry, szukanie i sortowanie to zwykłe formularze GET. Z włączonym JS
   wysyłają się same, więc te przyciski siedzą w <noscript> i normalnie ich
   nie ma — ale bez nich sklep przestałby działać bez JavaScriptu. */
body.shop .nojs-go {
  border: 1px solid var(--brand); background: #fff; color: var(--brand);
  border-radius: 10px; padding: 10px 16px; cursor: pointer;
  font-family: 'Oswald'; font-size: 12px; font-weight: 500;
  letter-spacing: .05em; text-transform: uppercase;
}
body.shop .nojs-go:hover { background: var(--brand); color: #fff }
body.shop .nojs-block { display: block; width: 100%; margin-top: 18px }

/* --- strona koszyka ---
   Makieta pokazuje koszyk jako wysuwany panel (`.drawer`) — ten wchodzi razem
   z kasą. Układ pozycji jest wzięty z panelu (okładka, opis, cena po prawej),
   bo to ta sama treść, tylko rozłożona na szerokość strony. */
body.shop .cartpage { padding: 34px 0 70px }
body.shop .cartpage h1 { font-size: 30px; text-transform: uppercase; margin-bottom: 22px }
body.shop .cartgrid { display: grid; grid-template-columns: 1fr 330px; gap: 28px; align-items: start }
body.shop .clines {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 16px; padding: 6px 24px;
}
body.shop .cline { display: flex; gap: 16px; padding: 20px 0; border-bottom: 1px solid var(--line) }
body.shop .cline:last-child { border-bottom: none }
/* 21.10: bez tła i bez `overflow` — wokół okładki widać białą kartę
   `.clines`, na której wiersz koszyka leży. Zaokrąglenie przeszło na
   obrazek. Cienia ten prostokąt nie ma i nie miał: ma 62 piksele
   i cień zrobiłby z niego brudną plamkę obok tytułu. */
body.shop .cline .cv {
  width: 62px; flex-shrink: 0; aspect-ratio: 2/3;
  --ok-promien: 7px;
}
/* Okładka w środku: `.ok-obraz`. Zaślepka to `div.nocover`. */
body.shop .cline .cv .nocover {
  width: 100%; height: 100%; display: grid; place-items: center;
  background: linear-gradient(160deg, #efe9e0, #ddd3c8); color: #9a8e85; font-size: 20px;
}
body.shop .cline .cd { flex: 1; min-width: 0 }
body.shop .cline .cd h3 {
  font-family: 'Oswald'; font-weight: 500; font-size: 16px;
  text-transform: uppercase; margin: 6px 0 2px;
}
body.shop .cline .cd .au { font-size: 13px; color: var(--muted) }
body.shop .cline .cqty { font-size: 13.5px; color: var(--muted); margin-top: 8px }

/* Sterowanie ilością — `− [pole] + Zmień`.
   W kodzie szablonu „Zmień” stoi pierwszy, bo przeglądarka wysyła formularz
   po Enterze pierwszym przyciskiem `submit` w kolejności dokumentu, a Enter
   w polu liczby ma ustawić wpisaną ilość, nie ją zmniejszyć. Na ekranie
   wraca na koniec przez `order` — jedyna rzecz, jaką ten CSS musi zrobić
   poza wyglądem. Bez CSS-a (albo gdy się nie wczyta) układ jest tylko
   przestawiony, a formularz działa dalej. */
body.shop .cqtyform { display: flex; align-items: center; gap: 6px; margin-top: 10px }
body.shop .cqtyform .cqapply { order: 3 }
body.shop .cqtyform .cqstep[value="mniej"] { order: 0 }
body.shop .cqtyform .cqlabel { order: 0 }
body.shop .cqtyform .cqinput { order: 1 }
body.shop .cqtyform .cqstep[value="wiecej"] { order: 2 }
/* Etykieta jest dla czytnika ekranu — pole z samą liczbą nic mu nie mówi.
   Nie `display: none`, bo to by ją schowało również przed nim. */
body.shop .cqlabel {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
body.shop .cqstep {
  width: 28px; height: 28px; flex-shrink: 0; cursor: pointer;
  border: 1px solid var(--line); background: var(--paper); border-radius: 7px;
  font-size: 15px; line-height: 1; color: var(--ink);
}
body.shop .cqstep:hover:not(:disabled) { border-color: var(--brand); color: var(--brand) }
body.shop .cqstep:disabled { opacity: .4; cursor: not-allowed }
body.shop .cqinput {
  width: 52px; height: 28px; text-align: center; font-size: 13.5px;
  border: 1px solid var(--line); border-radius: 7px; background: var(--card);
  color: var(--ink); font-family: inherit;
}
body.shop .cqapply {
  cursor: pointer; border: none; background: none; padding: 0 4px;
  font-family: 'Oswald'; font-size: 11.5px; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted);
}
body.shop .cqapply:hover { color: var(--brand); text-decoration: underline }
body.shop .cqhint { font-size: 12.5px; color: var(--muted); margin-top: 6px }
/* Uwaga nad tabelką — odpowiedź na poprzednią zmianę ilości („zostało
   sztuk: 5”). Ton ostrzeżenia, nie błędu: zmiana się udała, tylko nie
   w całości. */
body.shop .cwarn {
  font-size: 14px; padding: 12px 16px; border-radius: 10px; margin-bottom: 18px;
  background: #fdf3e3; color: #8a5a12; border: 1px solid #f0dcb4;
}
body.shop .cline .cr { text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 10px }
body.shop .cline .pr { font-family: 'Oswald'; font-weight: 600; font-size: 17px; white-space: nowrap }
body.shop .cdel {
  border: none; background: none; cursor: pointer; padding: 0;
  font-family: 'Oswald'; font-size: 11.5px; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted);
}
body.shop .cdel:hover { color: var(--brand); text-decoration: underline }

body.shop .csum {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 16px; padding: 24px; position: sticky; top: 150px;
}
body.shop .csum h4 {
  font-family: 'Oswald'; font-weight: 500; font-size: 13px;
  text-transform: uppercase; letter-spacing: .08em; color: var(--muted);
  margin-bottom: 16px;
}
body.shop .csum .line {
  display: flex; justify-content: space-between; gap: 12px;
  font-size: 14px; color: var(--muted); margin-bottom: 10px;
}
body.shop .csum .line.total {
  font-family: 'Oswald'; font-weight: 600; font-size: 20px; color: var(--ink);
  text-transform: uppercase; margin: 16px 0 4px;
  border-top: 1px solid var(--line); padding-top: 16px;
}
body.shop .cnote {
  font-size: 13px; padding: 11px 14px; border-radius: 10px; margin: 14px 0;
  background: var(--paper); color: #46403a;
}
body.shop .cnote.ok { background: #eaf6ef; color: #15803d }
body.shop .cnote.wait { background: #faf3e4; color: #7a5c17 }
/* Przejście do kasy — jedyny przycisk w podsumowaniu i jedyny na całej
   stronie koszyka, więc ma być szeroki na całą kolumnę i nie do pomylenia
   z „Usuń" przy pozycjach. */
body.shop .cgo {
  display: block; width: 100%; text-align: center; margin-top: 18px;
}
body.shop .cback {
  display: block; text-align: center; margin-top: 16px; font-size: 13.5px;
  color: var(--brand);
}
body.shop .cback:hover { text-decoration: underline }

@media (max-width: 1000px) {
  body.shop .cartgrid { grid-template-columns: 1fr }
  body.shop .csum { position: static }
  body.shop .formats { grid-template-columns: 1fr }
  body.shop .shoplayout { grid-template-columns: 1fr }
  body.shop .filters { position: static }
  body.shop .info-band { grid-template-columns: 1fr 1fr }

  /* Wyszukiwarka na telefonie.
     Pole nie zmieści się obok logo i koszyka, więc schodzi do własnego rzędu
     na pełną szerokość, a rozwija je lupa w prawym górnym rogu. Nagłówek musi
     wtedy rosnąć — stąd `height: auto`, bo na sztywnych 74 px drugi rząd
     wyszedłby poza tło nagłówka. */
  body.shop .nav { flex-wrap: wrap; height: auto; padding: 11px 26px; gap: 10px 14px }
  body.shop .searchtoggle:not([hidden]) { display: grid }
  body.shop .search { order: 3; flex: 1 1 100%; max-width: none }
  /* 16 px to nie estetyka, a obrona przed Safari na iOS: przy mniejszym kroju
     przeglądarka sama przybliża stronę po wejściu w pole i zostawia ją
     przesuniętą w bok. Na szerokim ekranie zostaje 14,5 px z makiety. */
  body.shop .search input { font-size: 16px }
  /* Zwinięte dopiero wtedy, gdy klasę doda skrypt. Bez JavaScriptu rząd
     z szukaniem zostaje otwarty: lepiej, żeby pole zajęło trochę miejsca,
     niż żeby na telefonie nie było czym szukać. */
  body.shop .nav.search-off .search { display: none }
}


/* ==========================================================================
   ZGODA NA CIASTECZKA I PODSTRONY PRAWNE
   Wspólne dla wizytówki i księgarni — baner wisi w base.html, więc musi
   wyglądać tak samo pod oba nagłówki. Dlatego krój i rozmiary są tu podane
   wprost: `body.shop` ma własną skalę i własną rodzinę (Inter), która bez
   tego przeciekłaby do banera.
   ========================================================================== */

.cookiebar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 90;
  background: var(--card); border-top: 2px solid var(--brand);
  box-shadow: 0 -8px 30px rgba(35, 26, 23, .14);
  font-family: 'Lora', Georgia, serif; color: var(--ink);
}
.cookiebar-in {
  max-width: 1180px; margin: 0 auto; padding: 18px 28px;
  display: flex; align-items: center; flex-wrap: wrap; gap: 18px 32px;
}
.cookiebar-text { flex: 1 1 400px; min-width: 0 }
.cookiebar-text h2 {
  font-family: 'Oswald', sans-serif; font-size: 16px; font-weight: 600;
  letter-spacing: .05em; text-transform: uppercase; margin: 0 0 6px;
  color: var(--ink);
}
.cookiebar-text p { margin: 0; font-size: 14px; line-height: 1.6; color: var(--muted) }
.cookiebar-text a { color: var(--brand); text-decoration: underline }

/* Trzy wyjścia, JEDEN wygląd — to nie estetyka, a wymóg.
   „Tylko niezbędne" wyblakłe obok kolorowego „Akceptuj wszystkie" jest
   wzorcem, który organy ochrony danych czytają jako zgodę wymuszoną, czyli
   brak zgody. Wspólna klasa, równa szerokość i ten sam kontrast są po to,
   żeby nikt nie „poprawił" tego osobnym kolorem dla jednego przycisku. */
/* Siatka, nie `flex` — i to jest właśnie ta „równa szerokość".
   Przy `flex` każdy przycisk rozpycha się do swojego napisu, a `min-width`
   trzyma tylko dolną granicę: „Akceptuj wszystkie" wychodziło 173 px obok
   168 px pozostałych. Różnica mała, ale zależna od długości tekstu — czyli
   zniknęłaby albo urosła przy pierwszej zmianie treści.
   `grid-auto-columns: 1fr` dzieli miejsce po równo niezależnie od napisów,
   więc równość pilnuje się sama. */
.cookiebar-acts {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: 10px; flex: 0 0 auto;
}
.cookiebtn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 168px; padding: 12px 22px;
  background: var(--brand); color: #fff; border: 1px solid var(--brand);
  border-radius: 100px;
  font-family: 'Oswald', sans-serif; font-size: 13.5px; font-weight: 500;
  letter-spacing: .06em; text-transform: uppercase;
  /* `line-height` jawnie, bo `<button>` nie dziedziczy go po stronie —
     przeglądarka daje mu własne `normal`. Dwa wyjścia to przyciski,
     trzecie („Ustawienia") to odnośnik, więc bez tej linijki wychodziły
     o 3 px różne. W rzędzie siatka zrównywała je rozciągnięciem i różnicy
     nie było widać; w kolumnie na telefonie — już tak. */
  line-height: 1.5;
  text-align: center; text-decoration: none; cursor: pointer; transition: .2s;
}
.cookiebtn:hover { background: var(--brand-d); border-color: var(--brand-d); color: #fff }
.cookiebtn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px }

/* „Zamknij" wygląda inaczej niż trzy pozostałe i to jest wyjątek od reguły
   wyżej, a nie jej złamanie. Reguła mówi o równej wadze **wyborów zgody** —
   a ten przycisk niczego nie wybiera: zostawia stan, który już jest, i wychodzi
   wyłącznie w okienku otwartym ponownie ze stopki. Gdyby wyglądał tak samo jak
   „Tylko niezbędne", byłby czwartą równorzędną odpowiedzią na pytanie, które
   ma trzy. */
.cookiebtn-zamknij {
  background: transparent; color: var(--muted); border-color: var(--line);
}
.cookiebtn-zamknij:hover { background: var(--paper); border-color: var(--muted); color: var(--ink) }

/* Zdanie zamiast pustej listy kategorii: dziś żadne narzędzie zewnętrzne nie
   jest włączone, więc w formularzu stoi sama pozycja „Niezbędne". Bez tego
   zdania wygląda to na niedokończony ekran. */
.cookie-pusto {
  margin: -6px 0 16px; font-family: 'Lora', Georgia, serif;
  font-size: 13.5px; line-height: 1.6; color: var(--muted);
}

/* Panel kategorii: w banerze rozwija go skrypt, na podstronie polityki
   stoi otwarty na stałe. */
.cookiebar-panel { border-top: 1px solid var(--line); background: var(--paper) }
.cookiebar-panel .cookieform { max-width: 1180px; margin: 0 auto; padding: 20px 28px }

.cookiecats { list-style: none; margin: 0 0 18px; padding: 0; display: grid; gap: 14px }
.cookiecats label {
  display: grid; grid-template-columns: 18px 1fr; gap: 12px;
  align-items: start; cursor: pointer;
}
.cookiecats input { width: 16px; height: 16px; margin: 3px 0 0; accent-color: var(--brand) }
.cookiecats input:disabled { cursor: default }
.cookiecats input:disabled + span { color: var(--muted) }
.cookiecats span {
  font-family: 'Lora', Georgia, serif; font-size: 14px; line-height: 1.6;
  color: var(--ink);
}
/* Nazwa kategorii to osobna klasa, a nie „każdy `strong` w środku".
   Opis kategorii przychodzi od A12 z panelu i jest zwykłym tekstem, ale
   reguła na sam znacznik wróciłaby jako usterka przy pierwszym pogrubieniu
   dopisanym w szablonie: `display: block` wyrzuciłoby je do własnego
   wiersza, rozcinając zdanie w pół. */
.cookiecats .cookiecat-name {
  display: block; font-family: 'Oswald', sans-serif; font-size: 13.5px;
  font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ink);
}
.cookiecats strong { font-weight: 700 }
.cookiecats em {
  display: inline-block; margin: 2px 0 4px; font-style: normal;
  font-family: 'Oswald', sans-serif; font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--gold);
}

/* --- podstrony prawne (regulamin, polityka prywatności, polityka cookies) --- */
.legal { background: var(--paper); padding: 56px 0 78px }
/* Węziej niż `.wrap`: 1180 px tekstu to wiersz, którego nie da się czytać.
   `.legal-in` siedzi na tym samym elemencie co `.wrap`, więc nadpisuje
   tylko szerokość — margines boczny zostaje z `.wrap`. */
.legal-in { max-width: 820px }
.legal h1 {
  font-family: 'Lora', Georgia, serif; font-size: 40px; font-weight: 600;
  line-height: 1.15; margin: 6px 0 10px; color: var(--ink);
  text-transform: none; letter-spacing: 0;
}
.legal h2 {
  font-family: 'Oswald', sans-serif; font-size: 17px; font-weight: 600;
  letter-spacing: .05em; text-transform: uppercase;
  margin: 38px 0 12px; color: var(--ink);
}
.legal p, .legal li {
  font-family: 'Lora', Georgia, serif; font-size: 15.5px; line-height: 1.75;
  color: var(--ink);
}
.legal p { margin: 0 0 14px }
.legal ul, .legal ol { margin: 0 0 16px; padding-left: 26px }
.legal li { margin-bottom: 8px }
/* Podpunkty literami, nie cyframi. W regulaminie poziom wyżej to ustęp
   („1.”), a poziom niżej punkt — dwa razy „1.” pod sobą nie pozwalają się
   powołać na konkretne miejsce bez przepisywania całego zdania. */
.legal ol ol { list-style-type: lower-alpha; margin: 8px 0 4px }
.legal ol ol > li { margin-bottom: 6px }
.legal a { color: var(--brand); text-decoration: underline }
.legal-meta { color: var(--muted); font-size: 13px !important; margin-bottom: 28px !important }

/* Profile autora na jego karcie (A4). Wygląd bierze z `.soc-light` wyżej
   — tutaj zostaje tylko to, czego oprawa `.legal` by zepsuła: podkreślenie
   spod `.legal a` (kreska pod kółkiem z ikoną) i odstęp przed biogramem,
   bo `.soc-light` ma margines tylko od góry. Przy autorze bez profili tej
   ramki nie ma wcale, więc odstęp też nie zostaje. */
.legal .autor-soc { margin: 4px 0 26px }
.legal .autor-soc a { text-decoration: none }

/* Podstrona osoby z zespołu (B4). Zdjęcie, oprawa kolumny i nagłówek
   przychodzą gotowe z `.author .av` i `.legal` — ta strona odpowiada na
   to samo pytanie co karta autora, więc ma wyglądać tak samo. Zostaje
   troje drobiazgów, których na karcie autora nie ma. */
.profil-rola {
  font-family: 'Oswald', sans-serif; font-size: 12.5px !important;
  letter-spacing: .1em; text-transform: uppercase; color: var(--brand);
  margin: 0 0 18px !important;
}
/* Zdanie z karty w siatce — kursywą i z kreską z boku, żeby było widać,
   że to cytat z tej osoby, a nie pierwszy akapit notki o niej. */
.profil-cytat {
  font-style: italic; color: #5a504a; border-left: 3px solid var(--gold);
  padding-left: 16px; margin: 0 0 24px !important;
}
.profil-kontakt ul { list-style: none; margin: 0; padding: 0 }
.profil-kontakt li {
  display: flex; align-items: center; gap: 10px; margin-bottom: 10px;
  font-family: 'Lora', Georgia, serif; font-size: 15.5px;
}
.profil-kontakt li svg { color: var(--brand); flex: none }

/* Panel zgód pod dokumentem — dodatek aplikacji, nie fragment pliku
   prawnika, więc odcina się ramką i nie wchodzi w numerację sekcji. */
.legal-panel {
  margin: 44px 0 0; padding: 26px 28px;
  background: var(--card); border: 1px solid var(--line); border-radius: 6px;
}
.legal-panel h2 { margin-top: 0 }
.legal-panel .legal-meta { margin: 14px 0 0 !important }
.legal code {
  font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12.5px;
  background: var(--card); border: 1px dashed var(--line);
  border-radius: 4px; padding: 2px 6px; color: var(--brand-d);
}
.legal-table { width: 100%; border-collapse: collapse; margin: 0 0 18px; font-size: 14px }
.legal-table th, .legal-table td {
  border: 1px solid var(--line); padding: 10px 12px;
  text-align: left; vertical-align: top;
  font-family: 'Lora', Georgia, serif; line-height: 1.55;
}
.legal-table th {
  background: var(--card); font-family: 'Oswald', sans-serif; font-size: 12px;
  font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted);
}
.legal-table code { font-size: 12px; border-style: solid }
/* Wzór oświadczenia o odstąpieniu — blok do zaznaczenia i skopiowania.
   `pre`, bo puste wiersze na wpisanie numeru zamówienia i adresu są tu
   treścią, a nie formatowaniem: po przeklejeniu do maila mają zostać.
   `white-space: pre-wrap` zamiast `pre`, żeby na telefonie zawijało
   zamiast wystawiać poziomy pasek przewijania. */
.legal-wzor {
  font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 13px;
  line-height: 1.7; white-space: pre-wrap; word-break: break-word;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 10px; padding: 18px 20px; margin: 0 0 18px; color: var(--ink);
}
/* Formularz zgód na podstronie stoi otwarty, bez ramki banera. */
.legal .cookieform { margin: 0 0 18px }

@media (max-width: 760px) {
  /* Na telefonie trzy przyciski obok siebie by się nie zmieściły, a
     ściśnięte do połowy szerokości przestałyby być równorzędne. Każdy
     dostaje własny wiersz i pełną szerokość — nadal identyczne. */
  .cookiebar-in { padding: 16px 20px; gap: 14px }
  .cookiebar-acts { width: 100%; grid-auto-flow: row; gap: 8px }
  .cookiebtn { width: 100%; min-width: 0 }
  .cookiebar-panel .cookieform { padding: 18px 20px }
  /* Baner na telefonie potrafi zająć pół ekranu — treść musi zostać
     przewijalna, dlatego ograniczamy wysokość, a nie rozpychamy strony. */
  .cookiebar { max-height: 80vh; overflow-y: auto }
  .legal { padding: 36px 0 54px }
  .legal h1 { font-size: 30px }
  .legal-panel { padding: 20px 18px }
  /* Tabela ciasteczek nie zmieści się w czterech kolumnach — zamiast
     ścinać tekst, pozwalamy ją przesunąć w poziomie. */
  .legal-table { display: block; overflow-x: auto; white-space: nowrap }
}


/* ==========================================================================
   WYDARZENIA — lista, wpis, wydarzenie (K4.5, przepisane w poprawkach 2)
   Podstrony pożyczają oprawę kolumny od stron prawnych (`.legal`) i siatkę
   kafelków od sekcji „Co u nas słychać" na wizytówce (`.acts` / `.act`).
   Nowe jest tu tylko to, czego żadna z nich nie miała: zdjęcie i akapit
   wiodący w treści wpisu, dane terminu wydarzenia i pasek „to już było".
   ========================================================================== */

/* Stronicowanie wpisów. `.pager` z księgarni siedzi pod `body.shop`,
   a te strony noszą nagłówek wizytówki — stąd własna klasa zamiast
   rozluźniania tamtego selektora, co przeniosłoby skalę Intera na
   podstrony składane w Lorze. */
.pager-wpisy {
  display: flex; align-items: center; justify-content: center;
  gap: 18px; margin: 34px 0 6px;
}
.pager-wpisy a {
  font-family: 'Oswald', sans-serif; font-size: 13px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink); text-decoration: none;
  border: 1px solid var(--line); border-radius: 9px; padding: 9px 16px;
  background: var(--card); transition: .2s;
}
.pager-wpisy a:hover { border-color: var(--brand); color: var(--brand) }
.pager-wpisy .pinfo { font-family: 'Oswald', sans-serif; font-size: 13px; color: var(--muted) }

/* Nadtytuł nad H1 jest na tych stronach linkiem do listy — wejście
   idzie najczęściej z Facebooka, więc „gdzie jestem" musi być klikalne. */
.legal .eyebrow a { color: inherit; text-decoration: none }
.legal .eyebrow a:hover { color: var(--brand); text-decoration: underline }

/* --- treść wpisu i wydarzenia --- */
.wpis-foto {
  margin: 4px 0 26px; border-radius: 16px; overflow: hidden;
  aspect-ratio: 16/9; box-shadow: 0 18px 40px rgba(40, 18, 18, .16);
  position: relative; background: var(--card);
}
/* Poprawki 3 (A12): duże zdjęcie u góry strony wpisu/wydarzenia zawsze
   w całości, bez przycinania — niezależnie od proporcji, bo to jedyne
   miejsce, gdzie przycięcie zawsze byłoby złe (plakat targów, zdjęcie
   portretowe). Rozmyte tło z tego samego obrazu wypełnia wolne miejsce
   po bokach/u góry zamiast pustych pasów. */
.wpis-foto-tlo {
  position: absolute; inset: 0; background-size: cover;
  background-position: center; filter: blur(26px) brightness(.92);
  transform: scale(1.15);
}
.wpis-foto img {
  width: 100%; height: 100%; object-fit: contain; display: block;
  position: relative;
}
/* Akapit wiodący to ta sama zajawka, która jedzie w opis dla Facebooka.
   Większy stopień odróżnia ją od treści, żeby nie czytało się jej jako
   powtórzonego pierwszego zdania. */
.wpis-lead {
  font-size: 18.5px !important; line-height: 1.6; color: var(--ink) !important;
  font-weight: 500; margin-bottom: 20px !important;
}

.wyd-minelo {
  background: #fdf6ec; border: 1px solid #e8d2ae; border-left: 3px solid #c99a4e;
  border-radius: 0 10px 10px 0; padding: 13px 16px; margin: 0 0 24px !important;
  font-size: 14.5px !important; color: #6b5324 !important;
}
/* Termin i miejsce jako lista opisowa, a nie dwa akapity: czytelnik
   skanuje je wzrokiem przed tekstem i musi je znaleźć bez czytania. */
.wyd-dane {
  display: grid; grid-template-columns: auto 1fr; gap: 6px 20px;
  margin: 0 0 26px; padding: 16px 18px;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
}
.wyd-dane dt {
  font-family: 'Oswald', sans-serif; font-size: 11.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--brand); padding-top: 3px;
}
.wyd-dane dd { margin: 0; font-size: 16px; color: var(--ink) }
.wyd-zapisy { margin: 26px 0 0 !important }
.wyd-zapisy .btn { text-decoration: none }

/* Wydarzenia pod listą wpisów — ta sama oprawa co sekcje „feat-wrap"
   na wizytówce, tylko bez górnego marginesu, bo nad nimi stoi już
   stronicowanie. */
.wyd-sekcja { margin-top: 46px }

/* --- wizytówki autorów i książek na stronie wydarzenia (B2c) ---
   Jedna siatka na dwa rodzaje, bo to ten sam kafelek: zdjęcie i podpis
   pod spodem. Różnią się wyłącznie proporcją kadru — twarz jest
   kwadratem, okładka prostokątem — i to jedyne, co rozdziela
   `.wyd-ludzie` od `.wyd-okladki`.

   `auto-fill` z `minmax`, tak jak w galerii: na stoisku bywa dwóch
   autorów i bywa jedenastu, a reguła ma działać w obu przypadkach bez
   liczenia kolumn. */
.wyd-wizytowki {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 18px 16px;
  grid-template-columns: repeat(auto-fill, minmax(124px, 1fr));
}
.wyd-wizytowki li { margin: 0 }
.wyd-wizytowki a {
  display: block; text-decoration: none; color: var(--ink);
}
/* Tylko fotografie osób (`.wyd-ludzie`), nie okładki. Do poprawek 20.10
   stało tu `.wyd-wizytowki img`, czyli JEDNO i DRUGIE — a `object-fit`
   było w tym wierszu `cover`. Przy swoistości (0,1,1) wiersz wygrywał
   z `.ok-obraz` (0,1,0), więc okładka na stronie wydarzenia była
   przycinana wbrew zasadzie z części pierwszej — mimo że szablon już
   wołał makro i mimo komentarza niżej, który obiecuje coś innego.
   Okładka bierze wszystko z `.wyd-okladki .ok-ramka` (niżej)
   i z `.ok-obraz`; tutaj zostaje twarz autora, dla której przycięcie
   do kwadratu jest poprawne, i zaślepka bez zdjęcia. */
.wyd-ludzie img, .wyd-bezfoto {
  display: block; width: 100%; border-radius: 12px;
  border: 1px solid var(--line); background: var(--card);
  object-fit: cover; transition: .22s;
}
.wyd-ludzie img, .wyd-ludzie .wyd-bezfoto { aspect-ratio: 1; border-radius: 50% }
/* Proporcja wspólna, cień rozdzielony (21.10): zaślepka bez zdjęcia jest
   płaskim prostokątem i cień rysuje jej `box-shadow`, a przy okładce
   rysuje go `drop-shadow` z obrazka, bo prostokątem być nie musi. */
.wyd-okladki .ok-ramka, .wyd-okladki .wyd-bezfoto { aspect-ratio: 2/3 }
.wyd-okladki .wyd-bezfoto { box-shadow: 0 10px 22px rgba(40, 18, 18, .14) }
/* Okładka książki idzie zawsze w całości, bez przycięcia — inaczej niż
   twarz autora wyżej (`.wyd-ludzie`), gdzie przycięcie do kwadratu jest
   poprawne. Poprawki 4A, 1.9.

   21.10: obwódka i kolor karty wypadają ze zlecenia („bez białej/kremowej
   karty pod okładką, bez ramki"). Zostaje wymiar — wokół okładki widać
   `--paper` strony wydarzenia. Zaokrąglenie i cień przechodzą na obrazek
   zmiennymi; zostawione na ramce obrysowywałyby prostokąt 2:3 także przy
   okładce, która go nie wypełnia. Najechanie podnosi okładkę i pogłębia
   jej cień — bo obwódki, którą dotąd podświetlało, już nie ma. */
.wyd-okladki .ok-ramka {
  display: block; width: 100%; transition: .22s;
  --ok-promien: 12px;
  --ok-cien: drop-shadow(0 10px 22px rgba(40, 18, 18, .14));
}
/* Brak zdjęcia to pusty kadr w kolorze karty, a nie dziura w siatce:
   bez niego podpisy przy autorach z fotografią i bez niej stanęłyby na
   różnych wysokościach i rząd przestałby wyglądać na rząd. Zastępcza
   sylwetka byłaby gorsza — podpowiadałaby, że ktoś tak wygląda. */
.wyd-bezfoto { background: linear-gradient(140deg, #f2ebe1, #e7ded2) }
/* Najechanie i ognisko biorą `.ok-ramka` razem z `img`: przy okładce
   ruch od poprawek 20.10 należy do ramki, nie do obrazka, więc
   `a:hover img` podnosiłby sam obraz WEWNĄTRZ nieruchomej ramki.
   `border-color` zostaje dla twarzy autora i dla zaślepki bez zdjęcia —
   tylko one mają jeszcze obwódkę; okładka straciła ją 21.10 i w zamian
   dostaje głębszy cień. */
.wyd-ludzie a:hover img,
.wyd-wizytowki a:hover .ok-ramka,
.wyd-wizytowki a:hover .wyd-bezfoto {
  border-color: var(--brand); transform: translateY(-3px);
}
.wyd-wizytowki a:hover .ok-ramka {
  --ok-cien: drop-shadow(0 16px 30px rgba(40, 18, 18, .22));
}
.wyd-ludzie a:focus-visible img,
.wyd-wizytowki a:focus-visible .ok-ramka,
.wyd-wizytowki a:focus-visible .wyd-bezfoto {
  outline: 2px solid var(--brand); outline-offset: 3px;
}
.wyd-wizytowki a > span:last-child {
  display: block; margin-top: 9px; font-size: 13.5px; line-height: 1.35;
  font-weight: 600; color: var(--ink);
}
.wyd-wizytowki a:hover > span:last-child { color: var(--brand) }
/* Nagłówki obu sekcji schodzą bliżej treści niż domyślne `.legal h2`:
   nad nimi stoi akapit opisu, a nie koniec rozdziału. */
.wyd-osoby h2, .wyd-tytuly h2 { margin-top: 34px }


/* Kalendarz po latach i miastach (`.wyd-lata`, `.wyd-rok`, `.wyd-miasto`,
   `.wyd-spis`) stał tu do poprawek 2. P3 zwinął go w jedną listę wspólną
   z wpisami, rysowaną siatką `.acts` — te klasy zniknęły razem
   z szablonem, więc i reguły poszły (decyzja 493). */

@media (max-width: 560px) {
  .wyd-wizytowki { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 14px 12px }
}


/* --- galeria zdjęć pod wpisem i pod wydarzeniem ---
   Rysuje `_galeria_macros.html`, składa `app/galeria.py`.

   Siatka `auto-fill` zamiast stałej liczby kolumn: galeria ma trzy
   zdjęcia albo dziewiętnaście i w obu przypadkach ma wypełnić szerokość
   kolumny tekstu. Przy trzech wychodzą trzy kafelki, przy dziewiętnastu
   — tyle rzędów, ile trzeba, bez reguły na każdą liczbę.

   Kwadrat (`aspect-ratio: 1`) równa rzędy mimo mieszanki pionowych
   i poziomych zdjęć z telefonu. Pełny kadr jest o kliknięcie dalej. */
.galeria { margin: 38px 0 0 }
.gal-naglowek {
  font-family: 'Oswald', sans-serif; font-size: 13px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--brand); font-weight: 600;
  margin: 0 0 16px;
}
.gal-siatka {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
}
.gal-kafel { margin: 0 }
.gal-kafel a {
  display: block; aspect-ratio: 1; border-radius: 12px; overflow: hidden;
  background: var(--card); border: 1px solid var(--line);
}
.gal-kafel img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .25s;
}
.gal-kafel a:hover img { transform: scale(1.04) }
.gal-kafel a:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px }
.gal-kafel figcaption {
  margin-top: 8px; font-size: 13.5px; line-height: 1.45; color: var(--muted);
}

@media (max-width: 560px) {
  /* Dwie kolumny na telefonie: jedna robi z galerii długie przewijanie,
     a kafelek 170 px nie mieści się w dwóch kolumnach na wąskim ekranie. */
  .gal-siatka { grid-template-columns: repeat(2, 1fr); gap: 10px }
}


/* ==========================================================================
   OSADZENIA — film z cudzego serwisu, plakat teraz, ramka po kliknięciu
   Używa tego cykl „Wydało się", wpis i wydarzenie. Rysuje makro
   `_osadzenia_macros.html`, rozpoznaje adres `app/osadzenia.py`.

   Proporcje jadą zmienną `--osad-proporcje` ustawianą w `style` przy
   kafelku, bo zależą od serwisu: 16/9 przy filmie, 4/5 przy Instagramie.
   Klasa na każdy serwis znaczyłaby nową regułę CSS przy każdym nowym
   serwisie — czyli poprawkę w dwóch plikach zamiast w jednym słowniku.
   ========================================================================== */
.osad {
  position: relative; aspect-ratio: var(--osad-proporcje, 16 / 9);
  border-radius: 14px; overflow: hidden; background: #221414;
}
/* Plakat jest linkiem, nie guzikiem — bez JavaScriptu otwiera film
   u źródła w nowej karcie. Guzik bez skryptu nie robi nic. */
.osad-plakat {
  position: absolute; inset: 0; display: block; text-decoration: none;
  cursor: pointer;
}
.osad-plakat img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .4s, filter .25s;
}
.osad-plakat:hover img { transform: scale(1.04); filter: brightness(.86) }
/* Kafelek z nazwą serwisu zamiast miniatury. Miniatura YouTube'a leży pod
   adresem Google'a, więc jej wstawienie byłoby tym samym żądaniem do
   trzeciej strony, którego unika cała ta konstrukcja. */
.osad-pusty {
  position: absolute; inset: 0; display: flex;
  align-items: center; justify-content: center;
  background: linear-gradient(135deg, #3a2320, #221414);
  color: #c7bbb2; font-size: 15px; letter-spacing: .14em; text-transform: uppercase;
}
/* Trójkąt odtwarzania. Półprzezroczysta czerń pod spodem, bo plakat bywa
   jasny i sam biały trójkąt gubi się na zdjęciu z sali. */
.osad-play {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 68px; height: 68px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(20, 10, 10, .62); color: #fff;
  border: 2px solid rgba(255, 255, 255, .82);
  transition: background .25s, transform .25s;
}
.osad-plakat:hover .osad-play,
.osad-plakat:focus-visible .osad-play {
  background: var(--brand); transform: translate(-50%, -50%) scale(1.08);
}
.osad-plakat:focus-visible { outline: 3px solid var(--brand); outline-offset: 3px }
/* Napis „Odtwórz · YouTube" mówi wprost, dokąd idzie kliknięcie. Bez niego
   czytelnik dowiaduje się o trzeciej stronie dopiero, gdy już się wczytała. */
.osad-info {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 26px 16px 12px; color: #f3ece6;
  font-family: 'Oswald', sans-serif; font-size: 12.5px;
  letter-spacing: .08em; text-transform: uppercase;
  background: linear-gradient(transparent, rgba(20, 10, 10, .78));
}
/* Ramkę wstawia skrypt po kliknięciu (`app/static/js/main.js`). */
.osad iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0 }

/* Osadzenie wklejone do wpisu albo do wydarzenia (B2d). Stoi w kolumnie
   tekstu (`.legal-in`, 820 px), więc dostaje marginesy akapitu zamiast
   siatki kafelków. */
.tresc-osad { margin: 30px 0 }
/* Post z Instagrama jest pionowy: na pełnej szerokości kolumny miałby
   ponad tysiąc pikseli wysokości i zepchnąłby galerię poza ekran. Węższa
   kolumna daje mu mniej więcej tyle miejsca, ile ma w przeglądarce
   u źródła. O tym, czy zwęzić, decyduje `Osadzenie.wysokie` — proporcje
   zna `app/osadzenia.py`, nie ten arkusz. */
.tresc-osad-waski { max-width: 440px; margin-inline: auto }


/* ==========================================================================
   CYKL „WYDAŁO SIĘ" — lista odcinków
   Dwie kolumny, nie trzy jak przy `.acts`: kafelek odcinka jest naraz
   odtwarzaczem i opisem, a film na jednej trzeciej szerokości jest za mały,
   żeby ktokolwiek chciał go oglądać na stronie.
   ========================================================================== */
.cykl-wstep { font-size: 17px; color: var(--muted); max-width: 62ch }
.cykl { display: grid; grid-template-columns: repeat(2, 1fr); gap: 34px 28px; margin-top: 10px }
.odc {
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  overflow: hidden; display: flex; flex-direction: column;
  /* Kafelek jest celem kotwicy z taśmy na stronie głównej (B2e). Bez
     zapasu skok wsuwa górę karty pod przyklejony nagłówek — ta sama
     poprawka, co przy nagłówkach lat na `/wydarzenia`. */
  scroll-margin-top: 90px;
}
/* Kafelek nie jest linkiem — jedynym linkiem w nim jest plakat, a pod
   spodem stoją dwa osobne odnośniki (autor i książka). Zagnieżdżone `<a>`
   nie istnieje w HTML, a gdyby istniało, nikt by nie wiedział, co kliknął. */
.odc .osad { border-radius: 0 }
.odc-txt { padding: 20px 22px 24px }
.odc-txt .date {
  font-family: 'Oswald', sans-serif; font-size: 12px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--brand);
}
/* `h2` na stronie cyklu, `h3` w zajawce pod aktualnościami — ten sam
   kafelek, inne miejsce w hierarchii nagłówków strony. Wygląd ma być
   identyczny, bo to ten sam obiekt; o poziomie decyduje to, co stoi
   nad nim, a nie stopień pisma. */
.odc-txt h2, .odc-txt h3 {
  font-family: 'Oswald', sans-serif; font-weight: 500; font-size: 22px;
  text-transform: uppercase; margin: 7px 0 10px; line-height: 1.25;
}
.odc-txt p { font-size: 14.5px; color: var(--muted); margin: 0 0 8px }
.odc-gosc { color: var(--ink) !important }
.odc-gosc a { color: var(--brand); text-decoration: none }
.odc-gosc a:hover { text-decoration: underline }
.odc-ksiazka { margin: 12px 0 0 !important }
.odc-ksiazka a {
  font-family: 'Oswald', sans-serif; font-size: 13px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink); text-decoration: none;
  border-bottom: 1px solid var(--brand); padding-bottom: 2px;
}
.odc-ksiazka a:hover { color: var(--brand) }

@media (max-width: 900px) {
  /* Jedna kolumna wcześniej niż przy `.acts`: film w dwóch kolumnach na
     tablecie ma już poniżej 300 px szerokości, czyli jest mniejszy niż
     plakietka „Odtwórz" pod nim. */
  .cykl { grid-template-columns: 1fr; gap: 26px }
}
@media (max-width: 560px) {
  .osad-play { width: 54px; height: 54px }
  .odc-txt { padding: 16px 18px 20px }
  .odc-txt h2, .odc-txt h3 { font-size: 19px }
}


/* ==========================================================================
   KONTO KLIENTA — logowanie, rejestracja, reset hasła, panel
   Wszystko pod `body.shop`, bo strony konta noszą nagłówek księgarni
   i jej skalę (Inter). Formularze stoją na klasach `.kt-*` z A1 — tutaj
   dochodzi wyłącznie oprawa strony, lista błędów i trzy listy panelu,
   bo w zatwierdzonej makiecie nie było ich do czego przypiąć.
   ========================================================================== */
body.shop .konto { background: var(--paper); padding: 46px 0 76px }
/* Logowanie, rejestracja i reset to jedna kolumna na środku. Formularz
   na 1280 px miałby pola szerokie jak pasek adresu — a wpisuje się w nie
   e-mail i hasło, nie opowiadanie. */
body.shop .konto-waska .wrap { max-width: 520px }

body.shop .konto-head { margin-bottom: 26px }
body.shop .konto-head h1 {
  font-family: 'Oswald', sans-serif; font-weight: 600;
  font-size: clamp(26px, 3.4vw, 34px); text-transform: uppercase;
  margin: 8px 0 0; color: var(--ink);
}
body.shop .konto-head p { color: var(--muted); font-size: 14.5px; margin-top: 8px }
body.shop .konto-card { padding: 30px }
/* Pola dziedziczą krój strony. `.kt-fld input` ma w makiecie wizytówki
   Lorę — na stronie księgarni (Inter) wyglądałaby jak wklejony fragment
   z innego serwisu. */
body.shop .konto .kt-fld input { font-family: inherit; font-size: 15px }

/* Komunikaty nad formularzem. Ostrzeżenie jest bordowe, nie czerwone
   jak `--err`: to odmowa, nie awaria, a czerwień w palecie A1 jest
   zarezerwowana dla błędu pola. */
body.shop .konto-bledy {
  background: #fdf2f2; border-left: 3px solid var(--err);
  border-radius: 8px; padding: 13px 16px; margin-bottom: 20px;
  font-size: 14px; color: #8c2b2b;
}
body.shop .konto-bledy p { margin: 0 }
body.shop .konto-bledy ul { margin: 0; padding-left: 18px }
body.shop .konto-bledy li + li { margin-top: 5px }
body.shop .konto-ok {
  background: #eef9f1; border-left: 3px solid var(--green);
  border-radius: 8px; padding: 13px 16px; margin-bottom: 20px;
  font-size: 14px; color: #14663a;
}
body.shop .konto-ok p { margin: 0 }

body.shop .konto-pod { margin-top: 18px; font-size: 14px; color: var(--muted) }
body.shop .konto-pod a { color: var(--brand) }
body.shop .konto-pod a:hover { text-decoration: underline }
body.shop .konto-pod span { margin: 0 8px; color: var(--line) }
/* `.form-note` ma ujemny margines z góry, bo pisane jest jako dopisek pod
   polem formularza. Pod wierszem odnośników ten minus sklejał oba akapity
   w jeden blok tekstu — stąd wyjątek. */
body.shop .konto-pod + .form-note { margin-top: 12px }

/* --- siatka panelu: menu z boku, treść po prawej --- */
body.shop .konto-grid {
  display: grid; grid-template-columns: 228px 1fr; gap: 28px; align-items: start;
}
body.shop .konto-menu {
  display: flex; flex-direction: column;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 10px; position: sticky; top: 92px;
}
body.shop .konto-menu a {
  padding: 11px 14px; border-radius: 9px; font-size: 14.5px; color: var(--ink);
}
body.shop .konto-menu a:hover { background: var(--paper) }
body.shop .konto-menu a.on {
  background: var(--brand); color: #fff; font-weight: 600;
}
body.shop .konto-wyloguj { margin-top: 6px; border-top: 1px solid var(--line); padding-top: 6px }
body.shop .konto-wyloguj button {
  width: 100%; text-align: left; border: none; background: none; cursor: pointer;
  padding: 11px 14px; border-radius: 9px; font-family: inherit; font-size: 14.5px;
  color: var(--muted);
}
body.shop .konto-wyloguj button:hover { background: var(--paper); color: var(--brand) }

body.shop .konto-tresc { min-width: 0 }
body.shop .konto-sek {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 24px 26px; margin-bottom: 20px;
}
body.shop .konto-sek > h2 {
  font-family: 'Oswald', sans-serif; font-weight: 600; font-size: 15px;
  letter-spacing: .06em; text-transform: uppercase; margin: 0 0 16px;
}
body.shop .konto-sek .kt-send { width: auto; padding: 13px 26px }
body.shop .konto-pusto { color: var(--muted); font-size: 14.5px; margin: 0 }
body.shop .konto-pusto a { color: var(--brand); text-decoration: underline }
body.shop .konto-data { color: var(--muted); font-size: 13px }

/* --- newsletter w ustawieniach konta (B5c) --- */
/* Zdanie o stanie zgody (`.konto-nl-stan`) stoi niżej, we wspólnej regule
   z bliźniaczym zdaniem przy powiadomieniach z ulubionych — oba mówią to
   samo o dwóch różnych zgodach i mają wyglądać identycznie. */
/* Okienko zgody dostaje tu te same proporcje co w kasie — ten sam napis
   nie może być raz drobnym drukiem, a raz zdaniem w treści strony. */
body.shop .konto-sek .kt-chk { margin-bottom: 16px }
body.shop .konto-sek .kt-chk span { font-size: 12.5px; line-height: 1.55 }
body.shop .konto-sek .kt-chk a { color: var(--brand); text-decoration: underline }
/* „Anuluj zapis" pod przyciskiem wysyłającym list jeszcze raz. Odstęp,
   żeby dwa przyciski pod sobą nie wyglądały na parę równorzędną — jeden
   ponawia to, co już trwa, drugi to przerywa. */
body.shop .konto-nl-anuluj { margin-top: 12px }
/* Komunikat po zapisie (`.nlform-info` z sekcji newslettera) stoi tu
   nad treścią sekcji, a nie wewnątrz formularza jak w stopce. */
body.shop .konto-sek .nlform-info { margin-bottom: 16px }

/* --- powiadomienia z ulubionych (B7) --- */
/* Sekcja zgody pod listą kafelków w „Moich ulubionych". Odstęp od góry
   większy niż między kafelkami, bo to już nie jest ciąg dalszy listy,
   tylko osobna sprawa — a bez tej przerwy przełącznik czyta się jak
   podpis pod ostatnim rzędem okładek. */
body.shop .ulub-pow { margin-top: 28px }
/* Zdanie o stanie zgody — jedna reguła na dwie zgody. Ciemniejsze od
   `.konto-pusto`, bo to nie jest informacja o pustce, tylko odpowiedź na
   pytanie „czy dostaję listy". Wspólna, nie dwie bliźniacze: rozjazd
   akurat tutaj znaczyłby, że jedna zgoda w tym samym panelu konta
   wygląda na ważniejszą od drugiej. */
body.shop .konto-nl-stan,
body.shop .ulub-pow-stan { margin: 0 0 16px; font-size: 14.5px; line-height: 1.6 }
/* Formularz jest tu samym przyciskiem (albo okienkiem i przyciskiem),
   więc nie potrzebuje własnego pudełka — zeruje tylko domyślny odstęp,
   żeby klauzula pod nim trzymała się tej samej siatki co w kasie. */
body.shop .ulub-pow-form { margin: 0 }
body.shop .ulub-pow-form .btn { width: auto; padding: 13px 26px }

/* --- lista zamówień --- */
body.shop .konto-zam { list-style: none; margin: 0; padding: 0 }
body.shop .konto-zam > li {
  border: 1px solid var(--line); border-radius: 12px;
  padding: 16px 18px; margin-bottom: 12px;
}
body.shop .konto-zam > li:last-child { margin-bottom: 0 }
body.shop .konto-zam-head {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 14px; flex-wrap: wrap;
}
body.shop .konto-zam-head b { font-family: 'Oswald', sans-serif; font-size: 15px }
body.shop .konto-zam-head .konto-data { display: block }
body.shop .konto-stany { display: flex; gap: 6px; flex-wrap: wrap }
/* Znacznik stanu: ten sam kształt dla stanu zamówienia i stanu płatności,
   bo oba czyta się jednym spojrzeniem. Kolorem wyróżniamy tylko to, co
   wymaga reakcji klienta — reszta jest spokojna, żeby „Zrealizowane"
   nie krzyczało głośniej od „Oczekuje na płatność". */
body.shop .konto-stan {
  font-family: 'Oswald', sans-serif; font-size: 10.5px; font-weight: 500;
  letter-spacing: .07em; text-transform: uppercase;
  padding: 5px 9px; border-radius: 7px;
  background: var(--paper); border: 1px solid var(--line); color: #4a443e;
  white-space: nowrap;
}
body.shop .konto-stan.s-wyslane, body.shop .konto-stan.s-zrealizowane {
  background: #eef9f1; border-color: #bfe6cc; color: #14663a;
}
body.shop .konto-stan.s-anulowane, body.shop .konto-stan.p-nieudana {
  background: #fdf2f2; border-color: #f0cfcf; color: #8c2b2b;
}
body.shop .konto-stan.p-oczekuje {
  background: #fdf6e6; border-color: #e8d6a8; color: #6b4f00;
}

body.shop .konto-poz { list-style: none; margin: 14px 0 0; padding: 0 }
body.shop .konto-poz li {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 8px 0; border-top: 1px solid var(--line); font-size: 14px;
}
body.shop .konto-poz-tyt { flex: 1; min-width: 160px }
body.shop .konto-poz-il { color: var(--muted); font-size: 13px; white-space: nowrap }
body.shop .konto-wysylka {
  margin: 12px 0 0; font-size: 13.5px; color: var(--muted);
}
body.shop .konto-wysylka b { color: var(--ink); font-family: 'Oswald', sans-serif }
body.shop .konto-zam-stopka {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--line);
  font-size: 13.5px; color: var(--muted);
}
body.shop .konto-zam-stopka b {
  font-family: 'Oswald', sans-serif; font-size: 16px; color: var(--ink);
}

/* --- adresy --- */
body.shop .konto-etykieta {
  display: inline-block; font-family: 'Oswald', sans-serif; font-size: 10.5px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--gold);
  margin-bottom: 6px;
}
body.shop .konto-adres p { margin: 0; font-size: 14.5px; line-height: 1.6 }
body.shop .konto-adresy { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px }
body.shop .konto-adresy > li {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 16px;
  border: 1px solid var(--line); border-radius: 12px; padding: 16px 18px;
}
/* Adres domyślny odcina się lewą krawędzią, nie tłem: w kasie podstawi się
   sam, więc musi być widać który, zanim klient kliknie cokolwiek. */
body.shop .konto-adresy > li.dom { border-left: 3px solid var(--brand) }
body.shop .konto-adres-akcje {
  display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
  flex-shrink: 0;
}
body.shop .konto-adres-akcje a, body.shop .konto-adres-akcje button {
  font-family: 'Oswald', sans-serif; font-size: 11px; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted);
  border: none; background: none; padding: 0; cursor: pointer;
}
body.shop .konto-adres-akcje a:hover { color: var(--brand) }
body.shop .konto-adres-akcje button:hover { color: var(--err) }

/* --- półka „Moje e-booki" --- */
body.shop .konto-polka { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px }
body.shop .konto-polka > li {
  display: flex; gap: 16px; border: 1px solid var(--line);
  border-radius: 12px; padding: 16px 18px;
}
/* Jak w koszyku i z tych samych powodów (21.10): wokół okładki widać
   wnętrze pozycji na półce, nie kartę pod spodem. */
body.shop .konto-polka-ok {
  width: 62px; flex-shrink: 0; aspect-ratio: 2/3;
  --ok-promien: 7px;
}
/* Okładka w środku: `.ok-obraz`. Zaślepka to `div.nocover`. */
body.shop .konto-polka-ok .nocover {
  width: 100%; height: 100%; display: grid; place-items: center;
  background: linear-gradient(160deg, #efe9e0, #ddd3c8); color: #9a8e85; font-size: 20px;
}
body.shop .konto-polka-op { flex: 1; min-width: 0 }
body.shop .konto-polka-op h3 {
  font-family: 'Oswald', sans-serif; font-weight: 500; font-size: 16px;
  text-transform: uppercase; margin: 6px 0 2px;
}
body.shop .konto-polka-op .au { font-size: 13px; color: var(--muted) }
body.shop .konto-polka-stan { margin: 8px 0 0; font-size: 13.5px; color: var(--muted) }
body.shop .konto-polka-stan.ok { color: #14663a }
body.shop .konto-polka-akcje { margin: 12px 0 0 }
/* Przycisk pobrania to `<button>` w formularzu POST (patrz szablon), a nie
   `<a class="btn">` jak wszędzie indziej. `.btn` daje mu krój i kursor,
   ale nie zdejmuje domyślnego tła `<button>` — bez `background: none`
   wariant bez `btn-primary` („Odnów link") wychodzi szary. Padding mniejszy,
   bo przycisk siedzi w wierszu listy, a nie w sekcji na pół ekranu. */
body.shop .konto-polka-akcje .btn { padding: 9px 20px }
body.shop .konto-polka-akcje .btn:not(.btn-primary) {
  background: none; border-color: var(--line); color: var(--ink);
}
body.shop .konto-polka-akcje .btn:not(.btn-primary):hover {
  border-color: var(--ink); background: var(--ink); color: #fff;
}

@media (max-width: 860px) {
  /* Menu konta na telefonie idzie nad treść i w poziom — pionowa kolumna
     zjadłaby pół ekranu przed pierwszym zamówieniem. `sticky` schodzi,
     bo przyklejony pasek nad krótką listą zasłaniałby jej połowę. */
  body.shop .konto-grid { grid-template-columns: 1fr; gap: 18px }
  body.shop .konto-menu {
    position: static; flex-direction: row; flex-wrap: wrap; gap: 4px; padding: 8px;
  }
  body.shop .konto-menu a { padding: 9px 12px; font-size: 14px }
  body.shop .konto-wyloguj {
    margin: 0; border-top: none; border-left: 1px solid var(--line);
    padding: 0 0 0 6px; margin-left: auto;
  }
  body.shop .konto-wyloguj button { padding: 9px 12px; width: auto }
  body.shop .konto { padding: 28px 0 54px }
  body.shop .konto-card, body.shop .konto-sek { padding: 20px 18px }
  body.shop .konto-adresy > li { flex-direction: column; gap: 12px }
  body.shop .konto-adres-akcje { flex-direction: row; align-items: center; gap: 16px }
  /* Safari na iOS przybliża stronę przy wejściu w pole mniejsze niż 16 px
     i zostawia ją przesuniętą w bok — ta sama obrona co w wyszukiwarce. */
  body.shop .konto .kt-fld input { font-size: 16px }
  body.shop .konto .kt-g2 { grid-template-columns: 1fr }
}


/* ==========================================================================
   KASA I STRONA ZAMÓWIENIA
   Dalszy ciąg idiomu kont: karty `.kt-card`, pola `.kt-*`, podsumowanie
   `.csum` wprost z koszyka. Nowe są trzy rzeczy, których w makiecie nie
   było — kafelki wyboru dostawy i płatności, siatka „formularz + boczne
   podsumowanie" i blok z danymi do przelewu.
   ========================================================================== */

/* Formularz po lewej, podsumowanie po prawej — jak w koszyku, bo to ta
   sama czynność dokończona. Szerokość kolumny bocznej ta sama (320 px),
   żeby przejście koszyk → kasa nie przesuwało niczego na ekranie. */
body.shop .kasa-grid {
  display: grid; grid-template-columns: 1fr 320px; gap: 28px; align-items: start;
}
body.shop .kasa-formularz { display: grid; gap: 18px }
body.shop .kasa-formularz .kt-card { padding: 24px }
/* Nagłówek sekcji w karcie. Mniejszy od `h2` z wizytówki: to etykieta
   części formularza, nie tytuł rozdziału. */
body.shop .kasa-sek {
  font-family: 'Oswald', sans-serif; font-weight: 500; font-size: 13px;
  text-transform: uppercase; letter-spacing: .08em; color: var(--muted);
  margin: 0 0 16px;
}
body.shop .kasa-bok .csum { top: 92px }

/* --- kafelki wyboru: dostawa i płatność --- */
/* Cała etykieta jest klikalna, nie samo kółko. Pole wyboru ma 13 px —
   trafienie w nie palcem na telefonie jest loterią, a tu rozstrzyga
   o koszcie przesyłki. */
body.shop .kasa-opcje { display: grid; gap: 10px; margin-bottom: 18px }
body.shop .kasa-opcja {
  display: flex; align-items: flex-start; gap: 12px; cursor: pointer;
  border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px;
  background: var(--card); transition: border-color .15s, background .15s;
}
body.shop .kasa-opcja:hover { border-color: var(--gold) }
/* Wybrany kafelek znaczymy przez `:has()`. Gdzie go nie ma, zostaje samo
   kółko i obramowanie bez wyróżnienia — wybór nadal widać, tylko mniej
   wyraźnie. Pogorszenie jest łagodne, więc nie dokładamy JavaScriptu
   po to, żeby przerysować ramkę. */
body.shop .kasa-opcja:has(input:checked) {
  border-color: var(--brand); background: #fdf7f3;
}
/* Obrys z klawiatury na całym kafelku, nie na schowanym kółku — inaczej
   przechodząc tabulatorem nie widać, gdzie się jest. */
body.shop .kasa-opcja:has(input:focus-visible) {
  outline: 2px solid var(--brand); outline-offset: 2px;
}
body.shop .kasa-opcja input { margin: 2px 0 0; accent-color: var(--brand); flex-shrink: 0 }
body.shop .kasa-opcja-tresc { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; gap: 2px 10px }
body.shop .kasa-opcja-tresc b {
  font-family: 'Oswald', sans-serif; font-weight: 500; font-size: 15px;
  text-transform: uppercase; color: var(--ink);
}
body.shop .kasa-opcja-cena {
  font-family: 'Oswald', sans-serif; font-weight: 600; font-size: 15px;
  color: var(--brand); margin-left: auto; white-space: nowrap;
}
/* Opis idzie do nowego wiersza na pełną szerokość — „BLIK, karta,
   Przelewy24" wciśnięte obok nazwy łamałoby się po jednym słowie. */
body.shop .kasa-opcja-opis {
  flex-basis: 100%; font-size: 13px; color: var(--muted); line-height: 1.5;
}
/* Zamówienie wyłącznie z plików: zamiast pól adresowych jedno zdanie. */
body.shop .kasa-cyfrowa {
  margin: 0; font-size: 14px; color: #46403a;
  background: var(--paper); border-radius: 10px; padding: 13px 16px;
}

/* --- zgody i przycisk w kolumnie bocznej --- */
body.shop .kasa-zgody {
  display: grid; gap: 10px; margin: 18px 0 4px;
  border-top: 1px solid var(--line); padding-top: 16px;
}
body.shop .kasa-zgody .kt-chk span { font-size: 12.5px; line-height: 1.55 }
body.shop .kasa-zgody .kt-chk a { color: var(--brand); text-decoration: underline }
/* Hasło pokazuje się pod zgodą „założę konto" i jest z nią związane —
   wcięcie mówi to bez podpisu. */
body.shop .kasa-haslo { margin: 2px 0 6px 26px }
body.shop .kasa-haslo input { font-family: inherit; font-size: 15px }
/* Przycisk kończący zamówienie bywa długi („Zamawiam z obowiązkiem
   zapłaty"), więc nie trzymamy go w jednym wierszu na siłę. */
body.shop .kasa-zamow {
  margin-top: 14px; width: 100%; white-space: normal; line-height: 1.35;
}

/* --- pozycje w podsumowaniu kasy --- */
/* Węższe niż `.cline` w koszyku: tu nie ma okładek ani przycisku „usuń",
   bo poprawianie koszyka odbywa się w koszyku. */
body.shop .kasa-poz { list-style: none; margin: 0 0 16px; padding: 0 }
body.shop .kasa-poz li {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 10px;
  padding-bottom: 12px; margin-bottom: 12px; border-bottom: 1px solid var(--line);
  font-size: 13.5px;
}
body.shop .kasa-poz li:last-child { border-bottom: none; margin-bottom: 0 }
body.shop .kasa-poz-tyt { grid-column: 1 / -1; color: var(--ink); line-height: 1.45 }
body.shop .kasa-poz-tyt .fmt { margin-right: 6px }
body.shop .kasa-poz-il { color: var(--muted); font-size: 12.5px }
body.shop .kasa-poz b {
  font-family: 'Oswald', sans-serif; font-weight: 600; font-size: 14px;
  text-align: right; white-space: nowrap;
}

/* --- strona zamówienia (podziękowanie i status) --- */
/* Odwrotnie niż w kasie: treść szersza, bo to do czytania, nie do
   wypełniania, a kolumna boczna niesie tylko „co dalej". */
body.shop .zam-grid {
  display: grid; grid-template-columns: 1fr 300px; gap: 28px; align-items: start;
}
body.shop .zam-glowna { display: grid; gap: 18px }
body.shop .zam-glowna .kt-card, body.shop .zam-bok .kt-card { padding: 24px }
body.shop .zam-stan { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 14px }
body.shop .zam-lead { margin: 0; font-size: 15px; line-height: 1.6; color: var(--ink) }
body.shop .zam-lead a { color: var(--brand); text-decoration: underline }
body.shop .zam-wysylka {
  margin: 14px 0 0; font-size: 14px; color: var(--muted);
  border-top: 1px solid var(--line); padding-top: 14px;
}
body.shop .zam-wysylka b { color: var(--ink); font-family: 'Oswald', sans-serif }

/* Dane do przelewu odcinają się lewą krawędzią — klient wraca na tę
   stronę po numer rachunku i ma go znaleźć bez czytania reszty. */
body.shop .zam-przelew { border-left: 3px solid var(--gold) }
body.shop .zam-dl {
  display: grid; grid-template-columns: 112px 1fr; gap: 8px 16px; margin: 0;
  font-size: 14.5px;
}
body.shop .zam-dl dt {
  font-family: 'Oswald', sans-serif; font-size: 11px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted); padding-top: 3px;
}
body.shop .zam-dl dd { margin: 0; color: var(--ink); line-height: 1.5 }
/* Numer rachunku jednym ciągiem, w kroju o stałej szerokości znaku:
   przepisuje się go cyfra po cyfrze, a w proporcjonalnym 1 i 7 wyglądają
   zbyt podobnie. `user-select: all` daje go kliknięciem w całości. */
body.shop .zam-konto {
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 15px; letter-spacing: .02em; word-break: break-all;
  user-select: all; cursor: text;
}
body.shop .zam-glowna .line {
  display: flex; justify-content: space-between; gap: 12px;
  font-size: 14px; color: var(--muted); margin-top: 10px;
}
body.shop .zam-glowna .line.total {
  font-family: 'Oswald', sans-serif; font-weight: 600; font-size: 19px;
  color: var(--ink); text-transform: uppercase;
  border-top: 1px solid var(--line); padding-top: 14px; margin-top: 14px;
}
body.shop .zam-bok .kt-card { position: sticky; top: 92px }
body.shop .zam-bok h4 {
  font-family: 'Oswald', sans-serif; font-weight: 500; font-size: 13px;
  text-transform: uppercase; letter-spacing: .08em; color: var(--muted);
  margin: 0 0 14px;
}
body.shop .zam-bok p { margin: 0 0 12px; font-size: 14px; line-height: 1.6 }
body.shop .zam-bok a { color: var(--brand) }
body.shop .zam-bok a:hover { text-decoration: underline }
body.shop .zam-brak-lista { margin: 12px 0 18px; padding-left: 20px; font-size: 14.5px }
body.shop .zam-brak-lista li { margin-bottom: 7px; line-height: 1.55; color: #46403a }

@media (max-width: 1000px) {
  /* Podsumowanie schodzi pod formularz. Przyklejone na telefonie
     zajmowałoby trzecią część ekranu nad polem, w które się właśnie
     pisze. */
  body.shop .kasa-grid, body.shop .zam-grid { grid-template-columns: 1fr }
  body.shop .kasa-bok .csum, body.shop .zam-bok .kt-card { position: static }
}

@media (max-width: 860px) {
  body.shop .kasa-formularz .kt-card,
  body.shop .zam-glowna .kt-card, body.shop .zam-bok .kt-card { padding: 20px 18px }
  body.shop .kasa-opcja { padding: 13px 14px }
  /* Cena wraca pod nazwę: przy „Paczkomat InPost" i „gratis" w jednym
     wierszu na 360 px nie zostaje miejsca na żadne z nich. */
  body.shop .kasa-opcja-cena { margin-left: 0 }
  body.shop .kasa-haslo { margin-left: 0 }
  /* Dwie kolumny w liście „nazwa — wartość" przy 112 px etykiety zostawiają
     na adres pół ekranu. Etykieta idzie nad wartość. */
  body.shop .zam-dl { grid-template-columns: 1fr; gap: 2px }
  body.shop .zam-dl dd { margin-bottom: 10px }
  body.shop .kasa-haslo input { font-size: 16px }
}

/* ==========================================================================
   Dymek „Dodano do koszyka" (A2)

   Zlecenie: „klient zostaje na stronie, licznik koszyka rośnie, krótki
   dymek «Dodano do koszyka · Przejdź do koszyka»". Dymek jest renderowany
   przez serwer po przekierowaniu (flash w sesji, `app/cart.py`), nie przez
   JavaScript — bez skryptu i tak się pokazuje, bo potwierdzenie dodania do
   koszyka jest treścią, nie ozdobą. Skrypt dokłada tylko znikanie.
   ========================================================================== */
/* Wyśrodkowanie idzie przez `margin: auto` między `left: 0` a `right: 0`,
   a nie przez `translateX(-50%)`. Transform jest tu potrzebny do wjazdu
   od dołu, a jedna właściwość nie może robić naraz dwóch rzeczy — przy
   `translateX(-50%)` w regule bazowej każda klatka animacji musiałaby
   powtarzać wyśrodkowanie, i to osobno w wersji na telefon. */
.dymek {
  position: fixed; z-index: 90; left: 0; right: 0; bottom: 24px;
  width: fit-content; max-width: min(520px, calc(100vw - 32px));
  margin: 0 auto;
  display: flex; align-items: center; gap: 16px;
  padding: 13px 16px;
  background: var(--ink); color: #fff;
  border-radius: 12px; box-shadow: 0 18px 40px rgba(35, 26, 23, .3);
  animation: dymek-wejscie .25s ease-out;
}
.dymek-txt { display: flex; flex-direction: column; min-width: 0 }
.dymek-txt b {
  font-family: 'Oswald'; font-size: 12px; font-weight: 500;
  letter-spacing: .06em; text-transform: uppercase;
}
/* Tytuł bywa dłuższy niż dymek. Przycięcie wielokropkiem zamiast zawijania:
   dymek ma być paskiem na dole, a nie akapitem zasłaniającym stronę. */
.dymek-txt span {
  font-size: 13.5px; color: #e4dad1; line-height: 1.4;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dymek-go {
  flex-shrink: 0; white-space: nowrap;
  font-family: 'Oswald'; font-size: 12px; font-weight: 500;
  letter-spacing: .05em; text-transform: uppercase;
  color: #fff; border-bottom: 1px solid rgba(255, 255, 255, .45);
  padding-bottom: 1px; transition: .2s;
}
.dymek-go:hover { border-bottom-color: #fff }
/* Krzyżyk pokazuje dopiero skrypt (`hidden` w HTML): bez JS nie ma czym
   zamknąć, a przycisk, który nic nie robi, jest gorszy od jego braku. */
.dymek-x {
  flex-shrink: 0; background: none; border: none; cursor: pointer;
  color: #b9aca2; font-size: 20px; line-height: 1; padding: 0 2px;
  transition: .2s;
}
.dymek-x:hover { color: #fff }
.dymek.znika { opacity: 0; transition: opacity .3s ease-in }

@keyframes dymek-wejscie {
  from { opacity: 0; transform: translateY(14px) }
  to { opacity: 1; transform: translateY(0) }
}

@media (prefers-reduced-motion: reduce) {
  /* Dymek zostaje — znika sama jazda i wygaszanie. */
  .dymek { animation: none }
  .dymek.znika { transition: none }
}

@media (max-width: 560px) {
  /* Na telefonie dymek jest paskiem na całą szerokość: wyśrodkowany
     z marginesami zostawiałby na tytuł książki jakieś 120 px. */
  .dymek { left: 12px; right: 12px; bottom: 12px; width: auto; max-width: none }
}

/* ==========================================================================
   KATALOG WYDAWNICZY `/publikacje` (B3)

   Dorobek wydawnictwa, nie oferta — stąd oprawa wizytówki, a nie
   księgarni: kolumna jak na stronach prawnych (`.legal`), siatka
   i kafelek jak na półce strony głównej (`.news-books` / `.nbook`).
   Strona nie nosi `body.shop` i nie może, bo tamta klasa zawęża też
   nagłówek, a `/publikacje` jedzie przy zgaszonym sklepie.

   Nowy jest wyłącznie pasek filtrów i trzy drobiazgi pod tytułem
   kafelka: autor, rok i status zamiast ceny. Reszta to reguły, które
   już są.
   ========================================================================== */

.kw { padding: 54px 0 70px }
.kw-in { max-width: 820px; margin-bottom: 30px }
.kw-in h1 { font-size: 42px; margin: 8px 0 14px }
.kw-lead { color: var(--muted); font-size: 16.5px; max-width: 70ch }

/* --- pasek filtrów ---
   Nad siatką, nie w kolumnie z boku jak w księgarni. Powód: filtrów jest
   pięć i wszystkie są listami jednokrotnego wyboru, więc zajmują jeden
   rząd — a boczna kolumna zabrałaby siatce jedną trzecią szerokości na
   stałe, także wtedy, gdy nikt z niej nie korzysta. */
.kw-filtry {
  display: flex; flex-wrap: wrap; gap: 14px 16px; align-items: flex-end;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  padding: 18px 20px; margin-bottom: 22px;
}
.kw-pole { display: flex; flex-direction: column; gap: 5px; min-width: 150px }
.kw-pole label {
  font-family: 'Oswald', sans-serif; font-size: 10.5px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted);
}
.kw-pole select, .kw-pole input {
  font-family: 'Lora', Georgia, serif; font-size: 14.5px; color: var(--ink);
  background: #fff; border: 1px solid var(--line); border-radius: 8px;
  padding: 9px 11px; max-width: 230px;
}
.kw-pole select:focus, .kw-pole input:focus {
  outline: 2px solid var(--brand); outline-offset: 1px;
}
/* Pole szukania rośnie, listy nie: fraza bywa długa, a nazwy formatów
   i roczniki mają z góry znaną szerokość. */
.kw-szukaj { flex: 1 1 240px }
.kw-szukaj input { max-width: none; width: 100% }

.kw-akcje { display: flex; align-items: center; gap: 14px; margin-left: auto }
.kw-go { padding: 10px 22px; font-size: 12.5px }
.kw-czysc {
  font-family: 'Oswald', sans-serif; font-size: 12px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); text-decoration: underline;
}
.kw-czysc:hover { color: var(--brand) }

.kw-licznik {
  font-family: 'Oswald', sans-serif; font-size: 12.5px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); margin: 0 0 18px;
}

/* --- trzy wiersze pod tytułem kafelka ---
   Półka na stronie głównej kończy się na tytule, bo tam liczy się
   okładka. W katalogu chodzi się szukając nazwiska i rocznika, więc
   jedno i drugie musi być czytelne bez wchodzenia na kartę. */
.kw-au {
  font-size: 13px; color: var(--muted); margin-top: 3px; line-height: 1.45;
}
/* --- równy rząd kafelków (poprawki 20.10, pkt 3) ---------------------------
   Siatka rozciąga wszystkie kafelki w rzędzie do wysokości najwyższego,
   więc SAME kafelki były równe już wcześniej. Rozjeżdżało się to, co
   w środku: zmierzone przy 1440 px (sześć kolumn, baza pokazowa
   z pozycją bez kategorii i tytułami na jeden, dwa, trzy i cztery
   wiersze) tytuły startowały na trzech różnych wysokościach
   (261 / 290 / 291 px od góry kafelka), autorzy na czterech
   (300…364 px), a pasek rocznika i statusu na pięciu (315…388 px).

   Trzy reguły, w kolejności, w jakiej je widzi czytelnik:

   1. Etykieta kategorii ma stałe miejsce na jedną linijkę. 1.7em to
      dokładnie tyle, ile zajmuje etykieta wypełniona (10 px font × 1.7
      z `body`), więc kafelek Z kategorią nie drgnie ani o piksel, a ten
      BEZ niej przestaje podciągać swój tytuł w górę. Szablon wypisuje
      `div` zawsze, bo pusty element bez `min-height` ma zero wysokości
      i nie da się na nim niczego oprzeć.
   2. Tytuł ma stałe miejsce na dwa wiersze. 2.16em to dwa razy
      `line-height: 1.08` z nagłówków. Bez przycinania: tytuł na trzy
      wiersze rośnie dalej i widać go całego. Wielokropek zamiast
      końcówki byłby ukryciem treści, którą właściciel wpisał w panelu,
      a przy dwóch kolumnach na telefonie (~155 px na kafelek) obciąłby
      większość tytułów dłuższych niż jedno słowo.
   3. Pasek rocznika i statusu dosuwa się do dołu kafelka
      (`margin-top: auto`) — ta sama reguła, którą w księgarni ma
      `.buy-act` (poprawki 7, pkt 7) i z tego samego powodu: w rzędzie
      ma być wspólna linia, na której kończy się każdy kafelek. Tam jest
      nią przycisk, tu pasek stanu, bo kafelek katalogu przycisku nie ma
      wcale. `padding-top` zastępuje dawne `margin-top: 6px` — margines
      górny przegrałby z `auto`.

   Autor zostaje przyklejony pod tytułem, a nie dosunięty do dołu razem
   z paskiem stanu. Przy stałym miejscu na dwa wiersze wyrównuje się sam
   w każdym rzędzie bez tytułu dłuższego niż dwa wiersze, a przy dłuższym
   woli odjechać w dół razem ze swoim tytułem niż oderwać się od niego
   o pół kafelka: nazwisko jest podpisem tytułu, nie wierszem stopki.

   Reguły są pod `.kw`, czyli tylko w katalogu. Półka na stronie
   głównej, bibliografia autora, „Inne książki" i „Wkrótce" używają tego
   samego `.nbook`, ale stoi w nich kilka wybranych kafelków w jednym
   rzędzie — tam zapas na puste etykiety byłby pustym miejscem bez
   powodu. Przeniesienie zasady na cały silnik to zamiana `.kw .nbook`
   na `.nbook` w dwóch wierszach niżej; to decyzja właściciela, nie
   skutek uboczny tego punktu. */
.kw .nbook .k { min-height: 1.7em }
/* `h4` razem z `h3`: od pkt 6 cz. 2 tytuł w podsekcji katalogu stoi
   o poziom niżej (kategoria `h2`, półka `h3`, tytuł `h4`). Bez tego
   zapas miejsca na dwa wiersze przepadałby dokładnie w trybach
   z podsekcjami i równy rząd z pkt 3 rozjeżdżałby się tam z powrotem. */
.kw .nbook h3, .kw .nbook h4 { min-height: 2.16em }
.kw-stan {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin-top: auto; padding-top: 6px;
}
.kw-rok { font-family: 'Oswald', sans-serif; font-size: 12.5px; color: var(--muted) }
.kw-cena { font-family: 'Oswald', sans-serif; font-size: 14.5px; color: var(--brand) }
/* Status stoi dokładnie tam, gdzie przy innym tytule stoi cena — i to
   jest sens tego miejsca. Czytelnik wodzi wzrokiem po jednej kolumnie
   i w każdym kafelku znajduje odpowiedź na to samo pytanie. */
.kw-status {
  font-family: 'Oswald', sans-serif; font-size: 11px; letter-spacing: .06em;
  text-transform: uppercase; color: #585049;
  border: 1px solid var(--line); border-radius: 100px; padding: 2px 9px;
}

/* --- katalog w sekcjach (poprawki 20.10, pkt 6) ----------------------------
   Nagłówek sekcji musi być widoczny z odległości rzutu okiem, bo po to
   jest podział: czytelnik przewija stronę szukając nie tytułu, a działu.
   Stąd kreska nad nagłówkiem — ta sama rola, jaką na stronie głównej
   pełni odstęp między półkami, tylko tutaj sekcji jest kilkanaście
   i sam odstęp przestaje je rozdzielać.

   Pierwsza sekcja kreski nie ma: nad nią stoi pasek filtrów i licznik,
   więc druga linia pod linią byłaby podwójnym podkreśleniem tego
   samego miejsca. */
.kw-sekcja { margin-bottom: 42px }
.kw-sekcja + .kw-sekcja {
  border-top: 1px solid var(--line); padding-top: 34px;
}
.kw-sekcja-h {
  display: flex; align-items: baseline; gap: 12px; margin-bottom: 18px;
}
.kw-sekcja-h h2 {
  font-size: 22px; text-transform: uppercase; letter-spacing: .02em;
  margin: 0;
}
/* Liczba tytułów w sekcji — przygaszona i mniejsza od nagłówka, bo jest
   przypisem do niego, nie drugą jego połową. Oswald i cyfry tabelaryczne
   nie są tu potrzebne: liczby nie stoją jedna pod drugą w kolumnie. */
.kw-sekcja-ile { font-size: 13px; color: var(--muted) }

/* --- drugi poziom: podsekcje (pkt 6 cz. 2) ---------------------------------
   Półka wewnątrz kategorii ma być widocznie słabsza od nagłówka
   kategorii, a jednocześnie mocniejsza od rzędu kafelków — inaczej
   czytelnik przewijający stronę nie wie, czy „7-9 lat" jest działem,
   czy podpisem pod okładkami. Stąd trzy różnice naraz względem `h2`
   sekcji: mniejszy stopień pisma, brak kapitalików i kreska PIONOWA,
   nie pozioma.

   Pionowa kreska, bo pozioma należy do pierwszego poziomu: wszystkie
   sekcje oprócz pierwszej rozdziela `border-top`, więc drugi poziom
   z własną kreską poziomą czytałby się jako kolejna kategoria. Pasek
   z boku mówi „to jest w środku czegoś" i nie da się go pomylić
   z granicą działu. */
.kw-podsekcja { margin-bottom: 30px }
.kw-podsekcja:last-child { margin-bottom: 0 }
.kw-podsekcja-h {
  display: flex; align-items: baseline; gap: 10px; margin-bottom: 14px;
  border-left: 3px solid var(--line); padding-left: 12px;
}
.kw-podsekcja-h h3 {
  font-family: 'Oswald', sans-serif; font-weight: 500; font-size: 16px;
  letter-spacing: .04em; margin: 0; color: #585049;
}
/* Odnośnik „Pokaż więcej" podsekcji jest drobniejszy od tego pod
   sekcją — ta sama hierarchia co w nagłówkach. Gdyby był identyczny,
   pod jedną kategorią stałoby kilka jednakowych przycisków i żaden nie
   mówiłby, czego dotyczy. */
.kw-podsekcja .kw-wiecej { margin-top: 12px }
.kw-podsekcja .kw-wiecej a { font-size: 11.5px; padding: 7px 16px }

/* „Pokaż więcej" / „Zwiń" — odnośnik wyglądający jak podpis, a nie jak
   przycisk zakupu. Wyrównany do lewej krawędzi siatki, czyli tam, gdzie
   kończy się czytanie rzędu kafelków i wraca wzrok. */
.kw-wiecej { margin-top: 16px }
.kw-wiecej a {
  font-family: 'Oswald', sans-serif; font-size: 12.5px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
  border: 1px solid var(--line); border-radius: 100px; padding: 9px 20px;
  display: inline-block;
}
.kw-wiecej a:hover { color: var(--brand); border-color: var(--brand) }

.kw-pusto {
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  padding: 40px 28px; text-align: center;
}
.kw-pusto h2 { font-size: 20px; text-transform: uppercase; margin-bottom: 10px }
.kw-pusto p { color: var(--muted) }
.kw-pusto a { color: var(--brand); text-decoration: underline }

@media (max-width: 900px) {
  .kw { padding: 36px 0 54px }
  .kw-in h1 { font-size: 32px }
  /* Na telefonie siatka ma dwie kolumny, więc sekcja bez rozwinięcia to
     sześć rzędów — nagłówek następnej jest dwa ekrany niżej i musi się
     obronić sam. Odstępy zostają duże mimo ciasnoty, bo to one są tu
     podziałem; „Pokaż więcej" dostaje całą szerokość, żeby trafiało
     w nie palcem bez celowania. */
  .kw-sekcja { margin-bottom: 34px }
  .kw-sekcja + .kw-sekcja { padding-top: 26px }
  .kw-sekcja-h h2 { font-size: 18px }
  .kw-podsekcja { margin-bottom: 24px }
  .kw-podsekcja-h { padding-left: 9px; margin-bottom: 11px }
  .kw-podsekcja-h h3 { font-size: 14.5px }
  .kw-wiecej a { display: block; text-align: center }
  /* Przyciski na dół, pod listy — przy dwóch kolumnach filtrów
     `margin-left: auto` zostawiałby je przyklejone do prawej krawędzi,
     daleko od pola, które się właśnie zmieniło. */
  .kw-akcje { margin-left: 0; width: 100% }
  .kw-pole, .kw-pole select { min-width: 0; width: 100% }
}

/* ——————————————————————————————————————————————————————————————————————
   SKŁAD AKAPITÓW TREŚCI — JUSTOWANIE NA WSZYSTKICH SZEROKOŚCIACH
   (zlecenie 22.10.2026, korekta zakresu z tego samego dnia)

   Przełącznik „Justowanie tekstów" (Ustawienia → Wygląd, domyślnie
   włączony) dokłada klasę `justowanie` do `<html>`. Blok stoi na końcu
   arkusza świadomie: nadpisuje `text-align` ustawiony wyżej przy
   poszczególnych sekcjach, a przy równej wadze selektora wygrywa reguła
   późniejsza.

   Pierwsza wersja (3ff2955) justowała tylko od 1024 px — próba na
   telefonie pokazała, że decyzja miała chodzić po wszystkich
   szerokościach, nie po jednej. Breakpoint zniknął, ale powód, dla
   którego tam stał, nie: w wąskiej kolumnie justowanie bez dzielenia
   wyrazów rozpycha odstępy między wyrazami w „białe rzeki" — na telefonie
   kolumna ma trzydzieści kilka znaków, więc rzeka powstaje w co drugim
   wierszu i wychodzi gorzej niż wyrównanie do lewej. Dlatego justowanie
   nie stoi samo — jedzie w jednej regule razem z dzieleniem wyrazów,
   pod `@supports (hyphens: auto) or (-webkit-hyphens: auto)`. Przeglądarka
   bez wsparcia dzielenia nie dostaje żadnej z dwóch reguł i zostaje przy
   wyrównaniu do lewej zamiast przy justowaniu bez dzielenia (byłby to ten
   sam efekt białych rzek, tylko cichy — nikt by nie zobaczył w kodzie, że
   coś nie zadziałało).

   Słownik dzielenia przeglądarka wybiera po `lang` dokumentu, a ten jest
   ustawiony w `base.html` — i we wszystkich czterech szablonach panelu,
   które mają własne `<html>`. Zmienna `justowanie` z `_site_context`
   (`templating.py`) jest dostępna globalnie, bo Jinja ma jeden wspólny
   kontekst dla całej aplikacji, ale żaden z czterech `<html>` panelu
   (`panel/baza.html`, `panel/logowanie.html`, `panel/haslo.html`,
   `panel/targi-kasa.html`) o niej nie pyta — klasa `justowanie` siada
   wyłącznie w `base.html`, więc do panelu nie trafia z innego powodu niż
   strona sklepu: po prostu nikt o nią tam nie prosi. Prefiks `-webkit-` zostaje dla Safari, które
   nieprefiksowanej postaci do dziś nie rozumie. `hyphenate-limit-chars`
   trzyma dzielenie z dala od krótkich wyrazów (dzielimy od sześciu
   znaków, po trzy z każdej strony) — bez tego przeglądarka zostawia na
   końcu wiersza dwuliterowe ogonki. `overflow-wrap: break-word` łamie
   długie adresy i tytuły, których dzielenie sylabowe i tak by nie
   dotknęło. `text-wrap: pretty` (gdzie przeglądarka go rozumie) dobiera
   łamanie wierszy tak, żeby nie zostawiać sierot — czysty bonus, bez
   niego reguła działa tak samo.

   Co jest objęte i dlaczego tylko to — bez zmian względem pierwszej
   wersji:

   - `.legal-in > p` — wąska kolumna treści. Tą samą oprawą chodzą karta
     autora, karta osoby z zespołu, wpis, wydarzenie, dokumenty prawne
     i strony „Dostawa"/„Zwroty". Selektor dziecka (`>`), a nie potomka:
     kafelki na `/wydarzenia`, `/autorzy` i `/wydalo-sie` stoją w osobnym
     `.wrap` pod kolumną, ale gdyby kiedyś weszły do środka, `>` i tak je
     minie. Zlecenie wyłącza kafelki wprost.
   - `.kb-desc p` — opis publikacji na karcie książki.
   - `.kb-author-bio` — zajawka biogramu w bloku „O autorze" na tej karcie.
     Na telefonie ten blok jest wyśrodkowany i ma własne `text-align: left`
     w progu 900 px; tam ta reguła nie wchodzi, bo jest wcześniej w
     arkuszu i o niższej wadze — kolizji nie ma, wygrywa justowanie.
   - `.about p` — „O nas": sekcja na stronie głównej i cała podstrona.

   Wyjęte spod reguły akapity — każdy z własnego powodu, nie hurtem:
   `.eyebrow` to nadtytuł (nagłówek), `.legal-meta` metryka i odsyłacze
   „← wróć", `.profil-rola` stanowisko wersalikami, `.profil-cytat` cytat
   z kreską z boku, `.wyd-minelo` komunikat w ramce, `.wyd-zapisy` akapit
   z samym przyciskiem, `.about-cta` to samo w sekcji „O nas". Na żadnym
   z nich justowanie nie dałoby dziś efektu (jeden wiersz justuje się sam,
   bo ostatniego wiersza reguła nie dotyczy) — stoją na liście, żeby nie
   zaczęło go dawać po tym, jak komuś urośnie tekst.

   Niezmienione i celowo nieobjęte: tytuły i nagłówki, kafelki, przyciski,
   formularze, stopka i cały panel (ten ma własny arkusz `panel.css`
   i własne `<html>` bez tej klasy).  */
@supports (hyphens: auto) or (-webkit-hyphens: auto) {
  .justowanie .legal-in > p:not(.eyebrow):not(.legal-meta):not(.profil-rola):not(.profil-cytat):not(.wyd-minelo):not(.wyd-zapisy),
  .justowanie .legal-in > ul li,
  .justowanie .legal-in > ol li,
  .justowanie .kb-desc p,
  .justowanie .kb-author-bio,
  .justowanie .about p:not(.about-cta) {
    text-align: justify;
    text-justify: inter-word;
    -webkit-hyphens: auto;
    hyphens: auto;
    hyphenate-limit-chars: 6 3 3;
    overflow-wrap: break-word;
    text-wrap: pretty;
  }
}
