﻿/* 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;
}

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%;
}

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;
  border-radius: var(--eko-radius-sm);
  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
   (#2b2e2b) 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;
  border-radius: 2px;
  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ą 2,945: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).

   Znak niesie pod napisem grafikę pola, więc same litery mają około połowy jego wysokości.
   Przy 40 px wychodziło z tego 25 px — mniej niż poprzedni napis tekstowy (26 px), przy
   nagłówku wysokim na 80 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: var(--eko-radius-md) 0 0 var(--eko-radius-md);
}
.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 var(--eko-radius-md) var(--eko-radius-md) 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);
  border-radius: var(--eko-radius-md);
  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);
  border-radius: var(--eko-radius-lg);
  /* „clip", nie „hidden": zaokrągla rogi tak samo, ale NIE tworzy kontekstu przewijania,
     który poniżej ubiłby „position: sticky" bez jednego słowa ostrzeżenia. */
  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;
}

.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 — a rozwinięcie wynika z adresu, nie ze stanu klikania. */
.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);
}

/* 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;
  }
}

/* Sticky dopiero od 1200 px. Przy 992 px okno ma zwykle 768 px wysokości i przyklejone menu
   zabiera połowę ekranu treści. Warunek działania: żaden przodek nie może mieć „overflow"
   innego niż „visible" — „.page", „body" i „html" go nie mają i nie mogą dostać. */
@media (min-width: 1200px) {
  .sidebar {
    position: sticky;
    top: var(--eko-space-4);
  }

  /* Przewija się LISTA, nie cały panel. Gdy kontenerem przewijania było „.sidebar",
     przy dłuższym drzewie (kilkanaście kategorii głównych na ekranie 768 px wysokości)
     zielony nagłówek „KATEGORIE SPRZĘTU" wyjeżdżał poza górę przyklejonego panelu
     i zostawała bezgłowa lista ucięta zaokrągleniem. Gałąź telefonowa niżej robiła to
     dobrze od początku — tu było niespójne.

     Odjęte 50 px to wysokość nagłówka, który zostaje na miejscu. */
  .sidebar .tree-nav {
    max-height: calc(100vh - 2 * var(--eko-space-4) - 50px);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* ── Sidebar: telefon i tablet ────────────────────────────────────────────── */

@media (max-width: 991.98px) {
  .sidebar[open] > summary {
    border-radius: var(--eko-radius-lg) var(--eko-radius-lg) 0 0;
  }
  /* Rozwinięte drzewo nie może zepchnąć treści poza ekran — po 50% wysokości okna
     przewija się wewnątrz panelu. */
  .sidebar[open] .tree-nav {
    max-height: 50vh;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* ── 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". Sprawdzone: wewnątrz „.content"
   nie ma ani jednego — pływający telefon, pasek ciasteczek i pasek awarii są jego
   RODZEŃSTWEM. Nie wstawiaj tu okna modalnego bez sprawdzenia tego ponownie. */
.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;
  border-radius: 2px;
  background: var(--eko-color-primary);
}

.panel {
  background: var(--eko-white);
  border: 1px solid var(--eko-color-border);
  border-radius: var(--eko-radius-md);
  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);
  border-radius: var(--eko-radius-lg);
  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);
  border-radius: var(--eko-radius-sm);
  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);
}

/* ── 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: var(--eko-radius-sm);
  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);
  border-radius: var(--eko-radius-md);
  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 ──────────────────────────────────────────────────────── */

.product {
  display: grid;
  gap: 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. */
@container (min-width: 700px) {
  .product-top {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    gap: var(--eko-space-6);
    align-items: start;
  }
}

.gallery-main {
  background: var(--eko-white);
  border: 1px solid var(--eko-color-border);
  border-radius: var(--eko-radius-md);
  aspect-ratio: 4 / 3;
  display: grid;
  place-items: center;
  overflow: hidden;
}
.gallery-main img,
.gallery-main svg {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.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);
  border-radius: var(--eko-radius-sm);
  overflow: hidden;
  background: var(--eko-white);
}
.gallery-thumbs a:hover {
  border-color: var(--eko-green-500);
}
.gallery-thumbs img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.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);
}

.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%;
}

.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;
  border-radius: var(--eko-radius-md);
  overflow: hidden;
}
.video-teaser img {
  display: block;
  width: 100%;
}
.video-teaser .play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(31, 33, 31, 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: var(--eko-radius-md);
  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: var(--eko-radius-md);
  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;
  border-radius: 0 var(--eko-radius-md) var(--eko-radius-md) 0;
  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 141 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;
  border-radius: var(--eko-radius-sm);
  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;
    border-radius: var(--eko-radius-sm);
    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: var(--eko-radius-sm);
    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;
    border-radius: var(--eko-radius-lg);
    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(31 33 31 / 88%) 0%,
        rgb(31 33 31 / 55%) 45%,
        rgb(31 33 31 / 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);
    /* Ten sam promień co karta maszyny — obie stoją obok siebie na stronie głównej. */
    border-radius: var(--eko-radius-lg);
    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);
    border-radius: 999px;
    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;
    border-radius: var(--eko-radius-md);
}

.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;
    border-radius: var(--eko-radius-sm);
}

.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);
}

.post-gallery img {
    border-radius: var(--eko-radius-sm);
}

/* ── 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);
  border-radius: var(--eko-radius-md);
  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.

   77ch daje 90 znaków — górna granica tego, co da się obronić, i wyraźnie 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 o jakieś
   17% szerszy od średniej litery w tekście ciągłym — 77ch to NIE jest 77 znaków.
   Wartość wzięła się z pomiaru w przeglądarce, nie z przeliczenia.

   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: 77ch;
}

/* ── 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);
  border-radius: var(--eko-radius-lg);
}

.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);
  border-radius: var(--eko-radius-lg);
  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);
}
