/* Set proper color for all input field */
table.dataTable span.dtcc div.dtcc-search>div input {
    border: 1px solid #aaaaaa;
    background-color: var(--bs-table-bg);
}

/* Fix problem with dropdown menu in FixedHeader */
#bootstrap-datatable .dtfh-floatingparent .dropdown-menu.show {
    top: 100%% !important;
}

.dropdown-menu.show {
    top: auto !important;
}

/* Fix problem with dropdown menu in normal view */
#bootstrap-datatable .dt-container>.dropdown-menu.show {
    top: 151px !important;
}

.dark .dt-datetime {
    background-color: var(--bs-input-bg);
    color: var(--bs-body-color);
}

.dark div.dt-datetime {
    border: 1px solid var(--bs-border-color);
}

.dark div.dt-datetime table td.selectable {
    color: var(--bs-body-color) !important;
    background-color: var(--bs-input-bg) !important;
}

/* Fix problem with wrong color of icon in dark mode */
.dark table.dataTable span.dtcc div.dtcc-search>div div.dtcc-search-type-icon svg,
.dark table.dataTable span.dtcc span.dtcc-button-icon {
    color: var(--bs-body-color);
}

/* Ciemny motyw: podświetlenie przycisku menu musi być CIEMNE.
   ============================================================================
   ColumnControl podświetla przycisk pod kursorem tłem
   `--dtcc-button_hover-background`, zdefiniowanym jako `rgb(var(--bs-tertiary-bg-rgb))`.
   Zmierzone w ciemnym motywie: wychodzi z tego `rgb(248, 249, 250)`, czyli BIAŁY
   kafelek na ciemnej stronie - a ikona na nim ma jasny `rgb(173, 181, 189)`, więc
   jeszcze jej nie widać.

   Powód jest ogólniejszy: ciemna paleta DataTables u nas w ogóle się nie włącza
   (dostawca aktywuje ją przez `:root.dark` / `:root[data-bs-theme=dark]` /
   `:root[data-theme=dark]`, a u nas klasa `dark` siedzi na `<body>`) - patrz rozdział
   „Ciemny motyw" w dokumentacji programisty.

   Nadpisujemy więc samą ZMIENNĄ, a nie skutki: zmienne CSS dziedziczą, więc definicja
   na `body.dark` dosięga wszystkich przycisków w tabeli. Ikona zostaje jasna
   (reguła niżej), bo tło jest teraz ciemne.

   Pierwsze podejście przyciemniało IKONĘ i zostawiało biały kafelek - czytelne, ale
   w ciemnym motywie wyglądające obco.
   ============================================================================ */
body.dark {
    --dtcc-button_hover-background: var(--bs-tertiary-bg);
}

/* Ciemny motyw: pole „Szukaj" WEWNĄTRZ menu nie może świecić.
   ============================================================================
   Zmierzone: to pole miało tło `rgb(245, 246, 248)`, czyli prawie białe, przy polu
   filtra kolumny (nad tabelą) `rgb(49, 52, 67)`. Dotychczasowa reguła niżej obejmuje
   tylko pola w `div.dtcc-search`, a wyszukiwarka listy wartości siedzi
   w `div.dtcc-list` - i dlatego jej nie łapała.

   Kolor bierzemy z `--bs-tertiary-bg` (zmierzone: `#313443`, czyli DOKŁADNIE tło pola
   filtra kolumny), a NIE z `--bs-table-bg`, którą tamta reguła stosuje przy polach
   w nagłówku. Menu jest doklejane do `div.dt-container`, czyli POZA tabelę, więc
   zmienne z zasięgu tabeli tam nie docierają - `--bs-table-bg` jest tam pusta
   i deklaracja po cichu przepada. To właśnie na tym poległo pierwsze podejście.

   Selektor jest DŁUŻSZY, niż wyglądałoby na potrzebny (`body.dark div.dt-container …`),
   i to celowo: krótszy `.dark div.dtcc-dropdown div.dtcc-list input.dtcc-list-search`
   przegrywał z regułą motywu graficznego. Sprawdzone pomiarem trzech wariantów -
   ta specyficzność wystarcza i dzięki temu nie trzeba `!important`.
   ============================================================================ */
body.dark div.dt-container div.dtcc-dropdown div.dtcc-list input.dtcc-list-search,
body.dark div.dt-container div.dtcc-dropdown div.dtcc-list input {
    background: var(--bs-tertiary-bg);
    color: var(--bs-body-color);
    border-color: var(--bs-border-color);
}

/* Fix problem with wrong color of selected column in dark mode */
.dark div.dtcc-dropdown div.dtcc-list div.dtcc-list-buttons,
div.dtcc-dropdown div.dtcc-search>div select,
div.dtcc-dropdown div.dtcc-search>div input {
    background: var(--bs-table-bg);
}

/* Fix problem with wrong place of clear button and select input */
table.dataTable span.dtcc div.dtcc-searchNumber>div div.dtcc-search-type-icon,
table.dataTable span.dtcc div.dtcc-searchDateTime>div div.dtcc-search-type-icon {
    left: auto;
    right: 0;
    z-index: 1
}

table.dataTable span.dtcc div.dtcc-searchNumber>div select,
table.dataTable span.dtcc div.dtcc-searchDateTime>div select {
    left: auto;
    right: 0;
    z-index: 2;
    padding: 0
}

table.dataTable span.dtcc div.dtcc-searchNumber>div span.dtcc-search-clear,
table.dataTable span.dtcc div.dtcc-searchDateTime>div span.dtcc-search-clear {
    left: auto;
    right: 28px;
    z-index: 1
}

table.dataTable span.dtcc div.dtcc-searchNumber>div input,
table.dataTable span.dtcc div.dtcc-searchDateTime>div input {
    padding: var(--dtcc-search-input_padding);
    padding-right: 46px;
    text-align: left;
}

/* Fix header text and icon for extra menu
   ============================================================================
   DataTables wyrównuje do PRAWEJ kolumny, których typ rozpoznał jako liczbę albo
   datę, i przy okazji odwraca układ nagłówka (`flex-direction: row-reverse`), czyli
   przenosi ikony menu na lewą stronę tytułu. U nas wszystkie listy mają wyglądać
   jednakowo, więc to cofamy.

   Klasa `dt-right` w tej samej liście nie jest nadmiarowa: nadaje ją KAŻDY typ
   zarejestrowany przez `DataTable.render.datetime()` (patrz `_get_column_date`
   w `apps/core/serializers.py`). Właśnie ona zrobiła kolumnę „Kontrahent" na liście
   ubezpieczeń wyrównaną do prawej z ikonami po lewej, gdy renderer daty trafiał
   w złą kolumnę. Sama przyczyna jest naprawiona i pilnuje jej test
   `test_column_renderers_follow_the_user_column_order`, ale gdyby taka klasa
   pojawiła się jeszcze kiedyś, nagłówek nie ma się od tego rozjeżdżać.
   ============================================================================ */
table.dataTable th.dt-type-numeric,
table.dataTable th.dt-type-date,
table.dataTable th.dt-right,
table.dataTable th.dt-head-right,
table.dataTable td.dt-type-numeric,
table.dataTable td.dt-type-date,
table.dataTable td.dt-right {
    text-align: left
}

table.dataTable th.dt-type-numeric div.dt-column-header,
table.dataTable th.dt-type-numeric div.dt-column-footer,
table.dataTable th.dt-type-date div.dt-column-header,
table.dataTable th.dt-type-date div.dt-column-footer,
table.dataTable th.dt-right div.dt-column-header,
table.dataTable th.dt-right div.dt-column-footer,
table.dataTable th.dt-head-right div.dt-column-header,
table.dataTable th.dt-head-right div.dt-column-footer,
table.dataTable td.dt-type-numeric div.dt-column-header,
table.dataTable td.dt-type-numeric div.dt-column-footer,
table.dataTable td.dt-type-date div.dt-column-header,
table.dataTable td.dt-type-date div.dt-column-footer,
table.dataTable td.dt-right div.dt-column-header,
table.dataTable td.dt-right div.dt-column-footer {
    flex-direction: row;
}

table.table.dataTable>tbody>tr.selected a {
    color: rgb(255, 255, 255);
    color: rgb(var(--dt-row-selected-text));
    font-weight: 600;
}

.select2-container .select2-selection--single .select2-selection__rendered {
    line-height: 1.5;
    padding: 0;
    padding-left: 4px !important;
}

.select2-container .select2-selection--single .select2-selection__clear {
    position: absolute !important;
}

table.table-logbook tbody tr td {
    padding-top: 10px;
}

table.table-logbook tbody tr td.buttons {
    padding-top: 0;
}

table.table-files tbody tr td {
    padding: 10px 0;
}

/* Ikona kalendarza przy polu daty OTWIERA kalendarz (js/datepicker_toggle.js),
   więc ma wyglądać na klikalną - wcześniej była ozdobą i nic nie robiła. */
.input-group > .input-group-text:has(> .mdi-calendar),
.input-group > .input-group-text:has(> .mdi-clock-outline) {
    cursor: pointer;
}

/* Kolumna, która pochłania nadmiar szerokości tabeli inline.

   Przy `table-layout: auto` nadwyżkę bierze kolumna bez zadeklarowanej szerokości -
   a w tabeli z kilkoma wąskimi kolumnami była to kolumna „Usuń" (zmierzone: 661 px
   z 1425 px). Pola wyglądały wtedy na wciśnięte w lewy róg. Jedna kolumna z
   `width: 100%` zabiera całą nadwyżkę i tabela wypełnia dostępne miejsce. */
table.formset thead tr th.col-stretch {
    width: 100%;
    min-width: 150px;
}

/* Dwie kolumny tekstowe, które mają dzielić się miejscem po połowie.

   Powód: w tabeli adresów e-mail kolumna „E-mail" dostawała 100 px (bo `col-text`
   deklaruje tylko `min-width`), a „Opis" jako `col-stretch` zabierał całą resztę -
   dwie trzecie tabeli. Adresu nie dało się przeczytać bez klikania w pole, mimo że
   obok świeciło pół ekranu pustego miejsca na opis, który zwykle jest krótki. */
table.formset thead tr th.col-half {
    width: 50%;
    min-width: 220px;
}

/* Kwota płatności w tabeli wpłat.

   `col-price` (100 px) jest w sam raz dla pozycji dokumentu, ale w tabeli płatności
   to POLE, w które się najczęściej wpisuje - a całą nadwyżkę szerokości brała obok
   kolumna „Konto kasowe" (`col-stretch`). Własna, szersza kolumna odbiera jej tę
   nadwyżkę: kwota jest szersza, konto węższe, a tabela nadal wypełnia dostępne
   miejsce (stretch musi zostać, inaczej nadmiar wraca do kolumny „Usuń" - patrz
   komentarz przy `col-stretch`). */
table.formset thead tr th.col-amount {
    min-width: 200px;
    width: 18%;
}

/* Kolumna z przyciskami wiersza (usuń + odnośnik do powiązanego dokumentu).

   `col-delete` to 50 px - dokładnie na jeden przycisk. Wiersz, który ma obok
   „Usuń" jeszcze odnośnik (np. rata kredytu → dokument kosztowy), potrzebuje
   miejsca na dwa, inaczej drugi przycisk zawija się pod pierwszy. */
table.formset thead tr th.col-actions {
    width: 100px;
    text-align: center;
}

table.formset td.col-actions {
    white-space: nowrap;
}

/* Kolumna z przełącznikiem „tak/nie" w tabeli inline (np. „Domyślna" przy stopkach
   konta e-mail).

   Wcześniej takie pole dostawało klasę kolumny „usuń" (50 px) i renderowało się jako
   goły kwadracik bez etykiety, wciśnięty przy prawej krawędzi tabeli - wyglądało to
   na brak takiej możliwości w ogóle. Stąd własna szerokość kolumny, wyśrodkowanie
   pod nagłówkiem i większe pole trafienia. */
table.formset thead tr th.col-switch {
    width: 120px;
    text-align: center;
}

table.formset td.col-switch {
    text-align: center;
    vertical-align: middle;
}

table.formset td.col-switch > div {
    margin-bottom: 0 !important;    /* w komórce tabeli odstęp pod polem jest zbędny */
}

/* Sam kwadracik jest wielkości tekstu i przy szerokiej tabeli po prostu ginie.
   Powiększenie do 1.4em daje pole trafienia zbliżone do minimum dotykowego. */
table.formset td.col-switch input[type="checkbox"] {
    width: 1.4em;
    height: 1.4em;
    margin: 0 auto;
    float: none;
    cursor: pointer;
}

@media only screen and (min-device-width: 481px) and (max-device-width: 835px) and (orientation:portrait) {
    table.formset thead tr th.col-order {
        min-width: 50px;
        width: 3%;
    }

    table.formset thead tr th.col-price {
        min-width: 100px;
        width: 10%;
    }

    table.formset thead tr th.col-quantity {
        min-width: 100px;
        width: 7%;
    }

    table.formset thead tr th.col-text {
        min-width: 100px;
    }

    table.formset thead tr th.col-min-select {
        min-width: 80px;
        width: 10%;
    }

    table.formset thead tr th.col-mid-select {
        min-width: 115px;
        width: 12%;
    }

    table.formset thead tr th.col-max-select {
        min-width: 150px;
        width: 15%;
    }

    table.formset thead tr th.col-date {
        min-width: 165px;
        width: 10%;
    }

    table.formset thead tr th.col-datetime {
        min-width: 190px;
        width: 10%;
    }
}

@media only screen and (min-device-width: 836px) and (max-device-width: 1195px) and (orientation:landscape) {
    table.formset thead tr th.col-order {
        min-width: 50px;
        width: 3%;
    }

    table.formset thead tr th.col-price {
        min-width: 100px;
        width: 10%;
    }

    table.formset thead tr th.col-text {
        min-width: 100px;
    }

    table.formset thead tr th.col-min-select {
        min-width: 80px;
        width: 10%;
    }

    table.formset thead tr th.col-mid-select {
        min-width: 115px;
        width: 13%;
    }

    table.formset thead tr th.col-max-select {
        min-width: 150px;
        width: 16%;
    }

    table.formset thead tr th.col-date {
        min-width: 165px;
        width: 10%;
    }

    table.formset thead tr th.col-datetime {
        min-width: 190px;
        width: 10%;
    }
}

@media only screen and (min-device-width: 1196px) {
    table.formset thead tr th.col-order {
        min-width: 100px;
        width: 3%;
    }

    table.formset thead tr th.col-price {
        min-width: 100px;
        width: 9%;
    }

    table.formset thead tr th.col-quantity {
        min-width: 100px;
        width: 7%;
    }

    table.formset thead tr th.col-text {
        min-width: 100px;
    }

    table.formset thead tr th.col-min-select {
        min-width: 80px;
        width: 10%;
    }

    table.formset thead tr th.col-mid-select {
        min-width: 115px;
        width: 16%;
    }

    table.formset thead tr th.col-max-select {
        min-width: 150px;
        width: 23%;
    }

    table.formset thead tr th.col-date {
        min-width: 165px;
        width: 10%;
    }

    table.formset thead tr th.col-datetime {
        min-width: 190px;
        width: 9%;
    }

    table.formset thead tr th.col-delete {
        width: 50px;
    }

    table.formset thead tr th.col-switch {
        width: 120px;
    }
}

/* FOR SELECT2 SET CUSTOM STYLES */

.select2-container {
    min-width: 50px !important;
    /* max-width: 400px !important; */
}

/* POLE WIELOKROTNEGO WYBORU - ma wyglądać jak pole pojedyncze obok niego.

   Wszystkie selektory poniżej mają DWIE klasy kontenera
   (`.select2-container--bootstrap-5.select2-container`) i nie jest to ozdoba:
   `select2.min.css` wchodzi na stronę z `form.media` django-autocomplete-light,
   czyli PO `custom.css`. Przy równej specyficzności wygrywa arkusz późniejszy,
   więc dotychczasowa reguła z trzema klasami była martwa - obowiązywało
   `display: inline-block` z biblioteki. Skutek był widoczny gołym okiem: lista
   wybranych pozycji nie była flexem, więc każdy „chip" zajmował CAŁY wiersz
   i pole „Tagi" z trzema tagami miało 178 px wysokości zamiast 38 px. */

/* Wysokość i wyrównanie jak w polu pojedynczym: 38 px przy pustym polu.

   Reguła obejmuje TAKŻE filtry w panelu bocznym, choć mają być mniejsze - i to
   jest w porządku, bo `min-height` liczy się w `em`. Przy mniejszej czcionce
   `form-select-sm` wychodzi z tego 35 px, czyli tyle, ile pozostałe filtry
   w panelu. Wpisanie tu piksli zamiast `em` wymagałoby drugiej reguły
   i rozjechałoby się przy każdej zmianie rozmiaru czcionki. */
.select2-container--bootstrap-5.select2-container .select2-selection--multiple {
    display: flex;
    align-items: center;
    min-height: calc(1.5em + .75rem + 2px);
    padding-top: .25rem;
    padding-bottom: .25rem;
}

/* Wybrane pozycje w RZĘDZIE, zawijane do kolejnych wierszy dopiero gdy zabraknie
   miejsca. Odstępy dajemy przez `gap`, a nie marginesami na chipsach - inaczej
   ostatni w wierszu dokłada pusty margines i psuje wyrównanie do prawej. */
.select2-container--bootstrap-5.select2-container .select2-selection--multiple .select2-selection__rendered {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: .25rem;
    width: 100%;
    padding: 0;
    margin: 0;
}

.select2-container--bootstrap-5.select2-container .select2-selection--multiple .select2-selection__choice {
    margin: 0;
}

/* Chip niższy niż domyślny, żeby wiersz tagów mieścił się w wysokości pola.
   Bez tego pole z jednym tagiem rośnie do 47 px i odstaje od sąsiadów.
   Tu BEZ wyjątku dla filtrów: w panelu bocznym domyślny chip też jest za wysoki
   (zmierzone: 43 px pola przy 35 px reszty filtrów), a mniejsza czcionka
   `form-select-sm` sama utrzymuje właściwą proporcję. */
.select2-container--bootstrap-5.select2-container .select2-selection--multiple .select2-selection__rendered .select2-selection__choice {
    padding-top: .0625rem;
    padding-bottom: .0625rem;
}

/* Placeholder pola wielokrotnego wyboru (np. „Odbiorcy (z bazy kontrahentów)").
   Select2 w trybie multiple pokazuje placeholder w polu wyszukiwania, któremu sam
   wylicza szerokość z aktualnie wpisanego tekstu - przy pustym polu wychodziło
   kilkadziesiąt pikseli i placeholder był ucięty („Wys..."). Dopóki nic nie jest
   wybrane (pole wyszukiwania jest pierwszym elementem listy), rozciągamy je na
   całą szerokość kontrolki. */
.select2-container--bootstrap-5.select2-container .select2-selection--multiple .select2-selection__rendered > li.select2-search--inline:first-child,
.select2-container--bootstrap-5.select2-container .select2-selection--multiple .select2-selection__rendered > li.select2-search--inline:first-child .select2-search__field {
    width: 100% !important;
    max-width: 100%;
    text-overflow: ellipsis;
}

/* Gdy coś jest już wybrane, kursor staje OBOK chipsów, a nie w wierszu pod nimi.
   Motyw bootstrap-5 daje polu wyszukiwania `width: 100%`, co spychało je do
   własnej linii i pole rosło o kolejne 24 px przy pierwszym wybranym tagu. */
.select2-container--bootstrap-5.select2-container .select2-selection--multiple .select2-search {
    width: auto;
}

.select2-container--bootstrap-5.select2-container .select2-selection--multiple .select2-selection__rendered > li.select2-search--inline:not(:first-child) {
    flex: 1 1 4rem;
    width: auto !important;
}

.select2-container--bootstrap-5.select2-container .select2-selection--multiple .select2-selection__rendered > li.select2-search--inline:not(:first-child) .select2-search__field {
    width: 100% !important;
}

.form-switch .form-check-label {
    padding-left: 0.5rem;
    padding-top: 0.3rem;
}

/* DARK MODE STYLES FOR SELECT2 */

body.dark .select2-container--bootstrap-5 .select2-dropdown {
    color: #fff;
    background-color: #313443;
    border-color: #86b7fe;
}

body.dark .select2-container--bootstrap-5 .select2-dropdown .select2-results__options .select2-results__option.select2-results__option--selected,
body.dark .select2-container--bootstrap-5 .select2-dropdown .select2-results__options .select2-results__option[aria-selected=true]:not(.select2-results__option--highlighted) {
    background-color: #5b73e8;
}

body.dark .select2-container--bootstrap-5.select2-container--focus .select2-selection,
body.dark .select2-container--bootstrap-5.select2-container--open .select2-selection {
    box-shadow: none;
}

body.dark .select2-container--bootstrap-5 .select2-dropdown .select2-search .select2-search__field:focus {
    border-color: #adb9f4;
}

body.dark .select2-container--bootstrap-5 .select2-dropdown .select2-search .select2-search__field {
    background-color: var(--bs-input-bg);
    color: #fff;
}

body.dark .select2-container--bootstrap-5 .select2-dropdown .select2-results__options .select2-results__option.select2-results__option--highlighted {
    background-color: rgb(34, 37, 47);
    color: #fff;
}

/* Set search result item styles */

.search-result-box .search-item {
    padding-bottom: 20px;
    border-bottom: 1px solid #e3eaef;
    margin-bottom: 20px
}

.search-result-box .search-item.first {
    padding-top: 20px;
    border-top: 1px solid #e3eaef;
}

#globalsearch .form-check {
    float: left;
    min-width: 20%;
    padding-top: 5px;
    padding-bottom: 5px;
}
/* ============================================================================
   iOS: blokada automatycznego przybliżania ekranu po wejściu w pole tekstowe
   ============================================================================
   Safari na iPhonie/iPadzie sam przybliża stronę, gdy fokusowane pole ma czcionkę
   MNIEJSZĄ niż 16px - a Bootstrap ustawia `.form-control` na 0.9rem (14.4px),
   `.form-control-sm` jeszcze mniej. Po wpisaniu tekstu widok nie wraca do
   poprzedniego powiększenia i trzeba oddalać go ręcznie.

   Świadomie NIE używamy `maximum-scale=1` / `user-scalable=no` w <meta viewport>:
   to odbiera możliwość ręcznego powiększania (bariera dostępności), a Safari i tak
   bywa, że to ignoruje. Jedyne pewne rozwiązanie to 16px na polach.

   Warunek `(hover: none) and (pointer: coarse)` łapie urządzenia dotykowe, a nie
   szerokość okna - dzięki temu wygląd na desktopie (także po zwężeniu okna)
   pozostaje bez zmian, a poprawka działa też na iPadzie i w orientacji poziomej.
*/
@media (hover: none) and (pointer: coarse) {

    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="submit"]):not([type="button"]),
    select,
    textarea,
    .form-control,
    .form-control-sm,
    .form-select,
    .form-select-sm,
    .select2-container--bootstrap-5 .select2-selection,
    .select2-container--bootstrap-5 .select2-search__field,
    .dt-search input,
    table.dataTable span.dtcc div.dtcc-search > div input {
        /* 16px, nie 1rem - `rem` zależy od ustawień przeglądarki, a próg Safari
           jest podany w pikselach CSS. */
        font-size: 16px !important;
    }
}

/* ============================================================================
   Zwijane tabele pod formularzem (inline formsety)
   ============================================================================
   Nagłówek z tytułem, licznikiem wierszy i strzałką. Po zwinięciu zostaje sam
   nagłówek - w długim formularzu to jedyny sposób, żeby nie przewijać tabel,
   których akurat się nie uzupełnia. Stan trzyma `core.UserInlineState`.
   ============================================================================ */
.inline-header {
    margin-bottom: .5rem;
    padding-bottom: .25rem;
    border-bottom: 1px solid var(--bs-border-color);
}

.inline-title { font-weight: 600; }

.inline-count {
    font-size: .75rem;
    font-weight: 600;
}

/* Strzałka pokazuje stan: w dół = rozwinięte, w prawo = zwinięte */
.inline-toggle { color: var(--bs-secondary-color, #74788d); }
.inline-caret { font-size: 1.125rem; transition: transform .15s ease-in-out; }
.inline-toggle[aria-expanded="false"] .inline-caret { transform: rotate(-90deg); }


/* ============================================================================
   Listy na telefonie: pasek narzędzi, zwijanie ikon, wyszukiwarka
   ============================================================================
   Trzy bloki poniżej rozwiązują trzy różne usterki i mają CELOWO różne warunki:

   1. UKŁAD PASKA - wąski ekran ALBO dotyk. Poniżej 767 px sam DataTables
      (`css/datatables/datatables.bootstrap5.css`) daje paskowi
      `float: none; width: 100%; text-align: center`, a komórkom układu
      `text-align: center`. Przyciski przestają być wtedy elementami układu,
      a stają się TEKSTEM - płyną jak wyrazy w akapicie i łamią się do środka.
      Stąd wrażenie rozrzuconych ikon.
   2. ZWIJANIE IKON - tylko dotyk, bo to decyzja o SPOSOBIE UŻYCIA (na telefonie
      dane się przegląda, nie edytuje), a nie o ilości miejsca. Warunek oparty na
      urządzeniu działa też po obróceniu telefonu poziomo, gdzie 844 px szerokości
      to już „szeroki ekran" dla zapytań `max-width`.
   3. WYSZUKIWARKA - tylko wąski ekran, bo tylko tam biblioteka ją wyśrodkowuje.

   Poprawki są tutaj, a nie w plikach dostawcy - te wymieniamy przy każdej
   aktualizacji biblioteki.

   4. LISTA Z ROZWIJANYM WIERSZEM - na telefonie tabela nie przewija się w bok,
      tylko chowa kolumny do wiersza rozwijanego dotknięciem (rozszerzenie
      Responsive, włączane w `datatables.html` po tym samym warunku, co ten blok).
      Dlatego znika przycisk „Zablokuj pierwsze kolumny": nie ma przewijania
      w poziomie, więc nie ma czego blokować.

   Pułapka przy pisaniu selektorów: integracja z Bootstrapem opakowuje komórki
   układu w klasy SIATKI (`col-md-auto ms-auto d-md-flex`), a nie w `dt-layout-cell`
   - reguły celujące w to drugie wyglądają poprawnie i nie trafiają w nic.
   Bezpiecznie jest celować w nazwy samych elementów (`dt-buttons`, `dt-search`).
   ============================================================================ */

/* --- 1. Pasek jako układ, a nie tekst ------------------------------------- */
/* Pole filtra ma wypełniać szerokość swojej kolumny.
   ============================================================================
   BŁĄD W ARKUSZU DOSTAWCY (DataTables 3.0.2). Rozciągnięcie pola daje reguła:

       table.dataTable div.dt-column-header span.dt-column-title:empty + span.dtcc
       { flex-grow: 1; margin-left: auto; }

   czyli „rozciągnij `span.dtcc`, gdy tytuł kolumny jest pusty" - a pusty jest
   właśnie w wierszu filtrów. Tylko że w wersji 3 kod JS renderuje tytuł jako
   `<div class="dt-column-title">`, podczas gdy arkusz nadal celuje w `<span>`.
   Selektor przestał pasować i pole filtra zostało przy szerokości swojej TREŚCI.

   Zmierzone na liście dokumentów kosztowych, kolumna „Kontrahent" (514 px):

       2.3.6:  span.dtcc = 495 px  -> pole wypełnia kolumnę
       3.0.2:  span.dtcc = 204 px  -> pole zostaje wąskie

   Nasza reguła jest tą samą regułą, tylko z selektorem opartym na KLASIE, więc
   działa i dla `<div>`, i dla `<span>` - przetrwa poprawkę u dostawcy.

   `:empty` jest ścisłe (element ze spacją w środku już pusty nie jest), i to jest
   tu zaletą: w wierszu z tytułami kolumn tytuł pusty NIE jest, więc tam reguła nie
   zadziała i ikony sortowania zostaną tam, gdzie były.
   ============================================================================ */
table.dataTable div.dt-column-header > .dt-column-title:empty + span.dtcc,
table.dataTable div.dt-column-footer > .dt-column-title:empty + span.dtcc {
    flex-grow: 1;
    margin-left: auto;
}

/* Kolumna zaznaczania wąska jak checkbox, nie jak kolumna z treścią.
   ============================================================================
   Zgłoszenie z pola: „na listach dziwnie zachowuje się kolumna z checkbox -
   zajmuje dużo miejsca a powinna być wąska na szerokość checkboxa".

   Zmierzone na liście kosztów: kolumna miała **173 px** przy 12-pikselowym
   checkboxie - dokładnie tyle, ile kolumna z treścią (druga miała 203 px).

   Dlaczego to musi być CSS, a nie `columns.width` po stronie serwera: przy
   `scrollX` tabela ma `table-layout: auto` i **wyliczoną szerokość w `colgroup`**,
   którą DataTables wpisuje RAZ, przy inicjalizacji. Zmierzone: podpowiedź
   `"width": "36px"` w `columnDefs` nie zmieniła nic, wymuszenie szerokości na
   `col:first-child` (nawet z `!important`) też nie, a `columns.adjust()` po
   wstrzyknięciu CSS-a zabierał tylko tyle, ile zmieniły marginesy wewnętrzne.
   Liczy się wyłącznie stan arkusza W CHWILI inicjalizacji - stąd reguły tutaj.

   Ukrywamy przy tym puste elementy nagłówka tej kolumny (`dt-column-title`
   i `span.dtcc`): kolumna nie ma tytułu ani menu, a to one rozpychały nagłówek. */
table.dataTable th.dt-select,
table.dataTable td.dt-select {
    width: 20px;
    padding-left: 8px;
    padding-right: 8px;
}

table.dataTable th.dt-select div.dt-column-title,
table.dataTable th.dt-select span.dtcc,
table.dataTable td.dt-select span.dtcc {
    display: none;
}

/* Checkbox kolumny zaznaczania musi WYGLĄDAĆ na zaznaczony.
   ============================================================================
   Zgłoszenie z pola: „jak mamy checkbox nie zaznacza się on po wybraniu kolumny,
   po wybraniu kolumny z cmd i zaznaczeniu kilku elementów są one również nie
   zaznaczone".

   Zmierzone w przeglądarce na /costs/cost/: samo zaznaczanie działa bez zarzutu -
   po kliknięciu w wiersz (i po ctrl/cmd+kliknięciu w kilka) `tr.selected`,
   `rows({selected:true}).count()` i `input.dt-select-checkbox:checked` zgadzają
   się co do jednego na wszystkich 24 listach. Niewidoczny jest sam STAN pola:

   * `input.dt-select-checkbox:checked` ma tło **białe**, a nie #5b73e8 - ten sam
     `form-check-input` BEZ klasy Select, wstawiony do tej samej komórki, dostaje
     tło niebieskie;
   * znaczek Bootstrapa to biała „ptaszka" w tle (`--bs-form-check-bg-image`),
     więc na białym tle nie widać jej wcale;
   * ramkę DataTables ustawia na `border: 1px solid` (bez koloru), czyli
     `currentColor` - a w zaznaczonym wierszu tekst jest biały.

   Biała ptaszka na białym tle w białej ramce = pole nie do odróżnienia od
   pustego. Dlatego kolor tła i ramki podajemy tutaj wprost.

   Na ZAZNACZONYM wierszu tło wiersza to mocny błękit (zmierzone: rgb(13,110,253),
   kontrast do białego pola tylko 4,5:1), więc niebieski checkbox zlewałby się
   z wierszem. Tam odwracamy układ: białe pole i CIEMNY znaczek. */
table.dataTable input.dt-select-checkbox:checked {
    background-color: var(--bs-primary, #5b73e8);
    border-color: var(--bs-primary, #5b73e8);
}

table.dataTable > tbody > tr.selected input.dt-select-checkbox:checked,
table.dataTable > tbody > tr.selected > td input.dt-select-checkbox:checked {
    background-color: #fff;
    border-color: #fff;
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
}

/* Menu kolumny nie może wychodzić za dolną krawędź okna.
   ============================================================================
   ColumnControl pozycjonuje rozwijane menu pod nagłówkiem i nie ogranicza jego
   wysokości: `max-height: none`, `overflow: hidden`. Przy dłuższej zawartości
   wszystko poniżej krawędzi okna jest po prostu NIEOSIĄGALNE - nie da się tam
   dojechać, bo nic się nie przewija.

   Zmierzone na „Dokumentach kosztowych" (976 wierszy, 25 kolumn): menu miało
   1088 px przy 932 px okna, czyli 458 px poza ekranem. Sam blok listy kontrahentów
   (`searchList`, 110 pozycji) to 379 px, a lista widoczności kolumn - 408 px.
   Listę widoczności przenieśliśmy do okna „Konfiguracja tabeli", ale samo menu
   z długą listą wyboru nadal potrafi nie zmieścić się w niskim oknie.

   `65vh` daje przy oknie 932 px około 606 px, czyli mieści się pod nagłówkiem,
   a na telefonie (844 px) zostawia 549 px. Listy w środku mają własne
   ograniczenie (`--dtcc-list-buttons_max-height`), więc przewijanie zagnieżdżone
   dotyczy tylko naprawdę długich list.
   ============================================================================ */
div.dtcc-dropdown {
    max-height: 65vh;
    overflow-y: auto;
}

/* Telefon: przycisk „Zablokuj pierwsze kolumny" nie ma tu sensu.
   ============================================================================
   Na telefonie tabela nie przewija się w poziomie - kolumny, które się nie mieszczą,
   chowają się do wiersza rozwijanego (Responsive). Nie ma więc czego przyklejać,
   a `fixedColumns: false` w konfiguracji sprawia, że akcja przycisku
   (`dt.fixedColumns().start(0)`) nie miałaby na czym pracować.

   Warunek MUSI być ten sam, co `PHONE_MEDIA` w `datatables/datatables.html`: wąski
   ekran ALBO dotyk z niskim ekranem. Opisuje URZĄDZENIE, nie orientację - telefon
   trzymany poziomo ma 844 px szerokości, więc sam `max-width` uznałby go za desktop,
   a sam dotyk objąłby też tablety, na których szeroka tabela działa dobrze.
   ============================================================================ */
@media (max-width: 767.98px),
       (hover: none) and (pointer: coarse) and (max-height: 575.98px) {

    button.dt-btn-fixed-columns {
        display: none !important;
    }
}

@media (max-width: 767.98px), (hover: none) and (pointer: coarse) {

    div.dt-container div.dt-buttons {
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-start;
        align-items: center;
        gap: .5rem;
    }

    /* Marginesy, które DataTables dokłada sąsiadującym elementom - razem z `gap`
       dałyby podwójny odstęp. */
    div.dt-container div.dt-layout-start > *:not(:last-child),
    div.dt-container div.dt-layout-end > *:not(:first-child) {
        margin: 0;
    }

    /* Pole dotyku 44 x 44 px - zalecane minimum w wytycznych Apple. Ikony miały
       40 px; różnica jest mała w pikselach, a duża w liczbie chybionych kliknięć. */
    div.dt-container div.dt-buttons .btn {
        min-width: 44px;
        min-height: 44px;
    }
}

/* --- 2. Ikony schowane za strzałką --------------------------------------- */

/* Poza dotykiem strzałka jest niepotrzebna, a ikony mają zostać dokładnie tam,
   gdzie były. Dlatego JavaScript ich NIE PRZENOSI, tylko znakuje klasą `dt-icon` -
   przeniesienie do wspólnego pojemnika psuło odstępy na desktopie, bo biorą się
   one ze SPACJI między przyciskami w HTML-u (są elementami liniowymi), a te
   zostawały na miejscu. */
div.dt-container div.dt-buttons .dt-icons-toggle {
    display: none;
}

@media (hover: none) and (pointer: coarse) {

    div.dt-container div.dt-buttons .dt-icons-toggle {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        /* Strzałka zaraz za przyciskami tekstowymi, przed ikonami */
        order: 1;
    }

    /* Ikony pod spodem (`order` wyższy) i domyślnie schowane. Pasek jest tu
       układem elastycznym, więc `order` działa bez ruszania drzewa dokumentu. */
    div.dt-container div.dt-buttons .dt-icon {
        order: 2;
        display: none;
    }
    div.dt-container div.dt-buttons.icons-expanded .dt-icon {
        display: inline-block;
    }
    div.dt-container div.dt-buttons .dt-icons-toggle .mdi {
        display: inline-block;
        transition: transform .15s ease-in-out;
    }
    /* W dół = „rozwinę", w górę = „zwinę": strzałka pokazuje skutek kliknięcia */
    div.dt-container div.dt-buttons .dt-icons-toggle[aria-expanded="true"] .mdi {
        transform: rotate(180deg);
    }

    /* Przyciski tekstowe drobniejsze, żeby oba razem ze strzałką zmieściły się
       w jednym wierszu na ekranie 390 px. Wysokość zostaje przy 44 px - zmniejszamy
       tylko to, co zjada szerokość. */
    div.dt-container div.dt-buttons > .btn-group > .btn {
        padding-left: .5rem;
        padding-right: .5rem;
        font-size: .8125rem;
    }
}

/* --- 3. Wyszukiwarka w jednej linii z paskiem ----------------------------- */
@media (max-width: 767.98px) {

    /* Domyślnie dostaje `text-align: center`, przez co etykieta i wąskie pole
       wisiały pośrodku, przesunięte względem paska nad nimi. */
    div.dt-container div.dt-search {
        display: flex;
        align-items: center;
        gap: .5rem;
        width: 100%;
        text-align: left;
    }
    div.dt-container div.dt-search input {
        flex: 1 1 auto;
        min-width: 0;
        margin-left: 0;   /* `margin-left: .5em` z biblioteki dubluje `gap` */
    }
}

/* ============================================================================
   Formularze na telefonie: przyciski akcji i tabele inline
   ============================================================================ */

/* --- Przyciski akcji formularza ------------------------------------------- */
/* Na wąskim ekranie „Zapisz", „Zapisz i kontynuuj edycję", „Zapisz i dodaj nowy"
   i „Usuń" nie zmieszczą się w jednym rzędzie (same napisy to ~480 px). Zamiast
   łamać je w nieprzewidywalnej kolejności, ustawiamy jeden pod drugim na pełną
   szerokość i narzucamy kolejność wprost:

     Zapisz  ->  Zapisz i kontynuuj  ->  Zapisz i dodaj nowy  ->  Usuń

   Najczęstsze działanie na wierzchu, niszczące na końcu i odsunięte. Kolejność
   bierzemy z atrybutu `value`, więc nie trzeba dokładać klas w szablonie. */
@media (max-width: 575.98px) {
    .form-actions {
        flex-direction: column;
        align-items: stretch;
    }
    .form-actions .btn {
        width: 100%;
        min-height: 44px;   /* pole dotyku jak w pasku listy */
    }
    .form-actions button[value="_save"]       { order: 1; }
    .form-actions button[value="_continue"]   { order: 2; }
    .form-actions button[value="_addanother"] { order: 3; }
    .form-actions .btn-danger {
        order: 4;
        margin-top: .75rem;   /* wyraźna przerwa przed działaniem niszczącym */
    }
}

/* --- Tabele inline jako kartki -------------------------------------------- */
/*
   Problem: wiersz inline'a to tabela o 7 kolumnach. Zmierzone na ekranie 390 px:
   tabela ma 586 px w miejscu szerokim na 308 px, więc wychodzi za ekran - a rodzic
   ma `overflow-x: hidden`, czyli do uciętych kolumn NIE DA SIĘ dojechać. Pola
   ściśnięte do 50-70 px nie nadają się ani do czytania, ani do wpisywania.

   Rozwiązanie: na telefonie tabela przestaje być tabelą. Każdy wiersz staje się
   kartką, a pola układają się jedno pod drugim z etykietą z `data-label`.

   Dlaczego tak, a nie przez okno modalne na każdy wiersz (kusząca myśl, bo
   mechanizm okna już w formsecie istnieje): okno wymaga PRZENIESIENIA albo
   LUSTRZANIA wartości pól. Istniejąca obsługa (`editinline`) kopiuje je przez
   `.val()`, obsługuje więc tylko zwykłe pola tekstowe i wymaga zadeklarowania
   `edit_inline_form` na każdym formularzu z osobna. W wierszach siedzą listy
   select2, wybór pliku i pola daty - lustrzanie ich to pewne rozjechanie stanu.
   Rozkład na kartki jest czystym CSS: każdy widget zostaje tam, gdzie był, więc
   walidacja, błędy, dodawanie i usuwanie wierszy działają bez zmian.

   Warunek: wąski ekran ALBO dotyk z niskim ekranem. Sam `max-width` nie wystarczy,
   bo telefon poziomo ma 844 px szerokości - WIĘCEJ niż tablet w pionie (820 px),
   na którym tabela wygląda dobrze. Rozróżnia je wysokość.
*/
@media (max-width: 767.98px),
       (hover: none) and (pointer: coarse) and (max-height: 575.98px) {

    /* Nagłówek jest zbędny - etykiety wędrują do komórek */
    table.formset thead {
        display: none;
    }

    table.formset,
    table.formset tbody,
    table.formset tr,
    table.formset td {
        display: block;
        width: auto;
    }

    /* Wiersz jako kartka - widać, gdzie kończy się jedna pozycja, a zaczyna druga */
    table.formset tbody tr {
        border: 1px solid var(--bs-border-color);
        border-radius: .375rem;
        padding: .75rem;
        margin-bottom: .75rem;
        background: var(--bs-body-bg);
    }

    table.formset tbody td {
        border: 0;
        padding: .25rem 0 !important;
    }

    /* Etykieta pola z `data-label`; komórki nie mają własnych `label` */
    table.formset tbody td[data-label]::before {
        content: attr(data-label);
        display: block;
        margin-bottom: .25rem;
        font-size: .8125rem;
        font-weight: 600;
        color: var(--bs-secondary-color, #74788d);
    }

    /* Komórka bez etykiety (kolumna z przyciskami) - stopka kartki */
    table.formset tbody td:not([data-label]),
    table.formset tbody td[data-label=""] {
        display: flex;
        gap: .5rem;
        margin-top: .5rem;
    }

    /* Szerokości kolumn z desktopu przestają obowiązywać - kartka ma jedną kolumnę */
    table.formset thead tr th,
    table.formset tbody tr td {
        min-width: 0 !important;
        width: auto !important;
    }
}

/* --------------------------------------------------------------------------
   Podświetlenie CAŁEGO wiersza tabeli - ostrzeżenie i alarm

   Kolor niesie treść: na liście umów najmu żółty znaczy „kończy się za dwa
   miesiące", czerwony - „został miesiąc albo umowa już wygasła". Klasę przypina
   DataTables z pola `DT_RowClass` (patrz `DatatablesModelSerializer`), więc żeby
   dodać podświetlenie w innej tabeli, wystarczy serializer - bez zmian tutaj.

   Kolor podajemy przez zmienną `--bs-table-bg`, a NIE jako `background-color`.
   To jedyny sposób, żeby nie zepsuć dwóch rzeczy, które już działają:

   * **najechanie myszą** - Bootstrap 5.3 podstawia wtedy `--bs-table-bg-state`,
     które ma pierwszeństwo przed `--bs-table-bg`, więc podświetlenie kursorem
     zachowuje się dokładnie jak w każdej innej tabeli;
   * **zaznaczenie wiersza** - DataTables rysuje je wewnętrznym cieniem
     (`box-shadow: inset`), czyli NA tle; nasze tło mu nie przeszkadza.
   -------------------------------------------------------------------------- */
table.dataTable > tbody > tr.row-warning > * {
    --bs-table-bg: #fff3cd;         /* ostrzeżenie - zbliża się termin */
}

table.dataTable > tbody > tr.row-danger > * {
    --bs-table-bg: #f8d7da;         /* alarm - termin tuż-tuż albo po terminie */
}

/* W ciemnym motywie te same kolory byłyby oślepiające i zjadłyby tekst -
   zostają ich przygaszone odpowiedniki o tym samym znaczeniu. */
body.dark table.dataTable > tbody > tr.row-warning > * {
    --bs-table-bg: #4a3c14;
}

body.dark table.dataTable > tbody > tr.row-danger > * {
    --bs-table-bg: #4d2226;
}


/* ============================================================================
   Zablokowane pole wyboru w ciemnym motywie
   ============================================================================
   Select2 maluje pole `disabled` na sztywno jasnoszarym tłem z jasnego motywu
   (zmierzone: rgb(233, 236, 239)), a tekst bierze kolor z motywu ciemnego -
   wychodzi jasnoszary napis na prawie białym polu. Widać to na „Pozycji zapytania"
   w pozycjach oferty: to pole jest zablokowane celowo (pozycja z zapytania jest
   tożsamością wiersza), więc świeciło przy każdej ofercie.
   ============================================================================ */
body.dark .select2-container--bootstrap-5 .select2-selection--single[aria-disabled="true"],
body.dark .select2-container--bootstrap-5.select2-container--disabled .select2-selection,
body.dark .select2-container--bootstrap-5 .select2-selection[aria-disabled="true"] {
    background-color: var(--bs-secondary-bg);
    border-color: var(--bs-border-color);
    color: var(--bs-secondary-color);
}

body.dark .select2-container--bootstrap-5 .select2-selection[aria-disabled="true"]
    .select2-selection__rendered,
body.dark .select2-container--bootstrap-5.select2-container--disabled .select2-selection
    .select2-selection__rendered {
    color: var(--bs-secondary-color);
}

/* ============================================================================
   Pole wyłączone z edycji ma WYGLĄDAĆ na wyłączone
   ============================================================================
   Bootstrap 5.3 wygasza pola kolorem `--bs-secondary-bg`, a w jasnym motywie ta
   zmienna ma wartość `#fff` - czyli dokładnie tło pola zwykłego. Efekt: pole
   zablokowane wyglądało identycznie jak do wypełnienia, a że nie da się w nim nic
   wpisać, wyglądało po prostu na zepsute (zgłoszone z pracy nad kosztorysem:
   „Cena jednostkowa nie szara tylko biała, a chciałem aby było widać że jej nie
   można edytować").

   Dlatego w jasnym motywie bierzemy `--bs-tertiary-bg` (#f8f9fa) - szarość na tyle
   wyraźną, żeby było widać różnicę, i na tyle spokojną, żeby formularz z kilkoma
   takimi polami nie wyglądał w paski. Kursor „nie wolno" mówi to samo drugą drogą.
   ============================================================================ */
.form-control:disabled,
.form-control[readonly],
.form-select:disabled {
    background-color: var(--bs-tertiary-bg);
    cursor: not-allowed;
}

/* Zwykłe pola formularza wyłączone z edycji mają w ciemnym motywie ten sam problem */
body.dark .form-control:disabled,
body.dark .form-control[readonly],
body.dark .form-select:disabled {
    background-color: var(--bs-secondary-bg);
    color: var(--bs-secondary-color);
}

/* ============================================================================
   Treść wiadomości e-mail - zawsze jak na papierze
   ============================================================================
   Maile pisze się pod białe tło. W ciemnym motywie wiadomość bez własnych stylów
   wychodziła białym tekstem (przeglądarka odwraca kolory dokumentu, który deklaruje
   ciemny schemat), a wiadomość z własnym białym tłem - białym tekstem na białym.
   Ramka dostaje więc jasne tło i jasny schemat kolorów, niezależnie od motywu
   aplikacji. To samo dotyczy edytora treści (TinyMCE dostaje skórkę z motywu
   w formularzu - patrz `apps/emails/forms.py`).
   ============================================================================ */
.email-body-frame {
    background: #fff;
    color-scheme: light;
    border-radius: .25rem;
}

/* ============================================================================
   Menu ustawień (koło zębate) w trzech kolumnach
   ============================================================================
   Dwadzieścia pozycji w jednej kolumnie dawało listę wyższą niż ekran - trzeba
   ją było przewijać, żeby w ogóle zobaczyć, co jest do ustawienia. W trzech
   kolumnach cała konfiguracja mieści się na raz.

   Szerokość jest stała, ale ograniczona szerokością okna (`max-width`): przy
   wąskim ekranie menu i tak zwija się do jednej kolumny (`col-md-4`), a bez tego
   ograniczenia wyjeżdżałoby poza prawą krawędź.
   ============================================================================ */
.settings-menu {
    min-width: 42rem;
    max-width: calc(100vw - 1.5rem);
    padding-bottom: .5rem;
}

/* Wariant z DWIEMA kolumnami (administracja | system) - węższy, bo przy 42rem
   każda kolumna miałaby po 21rem pustego miejsca obok krótkich nazw. */
.settings-menu.settings-menu-two-columns {
    min-width: 30rem;
}

/* Kreska między kolumnami - tylko tam, gdzie kolumny naprawdę stoją obok siebie.
   Po zwinięciu do jednej kolumny zmienia się w poziomą, żeby grupy nadal były
   od siebie oddzielone. */
@media (min-width: 768px) {
    .settings-menu-column {
        border-right: 1px solid var(--bs-border-color);
    }
}

@media (max-width: 767.98px) {
    .settings-menu {
        min-width: 0;
        /* Pasek górny ma 70 px, reszta to zapas na dolną krawędź. `dvh` liczy
           ekran BEZ paska adresu przeglądarki, który na telefonie chowa się przy
           przewijaniu - przy `vh` dół menu chował się pod nim. Wiersz z `vh` stoi
           wyżej jako zapas dla przeglądarek bez `dvh`. */
        max-height: calc(100vh - 5.5rem);
        max-height: calc(100dvh - 5.5rem);
        overflow-y: auto;
        /* Płynne przewijanie palcem w iOS */
        -webkit-overflow-scrolling: touch;
        /* Dojechanie do końca listy nie ma przewijać strony POD menu */
        overscroll-behavior: contain;
    }

    .settings-menu-column {
        border-bottom: 1px solid var(--bs-border-color);
        padding-bottom: .25rem;
        margin-bottom: .25rem;
    }
}

/* Menu użytkownika na telefonie - ten sam problem, co przy konfiguracji, tylko
   na mniejszą skalę: przy kilku obszarach roboczych i pozycjach administratora
   lista bywa wyższa niż ekran. Osobna klasa, bo pozostałe menu w pasku
   (powiadomienia, wyszukiwarka) mają własne przewijanie w środku i dołożenie
   drugiego dawałoby dwa paski obok siebie. */
@media (max-width: 767.98px) {
    .header-dropdown-scroll {
        max-height: calc(100vh - 5.5rem);
        max-height: calc(100dvh - 5.5rem);
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        overscroll-behavior: contain;
    }
}

/* Oddech przy kresce między kolumnami - bez tego najdłuższe pozycje
   („Konfiguracja powiadomień") dotykały linii. */
.settings-menu .row > [class*="col-"] {
    padding-right: .5rem;
}

/* Ikony pozycji mają klasę `text-muted`, która w CIEMNYM motywie schodzi niemal do
   koloru tła - w efekcie menu traciło ikony akurat tam, gdzie najbardziej pomagają
   w szukaniu wzrokiem. */
.dark .settings-menu .dropdown-item i.text-muted {
    color: rgba(255, 255, 255, .6) !important;
}

/* Nagłówek sekcji: pierwszy w kolumnie bez odstępu u góry (jest już dopełnienie
   samego menu), kolejne oddzielone od poprzedniej grupy. */
.settings-menu .dropdown-header {
    text-transform: uppercase;
    letter-spacing: .04em;
    font-size: .6875rem;
    font-weight: 700;
    margin-top: .75rem;
}

.settings-menu .col > .dropdown-header:first-child,
.settings-menu [class^="col-"] > .dropdown-header:first-child {
    margin-top: 0;
}

/* Dolnego paska (stopki z podpisem) NIE MA - razem z rezerwą miejsca pod nim.
   ============================================================================
   Zgłoszenie z pola: „w całym systemie usuń dolny pasek; na iPhonie i iPadzie jest
   już ukrywany, a na MacBooku Pro 14" też szkoda mi miejsca na taki komunikat, jak
   mam już listy, formularze i inne rzeczy".

   Nic funkcjonalnego przy tym nie zginęło: pasek był samym podpisem („©
   Wykonane z ❤ przez…"), bez jednego odnośnika, którego nie ma gdzie indziej.

   DLACZEGO TA REGUŁA W OGÓLE MUSI ISTNIEĆ - usunięcie znacznika NIE WYSTARCZA.
   Motyw stawia stopkę poza przepływem strony, a miejsce pod nią robi wypełnieniem
   treści:

       .footer       { position: absolute; bottom: 0; height: 60px; }
       .page-content { padding-bottom: 60px; }

   Te 60 px istnieją WYŁĄCZNIE po to, żeby zrobić miejsce na pasek. Sam znacznik
   zniknął z `dashboard.html`, ale motyw nadal wysyła wypełnienie - bez tej reguły
   u dołu KAŻDEJ strony zostałoby puste 60 px.

   ZERO, A NIE 20 PX. Odstęp od dolnej krawędzi daje już WŁASNY MARGINES ostatniej
   karty (`.card { margin-bottom: 1.25rem }` z Bootstrapa), czyli dokładnie tyle, ile
   odstęp z boku (`.page-content` 10 px + `.container-fluid` 10 px). Własne
   wypełnienie dokładałoby się do tego marginesu - zmierzone na komputerze PRZED tą
   zmianą:

   | | przed | po |
   |---|---|---|
   | z boku | 20 px | 20 px |
   | od dołu | 80 px (60 px wypełnienia + 20 px marginesu karty) | 20 px (sam margines) |

   Ta sama droga została przejściu wcześniej na iPadzie (zgłoszenie: „margines na dole
   nie pasuje do marginesu z prawej i lewej strony") - teraz obowiązuje wszędzie.
   Wymagane 20 px z `AI.md` jest zachowane; zmienia się tylko to, KTO je daje.

   DRUGI SELEKTOR NIE JEST OZDOBĄ. Motyw ma dla układu poziomego osobną regułę
   `body[data-layout=horizontal] .page-content` o WYŻSZEJ swoistości - sam
   `.page-content` by jej nie pokonał i 60 px wróciłoby bocznymi drzwiami. Aplikacja
   tego układu dziś nie ustawia, ale bez tego selektora zostaje cicha pułapka.

   Mierzy to `apps/core/tests/test_bottom_gap_browser.py` (odstęp na komputerze)
   oraz `TestEveryScreenHasTheSameGap` w `test_tablet_layout_browser.py` (przegląd
   wszystkich ekranów). Że pasek nie wrócił - pilnuje `test_no_bottom_bar.py`.
   ============================================================================ */
.page-content,
body[data-layout=horizontal] .page-content {
    padding-bottom: 0;
}


/* Dotyk: menu główne jako NAKŁADKA, nigdy jako pasek ikon.
   ============================================================================
   Zgłoszenie z pola: „jest problem z lewym menu na iPadzie, jak jest ono zwinięte,
   wyświetla się jakoś dziwnie; jak jest otwarte, to jakby nie było tego problemu".

   CO BYŁO NIE TAK. Zwinięte menu (`data-sidebar-size=sm`) to w tym motywie pasek
   szeroki na 70 px, który POKAZUJE NAZWY DOPIERO NA `:hover`, a podmenu rozwija
   również `:hover`. Na dotyku nie ma czym najechać: Safari daje najwyżej
   „przyklejony" stan po dotknięciu, a pierwsze dotknięcie idzie na rozwinięcie,
   nie na wybór. Do tego dwie rzeczy zmierzone na iPadzie (1180 x 820 px):

   * `body[data-sidebar-size=sm] { min-height: 850px }` zmuszało KAŻDĄ stronę do
     pionowego przewijania, bo ekran ma 820 px - stąd „okna nie dopasowują się";
   * pasek ikon potrzebuje 894 px wysokości, a w trybie zwiniętym motyw ZDEJMUJE mu
     przewijanie (`overflow: visible`, żeby podmenu mogło wyjść poza pasek). Dolne
     pozycje menu były więc na iPadzie NIEOSIĄGALNE.

   CO ROBIMY. Na dotyku menu zachowuje się dokładnie tak, jak na telefonie: jest
   schowane, a hamburger w górnym pasku pokazuje je na całej szerokości 250 px -
   z nazwami, z własnym przewijaniem i z zamykaniem po wybraniu pozycji
   (`js/custom.js`). Zwinięcie do ikon oddaje 70 px, nakładka oddaje wszystkie 250 px,
   więc na tablecie jest to i więcej miejsca, i mniej klikania.

   Reguły poniżej są PRZENIESIONYM blokiem motywu dla `max-width: 992px` - tam siedzi
   ta sama nakładka, tylko zawężona do szerokości okna. Kopiujemy je zamiast zmieniać
   plik motywu, bo `app.min.css` wymieniamy przy każdej aktualizacji szablonu.
   Ustawienie `data-sidebar-size` na `lg` zdejmuje `js/custom.js`; tu zostaje sam
   układ. Próg `min-width: 992px` jest po to, żeby nie dublować reguł, które motyw
   i tak stosuje na wąskim ekranie.

   Mierzy to `apps/core/tests/test_tablet_layout_browser.py`.
   ============================================================================ */
@media (hover: none) and (pointer: coarse) and (min-width: 992px) {

    /* Menu poza przepływem strony - pokazuje je dopiero klasa `sidebar-enable`,
       którą przełącza hamburger. */
    .vertical-menu {
        display: none;
        top: 70px;
    }

    body.sidebar-enable .vertical-menu {
        display: block;
    }

    /* Pełna wysokość razem z własnym przewijaniem (simplebar): menu ma 894 px, więc
       na ekranie 820 px MUSI się przewijać, inaczej dolne pozycje są nieosiągalne. */
    .vertical-menu .sidebar-menu-scroll {
        height: 100%;
        margin-top: 0;
    }

    /* Logo i hamburger w środku menu są zbędne - oba stoją w górnym pasku. */
    .vertical-menu .navbar-brand-box,
    .vertical-menu .vertical-menu-btn {
        display: none;
    }

    /* Treść zajmuje całą szerokość - menu nakłada się na nią, a nie odsuwa jej. */
    .main-content {
        margin-left: 0 !important;
    }

    #page-topbar {
        left: 0;
    }

    /* Logo wraca do górnego paska (w układzie z menu przy krawędzi stoi w menu). */
    #page-topbar .navbar-header .navbar-brand-box {
        display: inline-block;
        position: relative;
    }

    /* Hamburger - jedyne wejście do menu, więc musi być widoczny zawsze. */
    #page-topbar .navbar-header .vertical-menu-btn {
        display: inline-block;
    }
}


/* Telefon: lista dostaje całą szerokość ekranu.
   ============================================================================
   Zgłoszenie z pola: „marginesy zabierają z prawej i lewej oraz od góry i dołu
   dużo pustego miejsca przez co elementy na liście są bardzo małe i źle się to
   czyta".

   Zmierzone na liście kosztów przy szerokości 500 px - na marginesy poziome szło
   **100 px, czyli 20% ekranu**, w czterech warstwach po kolei:

   | warstwa | lewo+prawo |
   |---|---|
   | `.page-content`   | 10 + 10 |
   | `.container-fluid`| 10 + 10 |
   | `.col-lg-12`      | 10 + 10 |
   | `.card-body`      | 20 + 20 |

   W pionie: `.page-content` ma 90 px u góry przy pasku wysokim na 70 px (20 px
   zapasu) i 60 px u dołu przy stopce wysokiej na 60 px, a `.page-title-box`
   dokłada 20 px pod tytułem.

   Reguły są zawężone do stron Z LISTĄ, bo na formularzach marginesy pełnią swoją
   rolę - pola dotykałyby krawędzi ekranu. Rozpoznajemy je po `#bootstrap-datatable`,
   czyli karcie z szablonu listy, a NIE po `#datatable_wrapper`: ten drugi tworzy
   dopiero JavaScript, więc dokłada go KAŻDA strona z tabelą - także formularz
   z tabelą w zakładce.
   Próg jest ten sam, którym `datatables.html` rozpoznaje telefon (`PHONE_MEDIA`),
   żeby CSS i JS mówiły o tym samym urządzeniu. */
@media (max-width: 767.98px),
       (hover: none) and (pointer: coarse) and (max-height: 575.98px) {

    /* --- poziomo: zdejmujemy wszystkie cztery warstwy ---------------------- */
    .page-content:has(#bootstrap-datatable),
    .page-content:has(#bootstrap-datatable) > .container-fluid,
    .page-content:has(#bootstrap-datatable) .row > [class*="col-"] {
        padding-left: 0;
        padding-right: 0;
    }

    .page-content:has(#bootstrap-datatable) .row {
        margin-left: 0;
        margin-right: 0;
    }

    /* Karta zostaje z minimalnym oddechem - bez niego tekst dotyka krawędzi. */
    .page-content:has(#bootstrap-datatable) .card-body {
        padding-left: 4px;
        padding-right: 4px;
    }

    /* --- pionowo ----------------------------------------------------------- */
    /* Górny odstęp to miejsce na PRZYKLEJONY pasek (70 px) - zostawiamy 4 px
       zapasu, mniej wsunęłoby treść pod pasek. */
    .page-content:has(#bootstrap-datatable) {
        padding-top: 74px;
    }

    /* Dolnego paska nie ma już nigdzie, a wypełnienie `.page-content` jest zerowe
       globalnie (sekcja „Dolnego paska NIE MA" wyżej) - telefon nie potrzebuje tu
       więc żadnej własnej reguły odstępu.

       Stało tu wcześniej `padding-bottom: 16px` (plus zero dla list). Te 16 px NIE
       były wartością samą w sobie, tylko OBNIŻENIEM motywowych 60 px rezerwy na
       pasek. Przy zerze globalnym zostawienie ich zrobiłoby z telefonu jedyne
       urządzenie z odstępem 36 px (16 px wypełnienia + 20 px marginesu karty) przy
       20 px wszędzie indziej.

       Formularze na telefonie mają więc teraz te same 20 px z marginesu karty, co
       reszta aplikacji, a listy nadal zero - bo daje je reguła
       `.page-content:has(#bootstrap-datatable) .card { margin-bottom: 0 }` niżej
       i to ona realizuje zgłoszenie „na telefonie nie potrzebuję niczego z dołu".
       Mierzy to `test_the_phone_keeps_the_same_gap_as_the_desktop`
       w `apps/core/tests/test_bottom_gap_browser.py`. */

    /* `!important` wyjątkowo i z powodu: karta listy ma w szablonie klasę `pb-2`,
       a narzędziowe klasy odstępów Bootstrapa same są `!important` - bez tego reguła
       przegrywała i pod stronicowaniem zostawało 8 px. Zmierzone: pusta przestrzeń
       pod tabelą 11 px -> 5 px. */
    .page-content:has(#bootstrap-datatable) .card-body {
        padding-bottom: 2px !important;
    }

    /* Tytuł zostaje z odstępem od krawędzi - tabela może jej dotykać, tekst nie.
       Zmierzone na zrzucie: bez tego „Dokumenty kosztowe" kleiło się do brzegu. */
    .page-content:has(#bootstrap-datatable) .page-title-box {
        padding-bottom: 8px;
        padding-left: 8px;
        padding-right: 8px;
    }

    /* Zero, nie 8 px: pod listą nie ma już nic (dolny pasek jest ukryty), więc każdy
       piksel oddajemy tabeli. */
    .page-content:has(#bootstrap-datatable) .card {
        margin-bottom: 0;
    }

    /* Komórki tabeli: 12 px z każdej strony to na telefonie 24 px zabrane
       treści wiersza. */
    table.dataTable > thead > tr > th,
    table.dataTable > tbody > tr > td {
        padding-left: 8px;
        padding-right: 8px;
    }
}


/* Miniatura zdjęcia w tabeli - po najechaniu trzy razy większa.
   ============================================================================
   Kolumna włącza się w serializerze (`datatables_columns_image`), a renderer
   wstawia `<img class="dt-thumb">` - patrz `apps/core/serializers.py`.

   Powiększenie robi `transform`, nie zmiana `width`/`height`: transformacja nie
   przelicza układu tabeli, więc wiersze nie skaczą pod kursorem. `transform-origin`
   jest po lewej stronie, bo kolumna stoi jako pierwsza - rosnąc w prawo i w dół,
   obrazek zostaje w widocznym obszarze przewijania (`.dt-scroll-body` przycina to,
   co z niego wychodzi).

   `z-index` razem z `position: relative`, inaczej powiększony obrazek schowałby się
   pod sąsiednimi komórkami. */
table.dataTable img.dt-thumb {
    height: 2rem;
    width: auto;
    max-width: 4rem;
    object-fit: contain;
    transition: transform .12s ease-out;
    transform-origin: left center;
}

/* Sama miniatura już się NIE powiększa: podgląd rysuje się poza tabelą
   (`#dt-thumb-preview`), bo `.dt-scroll-body` przycinał wszystko większe niż
   trzykrotność. Zostaje delikatne podświetlenie, żeby było widać, co jest pod
   kursorem. */
table.dataTable img.dt-thumb:hover {
    outline: 2px solid var(--bs-primary);
    outline-offset: 1px;
}

/* Podgląd zdjęcia z kolumny miniatur - element `position: fixed` dopięty do `body`,
   więc żaden `overflow` go nie przycina. Rozmiar to trzykrotność poprzedniego
   powiększenia (152x96 -> ~450x290), czyli praktycznie oryginał z biblioteki
   (560x354) - o to chodziło: rozpoznać aparat bez wchodzenia na kartę.

   `pointer-events: none` jest konieczne, nie kosmetyczne: podgląd pojawia się pod
   kursorem, więc gdyby przyjmował mysz, natychmiast zabierałby `mouseleave`
   miniaturze i migałby. */
#dt-thumb-preview {
    position: fixed;
    z-index: 1080;
    display: none;
    padding: .375rem;
    background-color: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: .5rem;
    box-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .2);
    pointer-events: none;
}

#dt-thumb-preview img {
    display: block;
    max-width: 28rem;
    max-height: 18rem;
    width: auto;
    height: auto;
    object-fit: contain;
}

/* Na telefonie powiększanie po najechaniu nie istnieje (nie ma kursora), a miniatura
   ma być mała, żeby wiersz zmieścił treść. */
@media (max-width: 767.98px),
       (hover: none) and (pointer: coarse) and (max-height: 575.98px) {
    table.dataTable img.dt-thumb {
        height: 1.5rem;
        max-width: 2.5rem;
    }

    table.dataTable img.dt-thumb:hover {
        outline: none;
    }

    /* Na dotyku podglądu nie ma - JavaScript go nie pokazuje - ale gdyby kiedyś
       wrócił, nie może zasłonić wiersza. */
    #dt-thumb-preview {
        display: none !important;
    }
}

/* ==========================================================================
   Wejście administratora na cudze konto
   ==========================================================================

   Dwa stany, dwie klasy na `<body>` i dwa różne odbiorcy:

   `impersonating`   - JA jestem na cudzym koncie. Cała górna belka robi się
                       czerwona, żeby nie dało się o tym zapomnieć przy żadnym
                       ekranie. Belka jest przypięta u góry (`position: fixed`),
                       więc jedzie z każdym przewinięciem - inaczej niż paski
                       nad treścią, które uciekają przy pierwszym scrollu.

   `account-watched` - KTOŚ jest na MOIM koncie. Obwódka avatara w belce robi
                       się czerwona zamiast szarej. To jedyne ostrzeżenie, jakie
                       ma właściciel konta, więc stoi tam, gdzie i tak patrzy.

   Kolory biorą się ze zmiennych Bootstrapa (`--bs-danger`), a nie z wpisanych
   wartości - motyw jasny i ciemny mają inne odcienie i mają je zachować. */

body.impersonating #page-topbar {
    background-color: var(--bs-danger);
    box-shadow: 0 2px 4px rgba(220, 53, 69, .35);
}

/* Wszystko w czerwonej belce musi być czytelne na czerwonym: ikony i napisy
   w tym motywie są domyślnie ciemnoszare i na tym tle giną. */
body.impersonating #page-topbar,
body.impersonating #page-topbar .btn.header-item,
body.impersonating #page-topbar .app-search .form-control,
body.impersonating #page-topbar .app-search span {
    color: #fff;
}

body.impersonating #page-topbar .app-search .form-control::placeholder {
    color: rgba(255, 255, 255, .7);
}

/* Pole wyszukiwarki na czerwonym: samo przezroczyste tło zlewa je z belką
   i nie widać, gdzie się klika. */
body.impersonating #page-topbar .app-search .form-control {
    background-color: rgba(255, 255, 255, .15);
}

body.impersonating .impersonation-banner {
    color: #fff;
    white-space: nowrap;
}

/* Przyciski w pasku („Odblokuj zapis", „Opuść konto") stoją w formularzach,
   więc bez tego układ łamie im się do pionu przy wąskim oknie. */
body.impersonating .impersonation-banner form {
    margin: 0;
}

/* Obwódka avatara. Sama obwódka to `padding: 3px` na obrazku plus kolor tła
   pod spodem (patrz `app.min.css`) - dlatego zmieniamy TŁO, a nie `border`.
   Dołożenie `border` przesunęłoby obrazek o dwa piksele i avatar podskakiwałby
   w chwili wejścia kogoś na konto. */
body.account-watched .header-profile-user {
    background-color: var(--bs-danger);
}

/* Neutralny „znaczek” Librusa (konto, rodzaj nowości, ocena na pulpicie).
   ============================================================================
   `bg-light text-dark` w CIEMNYM motywie dawało ciemny napis na ciemnym tle -
   motyw przestawia `bg-light` na ciemny kolor, a `text-dark` zostaje ciemny.
   Zmienne Bootstrapa (`--bs-secondary-bg`, `--bs-body-color`) same zmieniają się
   z `data-bs-theme`, więc znaczek jest czytelny w obu motywach. Klasa siedzi tu,
   a nie w `librus.css`, bo używają jej też pulpit i nagłówek.
   ============================================================================ */
.librus-chip {
    background-color: var(--bs-secondary-bg);
    color: var(--bs-body-color);
    border: 1px solid var(--bs-border-color);
    font-weight: 500;
}

a.librus-chip:hover {
    color: var(--bs-body-color);
    background-color: var(--bs-tertiary-bg);
}

/* Rodzaje powiadomień Librus w tabeli osób konta - checkboxy obok siebie, a nie
   w pionie (siedem pozycji rozciągałoby każdy wiersz). Kolumnę oznacza
   `columns_class` formularza (`LibrusAccountAccessForm`). */
.formset td.librus-kinds .form-check {
    display: inline-block;
    margin-right: 1rem;
    margin-bottom: 0;
}
