/* ── DS Design Tokens ──────────────────────────────────────── */
:root {
  /* Aplikacja ma jedną paletę — jasną, wpisaną w tokeny poniżej, bez wariantu ciemnego.
     Bez tej deklaracji przeglądarka nie ma skąd tego wiedzieć i na systemie ustawionym na tryb
     ciemny rysuje po swojemu wszystko, czego nie malujemy sami: paski przewijania, pola formularzy,
     menu <select>. Efekt to ciemne elementy na jasnym tle — najbardziej widoczne na pasku
     przewijania długiej listy, bo tam sąsiaduje z białą powierzchnią przez całą wysokość.
     Deklaracja dotyczy też ramek osadzonych na stronie (kafelek wyboru konta Google). */
  color-scheme: light;

  --ds-bg:       #fafafa;
  --ds-surface:  #ffffff;
  --ds-ink:      #0a0a0a;
  --ds-ink2:     #171717;
  --ds-muted:    #737373;
  --ds-muted2:   #a3a3a3;
  --ds-line:     #e5e5e5;
  --ds-line2:    #f0f0f0;
  --ds-accent:   #0a0a0a;
  --ds-ok:       #16a34a;
  /* Darker green (Tailwind green-700) matching the "ACTIVE" tone in
     AllegroStatusBadge, used for inline text labels (row status, confidence)
     so the Products table reads as one consistent palette. Keep --ds-ok for
     pill backgrounds and dashboard highlights. */
  --ds-ok-soft:  #15803d;
  --ds-bad:      #dc2626;
  --ds-warn:     #ca8a04;
  --ds-ok-bg:    #f0fdf4;
  --ds-bad-bg:   #fef2f2;
  --ds-warn-bg:  #fefce8;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html {
  height: 100%;
}

body {
  height: 100%;
  background: var(--ds-bg);
  font-family: 'Geist', 'Inter', -apple-system, sans-serif;
  font-size: 13px;
  color: var(--ds-ink);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

/* ── Menu boczne ─────────────────────────────────────────────
   Nawigacja stała kiedyś w poziomym pasku nad treścią. Powłoka jest nieprzewijalna
   (body { overflow:hidden }), więc te 53 px wysokości szły wprost z listy produktów — a wysokość
   wiersza była już raz przedmiotem optymalizacji (Trello #53). Pionowy pasek oddaje tę wysokość
   i przy okazji ma miejsce na więcej pozycji, niż mieściło się w rzędzie.

   Zwinięte = same ikony, rozwinięte = ikony z podpisami. Stan niesie klasa ds-sidebar-collapsed na
   <body>, wypisywana przez serwer w _Host.cshtml z tego samego ciasteczka, które zapisuje
   przełącznik. Dlatego stan menu w ogóle nie przechodzi przez render Blazora: po F5 przeglądarka
   maluje właściwą szerokość przed pierwszym rysowaniem, więc nie ma czym mrugnąć. */
:root {
  --ds-sidebar-w:            220px;
  --ds-sidebar-w-collapsed:   56px;
}

.ds-shell { height: 100%; display: flex; }

/* Szerokość przełącza się drugą regułą width przy klasie na <body>, a nie podmianą zmiennej pod
   jednym `width: var(...)`. Oba warianty działają; ten jest wybrany, bo zmiennej nikt poza tą jedną
   deklaracją nie czyta, więc pośrednik niczego nie oszczędzał, a ukrywał, gdzie właściwie zapada
   decyzja o szerokości. */
.ds-sidebar {
  flex: 0 0 auto;
  width: var(--ds-sidebar-w);
  transition: width 180ms ease;
  display: flex; flex-direction: column; gap: 14px;
  padding: 10px 10px 12px;
  background: var(--ds-surface);
  border-right: 1px solid var(--ds-line);
  overflow: hidden;               /* przycina podpisy w trakcie zwijania */
  /* position + z-index nie są ozdobą i nie wolno ich „posprzątać”: .ds-filter-backdrop na Produktach
     to position:fixed z inset:0, czyli tło na cały ekran. Leży w kontekście układania paska filtrów
     (.ds-subheader, z-index:10), więc na poziomie dokumentu maluje się na 10 — i dokładnie dlatego
     dawny pasek górny (z-index:20) był klikalny przy otwartym filtrze. Menu bez własnej warstwy
     byłoby blokiem na poziomie 0: pierwsze kliknięcie w nawigację przy otwartym filtrze trafiałoby
     w przezroczyste tło i tylko zamykało filtr. */
  position: relative; z-index: 20;
}
body.ds-sidebar-collapsed .ds-sidebar { width: var(--ds-sidebar-w-collapsed); }
/* Wąskie okno nie ma z czego oddać 220 px — menu wchodzi w tryb ikon niezależnie od ciasteczka.
   Ta sama specyficzność co reguła bazowa, ale stoi niżej w pliku, więc wygrywa; przy zwiniętym
   ciasteczku wygrywa reguła z klasą na <body>, a obie dają tę samą szerokość. */
@media (max-width: 900px) { .ds-sidebar { width: var(--ds-sidebar-w-collapsed); } }

/* Podpis znika przez display:none, nie przez opacity. Wariant „przezroczysty, ale dalej w układzie”
   kusi płynnym zanikaniem, ale rozjeżdża geometrię zwiniętego menu: w wyśrodkowanej główce
   niewidzialna nazwa „Deadstock” dalej liczy się do szerokości i zbija logo z osi, a w stopce
   rezerwuje pusty wiersz pod awatarem. Animowana jest szerokość samego menu i to wystarcza. */
.ds-side-label { white-space: nowrap; }
body.ds-sidebar-collapsed .ds-side-label { display: none; }

/* Główka: logo i przełącznik obok siebie; po zwinięciu jeden pod drugim, oba na osi 28 px
   (10 px paddingu menu + połowa z 36 px światła) — tam, gdzie stają ikony nawigacji. */
/* Wcięcie z lewej 6, nie 10 jak w .ds-nav-item: znak logo ma 22 px, ikona nawigacji 14, więc przy
   równym wcięciu ich środki rozjeżdżają się o (22-14)/2 = 4 px i logo wisi 2 px w lewo od kolumny
   ikon. Różnica wcięć kasuje różnicę rozmiarów — oba środki wypadają na 27 px od krawędzi menu. */
.ds-side-head { display: flex; align-items: center; gap: 8px; padding: 2px 4px 2px 6px; }
body.ds-sidebar-collapsed .ds-side-head { flex-direction: column; align-items: center; gap: 6px; padding: 2px 0; }
.ds-side-head .ds-brand { flex: 1; min-width: 0; }
body.ds-sidebar-collapsed .ds-side-head .ds-brand { flex: 0 0 auto; }

.ds-side-toggle {
  width: 26px; height: 26px; flex-shrink: 0; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--ds-line); border-radius: 6px;
  background: transparent; color: var(--ds-muted); cursor: pointer;
  transition: background 100ms, color 100ms;
}
.ds-side-toggle:hover { background: var(--ds-line2); color: var(--ds-ink); }
.ds-side-toggle svg { transition: transform 180ms ease; }
body.ds-sidebar-collapsed .ds-side-toggle svg { transform: rotate(180deg); }

.ds-brand { display: inline-flex; align-items: center; gap: 8px; text-decoration: none; }
/* Znak marki, nie kwadrat z literą: rysunek jest konturem, więc tło i kolor tekstu odpadają, a
   zostaje sam kolor kreski. 22 px to nie estetyka, tylko warunek wyrównania opisany wyżej —
   zmiana rozmiaru wymaga zmiany wcięcia .ds-side-head, inaczej logo zjeżdża z kolumny ikon. */
.ds-brand-icon {
  width: 22px; height: 22px; flex-shrink: 0;
  display: block; color: var(--ds-ink);
}
.ds-brand-icon svg { display: block; width: 100%; height: 100%; }
.ds-brand-name { font-weight: 600; font-size: 14px; color: var(--ds-ink); letter-spacing: -0.2px; }

.ds-nav { display: flex; flex-direction: column; gap: 2px; }
.ds-nav-item {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 10px; font-size: 13px; color: var(--ds-muted);
  text-decoration: none; border-radius: 6px; background: transparent;
  white-space: nowrap; overflow: hidden;
  transition: background 100ms, color 100ms;
}
.ds-nav-item svg { flex-shrink: 0; }
body.ds-sidebar-collapsed .ds-nav-item { justify-content: center; padding: 7px 0; }
.ds-nav-item:hover { background: var(--ds-line2); color: var(--ds-ink); }
.ds-nav-item.active { background: var(--ds-line2); color: var(--ds-ink); font-weight: 500; }
.ds-nav-item.disabled { pointer-events: none; opacity: 0.5; }

/* Czerwony wariant pozycji menu (.ds-nav-item--danger) miał jedno użycie — „Wyczyść dane”. Przycisk
   przeniósł się na /konto, do funkcji administratorskich, i jest tam zwykłym .ds-btn, więc reguła
   nie miała już czego malować. Menu składa się dziś wyłącznie z linków do widoków. */

/* ── Stopka menu: konto ─────────────────────────────────────
   Jedyny element stopki. Nie dla wyglądu: menu konta wysuwa się w górę z pozycji fixed, więc jego
   odstęp od dołu ekranu jest sumą wysokości wszystkiego, co stoi pod nim (patrz .ds-acct-menu).
   Numer wersji, który stał tu wcześniej, przeniósł się do stopki samego menu.

   Przełącznik języka (.ds-lang, .ds-lang-btn) stał tu wcześniej jako para przycisków z flagami;
   dziś jest wierszem w menu konta, ale reguły zostają — nosi je jeszcze arkusz „Więcej” na
   telefonie, gdzie język ma własny wiersz i miejsca na dwie flagi wystarcza. */
.ds-side-foot { display: flex; flex-direction: column; gap: 8px; }

.ds-lang { display: flex; align-items: center; gap: 2px; }
body.ds-sidebar-collapsed .ds-lang { flex-direction: column; align-items: center; }
.ds-lang-btn {
  display: flex; align-items: center; gap: 6px;
  padding: 4px 8px; border-radius: 5px;
  border: 1.5px solid transparent; background: transparent;
  cursor: pointer; font-family: inherit; font-size: 12px; font-weight: 600; line-height: 1;
  color: var(--ds-ink); opacity: .45;
  transition: opacity 150ms, border-color 150ms, background 150ms;
}
.ds-lang-btn img { border-radius: 2px; flex-shrink: 0; display: block; }
.ds-lang-btn--on { opacity: 1; border-color: var(--ds-accent); background: var(--ds-line2); }

/* ── Menu konta ─────────────────────────────────────────────
   Na zewnątrz widać wyłącznie nazwę konta; wszystko, co się z kontem robi — ustawienia, język,
   zarządzanie użytkownikami, wylogowanie — jest pod nią. Stopka niosła wcześniej cztery niezależne
   kontrolki obok siebie, więc rzecz klikana raz w życiu (język) sąsiadowała z tą, którą klika się
   przez pomyłkę (wylogowanie), a każda kolejna pozycja konta musiałaby walczyć o te same 200 px. */
.ds-acct { position: relative; }

.ds-acct-btn {
  display: flex; align-items: center; gap: 8px;
  /* Ujemny margines i dopełnienie wyrównują tło pod kursorem z powrotem do kolumny ikon nawigacji,
     którą samo tło by rozepchnęło — ta sama sztuczka, co przy dawnym linku konta. */
  margin: -3px -4px; padding: 3px 4px; width: calc(100% + 8px);
  border: none; border-radius: 6px; background: transparent;
  font-family: inherit; text-align: left; color: inherit; cursor: pointer;
  transition: background 100ms;
}
.ds-acct-btn:hover, .ds-acct-btn.is-open { background: var(--ds-line2); }
body.ds-sidebar-collapsed .ds-acct-btn { justify-content: center; margin: 0; padding: 3px 0; width: 100%; }
.ds-acct-btn-name {
  flex: 1; min-width: 0; font-size: 11.5px; color: var(--ds-muted);
  overflow: hidden; text-overflow: ellipsis;
}
.ds-acct-btn-chevron { color: var(--ds-muted2); flex-shrink: 0; }

/* position: fixed, a nie absolute względem .ds-acct: menu boczne ma overflow:hidden (przycina
   podpisy w trakcie zwijania) i przycięłoby też wysunięty panel. Fixed z tego wychodzi, bo
   .ds-sidebar nie ma transformu ani filtra, które robiłyby z niego blok zawierający — ale traci
   zaczep w przycisku, więc odstęp od dołu jest policzony: 12 px dopełnienia menu − 3 px ujemnego
   marginesu .ds-acct-btn + 32 px samego przycisku (26 px kółka + 2×3 px dopełnienia) + 6 px światła.
   Przycisk konta jest jedynym elementem stopki właśnie po to, żeby ta suma miała cztery składniki,
   a nie tyle, ile ktoś kiedyś dołoży pod spodem.
   z-index 26 — nad .ds-filter-backdrop (25), czyli tłem, które zamyka menu kliknięciem obok. */
.ds-acct-menu {
  position: fixed; left: 8px; bottom: 47px; z-index: 26;
  width: 246px; max-width: calc(100vw - 16px);
  display: flex; flex-direction: column; gap: 1px; padding: 6px;
  background: var(--ds-surface); border: 1px solid var(--ds-line);
  border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,.20);
}

/* Kto jest zalogowany, pełną nazwą i adresem. Po zwinięciu menu bocznego przycisk pod spodem
   pokazuje samo kółko z inicjałami — to jedyne miejsce, w którym da się sprawdzić, na czyim
   koncie się jest, bez wchodzenia w ustawienia. */
.ds-acct-head {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 8px 10px; margin-bottom: 5px;
  border-bottom: 1px solid var(--ds-line);
}
.ds-acct-head-id { min-width: 0; }
.ds-acct-head-name {
  font-size: 12.5px; font-weight: 600; color: var(--ds-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ds-acct-head-mail {
  font-family: 'Geist Mono', monospace; font-size: 10.5px; color: var(--ds-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px;
}

.ds-acct-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 7px 8px; border: none; border-radius: 6px; background: transparent;
  font-family: inherit; font-size: 12.5px; text-align: left; text-decoration: none;
  color: var(--ds-ink2); cursor: pointer;
  transition: background 100ms, color 100ms;
}
.ds-acct-item:hover { background: var(--ds-line2); color: var(--ds-ink); }
.ds-acct-item svg, .ds-acct-item img { flex-shrink: 0; }
.ds-acct-item img { border-radius: 2px; display: block; }
.ds-acct-item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Wybrana wartość na zwiniętym wierszu — po odpowiedź „w jakim języku jestem” nie trzeba nic otwierać. */
.ds-acct-item-value { flex-shrink: 0; font-size: 11.5px; color: var(--ds-muted); }
.ds-acct-item-chevron { color: var(--ds-muted2); transition: transform 150ms ease; }
.ds-acct-item-chevron.is-open { transform: rotate(90deg); }

/* Podmenu wysuwane w bok (dziś: język) — jak w menu aplikacji desktopowej. Panel jest w DOM-ie
   zawsze; pokazuje go najechanie ALBO klasa .is-open od kliknięcia, więc działa i myszą, i dotykiem,
   bez dwóch źródeł prawdy o tym, czy jest otwarty. */
.ds-acct-flyout { position: relative; }
/* 11 px = 6 px dopełnienia .ds-acct-menu (zaczep stoi w środku, nie przy krawędzi) + 5 px światła
   między dwoma cieniami. Bez składnika dopełnienia panel wchodzi 1 px POD menu i cień się zlewa. */
.ds-acct-submenu {
  position: absolute; left: calc(100% + 11px); top: -7px; z-index: 1;
  display: none; flex-direction: column; gap: 1px; padding: 6px;
  min-width: 176px;
  background: var(--ds-surface); border: 1px solid var(--ds-line);
  border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,.20);
}
/* Mostek dla kursora przez 5-pikselową szczelinę między menu a podmenu. Bez niego droga myszą do
   wybranej pozycji prowadzi przez obszar, w którym podmenu się zamyka — i pozycja ucieka spod
   kursora w połowie ruchu. Pseudoelement, a nie sklejenie paneli: szczelina jest tu po to, żeby
   dwa cienie nie zlewały się w jeden prostokąt. */
.ds-acct-submenu::before { content: ""; position: absolute; top: 0; bottom: 0; left: -13px; width: 13px; }
.ds-acct-flyout:hover > .ds-acct-submenu,
.ds-acct-flyout.is-open > .ds-acct-submenu { display: flex; }
.ds-acct-flyout:hover > .ds-acct-item,
.ds-acct-flyout.is-open > .ds-acct-item { background: var(--ds-line2); color: var(--ds-ink); }
.ds-acct-item.is-on { color: var(--ds-ink); font-weight: 500; }

.ds-acct-sep { height: 1px; background: var(--ds-line); margin: 5px 0; }

/* Wylogowanie jest jedyną pozycją, która coś kończy — i jedyną klikaną przez pomyłkę. Czerwień
   dopiero pod kursorem: stale czerwony wiersz w menu czyta się jak ostrzeżenie o awarii. */
.ds-acct-item--danger:hover { background: var(--ds-bad-bg); color: var(--ds-bad); }

/* Wersja buildu w stopce menu. Nie jest pozycją — nic nie robi po kliknięciu — więc nie dostaje
   ani tła pod kursorem, ani wskaźnika myszy. */
.ds-acct-version {
  margin: 5px -6px -6px; padding: 7px 14px 5px;
  border-top: 1px solid var(--ds-line);
  font-family: 'Geist Mono', monospace; font-size: 10px; color: var(--ds-muted2);
  user-select: none; cursor: default;
}

/* ── Awatar konta ───────────────────────────────────────────
   Jeden okrąg z inicjałem dla wszystkich miejsc, w których pojawia się człowiek: konto w topbarze,
   opiekun rekordu (Trello #106) w kolumnie tabeli, w panelu rekordu i w menu wyboru. Kolor tła bywa
   nadpisywany inline (liczony z id konta — to on, a nie litera, pozwala rozpoznać opiekuna rzutem
   oka na liście), rozmiar modyfikatorami -sm/-md; domyślne wartości poniżej obsługują topbar. */
.ds-avatar {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--ds-ink2); color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 600; line-height: 1;
  flex-shrink: 0; user-select: none;
}
.ds-avatar-sm { width: 24px; height: 24px; font-size: 10px; }
.ds-avatar-md { width: 32px; height: 32px; font-size: 12px; }

/* Pusty slot „przypisz opiekuna” — przerywany okrąg z plusem. */
.ds-avatar-add {
  width: 32px; height: 32px; border-radius: 50%;
  border: 1.5px dashed var(--ds-line);
  background: none; cursor: pointer; color: var(--ds-muted);
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0; padding: 0;
  transition: border-color 120ms, color 120ms, background 120ms;
}
.ds-avatar-add:hover:not(:disabled) {
  border-color: var(--ds-ink2); color: var(--ds-ink2); background: var(--ds-line2);
}
.ds-avatar-btn { background: none; border: none; padding: 0; cursor: pointer; line-height: 0; }
.ds-avatar-btn:disabled, .ds-avatar-add:disabled { cursor: default; opacity: .6; }

/* ── Popover wyboru ─────────────────────────────────────────
   Układ jak w oknie „Członkowie” w Trello: tytuł, ✕, wyszukiwarka, lista pozycji. Nosi go menu
   opiekuna w panelu rekordu i każdy filtr paska nad listą (DsFilterPicker) — te dwa miejsca mają
   wyglądać i zachowywać się identycznie, więc reguły są wspólne, a nie kopiowane. */
.ds-owner-pop {
  position: absolute; z-index: 30; width: 304px; max-width: 90vw;
  background: var(--ds-surface); border: 1px solid var(--ds-line);
  border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.18);
  overflow: hidden;
}
.ds-owner-pop-head {
  display: flex; align-items: center; padding: 8px 8px 8px 36px;
  border-bottom: 1px solid var(--ds-line);
}
.ds-owner-pop-title {
  flex: 1; text-align: center; font-size: 12px; font-weight: 600; color: var(--ds-ink2);
}
.ds-owner-pop-close {
  width: 28px; height: 28px; border: none; background: none; border-radius: 6px;
  cursor: pointer; color: var(--ds-muted); display: inline-flex;
  align-items: center; justify-content: center; flex-shrink: 0;
}
.ds-owner-pop-close:hover { background: var(--ds-line2); color: var(--ds-ink2); }
.ds-owner-pop-body { padding: 10px 12px 12px; }
.ds-owner-pop-label {
  font-size: 10.5px; font-weight: 600; color: var(--ds-muted);
  text-transform: uppercase; letter-spacing: .6px; margin: 10px 0 6px;
}
.ds-owner-option {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  background: none; border: none; border-radius: 6px; padding: 6px 8px;
  cursor: pointer; font-size: 12.5px; color: var(--ds-ink);
}
.ds-owner-option:hover:not(:disabled) { background: var(--ds-line2); }
.ds-owner-option:disabled { cursor: default; opacity: .6; }

/* ── Filtr paska nad listą (DsFilterPicker) ─────────────────
   Przycisk rozwijający powyższy popover. Wygląd niesie jedną informację: czy ten filtr coś teraz
   robi. Nieaktywny jest szary i cichy, aktywny dostaje ramkę akcentu, nazwę wybranej pozycji i
   licznik — bo „nic nie wybrane” zastąpiło pozycję „Wszystkie” i musi być widoczne bez otwierania. */
/* z-index nad tłem (25), żeby przejście z jednego otwartego filtra na drugi kosztowało jedno
   kliknięcie. Wartość działa WEWNĄTRZ kontekstu układania paska (.ds-subheader ma z-index: 10),
   więc tło też musi być rysowane w pasku — inaczej przykryłoby wysunięty panel. */
.ds-filter { position: relative; z-index: 26; }
.ds-filter-btn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 32px; padding: 0 9px; max-width: 260px;
  background: var(--ds-bg); border: 1px solid var(--ds-line); border-radius: 6px;
  font-family: inherit; font-size: 12px; color: var(--ds-ink); cursor: pointer;
  transition: border-color 120ms, background 120ms;
}
.ds-filter-btn:hover { border-color: var(--ds-ink2); }
.ds-filter-btn-label { color: var(--ds-muted); white-space: nowrap; }
.ds-filter-btn-value {
  font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Licznik dostaje się na przycisk dopiero od dwóch wybranych pozycji (patrz DsFilterPicker): przy
   jednej przycisk pisze jej nazwę, więc „1” obok byłoby powtórzeniem tego samego cyfrą. */
.ds-filter-btn-count {
  min-width: 16px; padding: 0 4px; border-radius: 8px;
  background: var(--ds-accent); color: #fff; flex-shrink: 0;
  font-family: 'Geist Mono', monospace; font-size: 10px; line-height: 16px; text-align: center;
}
.ds-filter-btn-chevron { color: var(--ds-muted); flex-shrink: 0; margin-left: auto; }
/* Pusty slot filtra — ten sam przerywany okrąg z plusem, co przy rekordzie bez opiekuna, tylko
   zmniejszony do wysokości przycisku. Kółko jest tu <span>-em wewnątrz przycisku, więc kursor i
   stan hover bierze z niego; własnego :hover nie potrzebuje. */
.ds-filter-add { width: 18px; height: 18px; margin-left: auto; border-width: 1.2px; }
.ds-filter-btn:hover .ds-filter-add { border-color: var(--ds-ink2); color: var(--ds-ink2); }
.ds-filter-btn.is-active {
  border-color: var(--ds-accent); background: var(--ds-surface);
}
.ds-filter-btn.is-active .ds-filter-btn-label::after { content: ":"; }
.ds-filter-pop { left: 0; top: calc(100% + 6px); }
/* Wysokość pod NAJDŁUŻSZĄ listę o stałej długości — osiem statusów — żeby ta, której nie da się
   skrócić wyszukiwarką, mieściła się bez przewijania pionowego. Ograniczenie zostaje, bo listy
   opiekunów i importów rosną w nieskończoność; tam pasek przewijania jest właściwą odpowiedzią. */
.ds-filter-options { max-height: 340px; overflow-y: auto; margin: 0 -4px; padding: 0 4px; }
.ds-filter-option-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Człon, który ma zostać w jednej linii niezależnie od tego, co dzieje się z etykietą obok — dziś
   autor importu. flex-shrink: 0 plus nowrap, bo cała jego rola polega na tym, że NIE bierze udziału
   w łamaniu wiersza (patrz FilterOption.TrailingLabel). Wyblakły, bo odpowiada na pytanie zadawane
   drugie w kolejności: najpierw „który import”, dopiero potem „czyj”. */
.ds-filter-option-trailing {
  flex-shrink: 0; white-space: nowrap; color: var(--ds-muted); font-size: 11.5px;
  /* inline-flex, bo końcówka bywa parą „nazwisko + awatar” i te dwie rzeczy mają trzymać się razem
     — kółko opisuje człowieka stojącego obok, nie wiersz. Kółko idzie NA KOŃCU: przed nazwiskiem
     jego pozycja w poziomie zależała od długości nazwiska i kolumna awatarów była poszarpana. */
  display: inline-flex; align-items: center; gap: 6px;
}
/* Ptaszek wyboru zajmuje miejsce zawsze, widoczny jest tylko przy wybranej pozycji — inaczej
   kliknięcie przesuwałoby końcówkę wiersza w lewo o jego szerokość (patrz DsFilterPicker.razor). */
.ds-filter-option-check {
  margin-left: auto; flex-shrink: 0; color: var(--ds-ink2); visibility: hidden;
}
.ds-filter-option-check.is-on { visibility: visible; }
/* Kropka statusu w liście filtra (FilterOption.DotColor). Zajmuje tyle samo miejsca w poziomie co
   awatar opiekuna nie zajmuje — pozycje w obu listach zaczynają się w tej samej odległości od
   krawędzi, więc filtry stojące obok siebie nie wyglądają na rozjechane. Kolor idzie inline, bo
   jest własnością statusu, a nie klasy. */
.ds-filter-dot {
  width: 11px; height: 11px; border-radius: 50%;
  border: 1px solid transparent; box-sizing: border-box;
  flex-shrink: 0; margin-left: 3px; margin-right: 2px;
}

/* Wariant szeroki (filtr „Import"): nazwa importu to data plus opis źródła i musi być widoczna w
   całości — ucięta zostawia dwa importy wyglądające identycznie, czyli wybór po omacku. Stąd i
   szerszy panel, i zawijanie zamiast wielokropka: nazwa źródła bywa dłuższa niż każda rozsądna
   szerokość, więc samo poszerzenie panelu tylko przesuwałoby problem.

   460 → 520 px, odkąd wiersz niesie jeszcze autora importu (Trello #143). Sama szerokość NIE jest
   tu jednak lekarstwem na łamanie wierszy — o tym rozstrzyga rozdzielenie nazwiska od etykiety
   (.ds-filter-option-trailing); dokładanie pikseli tylko przesuwało granicę, za którą znowu trafiał
   się wiersz o kilka za długi. Wariant szeroki nosi wyłącznie filtr „Import”, więc ta liczba nie
   dotyka pozostałych filtrów paska. Górną granicą jest max-width: 90vw z .ds-owner-pop — na wąskim
   oknie panel dalej mieści się w ekranie. */
.ds-filter-wide .ds-filter-pop { width: 520px; }
.ds-filter-wide .ds-filter-option-label { white-space: normal; overflow: visible; text-overflow: clip; }
.ds-filter-wide .ds-owner-option { align-items: flex-start; }
.ds-filter-wide .ds-filter-btn { max-width: 320px; }
.ds-filter-clear {
  width: 100%; margin-top: 8px; padding: 6px 8px;
  background: none; border: 1px solid var(--ds-line); border-radius: 6px;
  font-family: inherit; font-size: 12px; color: var(--ds-muted); cursor: pointer;
}
.ds-filter-clear:hover { border-color: var(--ds-ink2); color: var(--ds-ink); }
/* Tło zamykające otwarty filtr kliknięciem obok. Leży pod popoverem (z-index 30) i pod samymi
   przyciskami filtrów, żeby przejście z jednego filtra na drugi kosztowało jedno kliknięcie,
   a nie dwa (zamknij, otwórz). */
.ds-filter-backdrop { position: fixed; inset: 0; z-index: 25; background: none; }

/* ── Page layout ───────────────────────────────────────────── */
/* main wypełnia całą wysokość powłoki; szerokość bierze to, co zostaje po menu bocznym.
   Było: height: calc(100% - 53px) — 53 to była wysokość paska górnego wpisana z ręki w dwóch
   miejscach naraz (tu i w .ds-subheader). Powłoka jest teraz flexem w poziomie, więc wysokość
   bierze się ze stretcha, a szerokość z flex:1 — żadna z tych liczb nie musi już nigdzie stać.
   min-width:0 jest obowiązkowe: bez niego tabela produktów rozepchnęłaby main poza ekran zamiast
   dostać własny suwak poziomy. */
main {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
}

/* ds-page wypełnia main; bez overflow:hidden — przewijaniem zajmuje się .ds-content */
.ds-page { flex: 1; min-height: 0; display: flex; flex-direction: column; }

.ds-subheader {
  display: flex; align-items: center; gap: 16px;
  padding: 10px 24px;
  border-bottom: 1px solid var(--ds-line);
  background: var(--ds-surface);
  /* position:relative, nie sticky. Sticky było tu martwe od początku: pasek jest RODZEŃSTWEM
     kontenera przewijania (.ds-content { overflow:auto }), a nie jego zawartością, więc nie miał
     od czego się odkleić — top:53px nie robił nic poza wiązaniem układu z wysokością nieistniejącego
     już paska górnego. Zostaje samo position, bo z-index:10 jest nośny: buduje kontekst układania,
     w którym mieszczą się tło filtrów (25) i wysunięte panele (30). Bez niego tło filtra uciekłoby
     na poziom dokumentu i przykryło menu boczne. */
  position: relative; z-index: 10;
  flex-shrink: 0;
}
.ds-subheader-title { font-size: 14px; font-weight: 600; }
.ds-subheader-meta  { font-size: 11.5px; color: var(--ds-muted); font-family: 'Geist Mono', monospace; margin-top: 2px; }
.ds-spacer { flex: 1; }

/* ── Liczba w pasku nad tabelą ──────────────────────────────────────────────
   Liczba stoi tuż przy swoim podpisie i nic nie rezerwuje z góry.

   Było inaczej: każda liczba miała `min-width: 4ch` z wyrównaniem do prawej, czyli miejsce na
   katalog do 9999 pozycji. Powód był układowy — pasek jest jednym rzędem flexa, więc szerokość
   liczby przesuwa wszystko, co za nią stoi, i przy wpisywaniu w wyszukiwarkę pasek drgał. Kosztem
   była pustka między krawędzią plakietki a krótką liczbą, przez którą „3 Niepełne” wyglądało na
   dosunięte do prawej, jakby plakietka miała dwa wyrównania naraz. Ten koszt został zgłoszony
   dwukrotnie jako usterka wyglądu i to rozstrzyga: rezerwacja znika, drganie przy filtrowaniu
   wraca jako świadoma cena.

   `tabular-nums` zostaje, bo jest za darmo — wyrównuje cyfry między sobą także w foncie
   proporcjonalnym (pasek nie jest monospace), więc liczby o tylu samo cyfrach nie ruszają niczym. */
.ds-count {
  font-variant-numeric: tabular-nums;
}

.ds-search {
  display: flex; align-items: center; gap: 8px;
  flex: 1; max-width: 380px; margin: 0 16px;
  border: 1px solid var(--ds-line); border-radius: 6px;
  padding: 5px 10px; background: var(--ds-bg);
}
.ds-search input {
  flex: 1; border: none; outline: none; background: transparent;
  font-size: 12.5px; color: var(--ds-ink); font-family: inherit;
}
/* Krzyżyk czyszczący pole. Chowa się i pokazuje SAM CSS-em, bez udziału serwera: `:placeholder-shown`
   trafia w pole puste, a `~` sięga po rodzeństwo stojące za inputem. To nie jest sprytniejszy zapis
   tego, co mógłby zrobić Blazor — to warunek, żeby DsSearchBox dalej nie renderował się na literę
   (Trello #96). Selektor wymaga niepustego placeholdera, o który dba EffectivePlaceholder. */
.ds-search-clear {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; flex-shrink: 0; padding: 0;
  border: none; border-radius: 4px; background: transparent;
  color: var(--ds-muted); cursor: pointer;
  transition: background 100ms, color 100ms;
}
.ds-search-clear:hover { background: var(--ds-line2); color: var(--ds-ink); }
.ds-search input:placeholder-shown ~ .ds-search-clear { display: none; }

.ds-search kbd {
  font-size: 10px; color: var(--ds-muted); font-family: 'Geist Mono', monospace;
  background: var(--ds-surface); padding: 1px 5px;
  border: 1px solid var(--ds-line); border-radius: 3px;
}

.ds-stats { display: flex; align-items: center; gap: 10px; }
.ds-progress { width: 72px; height: 4px; background: var(--ds-line2); border-radius: 2px; overflow: hidden; }
.ds-progress-fill { height: 100%; background: var(--ds-accent); border-radius: 2px; transition: width 400ms ease; }
.ds-stat-ok  { color: var(--ds-ok);  font-weight: 600; font-family: 'Geist Mono', monospace; }
.ds-stat-bad { color: var(--ds-bad); font-weight: 600; font-family: 'Geist Mono', monospace; }
.ds-divider  { width: 1px; height: 16px; background: var(--ds-line); }

/* 20/24 → 14/16 px (Trello #53): przy oknie 1280×600 ten padding zabierał 48 px szerokości
   i 40 px wysokości, czyli prawie cały zapas potrzebny tabeli i blisko całego wiersza. */
.ds-content { padding: 14px 16px; display: flex; flex-direction: column; gap: 14px; min-width: 0; overflow: auto; min-height: 0; }

/* ── Loading ───────────────────────────────────────────────── */
.ds-progress-card {
  padding: 18px 20px; background: var(--ds-surface);
  border: 1px solid var(--ds-line); border-radius: 10px;
  box-shadow: 0 2px 8px rgba(0,0,0,.04);
}
.ds-spinner {
  width: 16px; height: 16px; border: 2px solid var(--ds-line2);
  border-top-color: var(--ds-ink); border-radius: 50%;
  animation: spin 0.8s linear infinite; flex-shrink: 0;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Input panel ───────────────────────────────────────────── */
.ds-input-bar {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 12px 8px 16px;
  background: var(--ds-surface); border: 1px solid var(--ds-line); border-radius: 8px;
  font-size: 12.5px;
}
.ds-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.ds-dot-ok { background: var(--ds-ok); }
.ds-mono  { font-family: 'Geist Mono', monospace; }

.ds-input-panel {
  background: var(--ds-surface); border: 1px solid var(--ds-line); border-radius: 8px; padding: 18px;
}
.ds-input-header {
  display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;
}
.ds-input-label { font-size: 13px; font-weight: 600; }
.ds-input-hint  { font-size: 12px; color: var(--ds-muted); margin-top: 2px; }
.ds-textarea {
  width: 100%; resize: vertical; padding: 12px;
  font-family: 'Geist Mono', monospace; font-size: 12.5px; line-height: 1.6;
  color: var(--ds-ink); background: var(--ds-bg);
  border: 1px solid var(--ds-line); border-radius: 6px; outline: none;
}
.ds-textarea:focus { border-color: var(--ds-ink); }

/* ── Tabs ──────────────────────────────────────────────────── */
.ds-tabs {
  display: flex; gap: 2px; margin-bottom: 14px;
  border-bottom: 1px solid var(--ds-line); padding-bottom: 0;
}
.ds-tab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; font-size: 12.5px; font-weight: 500;
  color: var(--ds-muted); background: transparent; border: none;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  cursor: pointer; font-family: inherit; transition: color 100ms;
}
.ds-tab:hover { color: var(--ds-ink2); }
.ds-tab--active { color: var(--ds-ink); border-bottom-color: var(--ds-ink); }
.ds-tab-body { padding: 2px 0 4px; }

/* ── Buttons ───────────────────────────────────────────────── */
.ds-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 10px; font-size: 12px; font-weight: 500;
  color: var(--ds-ink2); background: var(--ds-surface);
  border: 1px solid var(--ds-line); border-radius: 6px;
  cursor: pointer; font-family: inherit;
  transition: background 100ms, border-color 100ms;
  white-space: nowrap;
}
.ds-btn:hover { background: var(--ds-line2); }
.ds-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.ds-btn-primary {
  color: #fff; background: var(--ds-ink); border-color: var(--ds-ink);
}
.ds-btn-primary:hover { background: #262626; border-color: #262626; }

/* ── Category chips ────────────────────────────────────────── */
.ds-chips { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ds-chips-label {
  font-size: 11px; color: var(--ds-muted);
  text-transform: uppercase; letter-spacing: 0.6px; margin-right: 4px;
}
.ds-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; font-size: 12px; font-weight: 500;
  color: var(--ds-muted); background: transparent;
  border: 1px solid var(--ds-line); border-radius: 999px;
  cursor: pointer; font-family: inherit;
  transition: all 100ms;
}
.ds-chip--on { color: var(--ds-ink); background: var(--ds-surface); border-color: var(--ds-ink); }
.ds-chip-count { font-family: 'Geist Mono', monospace; font-size: 10.5px; color: var(--ds-muted); }

/* ── Table ─────────────────────────────────────────────────── */
.ds-table-info {
  display: flex; justify-content: space-between; align-items: center;
  padding: 10px 16px; border-bottom: 1px solid var(--ds-line);
  font-size: 12.5px; color: var(--ds-muted);
}
.ds-table-info strong { color: var(--ds-ink); }

/* ── Doładowywanie listy partiami ───────────────────────────────
   Wybór rozmiaru partii: dwa przyciski jako jeden przełącznik, nie <select>. Wartości są dwie
   i obie mają być widoczne bez rozwijania — rozwijana lista chowałaby połowę stanu za kliknięciem
   i zajmowała tyle samo miejsca. */
.ds-pagesize { display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0; }
.ds-pagesize-label { font-size: 11px; color: var(--ds-muted); white-space: nowrap; }
.ds-pagesize-opt {
  font-family: 'Geist Mono', monospace; font-size: 11px; line-height: 1;
  padding: 4px 8px; cursor: pointer;
  color: var(--ds-muted); background: var(--ds-surface);
  border: 1px solid var(--ds-line); border-radius: 4px;
}
.ds-pagesize-opt:hover:not(:disabled) { color: var(--ds-ink); border-color: var(--ds-ink2); }
.ds-pagesize-opt:disabled { cursor: wait; opacity: .6; }
.ds-pagesize-opt--on { color: var(--ds-ink); border-color: var(--ds-ink); font-weight: 600; }

/* Stopka pod ostatnim wierszem. Przewija się razem z listą (jest w tym samym pojemniku), więc
   pojawia się dokładnie tam, gdzie użytkownik dojechał — a nie przykleja się do dołu ekranu,
   gdzie obiecywałaby więcej pozycji także wtedy, gdy nikt nie doszedł do końca. */
.ds-loadmore {
  display: flex; flex-direction: column; align-items: center;
  padding: 14px 16px 18px; gap: 2px;
}
.ds-loadmore-btn {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 12px; padding: 7px 16px; cursor: pointer;
  color: var(--ds-ink2); background: var(--ds-surface);
  border: 1px solid var(--ds-line); border-radius: 6px;
}
.ds-loadmore-btn:hover:not(:disabled) { color: var(--ds-ink); border-color: var(--ds-ink2); }
.ds-loadmore-btn:disabled { cursor: wait; color: var(--ds-muted); }
/* Wartownik: bez wysokości własnej treści, ale nie zerowej — element o zerowej wysokości bywa
   pomijany przez IntersectionObserver, więc automatyczne doładowanie po cichu przestawałoby działać
   i zostawałby sam przycisk. */
.ds-loadmore-sentinel { height: 1px; width: 100%; }
.ds-table {
  width: 100%; border-collapse: collapse; font-size: 12.5px;
  table-layout: fixed;
}
.ds-table th {
  text-align: left; padding: 8px 10px;
  font-size: 10.5px; font-weight: 500; color: var(--ds-muted);
  text-transform: uppercase; letter-spacing: 0.6px;
  background: var(--ds-bg); border-bottom: 1px solid var(--ds-line);
  white-space: nowrap;
}
.ds-table td { padding: 6px 10px; border-bottom: 1px solid var(--ds-line2); vertical-align: middle; }

/* Wykrzyknik przy nieudanym wywołaniu. Zastąpił napis („error”, „timeout”), który powtarzał się w co
   drugim wierszu i rozpychał kolumnę, niosąc tyle, co sam kolor. Kółko, a nie goły znak: pojedynczy
   wykrzyknik przy nazwie operacji czyta się jak literówka w tekście, a nie jak plakietka stanu.
   Powód stoi w title — kto pyta „co konkretnie”, i tak zatrzymuje się na tym wierszu. */
.ds-fail-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; margin-left: 5px; vertical-align: -2px;
  border-radius: 50%; background: var(--ds-bad-bg); border: 1px solid var(--ds-bad);
  color: var(--ds-bad); font-size: 10px; font-weight: 700; line-height: 1;
  cursor: help; flex-shrink: 0;
}

/* Wykres słupkowy na osi czasu (widok „Wykres” w Kosztach AI). Sam rysunek jest w SVG i skaluje się
   viewBoxem, więc tutaj zostaje tylko obudowa i stan pusty. */
.ds-chart { width: 100%; }
.ds-chart svg { overflow: visible; }              /* podpisy osi wychodzą poza pole rysunku */
.ds-chart-empty {
  padding: 48px 12px; text-align: center;
  font-size: 12.5px; color: var(--ds-muted);
}

/* Nagłówek sortujący. Przyciskiem jest CAŁA treść nagłówka, nie strzałka obok niej — celem
   kliknięcia jest kolumna. Dziedziczy typografię z `th`, więc nie odróżnia się od nagłówka, który
   sortowania nie ma; różnicę widać dopiero pod kursorem, gdzie i tak jest miejsce na tę informację. */
.ds-sort-header {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
}
.ds-sort-header:hover { color: var(--ds-ink2); }
/* Kolumny liczbowe są wyrównane do prawej — przycisk musi iść za komórką, inaczej nagłówek
   odkleja się od cyfr pod nim. */
.ds-table th[style*="right"] .ds-sort-header { justify-content: flex-end; width: 100%; }

/* ── Tabela produktów: szerokość i próg responsywny (Trello #53) ──────────────
   Padding 14 → 10 px w poziomie: przy 13 kolumnach te 4 px z każdej strony to
   ~100 px na wiersz oddane na marginesy zamiast na treść — dokładnie ten rząd
   wielkości, którego brakowało, żeby tabela zmieściła się bez suwaka. W pionie
   8 → 6 px, bo wysokość wiersza przekłada się 1:1 na to, ile produktów widać.

   min-width = suma stałych szerokości <th> (1028 px) + 200 px minimum na kolumnę
   „Nazwa”, która jako jedyna nie ma wymiaru i zbiera wolne miejsce. Zmiana
   szerokości którejkolwiek kolumny w Produkty.razor wymaga poprawienia tych
   dwóch liczb — inaczej albo wraca suwak, albo zostaje niewykorzystany pas.

   Poniżej progu znika „Data importu” (-106 px). Punkt wyjścia to 1300 px: moment,
   w którym pełny układ przestaje się mieścić w oknie, bo 1228 + 32 px paddingu
   .ds-content + ramka + pionowy suwak ≈ 1280 px, a szerokość suwaka zależy od
   systemu, więc próg stoi wyżej niż wyliczenie. Kolumna jest schowana, a nie
   usunięta — na szerokim ekranie wraca sama.

   Te 1300 liczyło się z założenia, że cała szerokość okna należy do treści. Odkąd po lewej stoi
   menu, ta sama tabela przestaje się mieścić o jego szerokość wcześniej — próg przesuwa się więc
   dokładnie o tyle, ile menu zabiera: 1300 + 220 dla rozwiniętego, 1300 + 56 dla zwiniętego.
   Media query nie czyta zmiennych CSS, więc stanem menu jest klasa na <body>; specyficzność
   „body.x .ds-table--products” (0,2,1) bije bazę (0,1,0) bez !important.

   Skutek uboczny jest zamierzony: między 1357 a 1520 px zwinięcie menu ODDAJE „Datę importu”,
   czyli przełącznik ma widoczny sens także wtedy, gdy nikt nie szuka miejsca na nazwę. */
.ds-table--products { min-width: 1294px; }

@media (max-width: 1520px) {   /* 1300 + 220 — menu rozwinięte */
  body:not(.ds-sidebar-collapsed) .ds-table--products { min-width: 1188px; }
  body:not(.ds-sidebar-collapsed) .ds-table--products .ds-col-imported { display: none; }
}
@media (max-width: 1356px) {   /* 1300 + 56 — menu zwinięte */
  body.ds-sidebar-collapsed .ds-table--products { min-width: 1188px; }
  body.ds-sidebar-collapsed .ds-table--products .ds-col-imported { display: none; }
}

/* Publish-in-flight badge in the Allegro column: a short "Wysyłanie" label plus the
   properties button. The step text and N/M counter used to sit in a CSS hover popup
   here; they moved into the properties modal, because hovering the row mid-send made
   a progress panel flash unbidden. Only the button's hover affordance remains. */
.ds-publish-info-btn    { transition: background 120ms, color 120ms; }
.ds-publish-info-btn:hover { background: var(--ds-line2); color: var(--ds-ink2); }
.ds-row { cursor: pointer; transition: background 80ms; }
.ds-row:hover td { background: #f7f7f7; }
/* Wiersz, do którego wrócił fokus po zamknięciu panelu rekordu (dsFocusRow). Znak jest ten sam,
   który nosi wiersz z otwartym panelem: belka na krawędzi, nie tło — tło mówi już "zaznaczony
   checkboxem", a dwa znaczenia na jednym nośniku czyta się jak jedno. Outline zdjęty, bo na <tr>
   przeglądarki rysują go rozbieżnie; belka na pierwszej komórce jest tu idiomem od początku. */
.ds-row--open td,
.ds-row:focus td { background: var(--ds-bg); }
.ds-row--open td:first-child,
.ds-row:focus td:first-child { box-shadow: inset 3px 0 0 var(--ds-accent); }
.ds-row:focus { outline: none; }
.ds-cell-img { width: 72px; padding: 4px 8px !important; }
.ds-product-img {
  width: 60px; height: 40px; object-fit: contain; border-radius: 4px;
  border: 1px solid var(--ds-line); background: #fff; display: block;
}
.ds-product-img-ph {
  width: 60px; height: 40px; border-radius: 4px;
  border: 1px solid var(--ds-line); background: var(--ds-line2);
  display: flex; align-items: center; justify-content: center;
  color: var(--ds-muted2);
}
.ds-cell-erp { width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ds-muted); font-size: 12px; }
.ds-cell-ean { width: 135px; white-space: nowrap; }
.ds-cell-name { min-width: 200px; overflow: hidden; }
.ds-cell-conf { width: 110px; white-space: nowrap; }
.ds-cell-cat  { width: 193px; white-space: nowrap; }
.ds-cell-status { width: 120px; white-space: nowrap; }
.ds-cell-chev { width: 28px; text-align: center; color: var(--ds-muted2); }

.ds-ean { font-family: 'Geist Mono', monospace; font-size: 12px; color: var(--ds-ink2); letter-spacing: 0.2px; }
.ds-name-primary { font-weight: 500; color: var(--ds-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-name-desc { font-size: 11px; color: var(--ds-muted); margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-alt-badge {
  margin-left: 8px; font-size: 10.5px; color: var(--ds-muted);
  font-family: 'Geist Mono', monospace; font-weight: 500;
  padding: 1px 6px; border: 1px solid var(--ds-line); border-radius: 3px;
  white-space: nowrap;
}

/* Confidence bar */
.ds-conf { display: inline-flex; align-items: center; gap: 8px; }
.ds-conf-pct { font-family: 'Geist Mono', monospace; font-size: 12px; font-weight: 500; min-width: 26px; text-align: right; font-variant-numeric: tabular-nums; }
.ds-conf-track { position: relative; width: 48px; height: 4px; background: var(--ds-line2); border-radius: 2px; }
.ds-conf-fill  { position: absolute; inset: 0; border-radius: 2px; }

/* Tag */
.ds-tag {
  display: inline-flex; align-items: center;
  padding: 2px 8px; font-size: 11px; font-weight: 500;
  color: var(--ds-ink2); background: var(--ds-line2);
  border: 1px solid var(--ds-line); border-radius: 999px;
  white-space: nowrap;
}

/* Status segmented */
.ds-status-seg { display: inline-flex; border: 1px solid var(--ds-line); border-radius: 6px; overflow: hidden; background: var(--ds-surface); }
.ds-status-btn {
  width: 32px; height: 26px; border: none; font-size: 12px; font-weight: 600;
  cursor: pointer; font-family: inherit; background: var(--ds-surface); color: var(--ds-muted2);
  transition: background 100ms, color 100ms;
}
.ds-status-btn + .ds-status-btn { border-left: 1px solid var(--ds-line); }
.ds-status-btn--ok  { background: var(--ds-ok-bg);  color: var(--ds-ok); }
.ds-status-btn--bad { background: var(--ds-bad-bg); color: var(--ds-bad); }
.ds-status-btn--def { background: var(--ds-line2);  color: var(--ds-muted); }

/* ── Drawer ────────────────────────────────────────────────── */
.ds-drawer {
  background: var(--ds-surface); border-left: 1px solid var(--ds-line);
  display: flex; flex-direction: column; overflow: hidden;
  min-height: 0;
}
.ds-drawer-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--ds-line);
  background: var(--ds-surface); position: sticky; top: 0; z-index: 1;
  flex-shrink: 0;
}
.ds-drawer-label { font-size: 10.5px; color: var(--ds-muted); text-transform: uppercase; letter-spacing: 0.6px; }
.ds-drawer-title { font-size: 14px; font-weight: 600; color: var(--ds-ink); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ds-drawer-body { padding: 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; }

/* ── Porównanie kandydatów (Trello #81) ────────────────────────
   Kolumna = kandydat, wiersz = cecha. Style są tutaj, a nie inline w Shared/DsCandidateCompare.razor,
   bo sticky, hover i pseudoelementy nie dają się zapisać atrybutem style. Poprzedni, martwy blok
   .ds-candidate* opisywał pionowe karty, których ten widok już nie renderuje. */
.ds-cmp { padding: 14px 0 14px 20px; }
.ds-cmp-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding-right: 20px; margin-bottom: 10px;
}
.ds-cmp-count { font-size: 11px; color: var(--ds-muted); text-transform: uppercase; letter-spacing: .6px; }
.ds-cmp-toggle {
  background: none; border: 1px solid var(--ds-line); border-radius: 4px;
  padding: 3px 9px; font-size: 11px; font-family: inherit; color: var(--ds-muted);
  cursor: pointer; transition: color 120ms, border-color 120ms; white-space: nowrap;
}
.ds-cmp-toggle:hover { color: var(--ds-ink); border-color: var(--ds-muted2); }

/* Przewijanie w bok żyje tutaj: przy większej liczbie kandydatów kolumny mają zostać czytelne,
   a nie ścisnąć się do nieczytelnej szerokości. */
.ds-cmp-scroll { overflow-x: auto; padding-right: 20px; }
/* Bez overflow i bez zaokrągleń na samej siatce: overflow (choćby hidden dla promienia) czyni z niej
   najbliższy kontener przewijania, a wtedy przyklejona kolumna etykiet przestaje się przyklejać —
   jej punktem odniesienia musi zostać .ds-cmp-scroll. */
.ds-cmp-grid {
  display: grid; min-width: min-content;
  border: 1px solid var(--ds-line); background: var(--ds-surface);
}
.ds-cmp-cell {
  padding: 8px 10px; font-size: 12px; color: var(--ds-ink2); line-height: 1.45;
  border-bottom: 1px solid var(--ds-line2); border-left: 1px solid var(--ds-line2);
  min-width: 0; overflow-wrap: anywhere;
}
/* Kolumna etykiet zostaje na ekranie, gdy kolumny kandydatów jadą w bok — bez niej po dwóch
   przesunięciach nie wiadomo, co się porównuje. */
.ds-cmp-lbl {
  position: sticky; left: 0; z-index: 2;
  border-left: none; background: var(--ds-bg);
  font-size: 11px; color: var(--ds-muted);
}
.ds-cmp-head { background: var(--ds-bg); border-bottom: 1px solid var(--ds-line); }
.ds-cmp-head-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ds-cmp-rank {
  font-size: 10px; font-weight: 600; color: var(--ds-muted);
  font-family: 'Geist Mono', monospace;
  padding: 1px 5px; border: 1px solid var(--ds-line); border-radius: 3px;
}
.ds-cmp-conf {
  display: flex; align-items: center; gap: 6px;
  font-size: 10.5px; font-weight: 600; font-family: 'Geist Mono', monospace;
}
.ds-cmp-conf-track {
  display: block; width: 44px; height: 4px; border-radius: 2px;
  background: var(--ds-line2); overflow: hidden;
}
.ds-cmp-conf-fill { display: block; height: 100%; }
.ds-cmp-pick {
  margin-top: 6px; padding: 3px 10px; font-size: 11px; font-family: inherit; font-weight: 500;
  background: var(--ds-ink); color: #fff; border: none; border-radius: 4px; cursor: pointer;
  transition: opacity 120ms;
}
.ds-cmp-pick:hover { opacity: .85; }
.ds-cmp-chosen {
  display: inline-block; margin-top: 6px; padding: 3px 10px;
  font-size: 11px; font-weight: 500; color: #fff;
  background: var(--ds-ok-soft); border-radius: 4px;
}
.ds-cmp-noean  { display: inline-block; margin-top: 6px; font-size: 11px; color: var(--ds-muted2); }
/* Różnica = powód, dla którego ta siatka istnieje; wspólne wiersze mają zniknąć w tle. */
.ds-cmp-cell.is-diff { background: var(--ds-warn-bg); }
/* Wybrany kandydat: cała jego kolumna na własnym tle, plus zielona plakietka i podkreślenie w
   nagłówku. Wcześniej była to czarna kreska przy lewej krawędzi kolumny — a że wybrany stoi zwykle
   pierwszy, kreska wypadała między nazwami cech a pierwszym kandydatem i czytała się jako linia
   podziału tabeli, nie jako zaznaczenie. Tło kolumny wygrywa z tłem różnicy (reguła niżej), żeby
   kolumna czytała się jako jedna całość — który wiersz się różni, mówi i tak przyklejona etykieta. */
.ds-cmp-cell.is-chosen { background: var(--ds-ok-bg); }
.ds-cmp-head.is-chosen { border-bottom: 2px solid var(--ds-ok-soft); }
.ds-cmp-cell.is-chosen .ds-cmp-name { color: var(--ds-ok-soft); }
.ds-cmp-name { font-weight: 600; color: var(--ds-ink); }
.ds-cmp-mono { font-family: 'Geist Mono', monospace; font-size: 11px; }
.ds-cmp-empty { color: var(--ds-muted2); }
.ds-cmp-thumbs { display: flex; flex-wrap: wrap; gap: 6px; }
.ds-cmp-thumbs img {
  width: 48px; height: 48px; object-fit: contain; background: #fff;
  border: 1px solid var(--ds-line); border-radius: 4px; cursor: pointer;
}
.ds-cmp-desc-rank {
  font-family: 'Geist Mono', monospace; font-size: 10px; color: var(--ds-muted2);
  margin-bottom: 4px;
}
.ds-cmp-desc { font-size: 11.5px; color: var(--ds-muted); line-height: 1.5; }

/* ── Źródła danych (Trello #85) ────────────────────────────
   Link do karty producenta jest przypisem, a nie treścią: ma dać się kliknąć i zniknąć z pola
   widzenia, kiedy się go nie potrzebuje. Stąd rozmiar 10.5px i kolor stonowany — nazwa produktu
   i EAN muszą zostać najgłośniejsze w kolumnie kandydata.

   Plakietka źródła wewnętrznego wygląda INACZEJ niż link i celowo nie udaje, że jest klikalna:
   nie ma podkreślenia, nie ma kursora-łapki, nie zmienia się pod myszą. Gdyby wyglądała jak link,
   pierwszą reakcją byłoby kliknięcie i wniosek, że aplikacja jest zepsuta. */
.ds-src {
  display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px;
  font-size: 10.5px; line-height: 1.4;
}
.ds-src-label { color: var(--ds-muted2); }
.ds-src-link {
  display: inline-flex; align-items: center; gap: 4px; max-width: 100%;
  padding: 1px 6px; border-radius: 4px;
  border: 1px solid var(--ds-line); background: var(--ds-surface);
  color: var(--ds-muted); text-decoration: none;
}
.ds-src-link:hover { color: var(--ds-ink); border-color: var(--ds-muted2); text-decoration: underline; }
.ds-src-ico  { font-size: 9px; line-height: 1; }
.ds-src-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 130px; }
.ds-src-more { color: var(--ds-muted2); }
.ds-src-internal {
  display: inline-block; padding: 1px 6px; border-radius: 4px;
  border: 1px dashed var(--ds-line); color: var(--ds-muted2);
  cursor: default; white-space: nowrap;
}
/* „Brak danych" to trzeci stan, nie odmiana źródła wewnętrznego — stąd brak ramki. Plakietka
   z ramką czyta się jak etykieta czegoś, co istnieje; tutaj nie istnieje nic i ma to wyglądać
   jak pusta rubryka, a nie jak nazwa źródła. */
.ds-src-none { color: var(--ds-muted2); cursor: default; white-space: nowrap; }

.ds-comment-section { display: flex; flex-direction: column; gap: 6px; }
.ds-drawer-hint {
  display: flex; justify-content: space-between; align-items: center;
  padding: 12px; border: 1px dashed var(--ds-line); border-radius: 6px;
  font-size: 11.5px; color: var(--ds-muted);
}

/* ── Allegro publishing animation ─────────────────────────── */
@keyframes al-cw     { to { transform: rotate(360deg);  } }
@keyframes al-ccw    { to { transform: rotate(-360deg); } }
@keyframes al-wrench {
  0%,100% { transform: rotate(-20deg); }
  50%     { transform: rotate(20deg);  }
}
@keyframes al-spark {
  0%   { opacity:1; transform:scale(.4) translate(0,0); }
  100% { opacity:0; transform:scale(1)  translate(var(--sx,12px),var(--sy,-16px)); }
}
@keyframes al-shimmer {
  0%   { background-position:-200% center; }
  100% { background-position: 200% center; }
}
@keyframes al-fadein { from { opacity:0; transform:translateY(4px); } to { opacity:1; transform:none; } }
@keyframes al-bar    { from { width:0%; } }

/* Gears — applied directly via style attr for reliable transform-origin */
.al-gear { display:block; flex-shrink:0; }

/* Sparks container */
.al-sparks { position:relative; width:12px; height:12px; margin:0 2px; }
.al-spark  {
  position:absolute; top:50%; left:50%;
  width:5px; height:5px; border-radius:50%; background:#ffcc00;
  animation:al-spark .8s ease-out infinite;
}
.al-spark:nth-child(2) { animation-delay:.27s; --sx:-10px; --sy:-12px; }
.al-spark:nth-child(3) { animation-delay:.54s; --sx:14px;  --sy:-8px;  }

/* Labels */
.al-title {
  font-size:13px; font-weight:700;
  background:linear-gradient(90deg,#ff5a00,#ff9040,#ff5a00);
  background-size:200%;
  -webkit-background-clip:text; -webkit-text-fill-color:transparent;
  animation:al-shimmer 2.2s linear infinite;
}
.al-product { font-size:12px; font-weight:500; color:var(--ds-ink2);
              animation:al-fadein .3s ease; white-space:nowrap;
              overflow:hidden; text-overflow:ellipsis; max-width:340px; margin:4px 0 2px; }
.al-step    { font-size:11.5px; color:#ff5a00; display:flex; align-items:center;
              justify-content:center; gap:5px; animation:al-fadein .3s ease; }
.al-counter { font-size:11px; color:var(--ds-muted); font-family:'Geist Mono',monospace; margin:6px 0 3px; }
.al-progress-bar  { height:4px; background:var(--ds-line2); border-radius:2px; overflow:hidden; }
.al-progress-fill { height:100%; background:linear-gradient(90deg,#ff5a00,#ffaa55);
                    border-radius:2px; transition:width .5s ease; animation:al-bar .5s ease; }

/* ── Modal ─────────────────────────────────────────────────── */
.ds-modal-backdrop {
  position: fixed !important;
  top: 0 !important; left: 0 !important;
  width: 100% !important; height: 100% !important;
  background: rgba(0, 0, 0, 0.6);
  display: flex !important; align-items: center !important; justify-content: center !important;
  z-index: 9999 !important;
  overflow: hidden;
}
.ds-modal {
  background: var(--ds-surface); border: 1px solid var(--ds-line);
  border-radius: 10px; width: 480px; max-width: calc(100% - 32px);
  box-shadow: 0 24px 64px rgba(0,0,0,.3);
  display: flex; flex-direction: column;
  max-height: 90vh; overflow-y: auto;
  transition: width .25s ease;
}
/* Publishing: +25% */
.ds-modal--publishing { width: 600px; }
.ds-modal--wide      { width: 620px; }
/* Done / results: +50% szerokości względem bazowej */
.ds-modal--done { width: 1080px; }
.ds-modal-header {
  display: flex; align-items: flex-start; justify-content: space-between;
  padding: 18px 20px 14px; border-bottom: 1px solid var(--ds-line);
}
.ds-modal-body  { padding: 18px 20px; display: flex; flex-direction: column; gap: 0; }
.ds-modal-footer {
  display: flex; justify-content: flex-end; gap: 8px;
  padding: 14px 20px; border-top: 1px solid var(--ds-line);
}

/* Radzen notification toasts. Allegro validation errors carry long unbreakable tokens
   ("ConstraintViolationException.ValidReturnPolicy", "AvailableStockMustEqualToZero");
   a per-product publish failure dumps the whole thing into a toast, which pushed the
   toast off the right edge of the screen. Cap the width and force wrapping. */
.rz-notification { max-width: min(460px, calc(100vw - 32px)); box-sizing: border-box; }
.rz-notification, .rz-notification * {
  overflow-wrap: anywhere;
  word-break: break-word;
  white-space: normal;
}

/* The marker is assigned only to automatically closing toasts, and carries their duration. It
   deliberately targets the
   element carrying NotificationMessage.Style, so the strip remains available whether Radzen puts
   that style on the wrapper or on the notification item. */
[style*="--ds-notification-countdown"] {
  position: relative !important;
  overflow: hidden !important;
}
[style*="--ds-notification-countdown"]::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  height: 3px;
  background: rgba(0, 0, 0, .42);
  transform-origin: left center;
  animation: ds-notification-countdown var(--ds-notification-countdown) linear forwards;
}
@keyframes ds-notification-countdown {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

/* ── Product description HTML (Opis ogólny, Trello #112) ─────── */
.ds-desc-html p { margin: 0 0 6px; }
.ds-desc-html p:last-child { margin-bottom: 0; }
.ds-desc-html ul, .ds-desc-html ol { margin: 4px 0 6px; padding-left: 18px; }
.ds-desc-html li { margin: 1px 0; }
.ds-desc-html b, .ds-desc-html strong { color: var(--ds-ink2); }

/* ── Allegro section editor (Opis Allegro, Trello #112) ─────── */
.ds-allegro-edit {
  border: 1px solid var(--ds-line2); border-radius: 4px; padding: 8px 10px;
  min-height: 120px; font-size: 12.5px; line-height: 1.5; color: var(--ds-ink2);
  background: var(--ds-surface); outline: none;
}
.ds-allegro-edit:focus { border-color: var(--ds-line); box-shadow: none; }   /* subtle — no dark/thick ring */
.ds-allegro-edit.ds-allegro-bad { border-color: #f5c77e; background: #fffbeb; }
.ds-allegro-edit h1 { font-size: 15px; font-weight: 700; margin: 2px 0 4px; }
.ds-allegro-edit h2 { font-size: 13px; font-weight: 700; margin: 4px 0 3px; }
.ds-allegro-edit p { margin: 0 0 4px; }
.ds-allegro-edit ul, .ds-allegro-edit ol { margin: 3px 0 4px; padding-left: 18px; }

/* Live preview mimics the Allegro listing look */
.ds-allegro-preview h1 { font-size: 16px; font-weight: 700; margin: 4px 0 6px; color: var(--ds-ink); }
.ds-allegro-preview h2 { font-size: 13.5px; font-weight: 700; margin: 8px 0 4px; color: var(--ds-ink); }

/* Small square buttons: formatting toolbar, section controls, add-kind chips */
.ds-fmt-btn, .ds-sec-btn {
  min-width: 24px; height: 22px; padding: 0 6px; font-size: 11px; line-height: 1;
  border: 1px solid var(--ds-line); border-radius: 3px; background: var(--ds-surface);
  color: var(--ds-ink2); cursor: pointer;
}
.ds-fmt-btn:hover, .ds-sec-btn:hover:not(:disabled) { background: var(--ds-bg); }
.ds-sec-btn:disabled { opacity: .35; cursor: default; }
.ds-sec-btn-danger:hover:not(:disabled) { border-color: #dc2626; color: #dc2626; }
.ds-add-kind {
  font-size: 11px; padding: 5px 12px; border: 1px solid var(--ds-line); border-radius: 4px;
  background: var(--ds-surface); color: var(--ds-ink2); cursor: pointer;
}
.ds-add-kind:hover { background: var(--ds-bg); border-color: var(--ds-accent); }

/* Kreator ↔ Podgląd segmented toggle */
.ds-seg { display: inline-flex; border: 1px solid var(--ds-line); border-radius: 5px; overflow: hidden; }
.ds-seg-btn {
  font-size: 10.5px; padding: 3px 10px; border: none; background: var(--ds-surface);
  color: var(--ds-muted); cursor: pointer;
}
.ds-seg-btn + .ds-seg-btn { border-left: 1px solid var(--ds-line); }
.ds-seg-on { background: var(--ds-ink); color: #fff; font-weight: 600; }

/* Image slot: photo with hover-✕ */
.ds-img-wrap { position: relative; display: inline-block; width: 100%; }
.ds-img {
  width: 100%; max-height: 180px; object-fit: contain; display: block;
  border: 1px solid var(--ds-line); border-radius: 6px; background: #fff;
}
.ds-img-x {
  position: absolute; top: 5px; right: 5px; width: 22px; height: 22px; line-height: 1;
  border: none; border-radius: 50%; background: rgba(17,17,17,.72); color: #fff; cursor: pointer;
  font-size: 12px; opacity: 0; transition: opacity 120ms; display: flex; align-items: center; justify-content: center;
}
.ds-img-wrap:hover .ds-img-x { opacity: 1; }
.ds-img-x:hover { background: #dc2626; }

/* Empty image slot: dashed "+" tile (Allegro-style add-photo field) */
.ds-img-add {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  min-height: 96px; border: 1.5px dashed var(--ds-line); border-radius: 6px;
  background: var(--ds-bg); color: var(--ds-muted); cursor: pointer; text-align: center; padding: 10px;
}
.ds-img-add:hover { border-color: var(--ds-accent); color: var(--ds-ink2); }
.ds-img-plus { font-size: 26px; line-height: 1; font-weight: 300; }

/* Image picker popover: gallery thumbs + upload tile */
.ds-img-picker { margin-top: 6px; padding: 8px; border: 1px solid var(--ds-line); border-radius: 6px; background: var(--ds-surface); }
.ds-img-thumb {
  width: 46px; height: 46px; object-fit: contain; border: 1px solid var(--ds-line);
  border-radius: 4px; background: #fff; cursor: pointer;
}
.ds-img-thumb:hover { border-color: var(--ds-accent); }
.ds-img-upload {
  width: 46px; height: 46px; display: flex; align-items: center; justify-content: center;
  border: 1.5px dashed var(--ds-line); border-radius: 4px; background: var(--ds-bg);
  color: var(--ds-muted); cursor: pointer;
}
.ds-img-upload:hover { border-color: var(--ds-accent); color: var(--ds-ink2); }

/* Allegro Sales Center-style section card: layout variants + actions + bottom toolbar */
.ds-sec-card { border: 1px solid var(--ds-line); border-radius: 8px; background: var(--ds-surface); }
.ds-sec-top {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 7px 10px; border-bottom: 1px solid var(--ds-line2);
}
.ds-layouts { display: flex; gap: 2px; }
.ds-lay {
  width: 30px; height: 26px; display: flex; align-items: center; justify-content: center;
  border: 1px solid transparent; border-radius: 5px; background: none; color: var(--ds-muted); cursor: pointer;
}
.ds-lay:hover { background: var(--ds-bg); color: var(--ds-ink2); }
.ds-lay-on { color: #e07b39; background: #fff4ec; border-color: #f3c9a8; }   /* Allegro orange */
.ds-sec-actions { display: flex; gap: 1px; }
.ds-act {
  width: 28px; height: 26px; display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 5px; background: none; color: var(--ds-muted); cursor: pointer;
}
.ds-act:hover:not(:disabled) { background: var(--ds-bg); color: var(--ds-ink); }
.ds-act:disabled { opacity: .3; cursor: default; }
.ds-sec-body { display: flex; gap: 12px; align-items: flex-start; padding: 12px; }

/* Inline model: the whole description is one document; sections are read-only until clicked */
.ds-allegro-doc {
  background: #fff; border: 1px solid var(--ds-line); border-radius: 8px;
  padding: 20px 22px; max-width: 1280px; margin: 0 auto;   /* +25% względem 900px */
  display: flex; flex-direction: column; gap: 4px;
}
.ds-sec-view {
  border: 1px dashed transparent; border-radius: 6px; padding: 6px 8px; margin: 0 -8px;
  cursor: text; transition: border-color 100ms;
}
.ds-sec-view:hover { border-color: var(--ds-line); }   /* jasna, przerywana obwódka; tło bez zmian */
.ds-sec-editing { margin: 4px 0; border-style: dashed; box-shadow: none; }   /* jasna przerywana ramka, nie ciemna */

/* Text field joins its bottom toolbar into one control (like Allegro) */
.ds-sec-body .ds-allegro-edit { border-radius: 5px 5px 0 0; border-bottom: none; min-height: 120px; }
.ds-textbar {
  display: flex; align-items: center; gap: 2px; flex-wrap: wrap;
  border: 1px solid var(--ds-line); border-radius: 0 0 5px 5px; padding: 3px 4px; background: var(--ds-surface);
}
.ds-tb {
  min-width: 26px; height: 26px; padding: 0 6px; display: inline-flex; align-items: center; justify-content: center;
  border: none; border-radius: 4px; background: none; color: var(--ds-ink2); cursor: pointer; font-size: 12px;
}
.ds-tb:hover:not(:disabled) { background: var(--ds-bg); }
.ds-tb:disabled { opacity: .3; cursor: default; }   /* wyszarzone undo/redo gdy akcja niemożliwa */
.ds-tb-style { font-size: 11.5px; white-space: nowrap; }
.ds-tb-sep { width: 1px; height: 18px; background: var(--ds-line); margin: 0 3px; }
.ds-styledrop { position: relative; }
.ds-styledrop-menu {
  position: absolute; bottom: 30px; left: 0; z-index: 20; min-width: 150px;
  background: var(--ds-surface); border: 1px solid var(--ds-line); border-radius: 6px;
  box-shadow: 0 6px 20px rgba(0,0,0,.14); padding: 4px; display: flex; flex-direction: column;
}
.ds-styledrop-menu button {
  text-align: left; border: none; background: none; padding: 6px 8px; border-radius: 4px;
  cursor: pointer; color: var(--ds-ink2);
}
.ds-styledrop-menu button:hover { background: var(--ds-bg); }

/* "DODAJ KOLEJNY WIERSZ" link-button */
.ds-add-row {
  align-self: center; margin-top: 4px; border: none; background: none; cursor: pointer;
  color: var(--ds-accent); font-size: 11.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; padding: 8px 12px;
}
.ds-add-row:hover { text-decoration: underline; }

/* Full-height maximize/restore handle on the drawer's left edge */
.ds-maxbar {
  position: absolute; left: 0; top: 0; bottom: 0; width: 14px; z-index: 5;
  border: none; border-right: 1px solid var(--ds-line2); background: var(--ds-bg);
  color: var(--ds-muted); cursor: pointer; padding: 0;
  display: flex; align-items: center; justify-content: center;
}
.ds-maxbar:hover { background: var(--ds-line2); color: var(--ds-ink); }

/* ── Odzyskiwanie połączenia (#components-reconnect-modal) ─────
   Zerwany obwód zatrzymuje w Blazor Serverze całą stronę, więc ten komunikat jest jedyną rzeczą,
   która wtedy jeszcze działa — i jedyną, która może powiedzieć, że nic nie jest zepsute na stałe.

   Widoczność wisi na atrybucie data-ds-rc, nie na klasach dopisywanych przez framework: powód
   rozpisany przy samym elemencie w _Host.cshtml. Selektor po id, żeby wygrał bez !important.

   Przyciski są schowane w pierwszych sekundach. Zerwanie zwykle mija samo, a przycisk pokazany od
   razu podpowiada klikanie w sytuacji, w której klikać nie ma po co — i zamienia mrugnięcie w awarię
   wymagającą decyzji. Odsłania je klasa ds-rc-slow: po 20 s ponowień albo od razu po nieudanej próbie. */
.ds-rc {
  position: fixed; inset: 0; z-index: 10000;
  display: none; align-items: center; justify-content: center;
  padding: 20px; background: rgba(10, 10, 10, .32);
}
#components-reconnect-modal[data-ds-rc="down"],
#components-reconnect-modal[data-ds-rc="failed"],
#components-reconnect-modal[data-ds-rc="rejected"] { display: flex; }

.ds-rc-card {
  display: flex; align-items: center; gap: 14px;
  width: 100%; max-width: 460px; padding: 16px 18px;
  background: var(--ds-surface); border: 1px solid var(--ds-line); border-radius: 10px;
  box-shadow: 0 20px 48px rgba(0, 0, 0, .22);
}
.ds-rc-spinner { width: 20px; height: 20px; border-width: 2.5px; }
.ds-rc-text  { flex: 1; min-width: 0; }
.ds-rc-title { font-size: 13px; font-weight: 600; color: var(--ds-ink); }
.ds-rc-note  { font-size: 11.5px; color: var(--ds-muted); margin-top: 2px; line-height: 1.45; }

.ds-rc-actions { display: none; flex-direction: column; gap: 6px; flex-shrink: 0; }
.ds-rc.ds-rc-slow .ds-rc-actions { display: flex; }
/* Nieudana próba niczego nie kręci — kręciołek znaczyłby, że coś jeszcze trwa. */
#components-reconnect-modal[data-ds-rc="failed"] .ds-rc-spinner { display: none; }

/* ── Blazor error UI (preserve) ────────────────────────────── */
#blazor-error-ui {
  background: lightyellow; bottom: 0; box-shadow: 0 -1px 2px rgba(0,0,0,.2);
  display: none; left: 0; padding: .6rem 1.25rem .7rem; position: fixed; width: 100%; z-index: 1000;
}
#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: .75rem; top: .5rem; }
.blazor-error-boundary {
  background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbmsiPjxwYXRoIGQ9Ik0yNjMuNTA2IDUxQzI2NC43MTcgNTEgMjY1LjgxMyA1MS40ODM3IDI2Ni42MDYgNTIuMjY1OEwyNjcuMDUyIDUyLjc5ODcgMjY3LjUzOSA1My42MjgzIDI5MC4xODUgOTIuMTgzMSAyOTAuNTQ1IDkyLjc5NSAyOTAuNjU2IDkyLjk5NkMyOTAuODc3IDkzLjUxMyAyOTEgOTQuMDgxNSAyOTEgOTQuNjc4MiAyOTEgOTcuMDY1MSAyODkuMDM4IDk5IDI4Ni42MTcgOTlMMjQwLjM4MyA5OUMyMzcuOTYzIDk5IDIzNiA5Ny4wNjUxIDIzNiA5NC42NzgyIDIzNiA5NC4zNzk5IDIzNi4wMzEgOTQuMDg4NiAyMzYuMDg5IDkzLjgwNzJMMjM2LjMzOCA5My4wMTYyIDIzNi44NTggOTIuMTMxNCAyNTkuNDczIDUzLjYyOTQgMjU5Ljk2MSA1Mi43OTg1IDI2MC40MDcgNTIuMjY1OEMyNjEuMiA1MS40ODM3IDI2Mi4yOTYgNTEgMjYzLjUwNiA1MVoiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==) no-repeat 1rem/1.8rem, #b32121;
  padding: 1rem 1rem 1rem 3.7rem; color: white;
}
.blazor-error-boundary::after { content: "An error has occurred."; }

/* ── Page-level error boundary ─────────────────────────────────
   Fallback shown by Shared/PageErrorBoundary.razor when a component throws. Deliberately calm and
   text-only: the user is not the audience for the failure, the log is. Its job is to say "this part
   is broken, the rest still works" without looking like data loss. */
.ds-error-boundary {
  max-width: 560px; margin: 48px auto; padding: 24px;
  background: var(--ds-surface); border: 1px solid var(--ds-line); border-radius: 8px;
  text-align: center;
}
.ds-error-boundary h2 { margin: 0 0 8px; font-size: 15px; font-weight: 600; color: var(--ds-ink); }
.ds-error-boundary p  { margin: 0 0 16px; font-size: 13px; line-height: 1.5; color: var(--ds-muted); }

/* ── Diagnostyka ───────────────────────────────────────────────
   Ekran administratora: zdarzenia, błędy, transkrypcje AI. Wszystko tu jest wariantem czegoś, co
   już istnieje w aplikacji — plakietka to ds-tag w kolorze skutku, kafelek to ta sama karta co na
   pulpicie — żeby ekran diagnostyczny nie wyglądał jak doklejony z innego systemu. */

/* Plakietka skutku. Kolory te same, którymi status jest wypisany w tabeli produktów, więc
   „czerwone” znaczy tu dokładnie to samo co tam. */
.ds-pill {
  display: inline-flex; align-items: center;
  padding: 1px 7px; font-size: 10.5px; font-weight: 500;
  border-radius: 999px; white-space: nowrap; border: 1px solid transparent;
}
.ds-pill--ok    { background: var(--ds-ok-bg);  color: var(--ds-ok-soft); border-color: var(--ds-ok); }
.ds-pill--bad   { background: var(--ds-bad-bg); color: var(--ds-bad);     border-color: var(--ds-bad); }
.ds-pill--muted { background: var(--ds-line2);  color: var(--ds-muted);   border-color: var(--ds-line); }

/* Wiersz z błędem. Samo lewe obramowanie, nie tło całego wiersza: przy dwustu wierszach, z których
   połowa jest czerwona, tło zamienia tabelę w płachtę i przestaje cokolwiek wyróżniać. */
.ds-row--bad td:first-child { box-shadow: inset 2px 0 0 var(--ds-bad); }

.ds-empty-cell { padding: 24px; text-align: center; color: var(--ds-muted); font-size: 12.5px; }

.ds-link { color: var(--ds-ink2); text-decoration: none; border-bottom: 1px solid var(--ds-line); }
.ds-link:hover { color: var(--ds-ink); border-bottom-color: var(--ds-muted); }

/* Kafelek-licznik. Jest przyciskiem, bo liczba bez drogi do tego, co liczy, jest ślepym zaułkiem. */
.ds-stat-tile {
  text-align: left; cursor: pointer; font-family: inherit;
  background: var(--ds-surface); border: 1px solid var(--ds-line); border-radius: 8px;
  padding: 14px 16px; transition: border-color .12s;
}
.ds-stat-tile:hover { border-color: var(--ds-muted2); }
.ds-stat-label { font-size: 11px; color: var(--ds-muted); text-transform: uppercase; letter-spacing: .6px; margin-bottom: 6px; }
.ds-stat-value { font-size: 24px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -.5px; }
.ds-stat-note  { font-size: 11.5px; color: var(--ds-muted); margin-top: 3px; }

.ds-details-toggle { cursor: pointer; font-size: 11px; color: var(--ds-muted); }
.ds-details-toggle:hover { color: var(--ds-ink2); }

/* Blok treści: JSON szczegółów zdarzenia, prompt, odpowiedź modelu. Zawijanie jest konieczne —
   prompt bywa jedną linią na kilkanaście tysięcy znaków i bez tego rozpycha całą stronę w poziomie. */
.ds-pre {
  margin: 4px 0 0; padding: 8px 10px; max-height: 220px; overflow: auto;
  background: var(--ds-surface); border: 1px solid var(--ds-line); border-radius: 6px;
  font-family: 'Geist Mono', ui-monospace, monospace; font-size: 11.5px; line-height: 1.5;
  white-space: pre-wrap; word-break: break-word; color: var(--ds-ink2);
}
.ds-pre--tall { max-height: 340px; }

.ds-transcript-label {
  font-size: 11px; color: var(--ds-muted); text-transform: uppercase; letter-spacing: .6px;
  margin: 10px 0 0;
}

/* ── Diagnostyka: uzupełnienia ─────────────────────────────────
   Kolumny o stałej szerokości muszą przycinać treść, nie rozpychać tabeli. Bez tego długi e-mail
   aktora wypychał kolumnę „Kto" na sąsiedni „Produkt" — komórka tabeli domyślnie rośnie do
   najdłuższego słowa, a szerokość w <th> jest wtedy tylko sugestią. */
.ds-truncate { max-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-nowrap   { white-space: nowrap; }
.ds-num      { font-variant-numeric: tabular-nums; }
.ds-muted    { color: var(--ds-muted); }

/* Pięć kafelków w rzędzie; zwijają się, zanim zrobi się z nich pasek nieczytelnych cyfr. */
.ds-stat-row {
  display: grid; gap: 14px; margin-bottom: 16px;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
}

/* Brak to nie awaria. Żółty, bo ktoś ma to uzupełnić — czerwony znaczy „program się wywalił”
   i jest zarezerwowany dla zakładki Wyjątki. Rozróżnienie ma być widoczne z drugiego końca pokoju,
   bo od niego zależy, czy ktoś rzuca wszystko i sprawdza, czy dopisuje sobie na listę zadań. */
.ds-pill--warn { background: var(--ds-warn-bg); color: var(--ds-warn); border-color: var(--ds-warn); }
.ds-row--warn td:first-child { box-shadow: inset 2px 0 0 var(--ds-warn); }

/* Mrugnięcie polem, do którego prowadzi przycisk przy błędzie publikacji (Trello #163).
   Delikatne z rozmysłem: chodzi o „to ten wiersz", a nie o alarm — dwa przebiegi obramowania
   i tła, bez ruchu i bez zmiany rozmiaru, żeby nie przesunąć układu w trakcie czytania.
   prefers-reduced-motion wyłącza pulsowanie, ale zostawia jednorazowe podświetlenie: informacja
   „to jest to pole" nie jest efektem ozdobnym i nie wolno jej zgubić. */
@keyframes ds-blink {
    0%, 100% { border-color: var(--ds-line2); background: var(--ds-surface); }
    25%, 75% { border-color: #f59e0b;         background: #fffbeb;           }
}
.ds-blink {
    animation: ds-blink 0.55s ease-in-out 2;
}
@media (prefers-reduced-motion: reduce) {
    .ds-blink {
        animation: none;
        border-color: #f59e0b;
        background: #fffbeb;
    }
}

/* ── Siatka potwierdzenia importu (Trello #182) ──────────────────────────────
   Komórka edytowalna wygląda jak komórka, a nie jak formularz: bez obwódki w spoczynku, z obwódką
   pod kursorem i w fokusie. Pola z ramką przy każdej z pięciu kolumn i trzystu wierszach zamieniają
   tabelę w ścianę prostokątów, w której nie widać tych kilku faktycznie wymagających uwagi — a cały
   ten ekran istnieje po to, żeby te kilka było widać. Podświetlenia stanu (żółte „ilość z domysłu”,
   czerwone „nazwa jest kodem”) idą stylem inline z wiersza i wygrywają, bo dotyczą konkretnej
   komórki, a nie klasy. */
.ds-cell-input {
  width: 100%; box-sizing: border-box;
  padding: 3px 6px; font: inherit; font-size: 12.5px;
  color: var(--ds-ink); background: transparent;
  border: 1px solid transparent; border-radius: 4px;
}
.ds-cell-input:hover { border-color: var(--ds-line); }
.ds-cell-input:focus {
  outline: none; background: var(--ds-surface);
  border-color: var(--ds-ink2);
}
/* Strzałki „w górę/w dół” przy ilości zabierają połowę wąskiej kolumny i podbijają liczbę o jeden,
   czyli o krok bez znaczenia przy stanie magazynowym. Liczbę i tak się tu wpisuje. */
.ds-cell-input[type="number"] { -moz-appearance: textfield; }
.ds-cell-input[type="number"]::-webkit-outer-spin-button,
.ds-cell-input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
