﻿/* Strona publiczna. Układ z assets/uklad-strony.png, kolory i kontrasty z docs/design-tokens.md.
   Bez Bootstrapa i Tailwinda — własny CSS to mniejszy transfer i brak walki z cudzymi stylami. */

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Atrybut „hidden" MUSI wygrywać z regułami niżej. Styl autora bije domyślny styl
   przeglądarki „[hidden] { display: none }", więc każda klasa ustawiająca „display"
   po cichu odbiera temu atrybutowi znaczenie: pasek o ciasteczkach wisiał na dole strony
   mimo ustawionego „hidden", a przycisk zapasowy filtrów zostawał widoczny obok działającego
   automatu. Jeden wiersz z „!important" zamyka całą tę klasę pomyłek — normalize.css
   ma go z dokładnie tego samego powodu. */
[hidden] {
  display: none !important;
}

/* Strona publiczna NIE MA zaokrągleń — ramki, zdjęcia, przyciski, pola i plakietki mają
   ostre rogi (decyzja właściciela, 2026-10-01). Wyjątek to dwa koła, bo to kształt, a nie
   zaokrąglony róg: pływający przycisk telefonu i kropki slidera.

   Pola, listy, przyciski i „.btn" mają „border-radius: 0" wpisane WPROST, a nie skasowane.
   Chrome przy kontrolce z własną ramką i tak rysuje ostre rogi (sprawdzone), ale Safari
   na iPhonie zaokrągla kontrolki formularzy z własnego arkusza domyślnego (znana właściwość
   WebKitu na iOS — na serwerze nie do sprawdzenia), a jawne zero to nadpisuje. Pozostałe elementy domyślnie mają ostre rogi, więc tam deklaracji po prostu nie
   ma. Tokeny „--eko-radius-*" zostały wyłącznie dla panelu administratora, który zachował swój
   wygląd — nie wracaj do nich tutaj. Pilnują tego OstreRogiTests (arkusz) i E2E (przeglądarka). */

body {
  margin: 0;
  font-family: var(--eko-font);
  font-size: 15px;
  line-height: 1.6;
  color: var(--eko-color-text);
  background: var(--eko-gray-100);
}

img {
  max-width: 100%;
}

/* Słowo dłuższe niż cały wiersz łamie się zamiast wylewać poza ramkę. Niemieckie nazwy
   złożone („Bodenbearbeitungsmaschinen" — 26 liter) nie mieściły się na ekranie 280 px
   w kafelku „O firmie" i wystawały poza stronę, która przewijała się przez to w poziomie.
   „break-word", nie „anywhere": nie zmienia minimalnej szerokości elementów, więc kolumny
   tabel i siatek liczą się jak dotąd, a przy zwykłej szerokości nic się nie zmienia.
   Tylko kontenery strony publicznej — „public.css" jest wczytywany także w panelu. */
.page,
.site-footer {
  overflow-wrap: break-word;
}

a {
  color: var(--eko-green-700);
}

:focus-visible {
  outline: 2px solid var(--eko-green-600);
  outline-offset: 2px;
}

/* „Routes.razor" ma <FocusOnNavigate Selector="h1" />, więc po wejściu na KAŻDĄ stronę
   przeglądarka przenosi fokus na nagłówek. To funkcja dostępności — czytnik ekranu ma ogłosić
   nową stronę — ale przy gołej regule wyżej każdy odwiedzający dostawał zieloną obwódkę wokół
   nagłówka od razu po otwarciu strony i wyglądało to na usterkę.

   Element, który dostaje fokus programowo i nie jest osiągalny z klawiatury, nie ma rysować
   pierścienia. Ogłoszenia dla czytnika to nie rusza. */
[tabindex="-1"]:focus,
[tabindex="-1"]:focus-visible {
  outline: none;
}

/* Tryb wymuszonych kolorów (kontrast w Windowsie) kasuje cienie i nadpisuje kolory
   obramowań. Pola wyszukiwarki i formularzy zastępują kontur cieniem plus zmianą
   obramowania — tam zostawałyby wtedy BEZ ŻADNEGO wskazania fokusu, bo autorskie
   „outline: none" obowiązuje dalej. Kontur wraca tu bezwarunkowo. */
@media (forced-colors: active) {
  :focus-visible,
  .search input:focus-visible,
  .field input:focus-visible,
  .field textarea:focus-visible,
  .field select:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}

/* Treść dla czytnika ekranu, niewidoczna na ekranie. „display: none" tu NIE zadziała —
   usuwa element także z drzewa dostępności, czyli dokładnie stamtąd, gdzie ma zostać. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--eko-green-700);
  color: var(--eko-white);
  padding: var(--eko-space-3) var(--eko-space-4);
  z-index: 100;
}
.skip-link:focus {
  left: 0;
}

/* ── Pasek górny ──────────────────────────────────────────────────────────── */

.topbar {
  background: var(--eko-graphite-800);
  color: var(--eko-on-dark);
  font-size: 12.5px;
}
.topbar-inner {
  max-width: var(--eko-container);
  margin: 0 auto;
  padding: 0 var(--eko-gutter);
  min-height: 30px;
  display: flex;
  flex-wrap: wrap;
  gap: var(--eko-space-2) var(--eko-space-4);
  align-items: center;
  justify-content: space-between;
}
.topbar a {
  color: var(--eko-white);
  text-decoration: none;
}
.topbar a:hover {
  text-decoration: underline;
}

/* Telefon i języki trzymają się razem po prawej — bez tej grupy „space-between" rozrzuca
   trzy elementy po całej szerokości i numer ląduje samotnie na środku paska. */
.topbar-actions {
  display: flex;
  align-items: center;
  gap: var(--eko-space-4);
  /* Zawijanie, nie zwężanie. Numer ma „nowrap", a cztery przełączniki języka mają wymagane
     24 px — więc grupa ma swoją najmniejszą szerokość i przy 280 px rozpychała CAŁY dokument
     o 26 px. Poziomy pasek przewijania na każdej stronie jest widoczny dopiero po przewinięciu
     w bok, więc na zrzucie pierwszego ekranu tego nie widać. */
  flex-wrap: wrap;
  justify-content: center;
}
.topbar-phone {
  display: inline-flex;
  align-items: center;
  /* 24 px to minimum z WCAG 2.5.8 (AA w 2.2) i obowiązuje NA KAŻDYM wskaźniku, nie tylko
     dotykowym. Pomiar pokazał 20 px — a to główna ścieżka kontaktu. „nowrap" dokładam,
     bo przy 280 px numer łamał się na trzy wiersze i cel robił się słupkiem 23×80. */
  min-height: 24px;
  font-weight: 600;
  white-space: nowrap;
}

/* Na wąskim ekranie godziny ustępują miejsca temu, po co odwiedzający tu sięga. */
@media (max-width: 575.98px) {
  .topbar-hours {
    display: none;
  }
  .topbar-inner {
    justify-content: center;
  }
}

.langs-switch {
  display: flex;
  gap: var(--eko-space-1);
  list-style: none;
  margin: 0;
  padding: 0;
}
.langs-switch a {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 24px;
  padding: 2px 6px;
  font-weight: 600;
  text-transform: uppercase;
  font-size: 12px;
}
.langs-switch a[aria-current="true"] {
  background: var(--eko-green-700);
}
/* Jasna obwódka na włos. Przełącznik stoi WYŁĄCZNIE na ciemnym — w pasku górnym
   i w stopce — a bez niej czarny pas flagi niemieckiej (#000) zlewa się z grafitem tła
   (#262626) i flaga wygląda na dwupasową, czyli dokładnie tak, jak wyglądała, zanim
   została poprawiona. Biały pas polskiej flagi też zyskuje krawędź.
   Gdyby przełącznik trafił kiedyś na jasne tło, ta reguła wymaga zmiany. */
.langs-switch svg {
  display: block;
  box-shadow: 0 0 0 1px rgb(255 255 255 / 45%);
}

/* ── Header ───────────────────────────────────────────────────────────────── */

.masthead {
  background: var(--eko-color-surface);
  border-bottom: 1px solid var(--eko-color-border);
}

/* Nazwane obszary, nie flex. Dzięki temu na telefonie wyszukiwarka schodzi do drugiego wiersza
   BEZ drugiego formularza: pole „q" musi być na stronie dokładnie jedno — tak je znajduje
   search-suggest.js (po „aria-controls" do jednej listy o stałym identyfikatorze) i tak go
   szukają testy E2E, które przy dwóch trafieniach kończą się błędem trybu ścisłego. */
.masthead-inner {
  max-width: var(--eko-container);
  margin: 0 auto;
  padding: var(--eko-space-3) var(--eko-gutter);
  min-height: 80px;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas: "brand search help";
  align-items: center;
  column-gap: var(--eko-space-5);
}

.brand {
  grid-area: brand;
  display: flex;
  align-items: center;
  text-decoration: none;
}
/* Wysokość rządzi, szerokość idzie za proporcją 3,375:1. Atrybuty „width" i „height"
   w znaczniku zostają mimo to — przeglądarka rezerwuje z nich miejsce, zanim plik się
   pobierze, i nagłówek nie podskakuje przy wczytywaniu (CLS).

   Plik ma 160 px wysokości, czyli zapas na ekrany o gęstości 3x przy 52 px. Na telefonie
   zostaje 40 px, bo tam znak dzieli wiersz z boksem kontaktu. */
.brand img {
  height: 40px;
  width: auto;
}
@media (min-width: 768px) {
  .brand img {
    height: 52px;
  }
}

.search {
  grid-area: search;
  display: flex;
  width: 100%;
  max-width: 640px;
  justify-self: center;
}
.search input {
  flex: 1 1 auto;
  min-width: 0;
  height: 46px;
  padding: 0 var(--eko-space-4);
  font: inherit;
  color: var(--eko-color-text);
  background: var(--eko-color-surface);
  border: 1px solid var(--eko-color-border-strong);
  border-right: none;
  border-radius: 0;
}
.search input::placeholder {
  color: var(--eko-color-text-muted);
}
/* Obwódka zamiast domyślnego konturu: kontur obrysowałby pole razem z brakującą prawą
   krawędzią i rozjechał się z przyciskiem. Wskazanie fokusu zostaje wyraźne. */
.search input:focus-visible {
  outline: none;
  position: relative;
  z-index: 1;
  border-color: var(--eko-green-600);
  box-shadow: var(--eko-focus-ring);
}
.search button {
  height: 46px;
  padding: 0 var(--eko-space-5);
  font: inherit;
  font-weight: 700;
  font-size: 14px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--eko-white);
  background: var(--eko-color-primary);
  border: none;
  border-radius: 0;
  cursor: pointer;
  transition: background var(--eko-transition);
}
.search button:hover {
  background: var(--eko-color-primary-hover);
}

.help-box {
  grid-area: help;
  display: flex;
  align-items: center;
  gap: var(--eko-space-3);
  padding: var(--eko-space-2) var(--eko-space-4);
  border: 1px solid var(--eko-color-border-strong);
  text-decoration: none;
  color: var(--eko-color-text);
  transition: border-color var(--eko-transition), background var(--eko-transition);
}
.help-box:hover {
  border-color: var(--eko-green-600);
}
.help-box svg {
  color: var(--eko-color-primary);
  flex: none;
}
.help-box-text {
  display: grid;
  line-height: 1.25;
}
.help-box-text a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  text-decoration: none;
  white-space: nowrap;
}
.help-box-phone {
  font-size: 15px;
  font-weight: 700;
  color: var(--eko-color-text);
}
.help-box-link {
  font-size: 12px;
  color: var(--eko-color-text-muted);
}
.help-box-phone:hover,
.help-box-link:hover {
  color: var(--eko-green-800);
  text-decoration: underline;
}

/* Na tablecie każde 40 px zabrane headerowi to szersza wyszukiwarka — podpis
   „Pomoc i kontakt" ustępuje, numer zostaje. Próg to 1200 px z systemu breakpointów
   (docs/design-tokens.md §4), a nie wymyślone 1100 — siódmy próg to siódme miejsce,
   w którym trzeba pamiętać o zmianie. */
@media (max-width: 1199.98px) {
  .help-box-link {
    display: none;
  }
}

/* Telefon: logo i kontakt w pierwszym wierszu, wyszukiwarka na całą szerokość w drugim.
   Ten sam jeden formularz — przenosi go siatka, nie druga kopia w znaczniku. */
@media (max-width: 767.98px) {
  .masthead-inner {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "brand help"
      "search search";
    min-height: 0;
    row-gap: var(--eko-space-3);
  }
  .search {
    max-width: none;
  }
  .help-box {
    padding: var(--eko-space-2) var(--eko-space-3);
    gap: var(--eko-space-2);
  }
  .help-box-phone {
    font-size: 14px;
  }
}

/* ── Nawigacja ────────────────────────────────────────────────────────────── */

.mainnav {
  background: var(--eko-color-primary);
}
.mainnav ul {
  max-width: var(--eko-container);
  margin: 0 auto;
  padding: 0 var(--eko-gutter);
  display: flex;
  /* „safe center" cofa się do wyrównania do lewej, gdy pozycje się nie mieszczą. Samo „center"
     przy przewijaniu w poziomie ucina tyle samo z obu stron, a do lewego końca nie da się
     dojechać — na telefonie „STRONA GŁÓWNA" była nie do odczytania i nie do kliknięcia. */
  justify-content: center;
  justify-content: safe center;
  gap: 0;
  list-style: none;
  overflow-x: auto;
  scrollbar-width: none;
}
.mainnav ul::-webkit-scrollbar {
  display: none;
}
.mainnav a {
  display: flex;
  align-items: center;
  min-height: 48px;
  padding: 0 var(--eko-space-4);
  color: var(--eko-white);
  text-decoration: none;
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
  border-bottom: 3px solid transparent;
  transition: background var(--eko-transition);
}
.mainnav a:hover {
  background: rgb(255 255 255 / 10%);
}
.mainnav a[aria-current="page"] {
  background: rgb(255 255 255 / 8%);
  border-bottom-color: var(--eko-white);
}

/* ── Szkielet strony ──────────────────────────────────────────────────────── */

.page {
  max-width: var(--eko-container);
  margin: 0 auto;
  padding: var(--eko-space-5) var(--eko-gutter) var(--eko-space-8);
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--eko-space-5);
}

@media (min-width: 992px) {
  .page {
    /* „minmax(0, 1fr)", nie „1fr". Ścieżka gridu o rozmiarze „1fr" ma domyślnie
       „min-width: auto", czyli nie schodzi poniżej najszerszej niełamliwej treści w środku —
       a w katalogu jest nią nazwa maszyny albo szeroka tabela specyfikacji. Kolumna rozpycha
       wtedy siatkę i cała strona dostaje poziomy pasek przewijania, mimo że nic w CSS nie
       wskazuje winnego. */
    grid-template-columns: var(--eko-sidebar) minmax(0, 1fr);
    align-items: start;
  }
}

/* ── Sidebar kategorii ────────────────────────────────────────────────────── */

/* Serwer NIE renderuje atrybutu „open" — nie zna szerokości ekranu, a zgadywanie kosztuje
   albo rozwiniętą listę na całą wysokość telefonu, albo mignięcie przy zwijaniu jej skryptem.
   Stan domyślny ustawiają więc dwie rzeczy naraz:
     • poniżej 992 px zwinięty panel to zachowanie natywne <details> — działa bez niczego,
     • od 992 px treść pokazuje „::details-content" niżej, a gdy przeglądarka go nie zna,
       otwiera panel category-drawer.js.
   Bez JS i na starszej przeglądarce desktop dostaje menu zwinięte do jednego paska —
   gorzej, ale nadal w pełni sprawnie, jedno kliknięcie od listy. */
.sidebar {
  background: var(--eko-color-surface);
  border: 1px solid var(--eko-color-border);
  /* Obcina to, co wystaje poza ramkę (bardzo długa nazwa kategorii). „clip", nie „hidden":
     nie robi z panelu kontenera przewijania — menu nie ma własnego paska, patrz
     „Sidebar: wysokość" niżej. */
  overflow: clip;
}
.sidebar > summary {
  display: flex;
  align-items: center;
  gap: var(--eko-space-2);
  min-height: 50px;
  padding: 0 var(--eko-space-4);
  margin: 0;
  background: var(--eko-color-primary);
  color: var(--eko-white);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  list-style: none;
}
.sidebar > summary::-webkit-details-marker {
  display: none;
}

/* Strzałka stanu panelu — tylko tam, gdzie panel faktycznie się zwija. */
.sidebar > summary::after {
  content: "\203A";
  margin-left: auto;
  font-size: 22px;
  line-height: 1;
  transform: rotate(90deg);
  transition: transform var(--eko-transition);
}
.sidebar[open] > summary::after {
  transform: rotate(-90deg);
}

.sidebar-menu-icon {
  flex: none;
}

/* Słowo bez spacji — niemiecka nazwa złożona albo nazwa kategorii wpisana w panelu — ma się
   dać złamać. Inaczej na najwęższym ekranie (280 px, okładka telefonu składanego) wystaje
   z wiersza i rozpycha CAŁĄ stronę w poziomie: nagłówek „MASCHINENKATEGORIEN" ma 181 px
   w Roboto, 188 w Segoe UI, 193 w Arialu i 211 w DejaVu Sans, a miejsca jest ok. 188 —
   na Androidzie wystarczy powiększony tekst. „anywhere", nie „break-word": tylko „anywhere"
   zmniejsza też minimalną szerokość elementu, a to ona rozpychała kolumnę siatki. Przy
   zwykłej szerokości nic się nie zmienia — łamane jest wyłącznie słowo dłuższe niż cały
   wiersz. Ładne miejsce podziału dla nagłówka daje miękki dywiz w tłumaczeniu niemieckim. */
.sidebar > summary,
.tree-nav a {
  overflow-wrap: anywhere;
}

.tree-nav,
.tree-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.tree-nav a {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: var(--eko-space-2) var(--eko-space-4);
  color: var(--eko-color-text);
  text-decoration: none;
  font-size: 15px;
  transition: background var(--eko-transition), color var(--eko-transition);
}
.tree-nav > li:not(:last-child) > a {
  border-bottom: 1px solid var(--eko-color-border);
}
.tree-nav a:hover {
  background: var(--eko-green-50);
  color: var(--eko-green-800);
}

/* Strzałka przy kategorii, która ma widoczne podkategorie. Obraca się, gdy gałąź jest
   rozwinięta. Rozwinięcie wynika z adresu — serwer renderuje podkategorie tylko na ścieżce
   oglądanej strony — a kliknięcie w rozwiniętą gałąź zwija ją w miejscu (category-drawer.js). */
.tree-nav .has-children > a::after {
  content: "\203A";
  margin-left: auto;
  padding-left: var(--eko-space-2);
  color: var(--eko-color-text-muted);
  font-size: 18px;
  line-height: 1;
  transition: transform var(--eko-transition);
}
.tree-nav .is-open > a::after {
  transform: rotate(90deg);
}

/* Gałąź zwinięta kliknięciem. „is-open" zostaje, bo nadal znaczy „tu jesteś" i trzyma
   wyróżnienie działu — znika tylko lista, a strzałka wraca do pozycji zwiniętej. Obie klasy
   w selektorze, żeby przebić obrót wyżej niezależnie od kolejności reguł. */
.tree-nav .is-open.is-collapsed > a::after {
  transform: none;
}
.tree-nav .is-collapsed > ul {
  display: none;
}

/* Zieleń wypełnia WYŁĄCZNIE oglądaną pozycję. Wcześniej tło „green-600" dostawała cała
   rozwinięta gałąź razem z każdą podkategorią — menu robiło się ścianą zieleni na pół
   ekranu i nie dało się na pierwszy rzut oka powiedzieć, gdzie się właściwie jest. */
/* Gałąź rozwinięta — ale NIE ta oglądana. Bez „:not(...)" ta reguła stoi niżej przy równej
   specyficzności (0,2,1) i nadpisywała biały tekst pozycji aktywnej zielenią na zielonym
   tle: kontrast 1.38:1 na jedynym wierszu, który menu ma wyróżniać. */
.tree-nav .is-open > a:not([aria-current="page"]) {
  color: var(--eko-green-800);
  font-weight: 600;
}

.tree-nav a[aria-current="page"] {
  background: var(--eko-color-primary);
  color: var(--eko-white);
  font-weight: 600;
}
.tree-nav a[aria-current="page"]::after {
  color: inherit;
}
/* Kursor zatrzymuje się po kliknięciu dokładnie na pozycji aktywnej, więc jej stan
   najechania musi zostać czytelny — samo tło z reguły „:hover" dawało biały tekst
   na jasnej zieleni, 1.06:1. */
.tree-nav a[aria-current="page"]:hover {
  background: var(--eko-green-800);
  color: var(--eko-white);
}

/* Podkategorie: białe tło, wcięcie i zielona kreska prowadząca — hierarchię widać
   z odstępu, nie z koloru. */
.tree-nav ul {
  background: var(--eko-color-surface);
  border-bottom: 1px solid var(--eko-color-border);
}
.tree-nav ul a {
  min-height: 40px;
  padding-left: var(--eko-space-6);
  font-size: 14px;
  color: var(--eko-graphite-600);
  border-bottom: none;
  box-shadow: inset 2px 0 0 var(--eko-green-50);
}
.tree-nav ul a:not([aria-current="page"]):hover {
  background: var(--eko-green-50);
  color: var(--eko-green-800);
  box-shadow: inset 2px 0 0 var(--eko-green-500);
}
.tree-nav ul a[aria-current="page"] {
  color: var(--eko-white);
  box-shadow: inset 2px 0 0 var(--eko-green-800);
}

/* ── Sidebar: desktop ─────────────────────────────────────────────────────── */

@media (min-width: 992px) {
  /* Pokazuje treść zwiniętego <details>. Przeglądarki bez „::details-content" tę regułę
     pominą — dla nich panel otwiera category-drawer.js.

     „:not([open])" jest tu wymogiem: bez niego reguła obowiązuje także po ROZWINIĘCIU,
     a wtedy naciśnięcie spacji na nagłówku zdejmuje atrybut „open", treść zostaje widoczna
     i czytnik ekranu ogłasza panel jako zwinięty — stan ARIA rozjeżdża się z tym, co widać. */
  .sidebar:not([open])::details-content {
    content-visibility: visible;
    block-size: auto;
  }

  /* Nagłówek przestaje być przyciskiem TYLKO wtedy, gdy panel faktycznie jest rozwinięty.
     Bezwarunkowe „pointer-events: none" odcinało myszy dostęp do drzewa kategorii
     w przeglądarkach bez „::details-content" i przy zablokowanym skrypcie: pasek
     „KATEGORIE" nie reagował na kliknięcie, a menu główne ani strona główna nie mają
     ani jednego odnośnika do kategorii — katalog zostawał osiągalny tylko wyszukiwarką. */
  .sidebar[open] > summary {
    cursor: default;
    pointer-events: none;
  }
  .sidebar[open] > summary::after,
  .sidebar[open] .sidebar-menu-icon {
    display: none;
  }
}

/* ── Sidebar: wysokość ────────────────────────────────────────────────────── */

/* Menu rośnie o tyle, o ile rozwinęła się gałąź, i przewija się razem ze stroną: bez limitu
   wysokości, bez własnego paska i BEZ przyklejenia (decyzja właściciela, 2026-09-30).
   Wcześniej od 1200 px było „position: sticky" z listą przyciętą do wysokości okna, a poniżej
   992 px lista do połowy okna — obie z paskiem przewijania w środku. Przy 14 działach pasek
   wychodził na laptopie po rozwinięciu każdego działu, na 1920×1080 przy czterech.

   Nie wracaj do przyklejenia bez limitu: przyklejony panel wyższy niż okno ma dół poza
   ekranem aż do końca strony. Długie menu skraca się kliknięciem w rozwiniętą kategorię. */

/* ── Treść ────────────────────────────────────────────────────────────────── */

/* Kolumna treści jest pojemnikiem zapytań o rozmiar. Powód: jej szerokość NIE rośnie
   monotonicznie z oknem — przy 992 px wraca sidebar i treść nagle się zwęża z 943 px
   do ~660. Układy wewnątrz (galeria produktu, kontakt) pytane o szerokość OKNA łamały się
   więc w złych miejscach: przy 992 px dostawały mniej miejsca niż przy 768 px, a wyglądało
   to jak przypadek.

   „container-type: inline-size" włącza też „contain: layout", przez co element staje się
   blokiem zawierającym dla potomków „position: fixed". Pływający telefon, pasek ciasteczek
   i pasek awarii są dlatego jego RODZEŃSTWEM. Jedyny wyjątek to okno powiększenia zdjęć
   (<dialog> galerii, od 2026-10-12): stoi wewnątrz, ale otwierane przez „showModal()"
   trafia do górnej warstwy, układanej względem okna przeglądarki — pilnuje tego test E2E
   mierzący okno na całym ekranie. Inne okno wstawione tutaj sprawdź tak samo. */
.content {
  min-width: 0;
  container-type: inline-size;
}

.breadcrumbs {
  font-size: 13px;
  color: var(--eko-gray-500);
  margin-bottom: var(--eko-space-3);
}
.breadcrumbs ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--eko-space-1);
}
.breadcrumbs a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}
.breadcrumbs li::after {
  content: "›";
  margin-left: var(--eko-space-1);
  color: var(--eko-color-border);
}
.breadcrumbs li:last-child::after {
  content: none;
}

h1 {
  font-size: 28px;
  line-height: 1.2;
  margin: 0 0 var(--eko-space-4);
}
@media (max-width: 575.98px) {
  h1 {
    font-size: 22px;
  }
}

/* ── Cele dotykowe na telefonie ───────────────────────────────────────────────
   Palec nie celuje w pigułkę wysoką na 22 px. Podnosimy to, co na telefonie naprawdę
   się klika; pasek nawigacji (48 px), drzewo kategorii (44 px) i pola formularzy (44 px)
   mają swój rozmiar z reguł wyżej.

   Przełącznik języka zostaje mniejszy — 32 px zamiast 44. Cztery cele po 44 px podwoiłyby
   wysokość paska górnego, a norma AA (WCAG 2.5.8) wymaga 24 px; 44 px to poziom AAA
   i należy się temu, co jest główną ścieżką, a nie zmianie języka raz na wizytę. */
@media (max-width: 767.98px) {
  .tag-nav a,
  .card-tags a {
    display: inline-flex;
    align-items: center;
    min-height: 40px;
    padding-inline: var(--eko-space-4);
  }

  .langs-switch a {
    min-height: 32px;
    padding-inline: var(--eko-space-2);
  }

  .breadcrumbs a {
    display: inline-block;
    padding-block: var(--eko-space-1);
  }

  .gallery-thumbs a {
    width: 72px;
  }
}

/* ── Nagłówek strony głównej ──────────────────────────────────────────────────
   Osobny blok, bo to on rozstrzyga, czy maszyny mieszczą się na pierwszym ekranie.
   Przed zbiciem odstępów tytuł z akapitem i nagłówkiem sekcji zjadały 226 px między
   sliderem a pierwszym rzędem kart — przy 1920×1080 pierwszy rząd zaczynał się dopiero
   na 865 px. Budżet pierwszego ekranu liczy docs/redesign-ui.md §5. */
.home-intro {
  margin: var(--eko-space-5) 0 0;
}

.home-intro h1 {
  margin: 0;
}

/* Zdanie o firmie mieści się w jednym wierszu — „.prose" przycinałby je do 68 znaków
   i robił z niego dwa, kosztem 24 px pierwszego ekranu. */
.home-intro p {
  max-width: 92ch;
  margin: var(--eko-space-2) 0 0;
  color: var(--eko-graphite-600);
}

/* Pierwszy nagłówek sekcji stoi bliżej — nad nim jest już wstęp, więc pełne 48 px
   odstępu tylko dokłada pustki. */
.home-intro + .section-title {
  margin-top: var(--eko-space-5);
}

/* Nagłówek drugiego stopnia bez własnej klasy — na stronie Kontakt i we wpisach bloga.
   Wcześniej ubierał go „admin.css" wczytywany obok; po odcięciu arkusza panelu od strony
   publicznej zostałby domyślny 1.5em z marginesami przeglądarki. */
/* Cała rodzina, nie sam h2. Sanitizer dopuszcza we wpisach blogowych „h2", „h3" i „h4"
   (GanssHtmlSanitizer), a po odcięciu arkusza panelu „h3" spadał do domyślnych 1.17em —
   czyli ~17.5 px przy tekście 15 px, ledwie 2 px nad akapitem — a „h4" do 1em, czyli
   dokładnie tyle co tekst. Hierarchia nagłówków w dłuższym wpisie przestawała istnieć. */
h2 {
  font-size: 22px;
  margin: var(--eko-space-6) 0 var(--eko-space-3);
}
h3 {
  font-size: 18px;
  margin: var(--eko-space-5) 0 var(--eko-space-2);
}
h4 {
  font-size: 16px;
  margin: var(--eko-space-4) 0 var(--eko-space-2);
}

/* Nagłówek sekcji: grafit z zieloną kreską, nie zielony tekst.
   „green-500" na białym daje 4.11:1 — zgodnie z docs/design-tokens.md wolno go pod tekst
   wyłącznie duży i pogrubiony, a to jest 20 px. Mieściło się w normie o włos i przy tym
   wyglądało blado obok czarnego H1. Kolor niesie teraz kreska, która nie musi spełniać
   progu dla tekstu, a sam napis ma pełny kontrast grafitu (14.96:1). */
.section-title {
  display: flex;
  align-items: center;
  gap: var(--eko-space-3);
  font-size: 21px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--eko-color-text);
  margin: var(--eko-space-7) 0 var(--eko-space-4);
}

.section-title::before {
  content: "";
  flex: none;
  width: 4px;
  align-self: stretch;
  min-height: 1em;
  background: var(--eko-color-primary);
}

.panel {
  background: var(--eko-white);
  border: 1px solid var(--eko-color-border);
  padding: var(--eko-space-5);
}

.prose {
  max-width: 68ch;
}
.prose p {
  white-space: pre-wrap;
}

/* ── Siatka kart ──────────────────────────────────────────────────────────── */

/* Liczba kolumn wynika z szerokości KOLUMNY TREŚCI, a ta nie rośnie razem z oknem:
   przy 992 px wraca sidebar i treść nagle się zwęża. Dlatego próg minimalnej karty
   zmienia się w breakpointach, zamiast być jedną wartością na całą stronę.

   „auto-fill", nie „auto-fit" — i to jest tu istotne. Przy trzech polecanych maszynach
   „auto-fit" zwinąłby puste ścieżki i rozciągnął trzy karty na całą szerokość, każda po
   340 px. Sklep tak nie wygląda: karta ma zostać kartą, a wolne miejsce ma zostać wolne. */
.cards {
  --eko-card-min: 150px;
  display: grid;
  /* „min(…, 100%)" jest tu zabezpieczeniem, nie ozdobą: samo „minmax(280px, 1fr)" wymusza
     ścieżkę szeroką na 280 px także wtedy, gdy kolumna treści ma mniej — a przy 280 px
     (ekran zewnętrzny składaka) ma 248 px. Karty wpisów wystawały wtedy poza okno
     i strona przewijała się w poziomie. */
  grid-template-columns: repeat(auto-fill, minmax(min(var(--eko-card-min), 100%), 1fr));
  gap: var(--eko-space-4);
}

@media (min-width: 576px) {
  .cards {
    --eko-card-min: 185px;
  }
}

@media (min-width: 768px) {
  .cards {
    --eko-card-min: 205px;
  }
}

/* Sidebar wraca i zabiera treści ~240 px — próg musi zejść, inaczej z czterech kart
   robią się trzy. */
@media (min-width: 992px) {
  .cards {
    --eko-card-min: 190px;
  }
}

@media (min-width: 1200px) {
  .cards {
    --eko-card-min: 200px;
  }
}

@media (min-width: 1400px) {
  .cards {
    --eko-card-min: 192px;
  }
}

/* MUSI stać za wszystkimi „@media" wyżej. Zapytanie medialne nie podnosi specyficzności,
   więc o zwycięzcy przy równej decyduje kolejność w pliku — postawione wcześniej,
   zostałoby nadpisane progiem 192 px i trzy wpisy blogowe wyszłyby jak karty maszyn. */
.cards--posts {
  --eko-card-min: 280px;
}

.card-product {
  display: flex;
  flex-direction: column;
  background: var(--eko-color-surface);
  border: 1px solid var(--eko-color-border);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: box-shadow var(--eko-transition), transform var(--eko-transition),
    border-color var(--eko-transition);
}
.card-product:hover {
  box-shadow: var(--eko-shadow-md);
  border-color: var(--eko-green-500);
  transform: translateY(-2px);
}

.card-figure {
  position: relative;
  aspect-ratio: 4 / 3;
  /* Delikatnie szare tło zamiast białego: zdjęcia maszyn są wycinane na biało, więc
     na białej karcie „pływały" bez granic. */
  background: var(--eko-color-bg);
  overflow: hidden;
}
.card-figure img,
.card-figure svg {
  width: 100%;
  height: 100%;
  /* Maszyny bywają fotografowane w bardzo różnych kadrach — przycinanie ucinałoby ładowacz. */
  object-fit: contain;
  transition: transform var(--eko-transition);
}
.card-product:hover .card-figure img {
  transform: scale(1.03);
}

.card-badge {
  position: absolute;
  top: var(--eko-space-2);
  left: var(--eko-space-2);
  z-index: 1;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 3px var(--eko-space-2);
  color: var(--eko-white);
}
/* Plakietka poza zdjęciem — na stronie produktu stoi w zwykłym przepływie nad nazwą. */
.card-badge--inline {
  position: static;
  display: inline-block;
  margin-bottom: var(--eko-space-2);
}

.card-badge.is-new {
  background: var(--eko-color-primary);
}
.card-badge.is-used {
  background: var(--eko-graphite-600);
}

.card-body {
  padding: var(--eko-space-3) var(--eko-space-3) var(--eko-space-4);
  display: flex;
  flex-direction: column;
  gap: var(--eko-space-1);
  flex: 1 1 auto;
  border-top: 1px solid var(--eko-color-border);
}
.card-name {
  font-size: 15px;
  font-weight: 600;
  line-height: 1.35;
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Miejsce na dwa wiersze rezerwujemy zawsze — inaczej ceny w rzędzie stoją na różnych
     wysokościach, zależnie od tego, czy nazwa maszyny się zawinęła. */
  min-height: 2.7em;
}
.card-usage {
  font-size: 12.5px;
  color: var(--eko-color-text-muted);
}
.card-price {
  margin-top: auto;
  padding-top: var(--eko-space-1);
  font-weight: 700;
  font-size: 17px;
  /* Cyfry o równej szerokości — ceny w kolumnie przestają „tańczyć". */
  font-variant-numeric: tabular-nums;
}
.card-price .on-request {
  font-weight: 500;
  font-size: 13.5px;
  color: var(--eko-color-text-muted);
}
/* Dopisek „netto / HT" — informacja, nie część kwoty: mniejszy i przygaszony. */
.card-price .price-net {
  font-weight: 500;
  font-size: 12.5px;
  color: var(--eko-color-text-muted);
}

/* ── Filtry i paginacja ───────────────────────────────────────────────────── */

.toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--eko-space-4);
  align-items: end;
  margin-bottom: var(--eko-space-4);
}
.toolbar label {
  font-size: 13px;
  font-weight: 600;
  display: block;
}
.toolbar select {
  display: block;
  margin-top: 2px;
  padding: var(--eko-space-2) var(--eko-space-3);
  font: inherit;
  border: 1px solid var(--eko-color-border);
  border-radius: 0;
  background: var(--eko-white);
}
.toolbar .count {
  margin-left: auto;
  font-size: 13px;
  color: var(--eko-gray-500);
}

.pagination {
  display: flex;
  gap: var(--eko-space-2);
  align-items: center;
  justify-content: center;
  margin-top: var(--eko-space-6);
  font-size: 14px;
}
.pagination a,
.pagination span {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--eko-space-4);
  border: 1px solid var(--eko-color-border);
  text-decoration: none;
  background: var(--eko-color-surface);
}
.pagination a:hover {
  border-color: var(--eko-green-600);
  background: var(--eko-green-50);
}
.pagination .current {
  border-color: var(--eko-green-700);
  color: var(--eko-green-700);
  font-weight: 600;
}

/* ── Strona produktu ──────────────────────────────────────────────────────── */

/* „minmax(0, 1fr)", nie domyślne „auto" — ta sama pułapka, co w „.page". Ścieżka „auto"
   nie schodzi poniżej najdłuższego niełamliwego słowa, więc niemiecki tytuł maszyny
   („…Separator zur Getreidevorreinigung") rozpychał przy 280 px całą kartę maszyny poza
   ekran — i nic w CSS-ie nie wskazywało winnego. */
.product {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--eko-space-6);
}

/* Opis, specyfikacja, pliki i film pod galerią. Odstęp od góry zamiast przerwy w siatce
   „.product-top": maszyna bez żadnej z tych sekcji nie renderuje tego kontenera wcale,
   a pusty wiersz siatki zostawiałby po sobie samą przerwę. */
.product-details {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--eko-space-6);
  margin-top: var(--eko-space-6);
}

/* Pytamy KOLUMNĘ, nie okno: przy oknie 992 px treść ma ~660 px, czyli mniej niż przy 768 px
   bez sidebara — galeria dostawała wtedy 390 px i panel z ceną 250 px, choć na węższym
   ekranie wyglądała lepiej.

   Panel z ceną zajmuje całą prawą kolumnę — obok galerii I szczegółów — bo tylko w takim
   obszarze ma gdzie jechać (niżej). „align-items: start" jest tu wymogiem: rozciągnięty
   na cały obszar panel nie miałby już o ile się przesunąć. */
@container (min-width: 700px) {
  .product-top {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    grid-template-areas:
      "gallery summary"
      "details summary";
    column-gap: var(--eko-space-6);
    align-items: start;
  }
  .product-gallery {
    grid-area: gallery;
  }
  .product-summary {
    grid-area: summary;
  }
  .product-details {
    grid-area: details;
  }
}

/* Panel z nazwą, ceną i przyciskiem „Zapytaj" jedzie przy przewijaniu obok opisu
   i specyfikacji, a staje przed „Podobnymi maszynami", które leżą poza siatką (decyzja
   właściciela, 2026-09-30).

   Tylko w układzie dwukolumnowym — na telefonie przyklejony blok zasłoniłby pół ekranu —
   i nie przy niskim oknie. Telefon trzymany poziomo (844×390) ma już dwie kolumny, a panel
   (302–393 px, zmierzone na 96 maszynach) nie zmieściłby się tam w całości: przycisk
   zostawałby pod krawędzią ekranu aż do końca specyfikacji. Próg ten sam co przy banerze,
   tylko odwrócony — nadal jedno zapytanie o wysokość. Przy krótkim opisie dłuższym niż
   dziś (limit bazy to 500 znaków) panel urośnie; wtedy sprawdź go na niskim laptopie. */
@media not all and (max-height: 480px) {
  @container (min-width: 700px) {
    .product-summary {
      position: sticky;
      top: var(--eko-space-4);
    }
  }
}

/* Zdjęcie mieści się w polu W CAŁOŚCI. Do 2026-10-12 tak nie było: zdjęcie pionowe
   (406×900) wychodziło poza pole 600×450 i widać było sam jego środek — nikt tego nie
   zauważył, bo na dużym polu stało zawsze pierwsze zdjęcie maszyny, prawie zawsze poziome,
   a strzałki pokazują już każde. Dwie przyczyny naraz, obie zmierzone w przeglądarce:
   – niejawny wiersz siatki ma rozmiar „auto" i rósł do wysokości zdjęcia (pole trzyma
     proporcję 4:3, ale wiersz w nim nie) — stąd ścieżki „minmax(0, 1fr)",
   – <picture> owijał się wokół obrazka, więc „height: 100%" nie miało od czego się liczyć —
     stąd odnośnik i <picture> na całe pole. Całe pole jest przy okazji celem kliknięcia. */
.gallery-main {
  position: relative;
  background: var(--eko-white);
  border: 1px solid var(--eko-color-border);
  aspect-ratio: 4 / 3;
  display: grid;
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  place-items: center;
  overflow: hidden;
}
.gallery-main-link,
.gallery-main picture {
  display: block;
  width: 100%;
  height: 100%;
}
.gallery-main-link {
  cursor: zoom-in;
}
/* „block": obrazek w wierszu tekstu zostawiał pod sobą kilka pikseli na linię bazową. */
.gallery-main img,
.gallery-main svg {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Strzałki widać zawsze, nie dopiero po najechaniu myszą — na telefonie najechania nie ma.
   Półprzezroczysty grafit, bo zdjęcia maszyn bywają i jasne, i ciemne: biała strzałka ma na nim
   kontrast 4,3:1 nawet nad białym tłem zdjęcia (przy 55% było 3,7:1). Przyciski są kwadratowe
   jak reszta strony (decyzja właściciela z 2026-10-01). */
.gallery-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: rgb(28 28 28 / 60%);
  color: var(--eko-white);
  cursor: pointer;
  transition: background var(--eko-transition);
}
.gallery-arrow--prev {
  left: var(--eko-space-2);
}
.gallery-arrow--next {
  right: var(--eko-space-2);
}
.gallery-arrow:hover {
  background: var(--eko-color-primary);
}
/* Pierścień rysowany wewnątrz przycisku: na zewnątrz ucinałby go „overflow: hidden" pola. */
.gallery-arrow:focus-visible {
  outline: 2px solid var(--eko-white);
  outline-offset: -4px;
}
/* Licznik nie przechwytuje kliknięć — pod nim nadal jest zdjęcie, które otwiera powiększenie. */
.gallery-counter {
  position: absolute;
  right: var(--eko-space-2);
  bottom: var(--eko-space-2);
  margin: 0;
  padding: 0 var(--eko-space-2);
  background: rgb(28 28 28 / 70%);
  color: var(--eko-white);
  font-size: 13px;
  line-height: 24px;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}
.gallery-thumbs {
  display: flex;
  gap: var(--eko-space-2);
  flex-wrap: wrap;
  margin-top: var(--eko-space-2);
}
.gallery-thumbs a {
  display: block;
  width: 84px;
  aspect-ratio: 4 / 3;
  border: 2px solid var(--eko-color-border);
  overflow: hidden;
  background: var(--eko-white);
}
.gallery-thumbs a:hover {
  border-color: var(--eko-green-500);
}
/* Miniatura zdjęcia, które stoi na dużym polu. Pod regułą „:hover" (ta sama
   specyficzność), żeby najechanie myszą jej nie rozjaśniało. */
.gallery-thumbs a[aria-current="true"] {
  border-color: var(--eko-color-primary);
}
.gallery-thumbs img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* ── Powiększenie zdjęć (strona maszyny i wpis na blogu) ──────────────────────
   Okno <dialog> z komponentu GalleryLightbox, otwierane przez „js/gallery.js"
   (zgłoszenie właściciela, 2026-10-12).

   Okno stoi w znaczniku WEWNĄTRZ „.content", która ma „container-type" — a ta zamyka
   w sobie elementy „position: fixed" (komentarz przy „.content"). Działa mimo to, bo
   „showModal()" przenosi okno do GÓRNEJ WARSTWY, układanej względem okna przeglądarki;
   sprawdza to test E2E mierzący okno na całym ekranie. Otwarte zwykłym „show()" albo
   samym atrybutem „open" zostałoby uwięzione w kolumnie treści. */
.lightbox {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: rgb(20 20 20 / 96%);
  color: var(--eko-white);
  overflow: hidden;
  overscroll-behavior: contain;
}
.lightbox::backdrop {
  background: rgb(20 20 20 / 96%);
}

/* Strona pod otwartym oknem się nie przewija: kółko myszy i palec przewijały ją pod spodem,
   a po zamknięciu odwiedzający lądował w innym miejscu niż był. */
html:has(.lightbox[open]) {
  overflow: hidden;
}

/* Scena to całe okno; zdjęcie mieści się w niej w całości, wyśrodkowane. Ścieżki
   „minmax(0, 1fr)" są wymogiem: przy zwykłym „auto" wiersz rósł do naturalnej wysokości
   zdjęcia (1920 px), a „max-height: 100%" liczyło się od niego, nie od okna. Scena to też
   „ciemne tło" — kliknięcie w nią poza zdjęciem zamyka okno. */
.lightbox-stage {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  place-items: center;
  padding: 64px var(--eko-space-2) var(--eko-space-4);
}
@media (min-width: 768px) {
  .lightbox-stage {
    padding: 64px 88px var(--eko-space-5);
  }
}
/* Z <picture> zostaje w siatce sam obrazek. Bez „display: none" na <source> pusty element
   zajmował pierwszą komórkę, a zdjęcie lądowało w dodatkowym wierszu o swojej naturalnej
   wysokości i wystawało poza ekran (zmierzone: wiersze „0px 743.75px" przy oknie 768 px).
   Wybór pliku przez <source> zależy od drzewa dokumentu, nie od wyświetlania. */
.lightbox-stage picture {
  display: contents;
}
.lightbox-stage source {
  display: none;
}
.lightbox-stage img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  user-select: none;
}

/* Na telefonie strzałki leżą NA zdjęciu, więc tło jest ciemne jak na dużym polu (biała strzałka
   ma na nim 4,3:1 nawet nad białym zdjęciem), a jasny obrys pokazuje przycisk na ciemnym tle
   okna. Pierwsza wersja miała 12% bieli — nad jasnym zdjęciem znikała razem ze strzałką. */
.lightbox-button {
  position: absolute;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: rgb(28 28 28 / 60%);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 25%);
  color: var(--eko-white);
  cursor: pointer;
  transition: background var(--eko-transition);
}
.lightbox-button:hover {
  background: var(--eko-color-primary);
}
.lightbox-button:focus-visible {
  outline: 2px solid var(--eko-white);
  outline-offset: 2px;
}
.lightbox-close {
  top: var(--eko-space-2);
  right: var(--eko-space-2);
}
.lightbox-prev,
.lightbox-next {
  top: 50%;
  transform: translateY(-50%);
}
.lightbox-prev {
  left: var(--eko-space-2);
}
.lightbox-next {
  right: var(--eko-space-2);
}
@media (min-width: 768px) {
  .lightbox-prev {
    left: var(--eko-space-4);
  }
  .lightbox-next {
    right: var(--eko-space-4);
  }
}
.lightbox-counter {
  position: absolute;
  top: var(--eko-space-2);
  left: var(--eko-space-4);
  margin: 0;
  font-size: 15px;
  line-height: 48px;
  font-variant-numeric: tabular-nums;
}

.product-price {
  font-size: 26px;
  font-weight: 700;
  margin: var(--eko-space-3) 0;
}
.product-price .on-request {
  font-size: 18px;
  font-weight: 400;
  font-style: italic;
  color: var(--eko-gray-500);
}
.product-price .price-net {
  font-size: 15px;
  font-weight: 500;
  color: var(--eko-color-text-muted);
}

.spec-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 15px;
}
.spec-table th,
.spec-table td {
  text-align: left;
  padding: var(--eko-space-2) var(--eko-space-3);
  border-bottom: 1px solid var(--eko-color-border);
}
.spec-table th {
  font-weight: 600;
  color: var(--eko-graphite-600);
  width: 45%;
}

/* Tabela nie schodzi poniżej najdłuższego słowa w komórce, a nazwy parametrów przyszły
   z importu: „Scheibendurchmesser", „Ladeflächenlänge". Przy 280 px 16 kart maszyn po
   niemiecku i francusku przewijało się przez to w poziomie (zmierzone na całej mapie
   strony). Łamanie słów TYLKO w najwęższej kolumnie treści: włączone bezwarunkowo łamało
   37 słów przy 375 px i 15 na tablecie, bo kolumna nazw ma 45% i bez tej granicy
   przestaje się rozszerzać do długości słowa. Od 320 px tabele mieszczą się bez tego. */
@container (max-width: 280px) {
  .spec-table th,
  .spec-table td {
    overflow-wrap: anywhere;
  }
}

.downloads {
  list-style: none;
  padding: 0;
  margin: 0;
}
.downloads li {
  padding: var(--eko-space-2) 0;
  border-bottom: 1px solid var(--eko-color-border);
}

.video-teaser {
  display: inline-block;
  position: relative;
  max-width: 420px;
  overflow: hidden;
}
.video-teaser img {
  display: block;
  width: 100%;
}
.video-teaser .play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(28, 28, 28, 0.35);
  color: var(--eko-white);
  font-weight: 700;
}

/* ── Przyciski ────────────────────────────────────────────────────────────── */

.btn {
  display: inline-block;
  padding: var(--eko-space-3) var(--eko-space-5);
  font: inherit;
  font-weight: 700;
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: 0;
  cursor: pointer;
  transition: background var(--eko-transition);
}
.btn-primary {
  background: var(--eko-green-700);
  color: var(--eko-white);
}
.btn-primary:hover {
  background: var(--eko-green-600);
}
.btn-outline {
  background: var(--eko-white);
  color: var(--eko-green-700);
  border-color: var(--eko-green-700);
}
.btn-outline:hover {
  background: var(--eko-green-50);
}

.content .btn-small {
  padding: var(--eko-space-2) var(--eko-space-3);
  font-size: 13px;
}

/* ── Formularze publiczne ─────────────────────────────────────────────────────
   Do 2026-09-20 tej sekcji NIE BYŁO. Formularze Kontakt i Zapytanie ubierał
   „admin.css", wczytywany wtedy na każdej stronie i PO tym pliku — czyli strona
   publiczna wyglądała poprawnie wyłącznie przez przypadek.

   Kosztowało to widoczny defekt na głównej ścieżce kontaktu z firmą: arkusz panelu
   stylizuje pola listą selektorów typowych („input[type=text]" i dalej), a blazorowy
   <InputText> NIE wypisuje atrybutu „type", jeśli nikt go nie poda. „Imię i nazwisko"
   oraz „Temat" renderowały się więc bez szerokości, obramowania i odstępu — w jednej
   linii z etykietą, obok poprawnie ubranych pól e-mail i telefonu.

   Dlatego selektory niżej celują w POŁOŻENIE (pole wewnątrz „.field"), a nie w typ.
   Pilnuje tego PublicStylesTests. */

.content .field {
  display: block;
  margin-bottom: var(--eko-space-4);
}

.content .field > span {
  display: block;
  margin-bottom: var(--eko-space-1);
  font-weight: 600;
  font-size: 14px;
  color: var(--eko-color-text);
}

.content .field input,
.content .field textarea,
.content .field select {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: var(--eko-space-2) var(--eko-space-3);
  font: inherit;
  color: var(--eko-color-text);
  background: var(--eko-color-surface);
  border: 1px solid var(--eko-color-border-strong);
  border-radius: 0;
  transition: border-color var(--eko-transition), box-shadow var(--eko-transition);
}

.content .field textarea {
  resize: vertical;
  line-height: 1.55;
}

.content .field input:focus-visible,
.content .field textarea:focus-visible,
.content .field select:focus-visible {
  outline: none;
  border-color: var(--eko-green-600);
  box-shadow: var(--eko-focus-ring);
}

/* Pole odrzucone przez walidację. Kolor to nie jedyny nośnik — obok stoi komunikat
   słowny, bo sama czerwona ramka nie mówi nic osobie, która jej nie odróżnia. */
.content .field input.invalid,
.content .field textarea.invalid {
  border-color: var(--eko-danger);
}

.content .validation-message {
  display: block;
  margin-top: var(--eko-space-1);
  color: var(--eko-danger);
  font-size: 13px;
}

.content .checkbox {
  display: flex;
  align-items: flex-start;
  gap: var(--eko-space-3);
  font-size: 14px;
  line-height: 1.5;
  cursor: pointer;
}

/* Pole wyboru ma zostać polem wyboru — reguła szerokości wyżej rozciągnęłaby je
   na całą kolumnę. */
.content .checkbox input {
  /* 24 px, nie 18: to pełnoprawny cel dotykowy, a nie ozdoba przy tekście. */
  width: 24px;
  height: 24px;
  min-height: 0;
  margin-top: 0;
  flex: none;
  accent-color: var(--eko-color-primary);
}

.content .actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--eko-space-3);
}

/* ── Komunikaty formularzy ────────────────────────────────────────────────── */

.content .msg {
  margin: 0 0 var(--eko-space-4);
  padding: var(--eko-space-3) var(--eko-space-4);
  border-left: 4px solid;
  font-size: 14px;
}

.content .msg-success {
  background: var(--eko-green-50);
  color: var(--eko-green-800);
  border-color: var(--eko-green-600);
}

.content .msg-warn {
  background: var(--eko-warning-bg);
  color: var(--eko-warning-text);
  border-color: var(--eko-warning);
}

.content .msg-error {
  background: var(--eko-danger-bg);
  color: var(--eko-danger);
  border-color: var(--eko-danger);
}

/* ── Stopka i telefon ─────────────────────────────────────────────────────── */

.site-footer {
  background: var(--eko-graphite-800);
  color: var(--eko-on-dark);
  margin-top: var(--eko-space-7);
  font-size: 14px;
}
/* Cztery kolumny z ustaloną proporcją zamiast „auto-fit": pierwsza niesie adres i jest
   najszersza, trzy pozostałe to krótkie listy. Przy „auto-fit" wszystkie dostawały tę samą
   szerokość i między nimi zostawały puste pasy. */
.site-footer-inner {
  max-width: var(--eko-container);
  margin: 0 auto;
  padding: var(--eko-space-7) var(--eko-gutter) var(--eko-space-6);
  display: grid;
  gap: var(--eko-space-6) var(--eko-space-5);
  grid-template-columns: 1fr;
}

@media (min-width: 576px) {
  .site-footer-inner {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 992px) {
  .site-footer-inner {
    grid-template-columns: 1.6fr 1fr 1fr 1.2fr;
  }
}

.footer-hours {
  margin: 0 0 var(--eko-space-3);
}
/* Znak w stopce jest większy niż w nagłówku: tam konkuruje o miejsce z wyszukiwarką
   i telefonem, tutaj otwiera kolumnę i nic go nie ciśnie. „max-width" pilnuje, żeby
   przy 280 px nie wyszedł poza kolumnę — 48 px wysokości to 162 px szerokości. */
.footer-logo {
  display: block;
  height: 48px;
  width: auto;
  max-width: 100%;
  margin-bottom: var(--eko-space-4);
}

.site-footer h2 {
  font-size: 15px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--eko-green-400);
  margin: 0 0 var(--eko-space-2);
}
/* Odnośniki stopki stoją w listach, każdy w osobnym wierszu — wyjątek „inline" z WCAG 2.5.8
   ich nie obejmuje. Pomiar pokazał 19 px. */
.site-footer a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  color: var(--eko-white);
  text-decoration: none;
}
.site-footer a:hover {
  text-decoration: underline;
}
.site-footer ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--eko-space-1);
}
.footer-bottom {
  border-top: 1px solid var(--eko-line-dark);
  text-align: center;
  padding: var(--eko-space-3) var(--eko-gutter);
  font-size: 13px;
}

.call-button {
  position: fixed;
  right: var(--eko-space-4);
  bottom: calc(var(--eko-space-4) + env(safe-area-inset-bottom, 0px));
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--eko-green-700);
  color: var(--eko-white);
  display: grid;
  place-items: center;
  box-shadow: var(--eko-shadow-lg);
  z-index: 50;
}
.call-button:hover {
  background: var(--eko-green-600);
}

/* Grafika zastępcza maszyny bez zdjęcia. W karcie i w galerii rozmiar narzucają
   „.card-figure svg" i „.gallery-main svg"; ta reguła obowiązuje wszędzie indziej
   i pilnuje, żeby SVG nie rozlał się na całą szerokość kolumny. */
.placeholder {
  display: block;
  width: 100%;
  max-width: 400px;
  height: auto;
}

/* Pasek awarii obwodu Blazora. Reguła kontenera MUSI tu być, nie tylko styl odnośnika:
   „MainLayout.razor.css" jest arkuszem ZAKRESOWYM i nie sięga kopii paska renderowanej
   przez „PublicLayout", a jedyna nieuzakresowiona reguła siedziała w „admin.css" — który
   od rozdziału arkuszy nie jedzie już na stronę publiczną.

   Bez „display: none" pasek wchodzi w normalny przepływ i wisi na dole KAŻDEJ strony
   publicznej, na stałe, z komunikatem o nieoczekiwanym błędzie. Przy pierwszym podejściu
   przeniosłem stąd wyłącznie regułę dziecka „.reload" i dokładnie to się stało. */
#blazor-error-ui {
  display: none;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  padding: var(--eko-space-3) var(--eko-space-4);
  background: var(--eko-warning-bg);
  border-top: 2px solid var(--eko-warning);
}

#blazor-error-ui .reload {
  color: var(--eko-graphite-800);
  text-decoration: underline;
}

.empty-state {
  text-align: center;
  padding: var(--eko-space-7) var(--eko-space-4);
  color: var(--eko-gray-500);
}

.muted {
  color: var(--eko-gray-500);
  font-size: 13px;
}

/* Znacznik tłumaczenia maszynowego — używany od Etapu 4, styl zakładamy teraz. */
.mt-badge {
  display: inline-block;
  font-size: 11px;
  color: var(--eko-gray-500);
  background: var(--eko-gray-100);
  padding: 1px 5px;
  vertical-align: middle;
}

/* ── Tłumaczenie automatyczne (PLAN.md §6.4) ──────────────────────────────── */

/* Adnotacja „to nie jest tłumaczenie". Ma być widoczna, ale nie ma krzyczeć —
   na telefonie i w Firefoksie zostaje na stronie na stałe, bo Translator API tam nie ma. */
.mt-notice {
    margin: 0 0 var(--eko-space-4);
    padding: var(--eko-space-2) var(--eko-space-3);
    border-left: 3px solid var(--eko-green-700);
    background: var(--eko-green-50);
    color: var(--eko-gray-500);
    font-size: 0.875rem;
}

/* Plakietka przy tekście, który podmienił tłumacz w przeglądarce. */
.mt-badge {
    display: inline-block;
    margin-left: var(--eko-space-2);
    padding: 0 0.4em;
    background: var(--eko-green-50);
    color: var(--eko-gray-500);
    font-size: 0.6875rem;
    line-height: 1.6;
    text-transform: lowercase;
    vertical-align: middle;
    white-space: nowrap;
}

/* Przycisk pobrania modelu. Pokazuje się tylko wtedy, gdy model trzeba dopiero ściągnąć —
   setek megabajtów nie pobieramy odwiedzającemu bez pytania. */
#mt-settings:not([hidden]) {
    margin-bottom: var(--eko-space-4);
}

.mt-button {
    padding: var(--eko-space-2) var(--eko-space-4);
    border: 1px solid var(--eko-green-700);
    border-radius: 0;
    background: transparent;
    color: var(--eko-green-700);
    cursor: pointer;
    font: inherit;
    font-size: 0.875rem;
}

.mt-button:hover:not(:disabled) {
    background: var(--eko-green-700);
    color: var(--eko-white);
}

.mt-button:disabled {
    cursor: progress;
    opacity: 0.7;
}

/* ── Slider na stronie głównej (PLAN.md §5.6) ─────────────────────────────── */

/* Przewijanie realizuje CSS, nie JavaScript: odnośniki pod slajdami to zwykłe kotwice,
   a scroll-snap zatrzymuje widok na krawędzi slajdu. Działa też, gdy skrypt się nie wczyta. */
.hero {
    position: relative;
    margin-bottom: var(--eko-space-5);
}

.hero-track {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    scrollbar-width: none;
}

.hero-track::-webkit-scrollbar {
    display: none;
}

.hero-slide {
    position: relative;
    flex: 0 0 100%;
    margin: 0;
    scroll-snap-align: start;
}

/* Wysokość liczona z szerokości OKNA, nie kolumny — hero stoi obok sidebara, więc sam
   procent szerokości dałby przy 992 px kadr prawie kwadratowy. Zakres trzyma proporcję
   między 2,4:1 a 2,9:1 na całym paśmie desktopu, a 420 px u góry to świadomy limit:
   powyżej tego pierwszy rząd maszyn wypada poza pierwszy ekran (patrz §5 budżet). */
.hero-slide img {
    display: block;
    width: 100%;
    height: clamp(300px, 26vw, 420px);
    object-fit: cover;
    /* Oś przycinania zależy od proporcji WGRANEGO pliku, nie od tej reguły. Kadr hero ma
       od 1.22:1 (telefon) do 2.9:1 (desktop), więc:
         • zdjęcie „wyższe" niż kadr — zwykła fotografia 3:2 albo 4:3 — przycina się
           w PIONIE i wtedy działa „40%": maszyna siedzi w kadrze zwykle w górnej połowie,
           a przycinanie od środka zostawiałoby same koleiny;
         • baner panoramiczny szerszy niż kadr (slajdy demo to 1920×640, czyli 3:1) przycina
           się w POZIOMIE i składowa pionowa jest wtedy bezczynna — obcięcie idzie równo
           z obu stron, bo składowa pozioma to „center".
       Wniosek dla panelu: hasło i maszyna mają być bliżej środka szerokości, inaczej na
       telefonie zniknie z kadru ~59% szerokości z każdej strony. */
    object-position: center 40%;
}

.hero-caption {
    position: absolute;
    inset: auto 0 0 0;
    padding: var(--eko-space-6);
    /* Trzy przystanki zamiast dwóch: pod tekstem przyciemnienie jest mocne, w połowie
       wysokości już tylko lekkie, a górna część zdjęcia zostaje nietknięta. Przy dwóch
       przystankach albo tekst gubił kontrast, albo zdjęcie robiło się szare do połowy. */
    background: linear-gradient(
        to top,
        rgb(28 28 28 / 88%) 0%,
        rgb(28 28 28 / 55%) 45%,
        rgb(28 28 28 / 0%) 100%
    );
    color: var(--eko-white);
}

/* Tekst ma swoją szerokość, gradient idzie przez całą — inaczej przyciemnienie kończyłoby
   się w połowie zdjęcia ostrą krawędzią. */
.hero-caption-inner {
    max-width: 560px;
}

.hero-caption h2 {
    margin: 0 0 var(--eko-space-2);
    font-size: clamp(1.5rem, 2.6vw, 2.5rem);
    font-weight: 700;
    line-height: 1.12;
    letter-spacing: -0.01em;
    text-transform: uppercase;
    text-shadow: 0 1px 3px rgb(0 0 0 / 35%);
}

.hero-caption p {
    margin: 0 0 var(--eko-space-4);
    max-width: 46ch;
    font-size: 15px;
    opacity: 0.92;
    text-shadow: 0 1px 2px rgb(0 0 0 / 30%);
}

.hero-caption .btn {
    padding: 0 var(--eko-space-5);
    height: 46px;
    display: inline-flex;
    align-items: center;
}

.hero-dots {
    display: flex;
    gap: var(--eko-space-1);
    justify-content: center;
    margin: var(--eko-space-2) 0 0;
    padding: 0;
    list-style: none;
}

/* Kropka jest mała, ale pole dotykowe ma 28 px — palec nie celuje w 10 px.
   Numer slajdu zostaje w znaczniku i jest widoczny: wersja bez numerów miała kontrast
   1.33:1, czyli poniżej progu 3:1 z WCAG 1.4.11 dla elementów sterujących, i odbierała
   odwiedzającemu jedyną informację, ILE jest slajdów i którym się patrzy.
   „align-content" jest tu potrzebne osobno — „place-items" ustawia wyrównanie W ścieżce,
   a nie ścieżek w kontenerze, więc bez niego dwa wiersze auto rozjeżdżały kropkę
   w jej własnym polu. */
.hero-dots a {
    display: grid;
    place-items: center;
    align-content: center;
    gap: 3px;
    width: 28px;
    height: 28px;
    color: var(--eko-gray-500);
    font-size: 0.6875rem;
    line-height: 1;
    text-decoration: none;
    transition: color var(--eko-transition);
}

.hero-dots a::before {
    content: "";
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--eko-gray-500);
    transition: background var(--eko-transition), transform var(--eko-transition);
}

.hero-dots a:hover,
.hero-dots a:focus-visible {
    color: var(--eko-color-primary);
}

.hero-dots a:hover::before,
.hero-dots a:focus-visible::before {
    background: var(--eko-color-primary);
    transform: scale(1.25);
}

/* ── Hero na telefonie ────────────────────────────────────────────────────── */

@media (max-width: 767.98px) {
    .hero-slide img {
        height: clamp(280px, 62vw, 340px);
    }

    .hero-caption {
        padding: var(--eko-space-4);
    }

    .hero-caption h2 {
        font-size: clamp(1.375rem, 6vw, 1.75rem);
    }

    /* Podtytuł zjada na 375 px połowę zdjęcia, a niesie najmniej treści z całego slajdu.
       Hasło i przycisk zostają. */
    .hero-caption p {
        display: none;
    }

    .hero-caption .btn {
        height: 44px;
    }
}

/* ── Niskie okno ──────────────────────────────────────────────────────────────
   Wysokość okna też jest wymiarem, a cały arkusz pytał wyłącznie o szerokość.
   Telefon trzymany poziomo ma 360–390 px wysokości, z czego pasek górny, header,
   nawigacja i zwinięte kategorie zabierają ~270 px. Hero liczone z SAMEJ szerokości
   dostawało wtedy 300–340 px i zostawał z niego pasek 31 px nad zgięciem — co wygląda
   jak usterka renderowania, a nie jak zdjęcie. Pierwsza maszyna lądowała 868 px niżej,
   czyli po dwóch ekranach przewijania.

   Progu nie zawężamy do telefonu: tak samo zachowuje się okno przeglądarki ściśnięte
   w pionie na komputerze. */
@media (max-height: 480px) {
    .hero-slide img {
        height: clamp(160px, 40vh, 220px);
    }

    /* Podtytuł nie zmieści się już w kadrze obok hasła i przycisku. */
    .hero-caption p {
        display: none;
    }
}

/* ── Blog ─────────────────────────────────────────────────────────────────── */

.card-post {
    display: flex;
    flex-direction: column;
    background: var(--eko-color-surface);
    border: 1px solid var(--eko-color-border);
    overflow: hidden;
    transition: box-shadow var(--eko-transition), border-color var(--eko-transition);
}

.card-post > a {
    color: inherit;
    text-decoration: none;
}

.card-post:hover {
    box-shadow: var(--eko-shadow-md);
}

.card-date {
    display: block;
    margin-bottom: var(--eko-space-1);
    color: var(--eko-gray-500);
    font-size: 0.8125rem;
}

.card-excerpt {
    margin: var(--eko-space-2) 0 0;
    color: var(--eko-graphite-600);
    font-size: 0.9375rem;
}

.card-tags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--eko-space-2);
    margin: 0;
    padding: var(--eko-space-3);
    list-style: none;
}

.card-tags a,
.tag-nav a {
    display: inline-block;
    padding: 2px var(--eko-space-3);
    border: 1px solid var(--eko-color-border);
    color: var(--eko-graphite-600);
    font-size: 0.8125rem;
    text-decoration: none;
}

.card-tags a:hover,
.tag-nav a:hover,
.tag-nav a[aria-current="page"] {
    border-color: var(--eko-green-700);
    background: var(--eko-green-50);
    color: var(--eko-green-800);
}

.tag-nav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--eko-space-2);
    margin-bottom: var(--eko-space-5);
}

.post {
    max-width: 75ch;
}

.post-cover img {
    width: 100%;
    height: auto;
}

.post-lead {
    font-size: 1.0625rem;
    color: var(--eko-graphite-600);
}

/* Treść wpisu pochodzi z edytora, więc odstępy ustawiamy tutaj — w HTML-u ich nie ma. */
.post-content > * + * {
    margin-top: var(--eko-space-4);
}

.post-content h2,
.post-content h3 {
    margin-top: var(--eko-space-6);
}

.post-content img {
    max-width: 100%;
    height: auto;
}

.post-content table {
    width: 100%;
    border-collapse: collapse;
}

.post-content th,
.post-content td {
    padding: var(--eko-space-2);
    border: 1px solid var(--eko-color-border);
    text-align: left;
}

.post-content blockquote {
    margin-left: 0;
    padding-left: var(--eko-space-4);
    border-left: 3px solid var(--eko-green-500);
    color: var(--eko-graphite-600);
}

.post-gallery {
    display: flex;
    flex-wrap: wrap;
    gap: var(--eko-space-3);
    margin-top: var(--eko-space-5);
}

/* ── Podpowiedzi wyszukiwarki ─────────────────────────────────────────────── */

/* Kontener musi mieć własny układ współrzędnych — lista wisi pod polem, a nie rozpycha
   paska nagłówka. */
.search {
  position: relative;
}

.search-suggestions {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 40;
  margin: 2px 0 0;
  padding: 0;
  list-style: none;
  background: var(--eko-white);
  border: 1px solid var(--eko-color-border);
  box-shadow: var(--eko-shadow-md);
  overflow: hidden;
}

.search-suggestions a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--eko-space-3);
  padding: var(--eko-space-3);
  text-decoration: none;
  color: var(--eko-color-text);
}

.search-suggestions li.is-active a,
.search-suggestions a:hover {
  background: var(--eko-green-50);
}

.suggestion-kind {
  flex: 0 0 auto;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--eko-gray-500);
}

/* ── Pasek o ciasteczkach ─────────────────────────────────────────────────── */

.cookie-notice {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--eko-space-4);
  padding: var(--eko-space-4);
  background: var(--eko-graphite-800);
  color: var(--eko-on-dark);
  font-size: 14px;
  box-shadow: 0 -2px 12px rgb(0 0 0 / 25%);
}

.cookie-notice p {
  margin: 0;
  max-width: 70ch;
}

.cookie-notice a {
  color: var(--eko-green-400);
}

/* Telefon: pasek na całą szerokość, przycisk pod tekstem — inaczej zjadałby treść.
   Próg 576 z systemu, nie 640. */
@media (max-width: 575.98px) {
  .cookie-notice {
    justify-content: flex-start;
  }

  .cookie-notice .btn {
    width: 100%;
    text-align: center;
  }
}

/* Pływający przycisk telefonu nie może chować się pod paskiem. */
.cookie-notice:not([hidden]) ~ .call-button {
  bottom: 120px;
}

/* ── Strona kontaktowa ────────────────────────────────────────────────────── */

.contact-layout {
  display: grid;
  gap: var(--eko-space-5);
  /* „minmax(0, 1fr)", nie „1fr" — to drugie znaczy „minmax(auto, 1fr)", a „auto" nie zejdzie
     poniżej min-content ścieżki. Odkąd w panelu stoi mapa, min-content wyznacza <iframe>:
     element zastępowany o własnej szerokości 300 px, którego „width: 100%" przy liczeniu
     rozmiarów wewnętrznych jest traktowane jak „auto". Panel z adresem i telefonem
     rozpychał przez to stronę o 106 px na ekranie 280 px. Ta sama poprawka, co przy
     siatce „.page" w Etapie 1. */
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}

/* To samo co przy stronie produktu — i przy okazji znika siódmy próg (900 px), którego
   nie ma w systemie z docs/design-tokens.md §4. */
@container (min-width: 680px) {
  .contact-layout {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

/* „minmax(0, max-content)" zamiast gołego „max-content": ta druga postać nie pozwala kolumnie
   zejść poniżej najdłuższej etykiety („Godziny pracy"), więc na bardzo wąskim ekranie panel
   robił się szerszy od okna. */
.contact-data {
  display: grid;
  grid-template-columns: minmax(0, max-content) minmax(0, 1fr);
  gap: var(--eko-space-2) var(--eko-space-4);
  margin: var(--eko-space-4) 0 0;
}

/* Poniżej 360 px dwie kolumny nie mają sensu — etykieta i wartość idą jedna pod drugą. */
@media (max-width: 359.98px) {
  .contact-data {
    grid-template-columns: 1fr;
    gap: 0;
  }
  .contact-data dd {
    margin-bottom: var(--eko-space-2);
  }
}

.contact-data dt {
  font-weight: 700;
  color: var(--eko-graphite-600);
}

.contact-data dd {
  margin: 0;
}

/* Telefon i adres poczty to na stronie Kontakt główna ścieżka, a nie odnośnik w zdaniu:
   stoją same w swoim wierszu, więc wyjątek „Inline" z WCAG 2.5.8 ich nie obejmuje.
   Zmierzone 20 px podnosimy do wymaganych 24. */
.contact-data dd a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}

/* Wprowadzenie i rząd przycisków wyglądają tak samo w kaflu („.prose") i na stronie
   bez kafla („.static-page"). Selektory są WSPÓLNE, a nie skopiowane: dopisanie drugiego
   zestawu reguł znaczy, że kolejna zmiana wyglądu trafi w jedno miejsce z dwóch,
   a rozjazd wyjdzie dopiero na którejś podstronie. */
.prose .lead,
.static-page .lead {
  font-size: 18px;
  color: var(--eko-graphite-600);
}

.prose address {
  font-style: normal;
  line-height: 1.7;
}

.prose .actions,
.static-page .actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--eko-space-3);
  margin-top: var(--eko-space-5);
}

/* ── Strony statyczne bez kafla (O NAS, DOSTAWA) ───────────────────────────────
   Bez białego pudełka i bez przycięcia do 68 znaków, które nakładał „.prose" —
   ale też nie bez żadnego ograniczenia.

   Zmierzone, nie przyjęte na oko. Kolumna treści zatrzymuje się na 1032 px (kontener
   ma 1400 px, z czego sidebar bierze 280) i mieści **150 znaków w wierszu**. Zalecenie
   typograficzne to 45–75, najwyżej 85; przy 128 oko gubi początek następnego wiersza
   i tekst czyta się schodkami.

   75ch daje od 88 do 91,4 znaku — górna granica tego, co da się obronić, i więcej niż
   68ch zwykłego „.prose" (80 znaków), czyli szerokość zostaje odczuwalnie większa niż
   przed zmianą. Uwaga na jednostkę: „ch" to szerokość znaku „0", a ten jest szerszy od
   średniej litery w tekście ciągłym — 75ch to NIE jest 75 znaków. Wartość wzięła się
   z pomiaru w przeglądarce, nie z przeliczenia.

   Ile znaków daje „ch", zależy od KROJU, a krój jest systemowy (--eko-font) — każdy
   system pokazuje stronę inną czcionką. Do 2026-10-01 stało tu 77ch, zmierzone wyłącznie
   na Segoe UI (Windows): 90 znaków. Ten sam akapit przy 77ch (zmierzone na krojach o tych
   samych wymiarach): Roboto — Android i Chromebook — 93,8, Arial 92,3, DejaVu Sans 93,4,
   czyli ponad próg 92 z StronyStatyczneTests. Przy 75ch: Segoe UI 88,0, Roboto 91,4,
   Arial 89,9, DejaVu Sans 90,9 — wszystkie w progu. SF Pro (macOS, iOS) niezmierzony:
   tego kroju nie da się użyć poza sprzętem Apple.

   Jednostka „ch" zamiast pikseli, bo próg ma iść za rozmiarem tekstu, a nie za
   przypadkową liczbą.

   Klasa robi jeszcze jedną rzecz, której nie widać: daje „.lead" i „.actions" czego
   się uczepić. Bez niej rząd przycisków rozsypałby się na osobne wiersze,
   a wprowadzenie zrównało z resztą tekstu — i to bez żadnego błędu po drodze. */
.static-page {
  max-width: 75ch;
}

/* ── O NAS: wstęp, kafelki, hasło (2026-10-01) ─────────────────────────────────
   Właściciel chciał opisu „ładniej ułożonego" niż siedem akapitów jeden pod drugim.
   Wstęp trzyma limit 75ch (pomiary wyżej), a cała strona go zdejmuje: kafelki dzielą
   kolumnę na dwie i to ICH szerokość pilnuje długości wiersza — ok. 60 znaków przy
   kolumnie 1032 px, ok. 40 na tablecie. Jedna kolumna kafelków dopiero przy kolumnie
   treści węższej niż 680 px (kontenerem jest „.content"). */
.static-page.about-page {
  max-width: none;
}

.about-intro {
  max-width: 75ch;
}

/* Pogrubienia z opisu właściciela (<Emphasized>). Wstęp jest grafitem drugoplanowym,
   więc pogrubiony fragment dostaje pełny kontrast tekstu — sama grubość w jaśniejszym
   kolorze ginęła. Ta sama reguła obejmuje kafelek „O firmie" na stronie głównej. */
.static-page .lead strong,
.prose .lead strong {
  color: var(--eko-color-text);
}

/* „minmax(0, 1fr)" także w jednej kolumnie: zwykłe „1fr" nie schodzi poniżej najdłuższego
   słowa w kafelku, więc niemiecki tytuł („Metallbearbeitung" obok ikony) rozpychał kolumnę
   przy 280 px poza ekran, a tekst kafelków był ucięty z prawej. */
.about-features {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--eko-space-5);
  margin: var(--eko-space-6) 0 0;
  padding: 0;
  list-style: none;
}

@container (min-width: 680px) {
  .about-features {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Biały kafel na szarym tle strony z zieloną kreską u góry — tą samą, co przy nagłówkach
   sekcji, tylko poziomą. Bez cienia i bez reakcji na kursor: kafel niczego nie otwiera. */
.about-feature {
  padding: var(--eko-space-5);
  background: var(--eko-color-surface);
  border: 1px solid var(--eko-color-border);
  border-top: 3px solid var(--eko-green-700);
}

.about-feature-head {
  display: flex;
  align-items: center;
  gap: var(--eko-space-3);
  margin-bottom: var(--eko-space-3);
}

.about-feature-icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 44px;
  height: 44px;
  background: var(--eko-green-50);
  color: var(--eko-color-primary);
}

.about-feature h2 {
  min-width: 0;
  margin: 0;
  font-size: 18px;
  line-height: 1.3;
}

.about-feature p {
  margin: 0;
}

/* Hasło na końcu: jasnozielone tło jak kafelek „O firmie" na stronie głównej, kreska
   z lewej jak przy nagłówkach sekcji. Przyciski stoją w środku — hasło je zapowiada. */
.about-tagline {
  margin-top: var(--eko-space-6);
  padding: var(--eko-space-5) var(--eko-space-6);
  background: var(--eko-green-50);
  border: 1px solid var(--eko-green-100);
  border-left: 4px solid var(--eko-green-700);
}

.about-tagline-text {
  margin: 0;
  font-size: 20px;
  line-height: 1.4;
}

.static-page .about-tagline .actions {
  margin-top: var(--eko-space-4);
}

/* Telefon: mniej powietrza w ramkach, żeby tekst nie szedł wąską szpaltą. */
@container (max-width: 480px) {
  .about-feature,
  .about-tagline {
    padding: var(--eko-space-4);
  }

  .about-tagline-text {
    font-size: 18px;
  }
}

/* Najwęższe ekrany (do ok. 350 px): ikona nad tytułem. Przy 280 px obok ikony zostaje na tytuł
   158 px, a pogrubione niemieckie „Metallbearbeitung" i „Kundenbetreuung" mają 144–188 px
   (Roboto – DejaVu Sans) — złamałyby się w przypadkowej literze. Pod ikoną mają 214 px. */
@container (max-width: 320px) {
  .about-feature-head {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--eko-space-2);
  }
}

/* ── Akapit o firmie na stronie głównej ───────────────────────────────────── */

.home-about {
  margin-top: var(--eko-space-7);
  padding: var(--eko-space-5) var(--eko-space-6);
  background: var(--eko-green-50);
  border: 1px solid var(--eko-green-100);
}

.home-about .section-title {
  margin-top: 0;
}

/* Akapity bloku o firmie są krótkie — pełne 68 znaków „.prose" robiło z nich wąską
   szpaltę pośrodku szerokiego kafla. */
.home-about .prose {
  max-width: 78ch;
}

/* Odnośnik do kanału RSS pod listą znaczników — dyskretny, ale istniejący. */
.feed-link {
  margin: var(--eko-space-3) 0 0;
  font-size: 14px;
}

/* Akapit zawiera wyłącznie ten odnośnik — nie ma obok tekstu, który ograniczałby jego
   wysokość, więc wyjątek „Inline" z WCAG 2.5.8 nie działa i cel musi mieć swoje 24 px. */
.feed-link a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  color: var(--eko-graphite-600);
}

/* ── Mapa dojazdu (Kontakt) ───────────────────────────────────────────────────
   Mapa siedzi W PANELU z danymi firmy, czyli w kolumnie węższej od całej treści —
   przy 1400 px to około 440 px, a nie 1340. Wymiary są dobrane pod tę szerokość. */
.map-block {
  margin-top: var(--eko-space-6);
}

.map-block .section-title {
  margin-top: 0;
}

.map-embed {
  /* Szerokość MUSI być jawna. Przy „width: auto" proporcja liczy ją z WYSOKOŚCI, więc
     „min-height" dawało 260 × 4/3 = 347 px w kolumnie szerokiej na 248 px i cała strona
     Kontakt przewijała się w poziomie na każdym telefonie. Element ma „overflow: hidden",
     więc nie było tego widać na pierwszy rzut oka: wystawał sam kafel, a nie zawartość. */
  width: 100%;
  /* 4:3, nie 16:9 — w wąskiej kolumnie panelu kadr panoramiczny robi się paskiem,
     z którego nie da się odczytać ani ulicy, ani okolicy. */
  aspect-ratio: 4 / 3;
  /* Poniżej tego mapa przestaje cokolwiek pokazywać… */
  min-height: 240px;
  /* …ale „min-height" razem z „aspect-ratio" PRZENOSI SIĘ na minimalną szerokość:
     240 × 4/3 = 320 px. Panel jest elementem siatki, a ten domyślnie nie schodzi poniżej
     minimum swojej zawartości — więc przy 280 px cały panel z adresem i telefonem
     rozpychał stronę o 106 px. „min-width: 0" wyłącza to przeniesienie; proporcja
     ustępuje wtedy wysokości, co jest dokładnie tym, czego chcemy. */
  min-width: 0;
  /* …a przy niskim oknie (telefon poziomo) ta sama proporcja wypycha wszystko poniżej
     zgięcia. Ta sama zasada co przy banerze. */
  max-height: 60vh;
  border: 1px solid var(--eko-color-border);
  overflow: hidden;
  /* Widoczne, dopóki ramka się nie wczyta — bez tego w jej miejscu jest biała dziura. */
  background: var(--eko-gray-100);
}

.map-embed iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Nota pod mapą mówi wprost, że mapa idzie z Google. Nie jest ozdobnikiem: mapa wczytuje
   się przy wejściu na stronę, więc to jedyne miejsce, gdzie odwiedzający dowiaduje się
   o tym, zanim zajrzy do polityki prywatności. */
.map-note {
  margin: var(--eko-space-2) 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--eko-graphite-600);
}
