html, body {
    /* Font systemowy (spójnie z motywem MudBlazora i design tokens) — zero webfontów. */
    font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}

/* ── Pasek nieobsłużonego błędu (runtime Blazora) ────────────────────────────────────────────────────
   Jedyny fragment interfejsu, którego markup siedzi w `index.html`, a nie w komponencie: pokazuje go
   Blazor, gdy wyjątek wyrwie się poza kod aplikacji. Treść jest po polsku od dawna, ale wygląd został
   z szablonu (jasnożółte tło, niebieski link) — a przede wszystkim pasek stał na `bottom: 0` z z-indeksem
   1000, czyli w CAŁOŚCI pod dolną nawigacją (ta ma 1200 i 64 px wysokości). Na telefonie awaria nie
   pokazywała więc niczego. Stąd: kolory awaryjne z tokenów, z-index ponad nawigacją i odsunięcie
   dokładnie o jej wysokość — liczone tą samą zmienną co rezerwa treści, żeby nie rozjechało się
   przy zmianie paska. */
#blazor-error-ui {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1300;
    display: none;
    box-sizing: border-box;
    padding: 12px 56px 12px 16px;
    background: #FEE2E2;
    color: #B91C1C;
    border-top: 1px solid #FCA5A5;
    box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
    font-size: 14px;
    color-scheme: light only;
}

/* Ten sam warunek co przy rezerwie treści: pasek nawigacji renderuje się tylko dla zalogowanych,
   więc na ekranie logowania i na publicznej ofercie komunikat ma stać przy krawędzi. */
body:has(.cb-bottom-nav) #blazor-error-ui {
    bottom: calc(var(--cb-bottom-nav-height) + env(safe-area-inset-bottom, 0px));
}

    /* Wyjście z awarii jest celem dotykowym, a nie napisem w zdaniu — link z szablonu miał wysokość tekstu. */
    #blazor-error-ui .reload {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
        padding: 0 8px;
        margin-left: 4px;
        color: inherit;
        font-weight: 600;
    }

    #blazor-error-ui .dismiss {
        position: absolute;
        top: 0;
        right: 4px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 44px;
        height: 100%;
        cursor: pointer;
    }

/* Pasek „nowa wersja aplikacji". Ten sam układ co komunikat o awarii (dół ekranu, nad paskiem nawigacji),
   ale w kolorze marki, nie alarmu: to jest informacja, a nie awaria — użytkownik może ją zignorować
   i dokończyć, co robił. */
#cb-update-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1300;
    display: flex;
    align-items: center;
    gap: 8px;
    box-sizing: border-box;
    padding: 8px 16px;
    background: #B45309;
    color: #FFFFFF;
    box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
    font-size: 14px;
    color-scheme: light only;
}

#cb-update-bar[hidden] {
    display: none;
}

body:has(.cb-bottom-nav) #cb-update-bar {
    bottom: calc(var(--cb-bottom-nav-height) + env(safe-area-inset-bottom, 0px));
}

    /* Cel dotykowy, nie napis w zdaniu — ta sama reguła co przy „Odśwież" w komunikacie o awarii. */
    #cb-update-bar button {
        min-height: 44px;
        padding: 0 12px;
        margin-left: auto;
        border: 1px solid rgba(255, 255, 255, 0.7);
        border-radius: 8px;
        background: transparent;
        color: inherit;
        font: inherit;
        font-weight: 600;
        cursor: pointer;
    }

@media (min-width: 960px) {
    /* Na desktopie dolnego paska nie ma (jest sidebar), więc odsunięcie znika razem z nim. */
    body:has(.cb-bottom-nav) #blazor-error-ui {
        bottom: 0;
    }
}

/* ── Ekran startowy (przed uruchomieniem WASM) ───────────────────────────────────────────────────────
   Kolory wprost z design tokens (docs/plany/makiety/tokens.css) — motyw MudBlazora jeszcze nie istnieje,
   więc nie ma tu zmiennych --mud-*. Zero webfontów i zero JS: to musi się narysować przy pierwszej klatce. */
.cb-splash {
    position: fixed;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 24px;
    background: #FAF9F7; /* ciepła biel */
    color: #1F2937;      /* grafit */
    text-align: center;
}

.cb-splash-brand {
    font-size: 2rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: #B45309; /* bursztyn */
    /* Delikatne pulsowanie: dopóki Blazor nie zna postępu, pasek stoi na 0% — bez tego ekran wygląda na zawieszony. */
    animation: cb-splash-pulse 1.6s ease-in-out infinite;
}

.cb-splash-status {
    font-size: 0.95rem;
    color: #4B5563;
}

.cb-splash-bar {
    width: min(260px, 70vw);
    height: 6px;
    border-radius: 999px;
    background: #E7E2DC;
    overflow: hidden;
}

.cb-splash-bar-fill {
    height: 100%;
    border-radius: inherit;
    background: #B45309;
    /* Blazor ustawia --blazor-load-percentage w trakcie pobierania zasobów; przed startem zmienna nie istnieje. */
    width: var(--blazor-load-percentage, 0%);
    transition: width 0.15s ease-out;
}

.cb-splash-percent {
    font-size: 0.8rem;
    color: #6B7280;
    min-height: 1em; /* rezerwa, żeby pojawienie się procentów nie przesuwało układu */
}

    .cb-splash-percent::after {
        content: var(--blazor-load-percentage-text, "");
    }

@keyframes cb-splash-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.55; }
}

/* Szanuj ustawienie systemowe „ogranicz animacje" — pulsowanie jest ozdobą, nie nośnikiem informacji. */
@media (prefers-reduced-motion: reduce) {
    .cb-splash-brand {
        animation: none;
    }
}

/* ── Powłoka: pasek górny, sidebar, kolumna treści ───────────────────────────────────────────────────
   Próg 960 px = Breakpoint.Md MudBlazora. Ta sama liczba rządzi sidebarem (parametr Breakpoint w MainLayout)
   i tymi regułami — rozjazd dałby stan „sidebar już jest, a dolny pasek jeszcze nie zniknął". */
:root {
    --cb-desktop-breakpoint: 960px;
    --cb-content-width: 880px;
}

/* Kolumna treści: wyśrodkowana, o stałej maksymalnej szerokości. MudContainer dostaje MaxWidth.False,
   więc cała metryka szerokości siedzi tutaj, obok rezerwy na dolny pasek. */
.cb-page {
    max-width: var(--cb-content-width);
    margin-inline: auto;
}

/* Kontekst strony (SectionOutlet) i akcje paska są na mobile ukryte: tam pasek niesie markę, a nagłówek
   zostaje w treści strony. Pokazujemy je dopiero na desktopie, gdzie marka przenosi się do sidebara. */
.cb-appbar-context {
    display: none;
    align-items: baseline;
    gap: 8px;
    min-width: 0;
}

/* Odpowiednik `MudSpacer` z szablonu, tylko własny — komponent biblioteki nie przyjmuje `Class`.
   Na mobile rozpycha markę i ikony konta na przeciwne końce paska. */
.cb-appbar-spacer {
    flex-grow: 1;
}

@media (min-width: 960px) {
    /* Biały pasek kontekstu zamiast bursztynowego paska marki. Kolory nakładamy z !important, bo MudBlazor
       maluje pasek klasą z palety motywu (AppbarBackground) — zwykła reguła by z nią przegrała.
       ⚠️ Warunek `body:has(.cb-sidebar)` NIE jest ozdobnikiem: marka przenosi się do sidebara, a sidebar
       renderuje się tylko dla zalogowanych. Bez tego warunku ekran logowania na desktopie dostawał pusty
       biały pasek — bez marki (ukrytej) i bez kontekstu (którego strona logowania nie wystawia). */
    body:has(.cb-sidebar) .cb-appbar {
        background-color: #FFFFFF !important;
        color: #1F2937 !important;
        border-bottom: 1px solid #E5E7EB;
    }

        body:has(.cb-sidebar) .cb-appbar .cb-appbar-brand,
        body:has(.cb-sidebar) .cb-appbar .cb-appbar-actions {
            display: none;
        }

        body:has(.cb-sidebar) .cb-appbar .cb-appbar-context {
            display: flex;
            flex: 1;
        }

        /* Spacer z paska mobilnego przestaje rosnąć: tam rozpychał markę i ikony konta, a tutaj oba te
           elementy są ukryte (reguła wyżej), więc dzielił wolne miejsce z kontekstem po połowie. Skutek był
           widoczny gołym okiem: akcja strony ma `margin-left: auto`, więc siadała na PRAWEJ KRAWĘDZI
           KONTEKSTU — czyli na środku paska, z pustką po prawej. */
        body:has(.cb-sidebar) .cb-appbar .cb-appbar-spacer {
            flex-grow: 0;
        }

    .cb-page {
        padding: 32px 40px !important;
    }
}

.cb-page-title {
    font-size: 15px;
    font-weight: 600;
}

/* ⚠️ Ten i kilka kolejnych drugorzędnych tekstów miały #9CA3AF („cichy szary" z tokenów) — 2,5:1 na białym,
   czyli poniżej wymaganych 4,5:1 dla tekstu tej wielkości. Hierarchię niesie tu rozmiar i waga, nie kontrast:
   token --color-ink-soft (#6B7280) daje 4,8:1 i nadal czyta się jak drugi plan. Nie podmieniać z powrotem —
   sprawdzenie jest w generator/audyt.mjs (kolumna „kontrast"). */
.cb-page-subtitle {
    font-size: 14px;
    color: #6B7280;
}

/* Akcja główna strony dosunięta do prawej krawędzi paska — margin-left:auto zamiast MudSpacera,
   bo ten fragment przychodzi z innego komponentu i nie zna swojego sąsiedztwa. */
.cb-page-actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 8px;
}

.cb-sidebar {
    border-right: 1px solid #E5E7EB;
}

.cb-sidebar-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 22px 24px;
}

/* Kafelek logo: bursztynowy kwadrat z ikoną — jedyny „głośny" element w białym sidebarze. */
.cb-sidebar-logo {
    width: 34px;
    height: 34px;
    border-radius: 9px;
    background: #B45309;
    color: #fff;
    display: grid;
    place-items: center;
    flex-shrink: 0;
}

.cb-sidebar-wordmark {
    font-size: 18px;
    font-weight: 700;
    letter-spacing: -0.01em;
}

/* Aktywna pozycja: bursztyn na bursztynowym tincie. MudBlazor zaznacza ją klasą .active (z NavLink),
   więc podświetlenie jedzie z routingu — nie ma tu osobnego stanu do utrzymania. */
.cb-sidebar .mud-nav-link {
    border-radius: 9px;
    margin: 0 12px 3px;
    font-size: 14px;
    font-weight: 600;
    color: #6B7280;
}

    /* Ciemniejszy bursztyn (--color-primary-hover z tokenów), bo #B45309 na tym tincie daje 4,43:1 —
       pół dziesiątej poniżej wymagania dla tekstu 14 px. Tło zostaje, więc pozycja nadal „świeci" bursztynem. */
    .cb-sidebar .mud-nav-link.active {
        color: #92400A;
        background-color: #FBEFE2;
    }

/* Stopka (Konto/Wyloguj) dociśnięta do dołu kreską — margin-top:auto działa, bo MudDrawer układa
   zawartość w kolumnę flex. */
.cb-sidebar-foot {
    margin-top: auto;
    border-top: 1px solid #F3F4F6;
    padding: 12px 0;
}

    .cb-sidebar-foot .mud-nav-link {
        font-size: 13px;
        font-weight: 400;
    }

/* Dolna nawigacja (mobile-first): stały pasek na dole z trzema głównymi celami.
   Wysokość jest USTALONA zmienną, a nie wynikowa z treści — dzięki temu ta sama liczba służy
   za rezerwę w treści strony (.cb-page). Wcześniej rezerwa była zgadywana (80 px) i wychodziła
   mniejsza niż realny pasek (ikona + etykieta + paddingi), więc ostatni element chował się pod nim. */
:root {
    --cb-bottom-nav-height: 64px;
}

.cb-bottom-nav {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1200;
    display: flex;
    justify-content: space-around;
    background: var(--mud-palette-surface, #fff);
    border-top: 1px solid var(--mud-palette-lines-default, #e0e0e0);
    box-shadow: 0 -1px 4px rgba(0, 0, 0, 0.08);
    /* Pasek gestów (iOS/Android w trybie gestów) zjada dolne piksele — bez tego etykiety siedzą na nim. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
}

    .cb-bottom-nav .mud-nav-link {
        flex: 1;
        flex-direction: column;
        /* Przy flex-direction: column to align-items centruje w poziomie (justify-content robi to w pionie).
           Padding zerujemy, bo MudBlazor daje własny lewy — inaczej treść odjeżdża w lewo mimo centrowania. */
        align-items: center;
        justify-content: center;
        text-align: center;
        gap: 2px;
        padding: 8px 0;
        /* box-sizing, żeby padding mieścił się w zadeklarowanej wysokości — inaczej pasek urósłby ponad rezerwę. */
        box-sizing: border-box;
        height: var(--cb-bottom-nav-height);
        /* 12 px to podłoga czytelności w tej aplikacji; 0,72 rem dawało 11,5 i było jedynym tekstem
           poniżej niej po przejrzeniu wszystkich ekranów. Różnica jest niewidoczna, próg — nie. */
        font-size: 0.75rem;
    }

/* Rezerwa w treści strony: dokładnie pasek + jego safe-area + oddech, żeby ostatni element
   (np. przycisk „Dodaj notatkę") nie kończył się tuż pod krawędzią paska.
   Warunek `:has(.cb-bottom-nav)` — pasek renderuje się tylko dla zalogowanych, więc login/rejestracja
   i publiczna oferta nie dostają pustej przestrzeni na dole. */
body:has(.cb-bottom-nav) .cb-page {
    padding-bottom: calc(var(--cb-bottom-nav-height) + env(safe-area-inset-bottom, 0px) + 16px) !important;
}

/* Pasek „nowa wersja" NIE znika sam i wisi nad dolną krawędzią (nad nawigacją, gdy jest) — bez własnej
   rezerwy zakrywał ostatni element strony bez możliwości doscrollowania (zmierzone na „Koncie": 60 px
   paska ponad 80 px rezerwy nawigacji). 80 px zapasu mieści też tekst zawinięty na dwie linie.
   Kolejność reguł jest tu znacząca: wariant z nawigacją stoi OSTATNI, bo przy tej samej ważności
   (!important) wygrywa późniejszy — a zalogowany z paskiem pasuje do obu selektorów. */
body:has(#cb-update-bar:not([hidden])) .cb-page {
    padding-bottom: calc(16px + 80px) !important;
}

body:has(.cb-bottom-nav):has(#cb-update-bar:not([hidden])) .cb-page {
    padding-bottom: calc(var(--cb-bottom-nav-height) + env(safe-area-inset-bottom, 0px) + 16px + 80px) !important;
}

/* Na desktopie nawigację przejmuje sidebar, więc dolny pasek znika — RAZEM z rezerwą na niego.
   Samo ukrycie paska zostawiłoby u dołu strony pustą przestrzeń wielkości paska, którego już nie widać. */
@media (min-width: 960px) {
    .cb-bottom-nav {
        display: none;
    }

    body:has(.cb-bottom-nav) .cb-page {
        padding-bottom: 32px !important;
    }

    /* Pasek „nowa wersja" wraca na sam dół: `:has(.cb-bottom-nav)` łapie także pasek UKRYTY przez
       display:none, więc bez tej korekty wisiał 64 px nad dolną krawędzią i przecinał stopkę sidebara.
       Selektory celowo powtarzają pełną formę mobilnych reguł — przy tej samej ważności rozstrzyga
       specyficzność i kolejność, a słabszy selektor przegrałby mimo media query. */
    body:has(.cb-bottom-nav) #cb-update-bar {
        bottom: 0;
    }

    body:has(.cb-bottom-nav):has(#cb-update-bar:not([hidden])) .cb-page {
        padding-bottom: calc(32px + 80px) !important;
    }
}

        /* Ikona w MudNavLink ma marginRight pod układ poziomy — w kolumnie zbijałaby etykietę w lewo. */
        .cb-bottom-nav .mud-nav-link .mud-nav-link-icon {
            margin: 0;
        }

        /* Etykieta: MudBlazor daje jej `width:100%; text-align:start; margin-inline-start:12px` (układ poziomy
           w szufladzie). W dolnym pasku to zostawiało napis przy lewej krawędzi mimo wycentrowanej ikony —
           centrowanie rodzica nie pomoże, bo blok i tak zajmuje całą szerokość. Zerujemy margines i tekst na środek. */
        .cb-bottom-nav .mud-nav-link .mud-nav-link-text {
            text-align: center;
            margin-left: 0;
            margin-inline-start: 0;
        }

/* ── Wspólne elementy ekranów (karta, nagłówek sekcji, metadane) ─────────────────────────────────────
   Karta = 1px linia + 12px radius, bez cienia (cień rezerwujemy dla elementów unoszonych: FAB, modal). */
.cb-card {
    background: #FFFFFF;
    border: 1px solid #E5E7EB;
    border-radius: 12px;
    padding: 13px 15px;
    margin-bottom: 10px;
}

/* Nagłówek ekranu w treści — na desktopie ukryty, bo tam tę rolę pełni pasek kontekstu. */
.cb-screen-head {
    margin-bottom: 18px;
}

.cb-screen-title {
    margin: 0;
    font-size: 24px;
    font-weight: 700;
    letter-spacing: -0.02em;
}

.cb-screen-date {
    font-size: 13px;
    color: #6B7280;
    margin-top: 2px;
}

@media (min-width: 960px) {
    .cb-screen-head {
        display: none;
    }
}

.cb-section-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
}

.cb-section-icon {
    font-size: 20px;
    color: #B45309;
}

.cb-section-title {
    margin: 0;
    font-size: 15px;
    font-weight: 700;
}

/* Na desktopie nagłówek sekcji rośnie do 18/700 z ikoną 22 px — tak ma makieta w obu układach naraz
   („Przypomnienia" na Dziś, „Notatki" w karcie klienta) i tam ta różnica ma sens: na telefonie ten sam
   nagłówek konkuruje o szerokość z treścią i przyciskiem sekcji, na monitorze konkurencji nie ma,
   a wyraźniejsza hierarchia jest jedyną rzeczą, która porządkuje szerszą kolumnę. */
@media (min-width: 960px) {
    .cb-section-icon {
        font-size: 22px;
    }

    .cb-section-title {
        font-size: 18px;
    }

    .cb-section-head,
    .cb-tab-head {
        margin-bottom: 16px;
    }
}

.cb-section-note {
    font-size: 12px;
    color: #6B7280;
    line-height: 1.4;
    margin: -4px 0 10px;
}

.cb-meta {
    font-size: 12px;
    color: #6B7280;
    margin-top: 2px;
}

.cb-meta-quiet {
    color: #6B7280;
    /* 12 px zamiast 11: to jest wiersz z datą notatki i ze stanem najnowszej oferty — czyta się go
       na budowie, a mikroplakietka `.cb-offer-pill` dziedziczy stąd rozmiar. */
    font-size: 12px;
}

.cb-empty {
    font-size: 13px;
    color: #6B7280;
    margin-bottom: 12px;
}

/* ── Pusty stan „prowadzący za rękę" (komponent EmptyState) ──────────────────────────────────────────
   Przerywana ramka, a nie pełna: karta z kreską mówi „tu coś będzie", karta z ramką ciągłą wygląda
   jak treść, której nie da się przeczytać. Zostaje obok `.cb-empty`, bo krótkie „Nic zaplanowanego"
   wewnątrz rozwiniętej sekcji nadal ma być jedną linijką, a nie kaflem na pół ekranu. */
.cb-emptystate {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 8px;
    padding: 28px 16px 32px;
    background: #FFFFFF;
    border: 1px dashed #E5E7EB;
    border-radius: 12px;
    margin-bottom: 12px;
}

.cb-emptystate-icon {
    width: 48px;
    height: 48px;
    border-radius: 14px;
    background: #FBEFE2;
    color: #B45309;
    display: grid;
    place-items: center;
}

    .cb-emptystate-icon .mud-icon-root {
        font-size: 26px;
    }

.cb-emptystate-title {
    font-size: 15px;
    font-weight: 600;
}

/* Miara w znakach, nie w pikselach: podpowiedź ma się czytać jak zdanie, a nie jak kolumna gazety. */
.cb-emptystate-hint {
    font-size: 13px;
    color: #6B7280;
    line-height: 1.45;
    max-width: 34ch;
}

.cb-emptystate-action {
    margin-top: 8px;
}

/* ── Nieudane wczytanie z ponowieniem (komponent LoadError) ──────────────────────────────────────────
   Wiersz, nie kafel na pół ekranu: ten sam komponent stoi raz jako CAŁA treść ekranu (nic się nie wczytało),
   a raz nad listą, która już coś pokazuje (padła dopiero kolejna strona). Kafel byłby w tym drugim miejscu
   większy od problemu. Przy wąskim ekranie przycisk schodzi pod komunikat (`flex-wrap`), zamiast ściskać go
   do dwóch liter. */
.cb-loaderror {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    padding: 14px 16px;
    background: #FEF2F2;
    border: 1px solid #FECACA;
    border-radius: 12px;
    margin-bottom: 12px;
}

.cb-loaderror-icon {
    color: #B91C1C;
    flex-shrink: 0;
}

.cb-loaderror-text {
    flex: 1 1 180px;
    min-width: 0;
    font-size: 14px;
    color: #7F1D1D;
    overflow-wrap: anywhere;
}

.cb-loaderror-action {
    flex-shrink: 0;
    margin-left: auto;
}

/* ⚠️ Pusty stan CELOWO nie ma już własnej szerokości. Stało tu `max-width: 520px; margin-inline: auto`
   z uzasadnieniem „wąski kafel czyta się jak zaproszenie" — i osobno wyglądał dobrze, ale nie stoi osobno:
   nad nim jest wiersz filtrów, pod nim lista, oba na całą kolumnę. Trzy bloki o trzech różnych szerokościach
   i trzech lewych krawędziach czytają się jak przesunięty układ, a nie jak zaproszenie. Jedna kolumna,
   jedna krawędź (zgłoszone ze zrzutów, 2026-08-23). */

.cb-overdue {
    color: #B91C1C;
    font-weight: 600;
}

.cb-price {
    /* Cyfry o równej szerokości — ceny w liście układają się w kolumnę, a nie w schodki. */
    font-variant-numeric: tabular-nums;
    font-size: 16px;
    font-weight: 700;
    color: #B45309;
    white-space: nowrap;
}

/* ── Ekran „Dziś" ────────────────────────────────────────────────────────────────────────────────────
   Mobile: jedna kolumna (sekcje pod sobą). Desktop: dwie równe kolumny wg makiety. */
.cb-today-grid {
    display: grid;
    gap: 24px;
}

@media (min-width: 960px) {
    .cb-today-grid {
        grid-template-columns: 1fr 1fr;
    }
}

.cb-reminder {
    display: flex;
    align-items: center;
    gap: 11px;
}

.cb-reminder-icon {
    font-size: 22px;
    flex-shrink: 0;
}

/* Zaległe na czerwono, pozostałe bursztynem — kolor DUBLUJE informację z podpisu „· zaległe”,
   nie zastępuje jej (sam kolor byłby nieczytelny przy daltonizmie). */
.cb-icon-overdue {
    color: #B91C1C;
}

.cb-icon-due {
    color: #B45309;
}

.cb-reminder-body {
    flex: 1;
    min-width: 0;
}

.cb-reminder-message {
    font-size: 14px;
    font-weight: 500;
}

/* Treść zgłoszenia bywa wielolinijkowa — zachowujemy podziały wierszy autora, ale łamiemy długie słowa. */
.cb-feedback-message {
    font-size: 14px;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* Zielony kafelek „odhacz" — jedyna akcja pozytywna na ekranie, więc dostaje własny kolor zamiast
   neutralnej ikony. Nadpisujemy promień MudBlazora (przyciski ikonowe są okrągłe), bo makieta chce kwadratu. */
.cb-check {
    background-color: #DCFCE7 !important;
    color: #15803D !important;
    border-radius: 8px !important;
    width: 34px;
    height: 34px;
    flex-shrink: 0;
}

/* Kafelek „Zadzwoń" przy przypomnieniu — bliźniak „odhacz", w kolorze marki zamiast zielonego:
   to akcja główna tego ekranu, nie domknięcie. Kolor ikony na bursztynowym tle spełnia próg dla GRAFIKI
   (WCAG 1.4.11, 3:1) — napisu w tym kafelku nie ma, bo obok stoi już drugi kafelek. */
.cb-call-tile {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    border-radius: 8px;
    background-color: #FEF3E2;
    color: #B45309;
    text-decoration: none;
}

/* Karta rozwijająca sekcję (progressive disclosure) — komponent Disclosure. */
.cb-disclosure-panel {
    background: #FFFFFF;
    border: 1px solid #E5E7EB;
    border-radius: 12px;
    overflow: hidden;
    margin-bottom: 10px;
}

.cb-disclosure {
    display: flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    padding: 15px 16px;
}

.cb-disclosure-body {
    padding: 0 16px 16px;
}

.cb-disclosure-icon {
    font-size: 20px;
    color: #6B7280;
}

.cb-disclosure-label {
    font-size: 14px;
    font-weight: 600;
    flex: 1;
}

.cb-disclosure-chevron {
    font-size: 22px;
    color: #6B7280;
    transition: transform 0.15s ease-out;
}

.cb-chevron-open {
    transform: rotate(180deg);
}

/* Szanuj „ogranicz animacje" — obrót strzałki jest ozdobą, stan niesie sama obecność listy. */
@media (prefers-reduced-motion: reduce) {
    .cb-disclosure-chevron {
        transition: none;
    }
}

/* ── Panel administratora ────────────────────────────────────────────────────────────────────────────
   Kafelki liczników: na mobile dwie kolumny (cztery w rzędzie dają nieczytelne słupki), na desktopie
   cztery + szerszy kafelek z kwotami. */
.cb-stats {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    margin-bottom: 16px;
}

.cb-stat {
    padding: 16px;
    margin-bottom: 0;
}

.cb-stat-value {
    /* Cyfry o równej szerokości — ceny w liście układają się w kolumnę, a nie w schodki. */
    font-variant-numeric: tabular-nums;
    font-size: 24px;
    font-weight: 700;
}

.cb-stat-value-sm {
    font-size: 20px;
}

.cb-stat-alert {
    color: #B91C1C;
}

/* Kwota faktyczna na zielono — to jedyna liczba na tym ekranie, która niczego nie zakłada. */
.cb-stat-fact {
    color: #15803D;
}

.cb-stat-label {
    font-size: 12px;
    color: #6B7280;
    margin-top: 3px;
    display: flex;
    align-items: center;
    gap: 3px;
}

.cb-stat-money {
    grid-column: 1 / -1;
    display: flex;
    gap: 18px;
}

.cb-stat-money-half {
    flex: 1;
    min-width: 0;
}

.cb-stat-money-split {
    width: 1px;
    background: #F3F4F6;
}

@media (min-width: 960px) {
    .cb-stats {
        grid-template-columns: repeat(4, 1fr) 1.6fr;
        gap: 14px;
        margin-bottom: 24px;
    }

    .cb-stat-money {
        grid-column: auto;
    }

    .cb-stat-value {
        font-size: 28px;
    }
}

.cb-admin-plan-select {
    display: none;
    width: 200px;
    flex-shrink: 0;
}

/* Przełącznik w ramce, żeby wyglądał jak sąsiadujące pola, a nie jak zgubiona kontrolka obok nich. */
.cb-switch-box {
    display: flex;
    align-items: center;
    padding: 0 12px;
    border: 1px solid #D1D5DB;
    border-radius: 10px;
    background: #fff;
    white-space: nowrap;
}

@media (min-width: 960px) {
    .cb-admin-plan-select {
        display: block;
    }
}

.cb-tenant {
    padding: 16px 18px;
}

.cb-tenant-row {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

.cb-tenant-body {
    flex: 1;
    min-width: 0;
}

/* Na telefonie nazwa firmy dostaje cały wiersz, a plakietki planu i „Zmień plan" schodzą pod nią.
   `flex: 1` z `min-width: 0` znaczyło, że nazwa oddaje miejsce plakietkom aż do 74 px szerokości —
   a wtedy `overflow-wrap: anywhere` (ochrona przed długim ciągiem) łamał ją w środku słowa na trzy
   linie. Zmierzone na koncie z długą nazwą; przy „Zakład Stolarski Bracia Nowak Sp. z o.o." to jest
   realny przypadek, nie wymyślony. */
@media (max-width: 959px) {
    .cb-tenant-body {
        flex: 1 1 100%;
    }
}

.cb-tenant-name {
    font-size: 16px;
    font-weight: 600;
}

.cb-admin-pill {
    background: #DBEAFE;
    color: #1D4ED8;
    margin-left: 4px;
}

.cb-badge-expired {
    background: #FEE2E2;
    color: #B91C1C;
    padding: 3px 11px;
    font-size: 12px;
}

.cb-plan-badge {
    padding: 4px 12px;
    font-size: 12px;
}

.cb-tenant-toggle {
    font: inherit;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    font-weight: 600;
    color: #B45309;
    background: transparent;
    border: none;
    cursor: pointer;
    white-space: nowrap;
    padding: 0;
}

.cb-tenant-form {
    display: flex;
    gap: 12px;
    margin-top: 16px;
    padding-top: 16px;
    border-top: 1px solid #F3F4F6;
    align-items: center;
    flex-wrap: wrap;
}

/* ── Konto ───────────────────────────────────────────────────────────────────────────────────────────
   Plan + wykup obok siebie na desktopie, akordeony pod spodem na pełnej szerokości. */
.cb-account-grid {
    display: grid;
    gap: 14px;
    margin-bottom: 14px;
    align-items: start;
}

@media (min-width: 960px) {
    .cb-account-grid {
        grid-template-columns: 1fr 1fr;
        gap: 20px;
        margin-bottom: 20px;
    }
}

.cb-panel {
    padding: 18px;
    border-radius: 14px;
    margin-bottom: 0;
}

.cb-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 14px;
}

.cb-panel-title {
    font-size: 15px;
    font-weight: 700;
}

/* Ramka informacyjna wewnątrz karty (ile zostało / subskrypcja wygasła). Kolor niesie pilność,
   ale treść mówi to samo słowami — sam kolor nigdy nie jest jedynym nośnikiem. */
.cb-info-box {
    border-radius: 10px;
    padding: 11px 13px;
    font-size: 13px;
    margin-bottom: 16px;
    display: flex;
    gap: 8px;
    align-items: flex-start;
    line-height: 1.45;
}

.cb-info-warn {
    background: #FEF9C3;
    color: #713F12;
}

    .cb-info-warn .mud-icon-root {
        color: #A16207;
    }

.cb-info-error {
    background: #FEE2E2;
    color: #7F1D1D;
}

    .cb-info-error .mud-icon-root {
        color: #B91C1C;
    }

.cb-limit {
    margin-bottom: 14px;
}

.cb-limit-head {
    display: flex;
    justify-content: space-between;
    font-size: 13px;
    margin-bottom: 6px;
    gap: 12px;
}

.cb-limit-usage {
    /* Cyfry o równej szerokości — ceny w liście układają się w kolumnę, a nie w schodki. */
    font-variant-numeric: tabular-nums;
    color: #6B7280;
    white-space: nowrap;
}

.cb-bar {
    height: 7px;
    border-radius: 999px;
    background: #F3F4F6;
    overflow: hidden;
}

.cb-bar-fill {
    height: 100%;
    border-radius: 999px;
    background: #B45309;
}

/* Powyżej 90% zużycia pasek robi się czerwony — to ostrzeżenie, że kolejny zapis może się nie udać. */
.cb-bar-alert {
    background: #B91C1C;
}

.cb-plan-offer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px;
    border: 1px solid #E5E7EB;
    border-radius: 10px;
}

.cb-plan-name {
    font-size: 15px;
    font-weight: 600;
}

.cb-order {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 6px;
}

.cb-order-amount {
    /* Cyfry o równej szerokości — ceny w liście układają się w kolumnę, a nie w schodki. */
    font-variant-numeric: tabular-nums;
    font-size: 14px;
}

.cb-upcoming-item {
    padding: 10px 12px;
    border-left: 2px solid #E5E7EB;
    margin-bottom: 4px;
}

.cb-upcoming-message {
    font-size: 13px;
}

.cb-stale-offer {
    display: flex;
    align-items: center;
    gap: 14px;
}

.cb-stale-offer-body {
    flex: 1;
    min-width: 0;
}

.cb-stale-offer-title {
    font-size: 14px;
    font-weight: 600;
}

/* ── Lista klientów ──────────────────────────────────────────────────────────────────────────────────
   Filtry: na mobile pole szukania w pełnej szerokości + przewijane chipy pod nim; na desktopie
   pole i select obok siebie, chipy schowane. */
.cb-filters {
    display: flex;
    gap: 12px;
    margin-bottom: 10px;
}

.cb-search {
    flex: 1;
}

.cb-status-select {
    display: none;
    width: 220px;
    flex-shrink: 0;
}

/* Filtr, który NIE ma zamiennika na telefonie — jedyna kontrolka na ekranie reklamacji i zgłoszeń.
   Odwrotność `.cb-status-select`: tam poniżej 960 px rolę selecta przejmują chipy, tu nie ma czego przejąć,
   więc select zostaje i bierze całą szerokość wiersza. Przedtem oba ekrany używały `.cb-admin-plan-select`
   i przez to na telefonie nie miały filtra wcale — na reklamacjach zostawał sam przełącznik „po terminie",
   a na zgłoszeniach pusty pasek. */
.cb-filter-select {
    width: 220px;
    flex-shrink: 0;
}

@media (max-width: 959px) {
    /* Wiersz filtrów zawija się na telefonie: select bierze całą szerokość, a to, co stoi obok niego
       (przełącznik „Tylko po terminie"), schodzi do drugiej linii zamiast wystawać poza ekran.
       Zmierzone przed zmianą: 5 px przewijania w poziomie przy 375 px i ucięta etykieta przełącznika. */
    .cb-filters {
        flex-wrap: wrap;
    }

    .cb-filters .mud-select:has(.cb-filter-select) {
        flex: 1 1 100%;
    }

    .cb-filter-select {
        width: 100%;
    }

    /* Szukajka bierze cały wiersz, a to, co obok (przełącznik „Tylko wygasłe" w panelu admina),
       schodzi niżej. Bez tego pole zwężało się do 133 px na 360 px ekranu i pokazywało „Szukaj po"
       zamiast „Szukaj po nazwie firmy…" — czyli placeholder, który jest tam JEDYNĄ etykietą pola. */
    .cb-filters .cb-search {
        flex: 1 1 100%;
    }
}

/* ⚠️ `Class` MudSelecta ląduje na WEWNĘTRZNYM `.mud-input-control`, a komponent renderuje wokół niego
   jeszcze `div.mud-select`. `display: none` na naszej klasie chowa więc kontrolkę, ale pusty wrapper
   zostaje w rzędzie flexa (`flex: 1 1 auto`) i zabiera POŁOWĘ szerokości: na 375 px pole szukania miało
   166 px zamiast 343, a placeholder urywał się w połowie słowa („Szukaj po nazw"). Zmierzone na żywej
   aplikacji — replika z `mud-shim.css` ma inną budowę i nigdy tego nie pokazała.
   `:has()` zamiast klasy na własnym wrapperze: warunek jest tu dokładnie ten, o który chodzi — „schowaj
   opakowanie kontrolki, którą schowaliśmy" — i nie wymaga dokładania diva do dwóch plików `.razor`. */
@media (max-width: 959px) {
    .cb-filters .mud-select:has(.cb-status-select),
    .cb-filters .mud-select:has(.cb-admin-plan-select) {
        display: none;
    }
}

/* ⚠️ Ten sam wrapper, druga jego twarz — tym razem na DESKTOPIE, gdzie select jest widoczny.
   `div.mud-select` ma od biblioteki `flex: 1 1 auto`, więc ROŚNIE razem z szukajką: zmierzone 377 px
   przy widocznej kontrolce szerokiej na 220. Te sto kilkadziesiąt pikseli pustego tła po prawej czyta się
   jak wiersz filtrów, który nie sięga krawędzi kolumny — a lista pod nim sięga. Wrapper przestaje rosnąć,
   całe wolne miejsce idzie do szukajki. Zgłoszone ze zrzutów (2026-08-23), potwierdzone pomiarem w DOM. */
@media (min-width: 960px) {
    .cb-filters .mud-select:has(.cb-status-select),
    .cb-filters .mud-select:has(.cb-admin-plan-select) {
        flex: 0 0 auto;
    }
}

/* Chipy przewijane w poziomie: 8 statusów nie zmieści się w rzędzie na telefonie, a zawijanie ich do
   trzech linii zepchnęłoby listę poza ekran. */
.cb-status-chips {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    padding-bottom: 12px;
    margin-bottom: 6px;
    scrollbar-width: none;
}

    .cb-status-chips::-webkit-scrollbar {
        display: none;
    }

.cb-chip {
    font: inherit;
    white-space: nowrap;
    padding: 6px 13px;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 600;
    background: #fff;
    color: #6B7280;
    border: 1px solid #E5E7EB;
    cursor: pointer;
}

.cb-chip-on {
    background: #B45309;
    color: #fff;
    border-color: #B45309;
}

@media (min-width: 960px) {
    .cb-status-select {
        display: block;
    }

    .cb-status-chips {
        display: none;
    }
}

/* Pozycja listy. Mobile: osobne karty. Desktop: jeden biały kontener, wiersze rozdzielone kreską —
   stąd obramowanie wędruje z wiersza na kontener dopiero w media query. */
.cb-client-row {
    display: flex;
    align-items: center;
    gap: 12px;
    background: #FFFFFF;
    border: 1px solid #E5E7EB;
    border-radius: 12px;
    padding: 12px 14px;
    margin-bottom: 10px;
    cursor: pointer;
}

.cb-avatar {
    width: 44px;
    height: 44px;
    border-radius: 11px;
    display: grid;
    place-items: center;
    font-weight: 700;
    font-size: 15px;
    flex-shrink: 0;
}

.cb-client-body {
    flex: 1;
    min-width: 0;
}

.cb-client-name {
    font-size: 15px;
    font-weight: 600;
}

.cb-truncate {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.cb-client-offer {
    margin-top: 3px;
}

/* Plakietka statusu — kolory wstrzykiwane z ClientStatusUi.BadgeStyle.
   ⚠️ 13 px, nie 11: to największy rozmiar, przy którym na 360 px nazwy klientów nadal mieszczą się
   w jednej linii (zmierzone: przy 14 px zawijają się „Anna Kowalska" i „Domex Sp. z o.o."). Tokeny mówią
   o plakietce `--text-sm` (14 px) — różnica jest świadoma i wynika z szerokości ekranu, nie z niedopatrzenia. */
.cb-badge {
    padding: 3px 9px;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 600;
    white-space: nowrap;
    flex-shrink: 0;
}

/* Strzałka „wejdź w kartę" ma sens tylko na desktopie: na telefonie cały wiersz jest celem dotyku
   i tak, a 22 px szerokości to na 375 px realna strata miejsca dla nazwy. */
.cb-client-chevron {
    display: none;
    color: #D1D5DB;
    flex-shrink: 0;
}

.cb-load-more {
    font: inherit;
    display: block;
    width: 100%;
    padding: 16px;
    text-align: center;
    font-size: 14px;
    font-weight: 600;
    color: #B45309;
    background: transparent;
    border: none;
    cursor: pointer;
}

    .cb-load-more:disabled {
        color: #9CA3AF;
        cursor: default;
    }

@media (min-width: 960px) {
    .cb-client-list {
        background: #FFFFFF;
        border: 1px solid #E5E7EB;
        border-radius: 12px;
        overflow: hidden;
    }

    .cb-client-row {
        border: none;
        border-bottom: 1px solid #F3F4F6;
        border-radius: 0;
        margin-bottom: 0;
        gap: 16px;
        padding: 15px 20px;
    }

    .cb-client-chevron {
        display: block;
    }

    /* Stała szerokość plakietki: przy zmiennej statusy skakałyby w poziomie z wiersza na wiersz,
       a kolumna ma się czytać jak kolumna. */
    .cb-badge {
        width: 150px;
        text-align: center;
        padding: 4px 12px;
        font-size: 13px;
    }
}

/* Mikro-plakietka stanu najnowszej oferty na liście klientów — kolory wstrzykiwane z OfferStatusUi.BadgeStyle. */
.cb-offer-pill {
    padding: 1px 6px;
    border-radius: 6px;
    font-weight: 600;
    white-space: nowrap;
}

/* ── Ekrany wejścia (logowanie, rejestracja, potwierdzenia z maila) ──────────────────────────────────
   Jeden cel na ekranie: wąska kolumna, bez nawigacji (dolny pasek i sidebar są za AuthorizeView). */
.cb-auth {
    max-width: 400px;
    margin-inline: auto;
    padding-top: 12px;
}

/* Kafelek marki — ekran wejścia ma najpierw powiedzieć „to Majsternik", potem „zaloguj się". */
.cb-auth-logo {
    width: 56px;
    height: 56px;
    border-radius: 16px;
    background: #FBEFE2;
    color: #B45309;
    display: grid;
    place-items: center;
    margin-bottom: 20px;
}

    .cb-auth-logo .mud-icon-root {
        font-size: 30px;
    }

.cb-auth-title {
    margin: 0 0 20px;
    font-size: 24px;
    font-weight: 700;
    letter-spacing: -0.02em;
}

.cb-auth-subtitle {
    font-size: 14px;
    color: #6B7280;
    margin: -14px 0 22px;
}

.cb-auth-switch {
    font-size: 14px;
    color: #6B7280;
    margin-top: 20px;
}

/* Brak uprawnień: ten sam układ co ekrany wejścia, ale wyśrodkowany i w neutralnej szarości —
   to nie jest błąd użytkownika ani ścieżka logowania, więc nie krzyczy bursztynem. */
.cb-denied {
    text-align: center;
    padding-top: 32px;
}

    .cb-denied .cb-auth-logo {
        margin-inline: auto;
    }

.cb-denied-logo {
    background: #F3F4F6;
    color: #6B7280;
}

/* ── Oferta publiczna ────────────────────────────────────────────────────────────────────────────────
   Jedyny ekran oglądany przez osoby SPOZA aplikacji, więc dostaje najwięcej oddechu i największą cenę:
   klient końcowy przyszedł tu po jedną liczbę i jedną decyzję. */
.cb-public {
    max-width: 720px;
    margin-inline: auto;
}

.cb-public-card {
    padding: 24px;
    border-radius: 16px;
}

/* Nadawca nad tytułem — drobniejszy od wyceny, bo klient końcowy przyszedł po cenę, ale musi wiedzieć,
   od kogo ona jest. Kolor drugiego planu, ale nie „cichy szary": to jest informacja, nie ozdobnik. */
.cb-public-from {
    font-size: 13px;
    color: #6B7280;
    margin-bottom: 6px;
}

.cb-public-title {
    margin: 0 0 12px;
    font-size: 20px;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.cb-public-scope {
    font-size: 14px;
    line-height: 1.55;
    color: #374151;
    white-space: pre-wrap; /* zakres prac jest wpisywany wielowierszowo — łamania muszą przeżyć wyświetlenie */
}

.cb-public-price {
    /* Cyfry o równej szerokości — ceny w liście układają się w kolumnę, a nie w schodki. */
    font-variant-numeric: tabular-nums;
    font-size: 26px;
    font-weight: 700;
    color: #B45309;
    letter-spacing: -0.01em;
}

.cb-decided-at {
    font-size: 12px;
    opacity: 0.75;
    margin-top: 2px;
}

.cb-comment-box {
    border: 1px solid #E5E7EB;
    border-radius: 10px;
    padding: 12px 14px;
    margin-top: 12px;
}

.cb-comment-text {
    font-size: 14px;
    color: #374151;
    margin-top: 4px;
    white-space: pre-wrap;
}

.cb-decide-row {
    display: flex;
    gap: 10px;
    margin-top: 16px;
}

.cb-public-footer {
    text-align: center;
    font-size: 12px;
    color: #6B7280;
    margin-top: 20px;
}

@media (min-width: 960px) {
    .cb-public-card {
        padding: 40px;
    }

    .cb-public-title {
        font-size: 28px;
        letter-spacing: -0.02em;
        margin-bottom: 16px;
    }

    .cb-public-scope {
        font-size: 15px;
        line-height: 1.65;
    }

    .cb-public-price {
        font-size: 34px;
        letter-spacing: -0.02em;
    }
}

/* ── Formularze (nowa oferta, dodanie klienta) ───────────────────────────────────────────────────────
   Węższa kolumna niż reszta ekranów: formularz czyta się lepiej, gdy pola nie rozciągają się na 880 px. */
.cb-narrow {
    max-width: 640px;
    margin-inline: auto;
}

.cb-form-card {
    padding: 20px;
}

/* Dwa pola w rzędzie (cena + termin) — na wąskim ekranie i tak zawijają się pod siebie. */
.cb-form-row {
    display: flex;
    gap: 16px;
    flex-wrap: wrap;
}

    .cb-form-row > * {
        flex: 1;
        min-width: 160px;
    }

.cb-offer-for {
    font-size: 13px;
    color: #6B7280;
    margin-bottom: 16px;
}

    .cb-offer-for strong {
        color: #1F2937;
        font-weight: 600;
    }

.cb-field-label {
    font-size: 13px;
    font-weight: 600;
    color: #374151;
    margin-bottom: 8px;
}

.cb-divider {
    height: 1px;
    background: #F3F4F6;
    margin: 20px 0;
}

.cb-price-lg {
    font-size: 22px;
    margin: 8px 0 2px;
}

/* Potwierdzenie wysyłki — zielone, bo to jedyny moment w aplikacji, w którym coś naprawdę „poszło w świat". */
.cb-success {
    background: #DCFCE7;
    border: 1px solid #BBF7D0;
    border-radius: 12px;
    padding: 12px 14px;
    margin-bottom: 16px;
    display: flex;
    gap: 10px;
    align-items: center;
    font-size: 14px;
    color: #14532D;
}

    .cb-success .mud-icon-root {
        color: #15803D;
    }

.cb-link-row {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
}

/* Ramka przerywana = „to jest do skopiowania, nie do klikania". Monospace, bo token jest ciągiem znaków,
   który bywa przepisywany ręcznie, gdy schowek zawiedzie. */
.cb-link-box {
    flex: 1;
    min-width: 200px;
    background: #F9F7F4;
    border: 1px dashed #D1D5DB;
    border-radius: 8px;
    padding: 12px 14px;
    font-size: 13px;
    color: #6B7280;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Karta klienta ───────────────────────────────────────────────────────────────────────────────────
   Nagłówek: awatar + nazwa + plakietka statusu + akcje. Kontakt ma dwie formy (rząd na desktopie,
   karta na mobile) — pokazywana zawsze dokładnie jedna. */
.cb-client-head {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 14px;
}

.cb-avatar-lg {
    width: 48px;
    height: 48px;
    border-radius: 12px;
    font-size: 18px;
}

.cb-client-head-body {
    flex: 1;
    min-width: 0;
}

.cb-client-head-name {
    margin: 0;
    font-size: 20px;
    font-weight: 700;
    letter-spacing: -0.01em;
}

/* Plakietka jako aktywator menu: kursor musi to zdradzać, bo inaczej wygląda na sam opis stanu. */
.cb-status-menu {
    cursor: pointer;
}

.cb-badge-lg {
    display: inline-flex;
    margin-top: 3px;
    padding: 3px 10px;
    font-size: 13px;
}

.cb-contact-inline,
.cb-client-head-actions {
    display: none;
}

.cb-contact-card {
    padding: 14px 16px;
    margin-bottom: 16px;
}

.cb-contact-row {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 14px;
}

    .cb-contact-row + .cb-contact-row {
        margin-top: 10px;
    }

    .cb-contact-row .mud-icon-root {
        color: #9CA3AF;
    }

/* ── Telefon jako akcja ──────────────────────────────────────────────────────────────────────────────
   Na karcie klienta numer nie jest napisem, tylko linkiem `tel:` — telefon do klienta to najczęstsza
   czynność na tym ekranie. Wiersz ma wyglądać jak akcja: numer półgrubo, po prawej bursztynowe „Zadzwoń".
   Wysokość 44 px jest tu WARUNKIEM, nie skutkiem reguły celów dotykowych: to jest cel, w który celuje się
   w biegu, jedną ręką. */
.cb-phone-row {
    color: inherit;
    text-decoration: none;
    min-height: 44px;
}

.cb-phone-number {
    font-weight: 600;
}

.cb-phone-cta {
    margin-left: auto;
    font-size: 13px;
    font-weight: 600;
    color: #B45309;
}

/* Wersja desktopowa: ten sam numer w rzędzie pod nazwą klienta, bez wołania „Zadzwoń" —
   na komputerze klik w `tel:` bywa ślepy, więc nie obiecujemy nim więcej, niż trzeba. */
.cb-phone-link {
    color: inherit;
    text-decoration: none;
}

    .cb-phone-link:hover {
        text-decoration: underline;
    }

.cb-edit-card {
    padding: 16px;
    margin-bottom: 16px;
}

@media (min-width: 960px) {
    /* Na desktopie powrót siedzi w pasku kontekstu, więc strzałka przy nazwie byłaby drugim wyjściem obok pierwszego. */
    .cb-back-mobile,
    .cb-client-head-menu,
    .cb-contact-card {
        display: none;
    }

    .cb-contact-inline {
        display: flex;
        flex-wrap: wrap;
        gap: 20px;
        margin-top: 8px;
        font-size: 14px;
        color: #6B7280;
    }

    .cb-contact-item {
        display: inline-flex;
        align-items: center;
        gap: 6px;
    }

    .cb-client-head-actions {
        display: flex;
        align-items: center;
        gap: 8px;
    }

    .cb-client-head {
        gap: 16px;
        margin-bottom: 22px;
    }

    .cb-avatar-lg {
        width: 60px;
        height: 60px;
        border-radius: 14px;
        font-size: 22px;
    }

    .cb-client-head-name {
        font-size: 26px;
        letter-spacing: -0.02em;
    }

    .cb-badge-lg {
        margin-top: 6px;
        padding: 4px 12px;
        font-size: 13px;
    }
}

/* Zakładki — segmenty (mobile) i podkreślenia (desktop). Ten sam stan, dwie formy: na 375 px poziomy
   pasek z podkreśleniami nie mieści czterech etykiet, a segmenty czytają się jak przełącznik. */
.cb-tabs-mobile {
    background: #F3F1EE;
    border-radius: 10px;
    padding: 4px;
    display: flex;
    gap: 2px;
    margin-bottom: 18px;
}

.cb-tab-m {
    font: inherit;
    flex: 1;
    min-width: 0;
    text-align: center;
    padding: 8px 2px;
    border: none;
    border-radius: 7px;
    font-size: 12px;
    font-weight: 600;
    background: transparent;
    color: #6B7280;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
}

.cb-tab-m-on {
    background: #fff;
    color: #B45309;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.10);
}

.cb-tab-count {
    opacity: 0.65;
    margin-left: 3px;
}

.cb-tabs-desktop {
    display: none;
    border-bottom: 1px solid #E5E7EB;
    gap: 6px;
    margin-bottom: 26px;
}

.cb-tab-d {
    font: inherit;
    padding: 12px 18px;
    font-size: 14px;
    font-weight: 600;
    color: #6B7280;
    background: transparent;
    border: none;
    /* Podkreślenie aktywnej zakładki nachodzi na kreskę kontenera — stąd ujemny margines. */
    border-bottom: 2.5px solid transparent;
    margin-bottom: -1px;
    display: inline-flex;
    gap: 7px;
    align-items: center;
    cursor: pointer;
}

.cb-tab-d-on {
    color: #B45309;
    border-bottom-color: #B45309;
}

.cb-tab-pill {
    background: #F3F4F6;
    color: #5F6672;
    border-radius: 999px;
    font-size: 12px;
    padding: 1px 7px;
}

/* Ten sam powód co przy aktywnej pozycji sidebara — tyle że tekst ma tu 11 px, więc kontrast boli podwójnie. */
.cb-tab-d-on .cb-tab-pill {
    background: #FBEFE2;
    color: #92400A;
}

@media (min-width: 960px) {
    .cb-tabs-mobile {
        display: none;
    }

    .cb-tabs-desktop {
        display: flex;
    }
}

/* Nagłówek zakładki: tytuł sekcji + jej akcja główna (nowa oferta, dodaj zdjęcie). */
.cb-tab-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}

/* Oferta. Mobile: kolumna (tytuł, cena, termin, akcje). Desktop: rząd z ceną i akcjami po prawej —
   stąd dwa miejsca na cenę, z których zawsze widać jedno. */
.cb-offer-card {
    padding: 14px 15px;
}

.cb-offer-main {
    flex: 1;
    min-width: 0;
}

.cb-offer-title-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 10px;
}

.cb-offer-title {
    font-size: 15px;
    font-weight: 600;
    line-height: 1.3;
}

.cb-offer-price-desktop {
    display: none;
}

.cb-offer-price-mobile {
    margin-top: 4px;
}

.cb-offer-comment {
    font-size: 13px;
    font-style: italic;
    color: #4B5563;
    margin-top: 6px;
}

.cb-offer-actions {
    display: flex;
    gap: 7px;
    margin-top: 12px;
    flex-wrap: wrap;
    align-items: center;
}

/* PDF to akcja poboczna — ikona w ramce, dosunięta do prawej, żeby nie konkurowała z „Wyślij". */
.cb-icon-outlined {
    border: 1px solid #E5E7EB;
    border-radius: 8px;
    color: #6B7280;
    margin-left: auto;
}

.cb-icon-primary {
    background-color: #B45309 !important;
    color: #fff !important;
    border-radius: 8px !important;
    flex-shrink: 0;
}

@media (min-width: 960px) {
    .cb-offer-card {
        display: flex;
        align-items: center;
        gap: 20px;
        padding: 20px 22px;
    }

    .cb-offer-title {
        font-size: 17px;
    }

    .cb-offer-price-mobile {
        display: none;
    }

    .cb-offer-price-desktop {
        display: block;
        font-size: 20px;
    }

    .cb-offer-actions {
        margin-top: 0;
        flex-wrap: nowrap;
    }

    .cb-icon-outlined {
        margin-left: 0;
    }

    /* W rzędzie plakietka statusu wraca pod tytuł, a nie na przeciwny koniec karty. */
    .cb-offer-title-row {
        align-items: center;
        justify-content: flex-start;
        gap: 12px;
    }
}

/* Formularz przypomnienia: na mobile pola pod sobą, na desktopie w jednym rzędzie. */
.cb-reminder-form {
    padding: 12px;
    margin-bottom: 16px;
}

/* Skróty terminu w formularzu przypomnienia. Zawijają się zamiast przewijać w poziomie (inaczej niż chipy
   filtrów): są trzy, więc mieszczą się w jednym rzędzie nawet na 360 px, a ukryty czwarty byłby martwy. */
.cb-quick-dates {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 8px;
}

.cb-reminder-form-row {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-top: 8px;
    flex-wrap: wrap;
}

/* Podpowiedź o skrócie klawiaturowym — widoczna wyłącznie tam, gdzie ta klawiatura jest.
   Na telefonie to byłby napis o czynności, której nie da się wykonać. */
.cb-note-hint {
    display: none;
}

@media (min-width: 960px) {
    .cb-note-hint {
        display: block;
        margin: -8px 0 12px;
        font-size: 12px;
        color: #6B7280;
    }
}

.cb-note-form {
    display: flex;
    gap: 8px;
    align-items: flex-end;
    margin-bottom: 16px;
}

.cb-note {
    padding: 12px 14px;
}

.cb-note-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px;
}

.cb-note-content {
    font-size: 14px;
    line-height: 1.45;
    white-space: pre-wrap; /* notatka z pomiaru bywa wielowierszowa — łamania z pola muszą przeżyć zapis */
}

.cb-note-actions {
    display: flex;
    flex-shrink: 0;
}

/* Dwie kolumny notatek na desktopie. `columns` zamiast grid, bo notatki mają różną wysokość
   i układ kolumnowy wypełnia je ciaśniej niż siatka z wierszami równej wysokości. */
@media (min-width: 960px) {
    .cb-note-grid {
        columns: 2;
        column-gap: 14px;
    }

    .cb-note {
        break-inside: avoid;
        padding: 15px 17px;
    }
}

/* Siatka miniatur zdjęć na karcie klienta — pełne obrazy skalowane CSS-em (MVP, bez endpointu miniatur). */
.cb-photo-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: 8px;
}

    /* Kafelek zdjęcia: kontener pozycjonujący dla przycisku kasowania w rogu miniatury. */
    .cb-photo-tile {
        position: relative;
    }

    /* Sama miniatura jest przyciskiem (dostęp z klawiatury) — zerujemy wszystko, co przeglądarka dokłada
       przyciskowi, żeby obraz wyglądał dokładnie tak, jak wyglądał jako goły <img>. */
    .cb-photo-open {
        display: block;
        width: 100%;
        padding: 0;
        border: 0;
        background: none;
        border-radius: 8px;
        overflow: hidden;
        cursor: pointer;
    }

        .cb-photo-open:focus-visible {
            outline: 2px solid #B45309;
            outline-offset: 2px;
        }

        .cb-photo-tile .cb-photo-delete {
            position: absolute;
            top: 2px;
            right: 2px;
            color: #fff;
            background: rgba(0, 0, 0, 0.45);
        }

    .cb-photo-grid img {
        width: 100%;
        aspect-ratio: 1;
        object-fit: cover;
        border-radius: 8px;
        display: block;
        cursor: pointer; /* miniatura otwiera podgląd — kursor to sygnalizuje na desktopie */
    }

/* Wybór zdjęć do oferty: ta sama siatka co na karcie klienta, ale kafelek jest przyciskiem przełączającym.
   Zaznaczenie sygnalizujemy DWOMA sygnałami naraz (obwódka + ikona „ptaszka"), bo sam kolor obwódki bywa
   nieczytelny na zdjęciu w podobnym odcieniu — a pomyłka kosztuje wysłanie oferty bez zdjęcia albo z nadmiarem. */
.cb-photo-picker {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: 8px;
}

    .cb-photo-pick {
        position: relative;
        padding: 0;
        border: 2px solid transparent;
        border-radius: 8px;
        background: none;
        cursor: pointer;
        overflow: hidden;
        aspect-ratio: 1;
    }

        .cb-photo-pick img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
        }

        /* Niewybrane przygaszamy, żeby wybrane „wychodziły" z siatki bez czytania ikon. */
        .cb-photo-pick:not(.cb-photo-pick-on) img {
            opacity: 0.55;
        }

        .cb-photo-pick-on {
            border-color: #B45309;
        }

        .cb-photo-pick .cb-photo-pick-mark {
            position: absolute;
            top: 2px;
            right: 2px;
            color: #fff;
            background: rgba(0, 0, 0, 0.45);
            border-radius: 50%;
            line-height: 0;
            padding: 2px;
        }

        .cb-photo-pick-on .cb-photo-pick-mark {
            color: #FDE68A;
        }

/* Galeria na publicznej stronie oferty — kafelki nieco większe niż w edytorze: to jedyne zdjęcia, jakie
   klient końcowy zobaczy, więc mają być czytelne bez powiększania. */
.cb-public-gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 8px;
    margin-top: 16px;
}

    .cb-public-photo {
        padding: 0;
        border: none;
        border-radius: 8px;
        background: none;
        cursor: pointer;
        overflow: hidden;
        aspect-ratio: 4 / 3;
        width: 100%;
    }

        .cb-public-photo img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
        }

/* Dialog kanałów wysyłki oferty (desktop bez navigator.share): pionowa lista przycisków-kanałów.
   Szerokość minimalna, żeby dialog nie zwężał się do najkrótszej etykiety („SMS"). */
.cb-share-dialog {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 280px;
}

/* Kanał e-mail niesie w podpisie adres, na który pójdzie — inaczej „Wyślij e-mailem" jest obietnicą bez
   adresata. Kolumna jest NASZA, nie MudBlazorowa: układamy dwa spany we własnym kontenerze, żeby nie
   opierać się o wewnętrzne klasy przycisku (te zmieniają się między wersjami paczki). */
.cb-share-email-text {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    line-height: 1.25;
}

    /* Adres bez wersalików przycisku — ma być czytany jako adres, a nie jako etykieta. */
    .cb-share-email-address {
        font-size: 0.75rem;
        text-transform: none;
        opacity: 0.7;
    }

/* ⚠️ MudOverlay użyty samodzielnie (nie jako tło dialogu, popovera ani szuflady) dostaje `z-index: 5` —
   sprawdzone w `MudBlazor.min.css` z paczki 9.7.0, nie z pamięci. Nasz dolny pasek nawigacji stoi na 1200,
   więc podgląd zdjęcia otwierał się POD nim: przyciemnienie zakrywało całą stronę oprócz paska, a pasek
   świecił na wierzchu modala i dało się w niego trafić kciukiem. Podnosimy overlay ponad pasek, ale poniżej
   dialogu (1400) i snackbara (1500) MudBlazora — potwierdzenie kasowania i komunikaty mają nadal wygrywać.
   Specyficzność jest równa `.mud-overlay`, więc decyduje kolejność arkuszy: `app.css` idzie po MudBlazorze. */
.cb-photo-overlay {
    z-index: 1250;
}

/* Podgląd pełnego zdjęcia w overlayu: mieści się w ekranie z zapasem, bez przewijania strony pod spodem. */
.cb-photo-viewer {
    position: relative;
    max-width: 92vw;
    max-height: 86vh;
}

    .cb-photo-viewer img {
        max-width: 92vw;
        max-height: 86vh;
        object-fit: contain;
        border-radius: 8px;
        display: block;
    }

/* MudOverlay jest `position: fixed` z left/right: 0, więc centruje się względem CAŁEGO okna — na desktopie
   sidebar spychał zdjęcie w prawo. Odsuwamy PADDINGIEM, nie krawędzią `left`: przyciemnienie ma nadal
   zakrywać sidebar (to modal — pod spodem nic nie powinno zapraszać do kliknięcia), a wyśrodkowanie
   ma się liczyć wewnątrz paddingu, czyli względem kolumny treści.
   Szerokość bierzemy ze zmiennej MudBlazora (ustawia ją sam wg szerokości szuflady), z zapasową wartością
   na wypadek, gdyby zmienna nie doszła — wtedy po prostu wraca stare, wyśrodkowane wobec okna zachowanie. */
@media (min-width: 960px) {
    .cb-photo-overlay {
        padding-left: var(--mud-drawer-width-left, 240px);
    }

    /* Procenty zamiast vw: 92vw liczyłoby się od szerokości OKNA, więc po odsunięciu obraz wystawałby
       poza dostępną kolumnę dokładnie o szerokość sidebara. */
    .cb-photo-overlay .cb-photo-viewer,
    .cb-photo-overlay .cb-photo-viewer img {
        max-width: 92%;
    }
}

    .cb-photo-viewer .cb-photo-viewer-close {
        position: absolute;
        top: 4px;
        right: 4px;
        color: #fff;
        background: rgba(0, 0, 0, 0.45);
    }

/* ── Treść wpisana przez człowieka ───────────────────────────────────────────────────────────────────
   Wszystko poniżej to teksty, których treści NIE kontrolujemy: notatka z pomiaru, zakres prac, uwaga
   klienta, nazwa firmy. Wystarczy jeden długi ciąg bez spacji — link do próbnika okleiny albo adres
   z parametrami — i cała strona zaczyna się przewijać w poziomie (zmierzone: 75 px przy 360 px szerokości).
   `anywhere`, nie `break-word`: tylko to pierwsze wpływa też na minimalną szerokość elementu, więc ratuje
   układy oparte na flexie — a na nich stoi wiersz klienta i karta oferty.

   Wyjątkiem jest `.cb-link-box` (publiczny link do oferty), który ma NIE łamać się w poprzek — tam token
   przepisuje się czasem ręcznie, więc ucięcie wielokropkiem jest celowe. */
.cb-client-name,
.cb-client-head-name,
.cb-note-content,
.cb-offer-title,
.cb-offer-comment,
.cb-comment-text,
.cb-public-title,
.cb-public-scope,
.cb-reminder-message,
.cb-upcoming-message,
.cb-stale-offer-title,
.cb-tenant-name {
    overflow-wrap: anywhere;
}

/* ── Kontrast placeholderów ──────────────────────────────────────────────────────────────────────────
   MudBlazor maluje placeholder kolorem tekstu z `opacity: .42`, czyli efektywnie rgb(158, 162, 166):
   **2,44 : 1** na tle strony i 2,47 : 1 na białej karcie (zmierzone w przeglądarce na realnym arkuszu,
   nie policzone z hexa — przezroczystości nie widać w wartości `color`). To jest poniżej progu WCAG 1.4.3
   i nie jest kosmetyką: w szukajce klientów, w polu notatki i w treści przypomnienia placeholder jest
   JEDYNĄ etykietą pola, więc nieczytelny placeholder znaczy pole bez nazwy.

   Własny kolor bez przezroczystości daje 4,59 : 1 na tle strony i 4,83 : 1 na karcie. Ta sama wartość,
   na którą przeszły teksty drugorzędne (`--color-ink-soft`), więc pole i podpis pod nim mówią tym samym
   głosem. Selektor musi powtórzyć strukturę MudBlazora (`.mud-input > input.mud-input-root`), bo jego
   reguła ma specyficzność (0,2,1) — samo `.cb-page ::placeholder` przegrywa i zmiana przechodzi cicho.
   Zawężone do naszych kontenerów: `.cb-page` obejmuje wszystkie ekrany (MudContainer w layoucie),
   `.mud-dialog` dokładamy, bo dialogi renderują się poza nim. */
.cb-page .mud-input > input.mud-input-root::placeholder,
.cb-page .mud-input > textarea.mud-input-root::placeholder,
.cb-page div.mud-input-slot.mud-input-root::placeholder,
.mud-dialog .mud-input > input.mud-input-root::placeholder,
.mud-dialog .mud-input > textarea.mud-input-root::placeholder,
.mud-dialog div.mud-input-slot.mud-input-root::placeholder {
    color: #6B7280;
    opacity: 1;
}

/* ── Widoczny fokus klawiatury ───────────────────────────────────────────────────────────────────────
   Aplikacja jest projektowana pod kciuk, ale na desktopie da się (i trzeba dać się) przejść po niej
   klawiaturą. Elementy własne — wiersz klienta, chipy, zakładki, kafelki zdjęć — to `div` i `button`
   bez własnego stylu fokusu, więc dostawały domyślną obwódkę przeglądarki: niebieską, czyli spoza palety.

   `:focus-visible`, nie `:focus` — po kliknięciu myszą obwódka ma NIE zostawać.
   W pasku górnym obwódka jedzie `currentColor`, bo pasek ma dwie twarze: bursztynową na telefonie
   (biały tekst) i białą na desktopie (grafitowy) — jeden kolor obwódki znikałby w jednej z nich. */
.cb-client-row:focus-visible,
.cb-chip:focus-visible,
.cb-load-more:focus-visible,
.cb-tab-m:focus-visible,
.cb-tab-d:focus-visible,
.cb-tenant-toggle:focus-visible,
.cb-disclosure:focus-visible,
.cb-photo-pick:focus-visible,
.cb-public-photo:focus-visible {
    outline: 2px solid #B45309;
    outline-offset: 2px;
}

.cb-appbar .cb-appbar-brand:focus-visible,
.cb-appbar .mud-icon-button:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 2px;
}

/* ── Cele dotykowe (reguła „kciuk, nie kursor": min. 44×44 px) ───────────────────────────────────────
   MudBlazor liczy wysokości pod kursor: przycisk ma 37 px, przycisk ikonowy 40 px, a jego wariant
   Size.Small — 30 px. Na telefonie, w rękawicy, przy 34-piksełowym „Odhacz" różnica jest między
   odhaczonym przypomnieniem a otwartą przez pomyłkę kartą klienta.

   Podnosimy je WYŁĄCZNIE w układzie mobilnym (ten sam próg 960 px co reszta layoutu): na desktopie
   celem jest kursor, 40 px jest tam w porządku, a rozdmuchane przyciski rozbiłyby gęstość list.
   Dwa wyjątki mają własną drogę: chipy filtrów i przyciski leżące NA zdjęciu dostają niewidoczne
   pole dotyku (pseudoelement), bo powiększenie ich samych zabrałoby miejsce treści albo zakryło zdjęcie. */
@media (max-width: 959px) {
    /* ⚠️ Selektory są ZAWĘŻONE do naszych powierzchni i to nie jest kosmetyka. Bez tego reguła trafiała
       w wnętrze MudBlazora: komórka dnia w kalendarzu to `.mud-day` z klasami `.mud-button-root`
       i `.mud-icon-button`, a nagłówek dnia tygodnia (`.mud-picker-calendar-header-day`) to zwykły span,
       więc siatka rozjeżdżała się w pionie i rosła ponad szerokość telefonu. Kalendarz i dialogi żyją
       w popoverach renderowanych przez `MudPopoverProvider` POZA `.cb-page`, więc zawężenie wystarcza;
       `.mud-dialog` jest na liście osobno, bo w nim stoją NASZE przyciski (np. potwierdzenie kasowania)
       i one mają zostać przy 44 px. */
    .cb-page .mud-button-root,
    .cb-appbar .mud-button-root,
    .cb-bottom-nav .mud-button-root,
    .mud-dialog .mud-button-root {
        min-height: 44px;
    }

    .cb-page .mud-icon-button,
    .cb-appbar .mud-icon-button,
    .cb-bottom-nav .mud-icon-button,
    .mud-dialog .mud-icon-button {
        min-width: 44px;
        min-height: 44px;
    }

    /* Pola tekstowe: `Margin.Dense` daje w realnym MudBlazorze **40 px** (zmierzone w przeglądarce —
       wysokość bierze się z paddingu wewnętrznego `input`, 10,5 px góra i dół, a nie z żadnej reguły
       wysokości). Warstwa dostępności podniosła kiedyś przyciski i przeoczyła pola, bo audyt mierzy
       repliki na `mud-shim.css`, gdzie pole ma inną budowę — dokładnie ta pułapka.
       Pole jest na formularzach głównym celem dotykowym (logowanie, dodanie klienta, oferta, szukajka),
       a makieta design systemu mówi o polach „wysokość ≥ 44 px — palce w rękawicach roboczych".
       `min-height` na kontenerze, nie padding na `input`: `.mud-input` jest `inline-flex` z
       `align-items: center`, więc treść zostaje wyśrodkowana, obramowanie rośnie razem z pudełkiem,
       a etykieta w stanie spoczynku ląduje DOKŁADNIE na środku (przy 40 px była 2 px poniżej). */
    .cb-page .mud-input.mud-input-margin-dense,
    .mud-dialog .mud-input.mud-input-margin-dense {
        min-height: 44px;
    }

    /* Przycisk „x" w polu tekstowym jest ozdobnikiem pola, nie osobnym celem na ekranie: podniesiony
       do 44 px rozpycha pole `Margin.Dense` i psuje wysokość szukajki. Celem dotykowym jest tu samo pole. */
    .cb-page .mud-input-adornment .mud-icon-button {
        min-width: 0;
        min-height: 0;
    }

    /* Te dwa mają w makiecie wpisany rozmiar 34 px, więc samo min-* zostawiłoby je w połowie drogi
       (min-width wygrywa z width, ale czytelniej jest powiedzieć to wprost). */
    .cb-check,
    .cb-call-tile,
    .cb-icon-primary {
        width: 44px;
        height: 44px;
    }

    /* Marka w pasku to link o wysokości samego tekstu — a jest to wyjście awaryjne z każdego ekranu. */
    .cb-appbar-brand {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
    }

    /* „Nie pamiętasz hasła?" i „Załóż konto" to linki w tekście: 16 px wysokości celu.
       Padding daje 44 px, nie zmieniając ani kroju, ani układu kolumny. */
    .cb-auth-switch a {
        display: inline-block;
        padding-block: 14px;
    }

    .cb-tenant-toggle {
        min-height: 44px;
    }

    /* Chipy statusów: wizualnie zostają niskie (osiem sztuk w przewijanym rzędzie), ale pole dotyku
       rozciąga się na 44 px. Rząd ma zapas w pionie (padding-bottom 12 px), więc nic nie nachodzi. */
    .cb-chip {
        position: relative;
    }

        .cb-chip::after {
            content: "";
            position: absolute;
            /* 6 px, nie 5: sam chip ma 33,5 px wysokości (13 px tekstu + paddingi), więc pięć piksełów
               z góry i z dołu dawało 43,5 px — pół piksela pod regułą i nikt by tego nie zauważył. */
            inset: -6px 0;
        }

    /* Przyciski na miniaturze zdjęcia: powiększone zakryłyby zdjęcie ciemnym kwadratem, a to one
       decydują o skasowaniu. Rośnie samo pole dotyku. */
    .cb-photo-delete,
    .cb-photo-viewer-close {
        min-width: 0;
        min-height: 0;
    }

        .cb-photo-delete::after,
        .cb-photo-viewer-close::after {
            content: "";
            position: absolute;
            inset: -7px;
        }
}

/* ── Druk publicznej oferty ──────────────────────────────────────────────────────────────────────────
   Makieta z etapu 1 mówiła o tym ekranie „czysto, drukowalnie" — i to nie jest ozdobnik: klient końcowy
   drukuje wycenę albo zapisuje ją jako PDF z przeglądarki (przycisk „Pobierz PDF" daje ładniejszy plik,
   ale nie każdy go zauważy). Bez tych reguł na kartce lądował bursztynowy pasek aplikacji, pole na uwagę
   i dwa przyciski decyzji — czyli trzy rzeczy, których na papierze nie da się kliknąć.

   Zostaje to, co jest ofertą: tytuł, zakres, zdjęcia, cena, warunki, termin i stopka wykonawcy. */
@media print {
    .mud-appbar,
    .cb-bottom-nav,
    .cb-sidebar {
        display: none !important;
    }

    .mud-main-content {
        padding-top: 0 !important;
        margin-left: 0 !important;
    }

    /* Pole uwagi i przyciski („Akceptuję", „Odrzucam", „Pobierz PDF") są akcjami — na papierze nie istnieją. */
    .cb-decide-row,
    .cb-public .mud-input-control,
    .cb-public .mud-button-root {
        display: none !important;
    }

    /* Karta na ekranie ma ramkę i cień, żeby odcinała się od tła; na kartce tło jest białe,
       więc ramka tylko zjada margines drukarki. */
    .cb-public-card {
        border: 0;
        padding: 0;
        box-shadow: none;
    }

    /* Zdjęcia bywają drukowane w skali szarości — zakres prac i cena muszą się bronić bez nich. */
    .cb-public-gallery {
        break-inside: avoid;
    }

    body {
        background: #FFFFFF;
    }
}