/*
 * Motyw "Miękka" — śliwka/koral/bursztyn, wybrany przez Jakuba 2026-07-12
 * z warsztatu wizualnego (4 rundy artefaktów; referencja: "Poranna zmiana
 * 2.0 — 3 typografie", wariant Gabarito+Figtree). Zastępuje wcześniejszą
 * terakotę/miedź z logo. Charakter: duże promienie, miękkie KOLOROWE
 * cienie (śliwka+bursztyn, nigdy czyste czarne w jasnym motywie),
 * liquid-glass navbar. Kolory statusów grafiku i stanowisk celowo BEZ
 * zmian — to dane, nie branding. NIE zgaduj/nie zaokrąglaj wartości hex
 * przy kolejnych zmianach — to jest źródło prawdy przepisane 1:1 z
 * zaakceptowanego mockupu.
 */

:root {
  /* Wysokość górnego navbara (`sticky-top`) — używana jako offset `top`
     dla innych sticky elementów niżej na stronie (siatka dni, mini-
     nawigacja mobile), żeby nie chowały się pod navbarem. 58px to tylko
     wartość startowa/fallback (zanim JS zdąży zmierzyć) — NIE jest to
     stała: `app.js` nadpisuje ją realnym pomiarem `getBoundingClientRect()`
     przy starcie i przy resize. Historia: przy dawnym płaskim
     `.navbar-nav` pozycje menu łamały się w przedziale ~768-1140px i
     navbar rósł nawet do 104px (zgłoszenie Jakuba: pasek u góry rozszerza
     się i zasłania nagłówek dni) — dok "Aplikacyjnego" wariantu ma teraz
     tryb samych ikon w tym zakresie właśnie po to, żeby się nie
     powtórzyło (patrz `.navbar-dock .nav-link span` niżej), ale realny
     pomiar zamiast stałej zostaje jako zabezpieczenie na przyszłość. Nie
     zmieniaj tu wartości bez sprawdzenia `app.js` — to on jest teraz
     źródłem prawdy w czasie działania. */
  --planer-navbar-height: 58px;

  /* Typografia — self-hosted (static/fonts/, patrz fonts.css), latin +
     latin-ext (polskie znaki). Gabarito wyłącznie do nagłówków/marki,
     Figtree do całej reszty (w tym dużych liczb — wartości liczbowe
     celowo NIE dostają kroju ozdobnego), Plex Mono do etykiet danych. */
  --planer-font-display: 'Gabarito', 'Figtree', system-ui, sans-serif;
  --planer-font-body: 'Figtree', system-ui, -apple-system, "Segoe UI", sans-serif;
  --planer-font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Consolas, monospace;

  /* Marka (śliwka) + akcenty (koral/bursztyn — używane oszczędnie:
     gradienty navbara/przycisku, alerty; nigdy jako kolor danych). */
  --planer-primary: #6b4f8a;
  --planer-primary-light: #8d7fa8;
  --planer-primary-dark: #4a3866;
  --planer-primary-subtle: #f0ecf5;
  --planer-primary-subtle-2: #e5ddef;
  --planer-on-primary: #ffffff;
  --planer-coral: #f2637b;
  --planer-amber: #f2914a;
  /* Składowe r,g,b (bez "rgb()") tych samych trzech kolorów wyżej — do
     gradientu navbara (.navbar-planer), który potrzebuje częściowej
     przezroczystości (rgba) na motyw, nie tylko pełnego koloru. Ten sam
     komplet powtórzony niżej pod [data-color-theme="…"] (jeden zestaw na
     motyw, WSPÓLNY dla jasnego/ciemnego — gradient navbara jest ciemny w
     obu trybach, patrz komentarz przy --planer-nav-ink). */
  --planer-primary-dark-rgb: 74, 56, 102;
  --planer-primary-rgb: 107, 79, 138;
  --planer-coral-rgb: 242, 99, 123;
  --planer-amber-rgb: 242, 145, 74;
  /* Tekst na navbarze — OSOBNY token od --planer-on-primary: gradient
     navbara jest ciemny w OBU motywach (w przeciwieństwie do przycisku
     primary, który w dark mode jest jasny z ciemnym tekstem), więc tekst
     na nim musi zostać jasny również w dark mode. */
  --planer-nav-ink: #fbf6ee;

  /* Neutralne — atrament śliwkowy + ciepłe tło "papieru" (nazwa tokenów
     `grafit` zostaje dla zgodności z ~50 użyciami niżej i w szablonach,
     mimo że odcień jest teraz śliwkowo-beżowy, nie grafitowy). */
  --planer-text: #2b2440;
  --planer-grafit-700: #635c72;
  --planer-grafit-500: #7a7288;
  --planer-grafit-300: #d9d3cb;
  --planer-grafit-100: #ece7de;
  --planer-bg: #f4f2ed;
  --planer-surface: #ffffff;

  /* Cienie i fokus (jasny motyw) — cienie KOLOROWE (śliwka + ślad
     bursztynu), nigdy czysta czerń; to część wybranego języka wizualnego. */
  --shadow-sm: 0 1px 2px rgba(74, 56, 102, .10), 0 2px 8px rgba(242, 145, 74, .05);
  --shadow-md: 0 14px 26px -12px rgba(74, 56, 102, .30), 0 3px 8px rgba(242, 145, 74, .12);
  --shadow-focus: 0 0 0 4px rgba(107, 79, 138, .25);

  /* Statusy grafiku — kolor+ikona+wzór, patrz wiki sekcja 3 */
  --status-work-bg: var(--planer-grafit-100);
  --status-work-text: var(--planer-text);
  --status-work-accent: var(--planer-primary-light);

  /* Czerwony zgodnie z referencją użytkownika (`wiki/materialyss/kolory1.png`
     — etykieta "WOLNE" ze starego planera). Świadomie jedyny czerwony status
     w siatce — zgodny z pierwowzorem, nie koliduje z paletą kolorów
     STANOWISK niżej (ta celowo jest bez czerwieni, inny system). */
  --status-dayoff-bg: #c62828;
  --status-dayoff-text: #ffffff;

  --status-holiday-bg: #8e2440;
  --status-holiday-text: #ffffff;
  /* Bordowy jako kolor TEKSTU (nie tła) — używany tam, gdzie plakietka z
     pełnym tłem byłaby za ciężka, np. `.rcp-calendar-holiday`. */
  --status-holiday-ink: #8e2440;

  --status-vacation-bg: #1f7a4d;
  --status-vacation-text: #ffffff;

  --status-vacation-request-bg: #0b7285;
  --status-vacation-request-text: #ffffff;

  --status-sick-bg: #7b3f91;
  --status-sick-text: #ffffff;

  --status-occasional-leave-bg: #b5651d;
  --status-occasional-leave-text: #ffffff;

  --status-childcare-leave-bg: #1d5fb5;
  --status-childcare-leave-text: #ffffff;

  --status-unpaid-leave-bg: #4f5b66;
  --status-unpaid-leave-text: #ffffff;

  --status-childcare-sick-leave-bg: #5f6d1e;
  --status-childcare-sick-leave-text: #ffffff;

  --status-holiday-day-off-bg: #23386b;
  --status-holiday-day-off-text: #ffffff;

  /* Tło identyczne z --planer-surface (białe na białym) sprawiało, że cała
     plakietka znikała w jasnym motywie — widoczny zostawał tylko blady
     tekst, co Jakub zgłosił jako "etykieta wolne robi się biała"
     (zrzut ekranu z /grafik, 2026-08-11). Tło przesunięte na ten sam
     jasny token co `--status-work-bg` (patrz `.status-work` niżej), żeby
     kreskowana ramka miała się o co odbić. */
  --status-vacant-bg: var(--planer-grafit-100);
  --status-vacant-text: var(--planer-grafit-500);
  --status-vacant-border: var(--planer-grafit-300);

  /* Paleta kolorów FLAG GRAFIKU (panel admina "Wygląd flag grafiku",
     2026-07-23) — `ENTRY_TYPE_COLOR_PALETTE` w schedule_entry.py. 1:1 z
     kolorami --status-*-bg powyżej, tylko przepięte na tokeny nazwane
     niezależnie od konkretnej flagi (admin może dać dowolnej fladze
     dowolny z tych 7 tokenów, nie tylko jej domyślny). Te same wartości w
     obu motywach, jak --status-* i --position-color-* — już dość nasycone. */
  --entry-color-czerwony-bg: #c62828;
  --entry-color-czerwony-text: #ffffff;
  --entry-color-bordowy-bg: #8e2440;
  --entry-color-bordowy-text: #ffffff;
  --entry-color-zielony-bg: #1f7a4d;
  --entry-color-zielony-text: #ffffff;
  --entry-color-morski-bg: #0b7285;
  --entry-color-morski-text: #ffffff;
  --entry-color-purpurowy-bg: #7b3f91;
  --entry-color-purpurowy-text: #ffffff;
  --entry-color-bursztynowy-bg: #b5651d;
  --entry-color-bursztynowy-text: #ffffff;
  --entry-color-niebieski-bg: #1d5fb5;
  --entry-color-niebieski-text: #ffffff;
  --entry-color-grafitowy-bg: #4f5b66;
  --entry-color-grafitowy-text: #ffffff;
  --entry-color-oliwkowy-bg: #5f6d1e;
  --entry-color-oliwkowy-text: #ffffff;
  --entry-color-granatowy-bg: #23386b;
  --entry-color-granatowy-text: #ffffff;

  /* Paleta kolorów STANOWISK (nie statusów) — dotyczy wyłącznie tła
     odznaki WORK ("Praca"), wybór kierownika per stanowisko
     (`Position.color`, patrz `POSITION_COLOR_LABELS`). Te same wartości
     w obu motywach (jak status-vacation/-sick niżej) — już dość
     nasycone, żeby biały tekst miał bezpieczny kontrast na jasnym i
     ciemnym tle strony jednocześnie. */
  --position-color-niebieski-bg: #1565c0;
  --position-color-niebieski-text: #ffffff;
  --position-color-zielony-bg: #2e7d32;
  --position-color-zielony-text: #ffffff;
  --position-color-fioletowy-bg: #6a1b9a;
  --position-color-fioletowy-text: #ffffff;
  --position-color-pomaranczowy-bg: #d84315;
  --position-color-pomaranczowy-text: #ffffff;
  --position-color-rozowy-bg: #ad1457;
  --position-color-rozowy-text: #ffffff;
  --position-color-turkusowy-bg: #00695c;
  --position-color-turkusowy-text: #ffffff;
  --position-color-zolty-bg: #8a6d00;
  --position-color-zolty-text: #ffffff;
  --position-color-brazowy-bg: #6d4c41;
  --position-color-brazowy-text: #ffffff;

  /* Mapowanie na zmienne Bootstrapa 5.3, żeby komponenty natywne (btn,
     dropdown, form-check itd.) też pojechały na tokenach marki bez
     przepisywania każdego komponentu z osobna. */
  --bs-primary: var(--planer-primary-dark);
  --bs-primary-rgb: 74, 56, 102;
  --bs-body-bg: var(--planer-bg);
  --bs-body-color: var(--planer-text);
  --bs-body-font-family: var(--planer-font-body);
  --bs-border-radius: 0.75rem;
  --bs-border-radius-lg: 1.25rem;
  --bs-border-radius-sm: 0.5rem;

  /* Bootstrap domyślnie wskazuje card/modal/dropdown/popover/list-group
     na --bs-body-bg — czyli identyczne z tłem strony. Podpinamy je na
     --planer-surface, żeby powierzchnie "uniesione nad tłem" faktycznie
     się od niego odróżniały (najbardziej widoczne w dark mode, gdzie
     bez tego wszystko zlewa się w jedną plamę). */
  --bs-card-bg: var(--planer-surface);
  --bs-modal-bg: var(--planer-surface);
  --bs-dropdown-bg: var(--planer-surface);
  --bs-popover-bg: var(--planer-surface);
  --bs-list-group-bg: var(--planer-surface);
  --bs-offcanvas-bg: var(--planer-surface);
  --bs-offcanvas-color: var(--planer-text);

  /* Bootstrap domyślnie podświetla .dropdown-item.active swoim domyślnym
     niebieskim (#0d6efd) — nigdzie w tym pliku nie nadpisany, więc każdy
     aktywny wpis w menu (np. "Czas pracy zespołu" w Panelu zarządzania)
     wyskakiwał obcym kolorem spoza palety "Miękkiej" (zgłoszenie Jakuba
     przy okazji screena z blado widocznym przełącznikiem zakładek). */
  --bs-dropdown-link-active-bg: var(--planer-primary-dark);
  --bs-dropdown-link-active-color: var(--planer-on-primary);
}

/* Tryb ciemny — `data-bs-theme="dark"` na <html>, ustawiany inline w
   <head> layoutu bazowego (przed renderem treści, żeby uniknąć mignięcia
   złego motywu) i przełączany przyciskiem w navbarze (app.js).
   Zasada: ten sam token = ta sama ROLA, w drugą stronę na skali jasności
   (patrz wiki sekcja 3, "Zasada projektowa dla dark mode"). */
[data-bs-theme="dark"] {
  --planer-bg: #241f30;
  --planer-surface: #302a40;

  --planer-text: #ece8f2;
  --planer-grafit-700: #c8c2d4;
  --planer-grafit-500: #a29bb2;
  --planer-grafit-300: #5f5872;
  --planer-grafit-100: #292337;

  /* Primary w dark mode odwraca kontrakt jak w starym motywie: jasna
     śliwka jako tło przycisku, ciemny atrament jako tekst na niej. */
  --planer-primary: #b09ed0;
  --planer-primary-light: #c4b5dc;
  --planer-primary-dark: #9a83c2;
  --planer-primary-subtle: #382f4d;
  --planer-primary-subtle-2: #453a5e;
  --planer-on-primary: #221b33;
  --planer-coral: #f2637b;
  --planer-amber: #f2914a;

  /* W ciemnym motywie cień kolorowy nie działa (ginie na ciemnym tle) —
     wracamy do klasycznej czerni, mocniejszej niż w jasnym. */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .45);
  --shadow-md: 0 8px 20px rgba(0, 0, 0, .55);
  --shadow-focus: 0 0 0 4px rgba(176, 158, 208, .35);

  --status-work-bg: var(--planer-surface);
  --status-work-text: var(--planer-text);
  --status-work-accent: var(--planer-primary);

  --status-dayoff-bg: #c62828;
  --status-dayoff-text: #ffffff;

  --status-holiday-bg: #8e2440;
  --status-holiday-text: #ffffff;
  /* Ta sama rola przeniesiona na drugą stronę skali jasności: bordowy jako
     TEKST na ciemnym tle byłby nieczytelny, więc rozjaśniony do różu. */
  --status-holiday-ink: #f0899f;

  --status-vacation-bg: #1f7a4d;
  --status-vacation-text: #ffffff;

  --status-vacation-request-bg: #0b7285;
  --status-vacation-request-text: #ffffff;

  --status-sick-bg: #7b3f91;
  --status-sick-text: #ffffff;

  --status-occasional-leave-bg: #b5651d;
  --status-occasional-leave-text: #ffffff;

  --status-childcare-leave-bg: #1d5fb5;
  --status-childcare-leave-text: #ffffff;

  --status-unpaid-leave-bg: #4f5b66;
  --status-unpaid-leave-text: #ffffff;

  --status-childcare-sick-leave-bg: #5f6d1e;
  --status-childcare-sick-leave-text: #ffffff;

  --status-holiday-day-off-bg: #23386b;
  --status-holiday-day-off-text: #ffffff;

  --status-vacant-bg: transparent;
  --status-vacant-text: #a39d95;
  --status-vacant-border: #6b655f;

  /* Paleta kolorów flag grafiku — te same wartości co w jasnym motywie,
     patrz komentarz przy pierwszej definicji w :root. */
  --entry-color-czerwony-bg: #c62828;
  --entry-color-czerwony-text: #ffffff;
  --entry-color-bordowy-bg: #8e2440;
  --entry-color-bordowy-text: #ffffff;
  --entry-color-zielony-bg: #1f7a4d;
  --entry-color-zielony-text: #ffffff;
  --entry-color-morski-bg: #0b7285;
  --entry-color-morski-text: #ffffff;
  --entry-color-purpurowy-bg: #7b3f91;
  --entry-color-purpurowy-text: #ffffff;
  --entry-color-bursztynowy-bg: #b5651d;
  --entry-color-bursztynowy-text: #ffffff;
  --entry-color-niebieski-bg: #1d5fb5;
  --entry-color-niebieski-text: #ffffff;
  --entry-color-grafitowy-bg: #4f5b66;
  --entry-color-grafitowy-text: #ffffff;
  --entry-color-oliwkowy-bg: #5f6d1e;
  --entry-color-oliwkowy-text: #ffffff;
  --entry-color-granatowy-bg: #23386b;
  --entry-color-granatowy-text: #ffffff;

  /* Paleta kolorów stanowisk — te same wartości co w jasnym motywie,
     patrz komentarz przy pierwszej definicji w :root. */
  --position-color-niebieski-bg: #1565c0;
  --position-color-niebieski-text: #ffffff;
  --position-color-zielony-bg: #2e7d32;
  --position-color-zielony-text: #ffffff;
  --position-color-fioletowy-bg: #6a1b9a;
  --position-color-fioletowy-text: #ffffff;
  --position-color-pomaranczowy-bg: #d84315;
  --position-color-pomaranczowy-text: #ffffff;
  --position-color-rozowy-bg: #ad1457;
  --position-color-rozowy-text: #ffffff;
  --position-color-turkusowy-bg: #00695c;
  --position-color-turkusowy-text: #ffffff;
  --position-color-zolty-bg: #8a6d00;
  --position-color-zolty-text: #ffffff;
  --position-color-brazowy-bg: #6d4c41;
  --position-color-brazowy-text: #ffffff;

  --bs-primary: var(--planer-primary-dark);
  --bs-body-bg: var(--planer-bg);
  --bs-body-color: var(--planer-text);
  --bs-border-color: var(--planer-grafit-300);

  --bs-card-bg: var(--planer-surface);
  --bs-modal-bg: var(--planer-surface);
  --bs-dropdown-bg: var(--planer-surface);
  --bs-popover-bg: var(--planer-surface);
  --bs-list-group-bg: var(--planer-surface);
  --bs-offcanvas-bg: var(--planer-surface);
  --bs-offcanvas-color: var(--planer-text);

  --bs-dropdown-link-active-bg: var(--planer-primary-dark);
  --bs-dropdown-link-active-color: var(--planer-on-primary);
}

/* ============================================================
 * Motywy kolorystyczne wybieralne przez użytkownika (Moje dane →
 * Wygląd), 2026-07-21. Domyślne wartości "Śliwka" zostają zdefiniowane
 * wprost w :root/[data-bs-theme="dark"] wyżej (bez zmian) — DODATKOWO
 * powtórzone niżej pod jawnym [data-color-theme="SLIWKA"], bo custom
 * properties dziedziczą się z najbliższego przodka: element zagnieżdżony
 * z własnym data-color-theme (np. podgląd karty motywu na Moje dane →
 * Wygląd, gdzie wszystkie 4 karty widać naraz niezależnie od aktualnie
 * wybranego motywu strony) inaczej dziedziczyłby wartości aktywnego
 * motywu z <html>, nie prawdziwej Śliwki. Kolejne trzy motywy NADPISUJĄ
 * wyłącznie tokeny marki/neutralne — --status-* i --position-color-*
 * celowo NIE są tu dotykane, to dane, nie branding (patrz nagłówek
 * pliku). Wartości hex przepisane 1:1 z zaakceptowanego artefaktu
 * porównawczego (przeliczone na kontrast WCAG AA), nie zaokrąglaj przy
 * kolejnych zmianach.
 * ============================================================ */

/* Śliwka — domyślny motyw, mirror :root/[data-bs-theme="dark"] wyżej */
[data-color-theme="SLIWKA"] {
  --planer-text: #2b2440;
  --planer-grafit-700: #635c72;
  --planer-grafit-500: #7a7288;
  --planer-grafit-300: #d9d3cb;
  --planer-grafit-100: #ece7de;
  --planer-bg: #f4f2ed;
  --planer-surface: #ffffff;

  --planer-primary: #6b4f8a;
  --planer-primary-light: #8d7fa8;
  --planer-primary-dark: #4a3866;
  --planer-primary-subtle: #f0ecf5;
  --planer-primary-subtle-2: #e5ddef;
  --planer-on-primary: #ffffff;
  --planer-coral: #f2637b;
  --planer-amber: #f2914a;
  --planer-nav-ink: #fbf6ee;
  --planer-primary-dark-rgb: 74, 56, 102;
  --planer-primary-rgb: 107, 79, 138;
  --planer-coral-rgb: 242, 99, 123;
  --planer-amber-rgb: 242, 145, 74;

  --shadow-sm: 0 1px 2px rgba(74, 56, 102, .10), 0 2px 8px rgba(242, 145, 74, .05);
  --shadow-md: 0 14px 26px -12px rgba(74, 56, 102, .30), 0 3px 8px rgba(242, 145, 74, .12);
  --shadow-focus: 0 0 0 4px rgba(107, 79, 138, .25);
}
[data-bs-theme="dark"][data-color-theme="SLIWKA"] {
  --planer-text: #ece8f2;
  --planer-grafit-700: #c8c2d4;
  --planer-grafit-500: #a29bb2;
  --planer-grafit-300: #5f5872;
  --planer-grafit-100: #292337;
  --planer-bg: #241f30;
  --planer-surface: #302a40;

  --planer-primary: #b09ed0;
  --planer-primary-light: #c4b5dc;
  --planer-primary-dark: #9a83c2;
  --planer-primary-subtle: #382f4d;
  --planer-primary-subtle-2: #453a5e;
  --planer-on-primary: #221b33;
  --planer-coral: #f2637b;
  --planer-amber: #f2914a;
  --planer-nav-ink: #fbf6ee;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .45);
  --shadow-md: 0 8px 20px rgba(0, 0, 0, .55);
  --shadow-focus: 0 0 0 4px rgba(176, 158, 208, .35);
}

/* Głębia — chłodny wariant, morska zieleń-petrol + akcent rdzy */
[data-color-theme="GLEBIA"] {
  --planer-text: #1c2e33;
  --planer-grafit-700: #3f5459;
  --planer-grafit-500: #5c7075;
  --planer-grafit-300: #c3d3d3;
  --planer-grafit-100: #e2ecec;
  --planer-bg: #f2f5f4;
  --planer-surface: #ffffff;

  --planer-primary: #1f6f72;
  --planer-primary-light: #4fa3a6;
  --planer-primary-dark: #163a40;
  --planer-primary-subtle: #e6f2f1;
  --planer-primary-subtle-2: #d3e8e7;
  --planer-on-primary: #ffffff;
  --planer-coral: #c9482f;
  --planer-amber: #f2b544;
  --planer-nav-ink: #f4faf9;
  --planer-primary-dark-rgb: 22, 58, 64;
  --planer-primary-rgb: 31, 111, 114;
  --planer-coral-rgb: 201, 72, 47;
  --planer-amber-rgb: 242, 181, 68;

  --shadow-sm: 0 1px 2px rgba(22, 58, 64, .12), 0 2px 8px rgba(242, 181, 68, .06);
  --shadow-md: 0 14px 26px -12px rgba(22, 58, 64, .32), 0 3px 8px rgba(242, 181, 68, .14);
  --shadow-focus: 0 0 0 4px rgba(31, 111, 114, .25);
}
[data-bs-theme="dark"][data-color-theme="GLEBIA"] {
  --planer-text: #e6f2f2;
  --planer-grafit-700: #bcd0d0;
  --planer-grafit-500: #93a9aa;
  --planer-grafit-300: #3e5257;
  --planer-grafit-100: #22343a;
  --planer-bg: #16232a;
  --planer-surface: #1f333b;

  --planer-primary: #5fb8ba;
  --planer-primary-light: #7fcbcb;
  --planer-primary-dark: #4a9fa0;
  --planer-primary-subtle: #24393f;
  --planer-primary-subtle-2: #2d454c;
  --planer-on-primary: #0c2224;
  --planer-coral: #e2664a;
  --planer-amber: #f2c56a;
  --planer-nav-ink: #f4faf9;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .45);
  --shadow-md: 0 8px 20px rgba(0, 0, 0, .55);
  --shadow-focus: 0 0 0 4px rgba(95, 184, 186, .35);
}

/* Szałwia — naturalny wariant, zieleń szałwii + terakota */
[data-color-theme="SZALWIA"] {
  --planer-text: #1f2a17;
  --planer-grafit-700: #46523a;
  --planer-grafit-500: #6b7460;
  --planer-grafit-300: #cdd3bd;
  --planer-grafit-100: #e7ebdd;
  --planer-bg: #f4f4ec;
  --planer-surface: #ffffff;

  --planer-primary: #4f7942;
  --planer-primary-light: #7fa66d;
  --planer-primary-dark: #2f5233;
  --planer-primary-subtle: #eef3e6;
  --planer-primary-subtle-2: #e0ead2;
  --planer-on-primary: #ffffff;
  --planer-coral: #d97757;
  --planer-amber: #e0a458;
  --planer-nav-ink: #f6f7ef;
  --planer-primary-dark-rgb: 47, 82, 51;
  --planer-primary-rgb: 79, 121, 66;
  --planer-coral-rgb: 217, 119, 87;
  --planer-amber-rgb: 224, 164, 88;

  --shadow-sm: 0 1px 2px rgba(47, 82, 51, .12), 0 2px 8px rgba(224, 164, 88, .06);
  --shadow-md: 0 14px 26px -12px rgba(47, 82, 51, .32), 0 3px 8px rgba(224, 164, 88, .14);
  --shadow-focus: 0 0 0 4px rgba(79, 121, 66, .25);
}
[data-bs-theme="dark"][data-color-theme="SZALWIA"] {
  --planer-text: #eef1e6;
  --planer-grafit-700: #c3cdb3;
  --planer-grafit-500: #a3ac93;
  --planer-grafit-300: #4a5540;
  --planer-grafit-100: #262f1f;
  --planer-bg: #1c2418;
  --planer-surface: #262f20;

  --planer-primary: #8bb673;
  --planer-primary-light: #a3c98f;
  --planer-primary-dark: #6f9c5b;
  --planer-primary-subtle: #2a341f;
  --planer-primary-subtle-2: #333f26;
  --planer-on-primary: #16210f;
  --planer-coral: #e0916f;
  --planer-amber: #e8bc7a;
  --planer-nav-ink: #f6f7ef;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .45);
  --shadow-md: 0 8px 20px rgba(0, 0, 0, .55);
  --shadow-focus: 0 0 0 4px rgba(139, 182, 115, .35);
}

/* Bursztyn — ciepły wariant, terakota/bursztyn + akcent śliwki */
[data-color-theme="BURSZTYN"] {
  --planer-text: #33241a;
  --planer-grafit-700: #5c493c;
  --planer-grafit-500: #8a7263;
  --planer-grafit-300: #ddccb9;
  --planer-grafit-100: #f0e6d8;
  --planer-bg: #f7f1e9;
  --planer-surface: #ffffff;

  --planer-primary: #c2521f;
  --planer-primary-light: #e07a42;
  --planer-primary-dark: #9a3d1f;
  --planer-primary-subtle: #fbeee3;
  --planer-primary-subtle-2: #f5dcc6;
  --planer-on-primary: #ffffff;
  --planer-coral: #6b4f8a;
  --planer-amber: #f2b705;
  --planer-nav-ink: #fff6ec;
  --planer-primary-dark-rgb: 154, 61, 31;
  --planer-primary-rgb: 194, 82, 31;
  --planer-coral-rgb: 107, 79, 138;
  --planer-amber-rgb: 242, 183, 5;

  --shadow-sm: 0 1px 2px rgba(154, 61, 31, .12), 0 2px 8px rgba(242, 183, 5, .06);
  --shadow-md: 0 14px 26px -12px rgba(154, 61, 31, .32), 0 3px 8px rgba(242, 183, 5, .14);
  --shadow-focus: 0 0 0 4px rgba(194, 82, 31, .25);
}
[data-bs-theme="dark"][data-color-theme="BURSZTYN"] {
  --planer-text: #f5ece2;
  --planer-grafit-700: #d3bfa9;
  --planer-grafit-500: #b39c88;
  --planer-grafit-300: #5c4534;
  --planer-grafit-100: #33251a;
  --planer-bg: #2a1f18;
  --planer-surface: #382a20;

  --planer-primary: #e0824a;
  --planer-primary-light: #eb9d6d;
  --planer-primary-dark: #c96a34;
  --planer-primary-subtle: #3d2c1f;
  --planer-primary-subtle-2: #513723;
  --planer-on-primary: #241407;
  --planer-coral: #9a83c2;
  --planer-amber: #f2c94c;
  --planer-nav-ink: #fff6ec;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .45);
  --shadow-md: 0 8px 20px rgba(0, 0, 0, .55);
  --shadow-focus: 0 0 0 4px rgba(224, 130, 74, .35);
}

body {
  background-color: var(--planer-bg);
  color: var(--planer-text);
  font-family: var(--planer-font-body);
}

/* Nagłówki w kroju wystawowym (Gabarito). Duże liczby (KPI, podsumowania)
   celowo ZOSTAJĄ w Figtree — krój ozdobny w wartościach liczbowych czyta
   się jak dekoracja, nie jak dane. */
h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6 {
  font-family: var(--planer-font-display);
  font-weight: 600;
}

h1, .h1, h2, .h2 {
  font-weight: 800;
  letter-spacing: -0.01em;
}

/* Bez tego przeglądarkowe "przewiń do elementu" (klawiszem Tab/focus,
   kotwica #fragment w URL, Ctrl+F, `scrollIntoView`) przewija element w
   SAM róg ekranu (y=0), gdzie fizycznie stoi sticky navbar (`sticky-top`)
   — element renderuje się pod navbarem, a klik/focus trafia w navbar
   zamiast w niego (potwierdzone empirycznie: Playwright `click()` na
   komórce grafiku łapał link z `<thead>` zamiast komórki).
   `/grafik`/`/moj-grafik` mają DRUGĄ warstwę sticky pod navbarem
   (nagłówek dni / mini-nawigacja miesiąca, ok. tyle samo co navbar) —
   rezerwujemy podwójną wysokość globalnie, żeby pokryć ten gorszy
   przypadek. Na stronach z tylko jedną warstwą sticky to tylko trochę
   więcej marginesu niż ściśle potrzeba — nieszkodliwe. */
html {
  scroll-padding-top: calc(var(--planer-navbar-height) * 2);
}

/* Alpine potrzebuje tej reguły ręcznie — bez niej [x-cloak] nie chowa
   elementu przed pierwszym przetworzeniem x-show, więc widać krótkie
   miganie obu stanów (zwiniętego i rozwiniętego) przy ładowaniu strony. */
[x-cloak] {
  display: none !important;
}

/* Ekran logowania — gradient terakota→grafit zamiast gołej karty na
   zwykłym tle strony, żeby pierwszy ekran, jaki widzi użytkownik, robił
   wrażenie. Sam gradient, bez zmiany layoutu formularza poniżej
   (`.card` zostaje bez zmian, tylko tło pod nią). */
.auth-page {
  min-height: 70vh;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Strona nadpisuje main_class i traci przez to Bootstrapowy `.container`
     (gutter/centrowanie) — bez tego paddingu karta login opiera się
     dosłownie o krawędzie viewportu na telefonie. */
  padding-inline: 1rem;
  /* Literały zamiast tokenów CELOWO: w dark mode --planer-primary* są jasne
     (kontrakt przycisku), a --planer-text prawie biały — gradient na
     tokenach robiłby się jasny. Ten ekran ma wyglądać identycznie
     (ciemna śliwka) w obu motywach, jak gradient navbara. */
  background: linear-gradient(160deg, #6b4f8a 0%, #4a3866 55%, #2b2440 100%);
}

.auth-page-logo {
  display: block;
  margin: 0 auto 1.5rem;
}

/* Publiczne demo: panel rozwijany należy do banera, a nie do sticky navbara. */
.demo-banner {
  position: relative;
  z-index: 1040;
  background: #202839;
  color: #fff;
  font-size: .875rem;
}

.demo-banner-row {
  min-height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .75rem;
  padding: 0 .75rem;
  white-space: nowrap;
}

.demo-banner-long { white-space: normal; }

.demo-guide summary,
.demo-switch-role button {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: #fff;
}

.demo-guide summary {
  padding: 0 .25rem;
  text-decoration: underline;
  text-underline-offset: .2em;
}

.demo-guide-panel {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  max-width: 720px;
  margin: 0 auto;
  padding: 1rem 1.25rem;
  white-space: normal;
  background: #202839;
  color: #fff;
  box-shadow: 0 10px 20px #0005;
}

.demo-guide-panel ol { margin: .5rem 0 0; padding-left: 1.5rem; }
.demo-guide-panel li + li { margin-top: .4rem; }
.demo-guide-panel a { color: #fff; text-decoration: underline; }
.demo-guide-mobile-note { display: none; }

.demo-switch-role { margin: 0; }
.demo-switch-role button {
  border: 1px solid #fff;
  border-radius: .375rem;
  background: transparent;
  padding: 0 .75rem;
  font-weight: 600;
}

.demo-guide summary:focus-visible,
.demo-switch-role button:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }
.demo-switch-role button:hover { background: #fff; color: #202839; }

.demo-login .card-body { padding: 1.25rem; }
.demo-login-notice summary { cursor: pointer; min-height: 44px; align-content: center; }
.demo-role-card { background: var(--bs-body-bg); }
.demo-role-card .card-body { padding: 1rem; }
.demo-role-enter { min-height: 44px; }
.demo-login-separator { color: var(--planer-text); }

@media (max-width: 767.98px) {
  .demo-banner-row { justify-content: space-between; gap: .25rem; padding: 0 .5rem; }
  .demo-banner-long { display: none; }
  .demo-guide-mobile-note { display: block; }
  .demo-switch-role button { padding: 0 .5rem; }
}

/* Ikony Bootstrap Icons — sprite SVG, nie webfont (patrz
   app/static/vendor/README.md). `fill="currentColor"` na symbolach +
   `color: inherit` tutaj = ikona dziedziczy kolor tekstu automatycznie w
   obu motywach, bez osobnego tokena koloru ikony. */
.bi {
  width: 1em;
  height: 1em;
  fill: currentColor;
  color: inherit;
  vertical-align: -0.125em;
  flex-shrink: 0;
}

/* Górny pasek nawigacji — "liquid glass": śliwkowy gradient domykający
   się śladem bursztynu poza prawą krawędzią (endpoint 165% > 100% —
   celowo, daje tylko delikatne ocieplenie prawego rogu), lekko
   przepuszczalny (rgba + backdrop-blur), więc treść przewijana pod
   spodem subtelnie prześwituje jak przez mleczne szkło. Fallback
   `background-color` kryje w całości, gdyby przeglądarka nie wspierała
   backdrop-filter — wtedy po prostu solidny gradient bez efektu szkła. */
.navbar-planer {
  background-color: var(--planer-primary-dark);
  background-image: linear-gradient(115deg,
    rgba(var(--planer-primary-dark-rgb), .92),
    rgba(var(--planer-primary-dark-rgb), .90) 45%,
    rgba(var(--planer-primary-rgb), .88) 78%,
    rgba(var(--planer-amber-rgb), .85) 165%);
  backdrop-filter: blur(12px) saturate(1.3);
  -webkit-backdrop-filter: blur(12px) saturate(1.3);
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, .10), 0 10px 26px -14px rgba(var(--planer-primary-dark-rgb), .6);
}

.navbar-planer .navbar-brand {
  font-family: var(--planer-font-display);
  font-weight: 800;
}

.navbar-planer .navbar-brand,
.navbar-planer .nav-link,
.navbar-planer .navbar-text {
  color: var(--planer-nav-ink) !important;
}

/* Promien na stanie BAZOWYM, nie tylko w :hover/.active — Bootstrap
   animuje `background-color` na `.nav-link` przez 150ms, wiec gdyby
   `border-radius` zyl wylacznie w stanie hover, to przy zjezdzie mysza
   wracalby do zera natychmiast, podczas gdy tlo jeszcze sie wygasza:
   podswietlenie mrugalo kwadratowymi rogami (zgloszenie Jakuba
   2026-08-18, nagranie ekranu). Radius stały w kazdym stanie = brak
   mrugniecia. */
.navbar-planer .nav-link {
  border-radius: 6px;
}

/* Aktywna/hover pozycja menu głównego — wyraźniejsza niż sam kolor tekstu
   (dziś identyczny biały dla aktywnej i nieaktywnej pozycji). */
.navbar-planer .nav-link.active,
.navbar-planer .nav-link:hover,
.navbar-planer .nav-link.show {
  background-color: rgba(255, 255, 255, .16);
}

/* Znak Planera (`static/img/planer-mark.svg`) jest już w kremie/koralu/
   bursztynie na przezroczystym tle — czytelny wprost na gradiencie navbara. */
.navbar-brand-logo {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

/* Przycisk przełącznika jasny/ciemny motyw w navbarze */
#theme-toggle .theme-toggle-icon-light {
  display: none;
}

[data-bs-theme="dark"] #theme-toggle .theme-toggle-icon-dark {
  display: none;
}

[data-bs-theme="dark"] #theme-toggle .theme-toggle-icon-light {
  display: inline-block;
}

/* Główny przycisk w kolorze marki — tło zawsze `--planer-primary-dark`
   (nigdy literalny `--planer-primary` wprost), bo tylko ta para z
   `--planer-on-primary` daje bezpieczny kontrast AA w obu motywach (patrz
   wiki sekcja 6). */
.btn-primary {
  --bs-btn-color: var(--planer-on-primary);
  --bs-btn-bg: var(--planer-primary-dark);
  --bs-btn-border-color: var(--planer-primary-dark);
  --bs-btn-hover-color: var(--planer-on-primary);
  --bs-btn-hover-bg: var(--planer-primary-dark);
  --bs-btn-hover-border-color: var(--planer-primary-dark);
  --bs-btn-active-color: var(--planer-on-primary);
  --bs-btn-active-bg: var(--planer-primary-dark);
  --bs-btn-active-border-color: var(--planer-primary-dark);
  --bs-btn-disabled-bg: var(--planer-primary-dark);
  --bs-btn-disabled-border-color: var(--planer-primary-dark);
  /* Gradient śliwka→jaśniejsza śliwka + miękki kolorowy cień — tło z
     tokena wyżej zostaje jako fallback i baza pod filter:brightness. */
  background-image: linear-gradient(120deg, var(--planer-primary-dark), var(--planer-primary));
  box-shadow: 0 10px 18px -10px rgba(var(--planer-primary-dark-rgb), .55);
}

.btn-primary:hover {
  filter: brightness(0.92);
}

.btn-primary:active {
  filter: brightness(0.85);
}

/* Wariant obrysowany Bootstrapa ma kolory wpisane na sztywno w skompilowany
   CSS (`--bs-btn-color:#0d6efd` itd.) — nadpisanie `--bs-primary` wyżej na
   to NIE działa, bo ta reguła w ogóle się do `--bs-primary` nie odwołuje.
   Bez tego bloku przycisk zostaje domyślnie niebieski w OBU motywach; ten
   sam mechanizm błędu, który dla `.btn-outline-secondary` opisuje komentarz
   niżej (tam naprawiony tylko dla ciemnego motywu, bo tylko tam był
   zgłoszony — tu potrzebne w obu, bo tu problem widoczny w obu). Zgłoszenie
   Jakuba 2026-08-07, przycisk "Aktywuj cykliczną wysyłkę" w kroku 4
   kreatora raportów. */
.btn-outline-primary {
  --bs-btn-color: var(--planer-primary-dark);
  --bs-btn-border-color: var(--planer-primary-dark);
  --bs-btn-hover-color: var(--planer-on-primary);
  --bs-btn-hover-bg: var(--planer-primary-dark);
  --bs-btn-hover-border-color: var(--planer-primary-dark);
  --bs-btn-active-color: var(--planer-on-primary);
  --bs-btn-active-bg: var(--planer-primary-dark);
  --bs-btn-active-border-color: var(--planer-primary-dark);
  --bs-btn-disabled-color: var(--planer-primary-dark);
  --bs-btn-disabled-border-color: var(--planer-primary-dark);
}

/* Domyślny szary Bootstrapa (--bs-secondary) nie jest w ogóle nadpisany
   na ciemny motyw (w odróżnieniu od --bs-primary wyżej), więc na ciemnym
   tle (--planer-bg) daje za słaby kontrast — zgłoszone jako "prawie
   niewidoczny przycisk" (np. "Historia" na /rcp). Naprawa lokalna do
   .btn-outline-secondary zamiast globalnego --bs-secondary, żeby nie
   zmieniać przy okazji innych elementów (badge/text-secondary itd.),
   które w jasnym motywie działają poprawnie i nie były zgłoszone. */
[data-bs-theme="dark"] .btn-outline-secondary {
  --bs-btn-color: var(--planer-grafit-700);
  --bs-btn-border-color: var(--planer-grafit-700);
  --bs-btn-hover-color: var(--planer-bg);
  --bs-btn-hover-bg: var(--planer-grafit-700);
  --bs-btn-hover-border-color: var(--planer-grafit-700);
  --bs-btn-active-color: var(--planer-bg);
  --bs-btn-active-bg: var(--planer-grafit-700);
  --bs-btn-active-border-color: var(--planer-grafit-700);
  --bs-btn-disabled-color: var(--planer-grafit-500);
  --bs-btn-disabled-border-color: var(--planer-grafit-500);
}

/* Przyciski jako pigułki — charakter "Miękkiej". Pola formularzy celowo
   NIE są pigułkami (pełny owal na inputach utrudnia skanowanie tekstu
   od lewej krawędzi) — dostają tylko większy promień. */
.btn {
  border-radius: 999px;
}

.form-control,
.form-select {
  border-radius: 0.75rem;
}

.card,
.modal-content {
  border-radius: 1.25rem;
}

.dropdown-menu,
.popover {
  border-radius: 1rem;
}

.card {
  box-shadow: var(--shadow-sm);
}

.dropdown-menu,
.popover {
  box-shadow: var(--shadow-md);
}

.card-body {
  padding: 1.25rem;
}

@media (max-width: 767.98px) {
  .card-body {
    padding: 1rem;
  }
}

a {
  color: var(--planer-primary-dark);
}

a:hover {
  color: var(--planer-primary);
}

/* Etykieta roli użytkownika */
.badge-role {
  background-color: var(--planer-primary-subtle);
  color: var(--planer-primary-dark);
  border: 1px solid var(--planer-primary);
  border-radius: 999px;
  padding: 0.15rem 0.6rem;
  font-size: 0.75rem;
  font-weight: 600;
  display: inline-block;
}

/* W dark mode primary-dark (#9a83c2) na subtle (#382f4d) daje tylko 3.8:1
   — poniżej AA. Jaśniejszy stopień tej samej rampy przechodzi (6+:1). */
[data-bs-theme="dark"] .badge-role {
  color: var(--planer-primary-light);
}

/* Etykieta "DZIŚ" (Mój grafik) — plain badge, nie status */
.badge-today {
  display: inline-block;
  background-color: var(--planer-primary-dark);
  color: var(--planer-on-primary);
  border-radius: 6px;
  padding: 0.125rem 0.5rem;
  font-size: 0.875rem;
  font-weight: 600;
}

/* Fokus pól formularzy w kolorze marki */
.form-control:focus,
.form-select:focus {
  border-color: var(--planer-primary-dark);
  box-shadow: var(--shadow-focus);
}

/* Toast błędu htmx (sieć/serwer) — jedyny widoczny sygnał, gdy
   htmx:responseError/htmx:sendError nie ma sensownego fragmentu do
   podmiany (patrz app.js). Bez tego niepowodzenie zapisu komórki
   grafiku byłoby ciche — nieakceptowalne przy grafiku/wnioskach. */
.htmx-error-toast {
  position: fixed;
  left: 50%;
  bottom: 1rem;
  transform: translate(-50%, 150%);
  z-index: 1080;
  max-width: min(90vw, 32rem);
  box-shadow: var(--shadow-md);
  border-radius: 0.75rem;
  transition: transform 0.2s ease-out;
  font-size: 1rem;
}

.htmx-error-toast.show {
  transform: translate(-50%, 0);
}

/* Pływający przycisk zgłaszania błędów (2026-08-21) — widoczny na każdej
   stronie po zalogowaniu, prawy dolny róg. `z-index` niżej niż toast błędu
   (1080) i dolny pasek zakładek mobile (1025 — patrz .navbar-tabbar), ale
   nad zwykłą treścią strony. */
.bug-report-fab {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 1020;
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: var(--planer-primary);
  color: var(--planer-on-primary);
  box-shadow: var(--shadow-md);
}

.bug-report-fab .bi {
  width: 1.35rem;
  height: 1.35rem;
}

.bug-report-fab:hover {
  background: var(--planer-primary-dark);
}

/* Telefon: FAB musi zostać NAD dolnym paskiem zakładek (.navbar-tabbar,
   ~3.7rem wysokości + safe-area) — inaczej byłby częściowo zasłonięty,
   ten sam problem co toast błędu wyżej. */
@media (max-width: 767.98px) {
  .bug-report-fab {
    bottom: calc(4.25rem + env(safe-area-inset-bottom, 0px));
  }
}

/* Legenda statusów — zawsze widoczna nad siatką /grafik i nad listą
   /moj-grafik (patrz wiki sekcja 4/7 pkt 8). */
.status-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Pasek liczbowego podsumowania dnia (/grafik?day=) */
.day-summary {
  display: flex;
  flex-wrap: wrap;
  column-gap: 1.5rem;
  row-gap: 0.5rem;
}

.day-summary-item {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 1rem;
  color: var(--planer-text);
}

.day-summary-value {
  font-size: 1.125rem;
  font-weight: 600;
}

.day-summary-item--vacation { color: var(--status-vacation-bg); }
.day-summary-item--sick { color: var(--status-sick-bg); }
.day-summary-item--holiday { color: var(--status-holiday-bg); }
.day-summary-item--vacant { color: var(--status-vacant-text); }

/* Odznaka statusu (`.status-badge`) — prostokątny tag 6px radius (NIE
   pigułka — w wąskiej kolumnie dnia pigułka marnuje miejsce poziome),
   zawsze kolor+ikona+tekst. Dwa rozmiary: domyślny (siatka /grafik, min.
   24px) i `.status-badge-lg` (listy /moj-grafik, min. 32px). */
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  min-height: 24px;
  padding: 0.125rem 0.375rem;
  border-radius: 6px;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.2;
}

.status-badge-lg {
  min-height: 32px;
  padding: 0.25rem 0.5rem;
  font-size: 1rem;
}

.status-badge-label {
  overflow-wrap: anywhere;
}

/* Boxy "Zespół"/"Wolne stanowiska" na /grafik — listy 3-kolumnowe z
   wewnętrznym scrollem, żeby duży zespół/długa lista wolnych stanowisk
   nie rozciągały całej strony w nieskończoność. Odznaki tu odrobinę
   większe niż `.status-badge` bazowe — ale WŁASNY scope (nie
   `.status-badge-lg`, ta jest współdzielona z /moj-grafik i nie powinna
   się zmieniać stąd), bo bazowa klasa jest współdzielona też z siatką
   miesięczną (kolumny dnia 48px) — powiększenie tam zepsułoby układ. */
.team-status-scroll {
  max-height: 260px;
  overflow-y: auto;
}

.team-status-scroll .status-badge {
  min-height: 28px;
  padding: 0.1875rem 0.4375rem;
  font-size: 0.9375rem;
}

.status-work {
  background-color: var(--status-work-bg);
  color: var(--status-work-text);
  border-left: 3px solid var(--status-work-accent);
}

.status-dayoff {
  background-color: var(--status-dayoff-bg);
  color: var(--status-dayoff-text);
}

.status-holiday {
  background-color: var(--status-holiday-bg);
  color: var(--status-holiday-text);
}

.status-vacation {
  background-color: var(--status-vacation-bg);
  color: var(--status-vacation-text);
}

.status-vacation-request {
  background-color: var(--status-vacation-request-bg);
  color: var(--status-vacation-request-text);
  background-image: repeating-linear-gradient(
    45deg,
    rgba(255, 255, 255, .16) 0 6px,
    transparent 6px 12px
  );
}

.status-sick {
  background-color: var(--status-sick-bg);
  color: var(--status-sick-text);
}

.status-occasional-leave {
  background-color: var(--status-occasional-leave-bg);
  color: var(--status-occasional-leave-text);
}

.status-childcare-leave {
  background-color: var(--status-childcare-leave-bg);
  color: var(--status-childcare-leave-text);
  background-image: repeating-linear-gradient(
    45deg,
    rgba(255, 255, 255, .16) 0 6px,
    transparent 6px 12px
  );
}

/* Urlop bezpłatny — bez kreskowania, płaski chłodny grafit: nieobecność
   bez wynagrodzenia, świadomie najmniej "kolorowa" flaga w siatce. */
.status-unpaid-leave {
  background-color: var(--status-unpaid-leave-bg);
  color: var(--status-unpaid-leave-text);
}

/* L4 na dziecko — kreskowanie jak przy `.status-childcare-leave` (to
   drugi rodzaj opieki, ta sama rodzina), kolor z rodziny L4 odsunięty od
   `.status-sick`, żeby dwa różne zwolnienia nie zlewały się w komórce. */
.status-childcare-sick-leave {
  background-color: var(--status-childcare-sick-leave-bg);
  color: var(--status-childcare-sick-leave-text);
  background-image: repeating-linear-gradient(
    45deg,
    rgba(255, 255, 255, .16) 0 6px,
    transparent 6px 12px
  );
}

/* Dzień wolny za święto w sobotę — kropkowanie zamiast ukośnych kresek
   (art. 130 §2 KP), żeby w siatce nie mylił się ze zwykłym "Wolne" ani z
   urlopem, także przy nadpisanym kolorze flagi z panelu admina. */
.status-holiday-day-off {
  background-color: var(--status-holiday-day-off-bg);
  color: var(--status-holiday-day-off-text);
  background-image: radial-gradient(
    rgba(255, 255, 255, .22) 1.5px,
    transparent 1.6px
  );
  background-size: 7px 7px;
}

.status-vacant {
  background-color: var(--status-vacant-bg);
  color: var(--status-vacant-text);
  border: 1px dashed var(--status-vacant-border);
}

/* Wniosek urlopowy anulowany (powiązany wpis grafiku usunięty/zmieniony,
   patrz `leave_request_service.reverse_manual_days`) — świadomie NIE
   `.status-dayoff` (czerwień = odrzucenie), tylko te same neutralne tokeny
   co "wolne stanowisko": nikt nic nie odrzucił, urlop po prostu przestał
   obowiązywać. */
.status-cancelled {
  background-color: var(--status-vacant-bg);
  color: var(--status-vacant-text);
  border: 1px dashed var(--status-vacant-border);
}

/* Dark mode: krawędź na nasyconych odznakach (Urlop/Na żądanie/L4)
   — wzmacnia postrzeganą granicę względem prawie-czarnego tła
   (WCAG 1.4.11), patrz wiki sekcja 3 rekomendacja implementacyjna. */
[data-bs-theme="dark"] .status-dayoff,
[data-bs-theme="dark"] .status-holiday,
[data-bs-theme="dark"] .status-vacation,
[data-bs-theme="dark"] .status-vacation-request,
[data-bs-theme="dark"] .status-sick,
[data-bs-theme="dark"] .status-occasional-leave,
[data-bs-theme="dark"] .status-childcare-leave,
[data-bs-theme="dark"] .status-unpaid-leave,
[data-bs-theme="dark"] .status-childcare-sick-leave,
[data-bs-theme="dark"] .status-holiday-day-off {
  border: 1px solid rgba(255, 255, 255, .08);
}

/* Kolor per stanowisko (`Position.color`) — nadpisuje tylko tło/tekst
   odznaki WORK ("Praca"), nigdy statusów urlop/L4/itd. (te mają swoje
   klasy `.status-*` renderowane zamiast `.status-work`, więc się nie
   spotykają na tym samym elemencie). Klasa dokładana OBOK
   `.status-work` w szablonie, więc specyficzność (dwie klasy) naturalnie
   wygrywa z pojedynczą `.status-work` bez potrzeby `!important`. */
.status-work.position-color-niebieski { background-color: var(--position-color-niebieski-bg); color: var(--position-color-niebieski-text); border-left-color: var(--position-color-niebieski-bg); }
.status-work.position-color-zielony { background-color: var(--position-color-zielony-bg); color: var(--position-color-zielony-text); border-left-color: var(--position-color-zielony-bg); }
.status-work.position-color-fioletowy { background-color: var(--position-color-fioletowy-bg); color: var(--position-color-fioletowy-text); border-left-color: var(--position-color-fioletowy-bg); }
.status-work.position-color-pomaranczowy { background-color: var(--position-color-pomaranczowy-bg); color: var(--position-color-pomaranczowy-text); border-left-color: var(--position-color-pomaranczowy-bg); }
.status-work.position-color-rozowy { background-color: var(--position-color-rozowy-bg); color: var(--position-color-rozowy-text); border-left-color: var(--position-color-rozowy-bg); }
.status-work.position-color-turkusowy { background-color: var(--position-color-turkusowy-bg); color: var(--position-color-turkusowy-text); border-left-color: var(--position-color-turkusowy-bg); }
.status-work.position-color-zolty { background-color: var(--position-color-zolty-bg); color: var(--position-color-zolty-text); border-left-color: var(--position-color-zolty-bg); }
.status-work.position-color-brazowy { background-color: var(--position-color-brazowy-bg); color: var(--position-color-brazowy-text); border-left-color: var(--position-color-brazowy-bg); }

/* Kolor flagi grafiku z panelu admina "Wygląd flag grafiku" — nadpisuje
   tylko tło/tekst odznaki statusu (`entry_type_badge_class`, patrz
   `entry_type_display_service.effective_badge_class`), NIE dotyka
   `background-image` (kreskowanie Urlopu na żądanie/Opieki nad dzieckiem
   zostaje z bazowej klasy `.status-*`, bo ta reguła nie ustawia tej
   właściwości). Klasa dokładana OBOK `.status-*` w szablonie (przez
   `entry_type_badge_class`), więc specyficzność (dwie klasy) naturalnie
   wygrywa bez `!important` — dokładnie wzorzec `.status-work.position-color-*`
   powyżej. Pełny iloczyn wszystkich flag × wszystkich tokenów (w tym token =
   domyślny danej flagi, nieszkodliwie nadmiarowe) zamiast wyjątków do
   pilnowania — blok jest GENEROWANY z `EDITABLE_ENTRY_TYPES` ×
   `ENTRY_TYPE_COLOR_PALETTE`, więc po dodaniu flagi albo tokena trzeba go
   przegenerować, nie dopisywać reguł ręcznie. */
.status-dayoff.entry-color-czerwony { background-color: var(--entry-color-czerwony-bg); color: var(--entry-color-czerwony-text); }
.status-dayoff.entry-color-bordowy { background-color: var(--entry-color-bordowy-bg); color: var(--entry-color-bordowy-text); }
.status-dayoff.entry-color-zielony { background-color: var(--entry-color-zielony-bg); color: var(--entry-color-zielony-text); }
.status-dayoff.entry-color-morski { background-color: var(--entry-color-morski-bg); color: var(--entry-color-morski-text); }
.status-dayoff.entry-color-purpurowy { background-color: var(--entry-color-purpurowy-bg); color: var(--entry-color-purpurowy-text); }
.status-dayoff.entry-color-bursztynowy { background-color: var(--entry-color-bursztynowy-bg); color: var(--entry-color-bursztynowy-text); }
.status-dayoff.entry-color-niebieski { background-color: var(--entry-color-niebieski-bg); color: var(--entry-color-niebieski-text); }
.status-dayoff.entry-color-grafitowy { background-color: var(--entry-color-grafitowy-bg); color: var(--entry-color-grafitowy-text); }
.status-dayoff.entry-color-oliwkowy { background-color: var(--entry-color-oliwkowy-bg); color: var(--entry-color-oliwkowy-text); }
.status-dayoff.entry-color-granatowy { background-color: var(--entry-color-granatowy-bg); color: var(--entry-color-granatowy-text); }
.status-holiday.entry-color-czerwony { background-color: var(--entry-color-czerwony-bg); color: var(--entry-color-czerwony-text); }
.status-holiday.entry-color-bordowy { background-color: var(--entry-color-bordowy-bg); color: var(--entry-color-bordowy-text); }
.status-holiday.entry-color-zielony { background-color: var(--entry-color-zielony-bg); color: var(--entry-color-zielony-text); }
.status-holiday.entry-color-morski { background-color: var(--entry-color-morski-bg); color: var(--entry-color-morski-text); }
.status-holiday.entry-color-purpurowy { background-color: var(--entry-color-purpurowy-bg); color: var(--entry-color-purpurowy-text); }
.status-holiday.entry-color-bursztynowy { background-color: var(--entry-color-bursztynowy-bg); color: var(--entry-color-bursztynowy-text); }
.status-holiday.entry-color-niebieski { background-color: var(--entry-color-niebieski-bg); color: var(--entry-color-niebieski-text); }
.status-holiday.entry-color-grafitowy { background-color: var(--entry-color-grafitowy-bg); color: var(--entry-color-grafitowy-text); }
.status-holiday.entry-color-oliwkowy { background-color: var(--entry-color-oliwkowy-bg); color: var(--entry-color-oliwkowy-text); }
.status-holiday.entry-color-granatowy { background-color: var(--entry-color-granatowy-bg); color: var(--entry-color-granatowy-text); }
.status-vacation.entry-color-czerwony { background-color: var(--entry-color-czerwony-bg); color: var(--entry-color-czerwony-text); }
.status-vacation.entry-color-bordowy { background-color: var(--entry-color-bordowy-bg); color: var(--entry-color-bordowy-text); }
.status-vacation.entry-color-zielony { background-color: var(--entry-color-zielony-bg); color: var(--entry-color-zielony-text); }
.status-vacation.entry-color-morski { background-color: var(--entry-color-morski-bg); color: var(--entry-color-morski-text); }
.status-vacation.entry-color-purpurowy { background-color: var(--entry-color-purpurowy-bg); color: var(--entry-color-purpurowy-text); }
.status-vacation.entry-color-bursztynowy { background-color: var(--entry-color-bursztynowy-bg); color: var(--entry-color-bursztynowy-text); }
.status-vacation.entry-color-niebieski { background-color: var(--entry-color-niebieski-bg); color: var(--entry-color-niebieski-text); }
.status-vacation.entry-color-grafitowy { background-color: var(--entry-color-grafitowy-bg); color: var(--entry-color-grafitowy-text); }
.status-vacation.entry-color-oliwkowy { background-color: var(--entry-color-oliwkowy-bg); color: var(--entry-color-oliwkowy-text); }
.status-vacation.entry-color-granatowy { background-color: var(--entry-color-granatowy-bg); color: var(--entry-color-granatowy-text); }
.status-vacation-request.entry-color-czerwony { background-color: var(--entry-color-czerwony-bg); color: var(--entry-color-czerwony-text); }
.status-vacation-request.entry-color-bordowy { background-color: var(--entry-color-bordowy-bg); color: var(--entry-color-bordowy-text); }
.status-vacation-request.entry-color-zielony { background-color: var(--entry-color-zielony-bg); color: var(--entry-color-zielony-text); }
.status-vacation-request.entry-color-morski { background-color: var(--entry-color-morski-bg); color: var(--entry-color-morski-text); }
.status-vacation-request.entry-color-purpurowy { background-color: var(--entry-color-purpurowy-bg); color: var(--entry-color-purpurowy-text); }
.status-vacation-request.entry-color-bursztynowy { background-color: var(--entry-color-bursztynowy-bg); color: var(--entry-color-bursztynowy-text); }
.status-vacation-request.entry-color-niebieski { background-color: var(--entry-color-niebieski-bg); color: var(--entry-color-niebieski-text); }
.status-vacation-request.entry-color-grafitowy { background-color: var(--entry-color-grafitowy-bg); color: var(--entry-color-grafitowy-text); }
.status-vacation-request.entry-color-oliwkowy { background-color: var(--entry-color-oliwkowy-bg); color: var(--entry-color-oliwkowy-text); }
.status-vacation-request.entry-color-granatowy { background-color: var(--entry-color-granatowy-bg); color: var(--entry-color-granatowy-text); }
.status-sick.entry-color-czerwony { background-color: var(--entry-color-czerwony-bg); color: var(--entry-color-czerwony-text); }
.status-sick.entry-color-bordowy { background-color: var(--entry-color-bordowy-bg); color: var(--entry-color-bordowy-text); }
.status-sick.entry-color-zielony { background-color: var(--entry-color-zielony-bg); color: var(--entry-color-zielony-text); }
.status-sick.entry-color-morski { background-color: var(--entry-color-morski-bg); color: var(--entry-color-morski-text); }
.status-sick.entry-color-purpurowy { background-color: var(--entry-color-purpurowy-bg); color: var(--entry-color-purpurowy-text); }
.status-sick.entry-color-bursztynowy { background-color: var(--entry-color-bursztynowy-bg); color: var(--entry-color-bursztynowy-text); }
.status-sick.entry-color-niebieski { background-color: var(--entry-color-niebieski-bg); color: var(--entry-color-niebieski-text); }
.status-sick.entry-color-grafitowy { background-color: var(--entry-color-grafitowy-bg); color: var(--entry-color-grafitowy-text); }
.status-sick.entry-color-oliwkowy { background-color: var(--entry-color-oliwkowy-bg); color: var(--entry-color-oliwkowy-text); }
.status-sick.entry-color-granatowy { background-color: var(--entry-color-granatowy-bg); color: var(--entry-color-granatowy-text); }
.status-occasional-leave.entry-color-czerwony { background-color: var(--entry-color-czerwony-bg); color: var(--entry-color-czerwony-text); }
.status-occasional-leave.entry-color-bordowy { background-color: var(--entry-color-bordowy-bg); color: var(--entry-color-bordowy-text); }
.status-occasional-leave.entry-color-zielony { background-color: var(--entry-color-zielony-bg); color: var(--entry-color-zielony-text); }
.status-occasional-leave.entry-color-morski { background-color: var(--entry-color-morski-bg); color: var(--entry-color-morski-text); }
.status-occasional-leave.entry-color-purpurowy { background-color: var(--entry-color-purpurowy-bg); color: var(--entry-color-purpurowy-text); }
.status-occasional-leave.entry-color-bursztynowy { background-color: var(--entry-color-bursztynowy-bg); color: var(--entry-color-bursztynowy-text); }
.status-occasional-leave.entry-color-niebieski { background-color: var(--entry-color-niebieski-bg); color: var(--entry-color-niebieski-text); }
.status-occasional-leave.entry-color-grafitowy { background-color: var(--entry-color-grafitowy-bg); color: var(--entry-color-grafitowy-text); }
.status-occasional-leave.entry-color-oliwkowy { background-color: var(--entry-color-oliwkowy-bg); color: var(--entry-color-oliwkowy-text); }
.status-occasional-leave.entry-color-granatowy { background-color: var(--entry-color-granatowy-bg); color: var(--entry-color-granatowy-text); }
.status-childcare-leave.entry-color-czerwony { background-color: var(--entry-color-czerwony-bg); color: var(--entry-color-czerwony-text); }
.status-childcare-leave.entry-color-bordowy { background-color: var(--entry-color-bordowy-bg); color: var(--entry-color-bordowy-text); }
.status-childcare-leave.entry-color-zielony { background-color: var(--entry-color-zielony-bg); color: var(--entry-color-zielony-text); }
.status-childcare-leave.entry-color-morski { background-color: var(--entry-color-morski-bg); color: var(--entry-color-morski-text); }
.status-childcare-leave.entry-color-purpurowy { background-color: var(--entry-color-purpurowy-bg); color: var(--entry-color-purpurowy-text); }
.status-childcare-leave.entry-color-bursztynowy { background-color: var(--entry-color-bursztynowy-bg); color: var(--entry-color-bursztynowy-text); }
.status-childcare-leave.entry-color-niebieski { background-color: var(--entry-color-niebieski-bg); color: var(--entry-color-niebieski-text); }
.status-childcare-leave.entry-color-grafitowy { background-color: var(--entry-color-grafitowy-bg); color: var(--entry-color-grafitowy-text); }
.status-childcare-leave.entry-color-oliwkowy { background-color: var(--entry-color-oliwkowy-bg); color: var(--entry-color-oliwkowy-text); }
.status-childcare-leave.entry-color-granatowy { background-color: var(--entry-color-granatowy-bg); color: var(--entry-color-granatowy-text); }
.status-unpaid-leave.entry-color-czerwony { background-color: var(--entry-color-czerwony-bg); color: var(--entry-color-czerwony-text); }
.status-unpaid-leave.entry-color-bordowy { background-color: var(--entry-color-bordowy-bg); color: var(--entry-color-bordowy-text); }
.status-unpaid-leave.entry-color-zielony { background-color: var(--entry-color-zielony-bg); color: var(--entry-color-zielony-text); }
.status-unpaid-leave.entry-color-morski { background-color: var(--entry-color-morski-bg); color: var(--entry-color-morski-text); }
.status-unpaid-leave.entry-color-purpurowy { background-color: var(--entry-color-purpurowy-bg); color: var(--entry-color-purpurowy-text); }
.status-unpaid-leave.entry-color-bursztynowy { background-color: var(--entry-color-bursztynowy-bg); color: var(--entry-color-bursztynowy-text); }
.status-unpaid-leave.entry-color-niebieski { background-color: var(--entry-color-niebieski-bg); color: var(--entry-color-niebieski-text); }
.status-unpaid-leave.entry-color-grafitowy { background-color: var(--entry-color-grafitowy-bg); color: var(--entry-color-grafitowy-text); }
.status-unpaid-leave.entry-color-oliwkowy { background-color: var(--entry-color-oliwkowy-bg); color: var(--entry-color-oliwkowy-text); }
.status-unpaid-leave.entry-color-granatowy { background-color: var(--entry-color-granatowy-bg); color: var(--entry-color-granatowy-text); }
.status-childcare-sick-leave.entry-color-czerwony { background-color: var(--entry-color-czerwony-bg); color: var(--entry-color-czerwony-text); }
.status-childcare-sick-leave.entry-color-bordowy { background-color: var(--entry-color-bordowy-bg); color: var(--entry-color-bordowy-text); }
.status-childcare-sick-leave.entry-color-zielony { background-color: var(--entry-color-zielony-bg); color: var(--entry-color-zielony-text); }
.status-childcare-sick-leave.entry-color-morski { background-color: var(--entry-color-morski-bg); color: var(--entry-color-morski-text); }
.status-childcare-sick-leave.entry-color-purpurowy { background-color: var(--entry-color-purpurowy-bg); color: var(--entry-color-purpurowy-text); }
.status-childcare-sick-leave.entry-color-bursztynowy { background-color: var(--entry-color-bursztynowy-bg); color: var(--entry-color-bursztynowy-text); }
.status-childcare-sick-leave.entry-color-niebieski { background-color: var(--entry-color-niebieski-bg); color: var(--entry-color-niebieski-text); }
.status-childcare-sick-leave.entry-color-grafitowy { background-color: var(--entry-color-grafitowy-bg); color: var(--entry-color-grafitowy-text); }
.status-childcare-sick-leave.entry-color-oliwkowy { background-color: var(--entry-color-oliwkowy-bg); color: var(--entry-color-oliwkowy-text); }
.status-childcare-sick-leave.entry-color-granatowy { background-color: var(--entry-color-granatowy-bg); color: var(--entry-color-granatowy-text); }
.status-holiday-day-off.entry-color-czerwony { background-color: var(--entry-color-czerwony-bg); color: var(--entry-color-czerwony-text); }
.status-holiday-day-off.entry-color-bordowy { background-color: var(--entry-color-bordowy-bg); color: var(--entry-color-bordowy-text); }
.status-holiday-day-off.entry-color-zielony { background-color: var(--entry-color-zielony-bg); color: var(--entry-color-zielony-text); }
.status-holiday-day-off.entry-color-morski { background-color: var(--entry-color-morski-bg); color: var(--entry-color-morski-text); }
.status-holiday-day-off.entry-color-purpurowy { background-color: var(--entry-color-purpurowy-bg); color: var(--entry-color-purpurowy-text); }
.status-holiday-day-off.entry-color-bursztynowy { background-color: var(--entry-color-bursztynowy-bg); color: var(--entry-color-bursztynowy-text); }
.status-holiday-day-off.entry-color-niebieski { background-color: var(--entry-color-niebieski-bg); color: var(--entry-color-niebieski-text); }
.status-holiday-day-off.entry-color-grafitowy { background-color: var(--entry-color-grafitowy-bg); color: var(--entry-color-grafitowy-text); }
.status-holiday-day-off.entry-color-oliwkowy { background-color: var(--entry-color-oliwkowy-bg); color: var(--entry-color-oliwkowy-text); }
.status-holiday-day-off.entry-color-granatowy { background-color: var(--entry-color-granatowy-bg); color: var(--entry-color-granatowy-text); }

/* Awatar-inicjał pracownika (lista pracowników, kolumna pracownika w
   /grafik) — kółko z inicjałami, kolor deterministyczny z nazwiska
   (`avatar_color_token`), reużywa palety kolorów STANOWISK powyżej zamiast
   nowych tokenów. Czysto wizualna pomoc do skanowania listy, bez znaczenia
   biznesowego (w odróżnieniu od koloru stanowiska na WORK). */
.avatar-initials {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  font-size: 0.75rem;
  font-weight: 700;
  flex-shrink: 0;
}

.avatar-initials-link {
  text-decoration: none;
}

.avatar-initials.position-color-niebieski { background-color: var(--position-color-niebieski-bg); color: var(--position-color-niebieski-text); }
.avatar-initials.position-color-zielony { background-color: var(--position-color-zielony-bg); color: var(--position-color-zielony-text); }
.avatar-initials.position-color-fioletowy { background-color: var(--position-color-fioletowy-bg); color: var(--position-color-fioletowy-text); }
.avatar-initials.position-color-pomaranczowy { background-color: var(--position-color-pomaranczowy-bg); color: var(--position-color-pomaranczowy-text); }
.avatar-initials.position-color-rozowy { background-color: var(--position-color-rozowy-bg); color: var(--position-color-rozowy-text); }
.avatar-initials.position-color-turkusowy { background-color: var(--position-color-turkusowy-bg); color: var(--position-color-turkusowy-text); }
.avatar-initials.position-color-zolty { background-color: var(--position-color-zolty-bg); color: var(--position-color-zolty-text); }
.avatar-initials.position-color-brazowy { background-color: var(--position-color-brazowy-bg); color: var(--position-color-brazowy-text); }

/* Odznaka działu koordynatora (lista użytkowników) — ten sam wzorzec
   deterministycznego koloru z nazwy co awatar-inicjał powyżej, tylko na
   klasie .badge zamiast .avatar-initials. */
.badge.position-color-niebieski { background-color: var(--position-color-niebieski-bg); color: var(--position-color-niebieski-text); }
.badge.position-color-zielony { background-color: var(--position-color-zielony-bg); color: var(--position-color-zielony-text); }
.badge.position-color-fioletowy { background-color: var(--position-color-fioletowy-bg); color: var(--position-color-fioletowy-text); }
.badge.position-color-pomaranczowy { background-color: var(--position-color-pomaranczowy-bg); color: var(--position-color-pomaranczowy-text); }
.badge.position-color-rozowy { background-color: var(--position-color-rozowy-bg); color: var(--position-color-rozowy-text); }
.badge.position-color-turkusowy { background-color: var(--position-color-turkusowy-bg); color: var(--position-color-turkusowy-text); }
.badge.position-color-zolty { background-color: var(--position-color-zolty-bg); color: var(--position-color-zolty-text); }
.badge.position-color-brazowy { background-color: var(--position-color-brazowy-bg); color: var(--position-color-brazowy-text); }

/* Karty KPI dashboardu kierownika (Update 5) — duża liczba + ikona +
   kolorowy akcent po lewej, wzorem `.today-row-accent`. Wzorzec do
   ponownego użycia przy kolejnych metrykach, nie tylko dzisiejszych 4. */
.kpi-card {
  border-left: 4px solid var(--planer-primary);
}

/* Mikrointerakcja "Miękkiej": karta KPI unosi się lekko pod kursorem.
   Tylko przy braku systemowego "ogranicz animacje". */
@media (prefers-reduced-motion: no-preference) {
  .kpi-card {
    transition: transform 0.18s ease, box-shadow 0.18s ease;
  }

  .kpi-card:hover {
    transform: translateY(-3px);
    box-shadow: var(--shadow-md);
  }
}

.kpi-card-icon {
  color: var(--planer-primary);
  width: 1.75rem;
  height: 1.75rem;
}

.kpi-value {
  font-size: 2rem;
  font-weight: 700;
  line-height: 1.1;
}

/* Etykiety metryk w kroju mono, uppercase — "maszynowy" głos danych,
   wspólny wzorzec z etykietami kafli dashboardu (.tile-label niżej). */
.kpi-label {
  color: var(--planer-grafit-700);
  font-family: var(--planer-font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

/* Nadgodziny — modyfikator na normalnym wpisie WORK, nie osobny status:
   obramowanie 2px wokół istniejącej odznaki + ikona w rogu (zawartość).
   `!important` na lewej krawędzi jest KONIECZNE (nie kosmetyka) — lewa
   krawędź odznaki WORK ma już własne, kolidujące źródło koloru:
   `.status-work` (border-left: 3px, akcent domyślny) i, dla stanowisk z
   ustawionym kolorem, albo `.status-work.position-color-X`
   (specyficzność 0,2,0 — WYŻSZA niż samo `.schedule-overtime` 0,1,0, więc
   wygrywa mimo że deklarowane wcześniej w pliku), albo inline
   `border-left-color` z `position_badge_style()` dla własnego hexa
   (`app/core/templates.py`) — inline zawsze bije jakikolwiek selektor
   klasowy niezależnie od specyficzności. Bez `!important` lewa krawędź
   odznaki nadgodzin renderowała się w kolorze stanowiska zamiast akcentu
   nadgodzin, wyglądając na "nieuzupełnioną" (zgłoszenie Jakuba ze zrzutem
   ekranu S23/S24/S11 z pomarańczowym akcentem). Pozostałe 3 boki nie mają
   takiego konfliktu, ale `!important` na całym skrócie `border` dla
   spójności (jeden punkt prawdy, nie tylko `border-left-color`). */
.schedule-overtime {
  border: 2px solid var(--planer-primary-dark) !important;
}

/* Karty działów w strukturze organizacji (/admin/departments) — bez tego
   sąsiednie .accordion-item dzielą krawędzie i wizualnie się zlewają. */
#departmentsAccordion .accordion-item {
  border: 1px solid var(--bs-border-color);
  border-radius: 12px;
  overflow: hidden;
  margin-bottom: 1rem;
  box-shadow: var(--shadow-sm);
}

#departmentsAccordion .accordion-item:last-child {
  margin-bottom: 0;
}

/* Siatka miesięczna grafiku (Update 2, dopieszczona wyglądowo w Update
   UX-redesign). `table-layout: fixed` jest KONIECZNE — bez tego domyślny
   układ 'auto' ignoruje `min-width` na `.schedule-employee-col`, gdy
   zawartość (długie nazwisko) się nie mieści, i po prostu poszerza
   kolumnę ponad zadeklarowane 110px, przesuwając całą resztę siatki poza
   1920px mimo że rachunek 31×48+110 się zgadza na papierze. Z `fixed`
   szerokości kolumn (patrz `width` niżej) są sztywnym limitem, a długie
   nazwiska łamią się (`overflow-wrap` na `.schedule-employee-col`)
   zamiast poszerzać kolumnę. */
.schedule-grid {
  table-layout: fixed;
}

.schedule-grid th,
.schedule-grid td {
  white-space: nowrap;
}

/* Ciało siatki (wiersze pracowników) — zawsze poziomo przewijalne,
   niezależnie od szerokości ekranu. Wcześniejsze wersje tej reguły
   próbowały użyć `overflow-x: clip` (żeby nie psuć sticky nagłówka
   dzielącego ten sam element), z media query przełączającym na `auto`
   pod ~1700px — okazało się to niewystarczające: `overflow-x: auto`
   wymusza w przeglądarce COMPUTED `overflow-y: auto` na tym samym
   elemencie (nawet bez jawnego ustawienia), co czyni go "kontenerem
   przewijania" i psuje `position: sticky` liczone względem STRONY dla
   czegokolwiek w środku (potwierdzone empirycznie, Playwright).
   Rozwiązanie: nagłówek dni już tu NIE JEST — żyje w osobnym
   `.schedule-grid-header-wrap` niżej (patrz `month.html`), poza tym
   elementem, więc `overflow-x: auto` tutaj nie ma już nic do zepsucia. */
.schedule-grid-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* Nagłówek dni — sticky względem STRONY, niezależnie od szerokości
   ekranu (bez progu/kompromisu, w przeciwieństwie do wcześniejszych
   wersji tej reguły). Działa niezawodnie, bo ten wrapper NIE jest
   zagnieżdżony w `.schedule-grid-scroll` (przewijalne ciało tabeli) —
   to osobny, równoległy element w normalnym przepływie strony, więc
   nie ma konfliktu między jego własnym `position: sticky` a
   `overflow-x: auto` ciała.

   `overflow-x: auto` TUTAJ (nie `hidden`) — naprawa niedostępnego poziomego
   przewijania (checklista testów świąt 2026-08-07, znalezisko 3, osobne
   zgłoszenie od funkcji świąt). Ciało `.schedule-grid-scroll` ma własny
   natywny scrollbar, ale przy N wierszach pracowników jest on fizycznie
   pod całą tabelą (potwierdzone: 57 pracowników → ~3600px w dół), czyli
   poza ekranem przy każdym rozsądnym przewijaniu strony — praktycznie
   nieosiągalny myszką/touchpadem bez wiedzy o geście poziomym. Nagłówek
   jest `position: sticky` i zawsze widoczny, więc JEGO scrollbar (ten sam
   `scrollWidth` co ciało, dzięki identycznym kolumnom) jest zawsze pod
   ręką niezależnie od tego, ile wierszy ma tabela. `overflow-x: auto` na
   TYM elemencie nie ma tej samej pułapki co przy `.schedule-grid-scroll`
   wyżej (wymuszone COMPUTED `overflow-y: auto` psujące sticky) — tamta
   pułapka dotyczyła nagłówka ZAGNIEŻDŻONEGO w scrollowanym rodzicu;
   `position: sticky` TEGO elementu liczy się względem JEGO WŁASNYCH
   przodków (strona), nie względem tego, czy on sam jest kontenerem
   przewijania dla swoich dzieci — potwierdzone empirycznie w przeglądarce
   (sticky trzyma przy przewijaniu strony niezależnie od dodania
   `overflow-x: auto` tutaj).

   Scroll obu tabel zsynchronizowany DWUKIERUNKOWO przez Alpine (`@scroll`
   w month.html na obu elementach) — użytkownik może przeciągnąć którykolwiek
   z dwóch scrollbarów (ciała albo nagłówka), drugi podąża. Identyczne
   szerokości kolumn w obu tabelach (`table-layout: fixed`, te same klasy)
   utrzymują wyrównanie. */
.schedule-grid-header-wrap {
  position: sticky;
  top: var(--planer-navbar-height);
  z-index: 2;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  background-color: var(--planer-surface);
}

.schedule-grid thead th {
  background-color: var(--planer-surface);
  /* Maskuje hairline-prześwit klasyczny dla `position: sticky`: przy
     subpixelowym scrollu przegląderka czasem zostawia 1px szparę na
     granicy przyklejonego wiersza, przez którą widać treść spod spodu
     (tu: boxy Zespół/Wolne stanowiska nad siatką). Maluje krawędź od
     środka i od zewnątrz kolorem tła nagłówka, niezależnie od tego, po
     której stronie granicy przegląderka pokazuje artefakt. */
  box-shadow: inset 0 1px 0 var(--planer-surface), 0 1px 0 var(--planer-surface);
}

.schedule-employee-col {
  width: 110px;
  position: sticky;
  left: 0;
  background-color: var(--planer-surface);
  z-index: 1;
}

/* Nazwisko obcinane wielokropkiem w jednej linii zamiast łamania na 2 —
   pełne nazwisko jest w `title` na rodzicu (tooltip), ten sam wzorzec co
   w boxie "Zespół" wyżej na `/grafik`. Flex-item potrzebuje `min-width: 0`
   (domyślnie `auto`), inaczej `text-truncate` (wymaga `overflow: hidden`)
   nie zadziała wewnątrz `d-flex`. */
.schedule-employee-name {
  min-width: 0;
  flex: 1 1 auto;
}

/* Róg tabeli (nagłówek kolumny "Pracownik") — sticky w lewo (jak
   `.schedule-employee-col` w ciele tabeli), więc musi mieć własne tło
   i być nad sąsiednimi `<th>` dni, które przy scrollu poziomym
   przesuwają się pod nim. */
.schedule-grid thead th.schedule-employee-col {
  z-index: 3;
}

.schedule-day-col {
  width: 48px;
  /* Kontener pozycjonowania dla `.schedule-cell-editing` (position:
     absolute) — bez tego panel edycji zakotwiczyłby się względem
     dalszego przodka (np. całej tabeli), nie tej konkretnej komórki. */
  position: relative;
}

/* Tło weekendu lekko wyszarzone dla szybszego skanowania (wiki 5.2). */
.schedule-weekend {
  background-color: var(--planer-grafit-100);
}

/* Dzień ustawowo wolny (`holiday_service`, adnotacja 2026-08-06) — kierownik
   do tej pory nie miał w siatce żadnego sygnału, że np. 6 stycznia jest
   świętem, i musiał o tym pamiętać sam. Delikatne tło zamiast mocnego
   koloru: to podpowiedź, a nie status wpisu — realne "Święto" w komórce
   nadal stawia człowiek (plakietka `status-holiday`), i te dwa znaczenia nie
   mogą wyglądać tak samo. Kolumna weekendowa wygrywa tłem, bo jest
   wcześniej w kaskadzie — świadomie, święto w sobotę i tak jest wolne. */
.schedule-holiday {
  background-color: color-mix(in srgb, var(--status-holiday-bg) 8%, transparent);
}

.schedule-holiday-dot {
  display: block;
  font-size: 0.6rem;
  line-height: 1;
  margin-top: 0.1rem;
  color: var(--status-holiday-ink);
}

/* Przełącznik "Ukryj weekendy" (Alpine `hideWeekends`, domyślnie włączony —
   patrz `.hide-weekends` na karcie siatki w schedule/month.html). Czysta
   klasa na kontenerze zamiast x-show na każdej komórce — mniej węzłów Alpine
   przy dużych siatkach (wielu pracowników × 31 dni). */
.hide-weekends .schedule-weekend {
  display: none;
}

/* Widok tygodniowy siatki grafiku na mobile (patrz `x-data` w month.html,
   `weekOffset`/`rangeStart`/`rangeEnd`) — kolumny dni spoza aktualnie
   wybranego zakresu (tydzień albo dwa, w zależności od szerokości ekranu)
   dostają tę klasę przez Alpine `:class`. Ukrywanie ograniczone do mobile
   media query, żeby na desktopie (gdzie Alpine state i tak istnieje, ale
   nawigacja tygodniowa jest `d-md-none`) zawsze był widoczny cały miesiąc
   bez zmiany istniejącego zachowania. */
@media (max-width: 767.98px) {
  .schedule-day-hidden-mobile {
    display: none;
  }
}

/* Pasek nawigacji tygodniowej siatki grafiku (mobile) — te same duże,
   łatwe do trafienia strzałki co `.month-nav-sticky-mobile` w
   `/moj-grafik`, ale NIE sticky: siatka niżej ma już własny sticky
   nagłówek dni (`.schedule-grid-header-wrap`) zajmujący ten sam
   `top: var(--planer-navbar-height)`, więc drugi sticky pasek na tej samej
   pozycji by się z nim nakładał. */
.schedule-week-nav-mobile {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.schedule-week-nav-label {
  font-weight: 600;
  text-align: center;
}

/* Zaznaczona kolumna dnia w siatce — deklarowane PO `.schedule-weekend`,
   żeby zaznaczenie miało pierwszeństwo nad tłem weekendu przy tej samej
   specyficzności selektora. Sam `background-color` był ledwo widoczny w
   komórkach z wpisem — tam badge statusu (WORK/urlop/L4) i tak zakrywa
   niemal całą komórkę swoim własnym tłem, więc subtelny tint pod spodem
   ginął. */
.schedule-day-selected {
  background-color: var(--planer-primary-subtle);
}

/* Wyraźna ramka w kolorze marki wokół CAŁEJ zaznaczonej kolumny, nie wokół
   każdej komórki z osobna — stąd boki na każdym segmencie, ale góra tylko
   na nagłówku i dół tylko na ostatnim wierszu, żeby wyglądało jak jedno
   ciągłe obramowanie kolumny, a nie stos oddzielnych "okienek".

   Boki (lewy/prawy) celowo jako `box-shadow: inset`, NIE `border` — `border`
   na `<td>` wchodzi w `border-collapse` z sąsiednią komórką z `.table-bordered`
   (Bootstrap, 1px szary na KAŻDEJ krawędzi). Teoretycznie szerszy border
   (2px) zawsze wygrywa konflikt niezależnie od strony, ale przy pewnych
   ułamkowych device-pixel-ratio (typowe na Androidzie) oba renderują się na
   ten sam efektywny piksel — remis rozstrzyga wtedy reguła "lewa/górna
   komórka wygrywa", więc lewa krawędź zaznaczonej kolumny (gdzie to ONA jest
   "prawą" komórką pary) przegrywa z szarym borderem sąsiada i purpurowa
   ramka znika tylko po tej stronie (zgłoszone na telefonie, potwierdzone
   naprawione tym fixem). `box-shadow` maluje się niezależnie od modelu
   collapse sąsiednich komórek, więc jest odporny na ten remis. Góra/dół
   zostają jako zwykły `border` (bez konfliktu — nie mają sąsiada z tej
   strony w tym samym kierunku kolumn), specyficzność selektorów i tak musi
   przebić `.table-bordered > :not(caption) > * > *`. */
.schedule-grid thead th.schedule-day-selected {
  border-top: 2px solid var(--planer-primary);
  box-shadow:
    inset 0 1px 0 var(--planer-surface), 0 1px 0 var(--planer-surface),
    inset 2px 0 0 0 var(--planer-primary), inset -2px 0 0 0 var(--planer-primary);
}

.schedule-grid tbody td.schedule-day-selected {
  box-shadow: inset 2px 0 0 0 var(--planer-primary), inset -2px 0 0 0 var(--planer-primary);
}

.schedule-grid tbody tr:last-child td.schedule-day-selected {
  border-bottom: 2px solid var(--planer-primary);
  box-shadow: inset 2px 0 0 0 var(--planer-primary), inset -2px 0 0 0 var(--planer-primary);
}

/* Sticky nagłówek + zsynchronizowany scroll dla dowolnej tabeli — wersja
   ogólna, wyodrębniona z `.schedule-grid-header-wrap`/`.schedule-grid-scroll`
   wyżej (a9f7c3, 2026-07-14). Ten sam dwutabelowy mechanizm i to samo
   uzasadnienie (`overflow-x: auto` na wspólnym z nagłówkiem elemencie psuje
   `position: sticky` liczone względem strony, patrz komentarz przy
   `.schedule-grid-scroll`) — tu bez kolorów/obramowań specyficznych dla
   grafiku (weekend, zaznaczona kolumna), używane w Warsztacie raportów
   (`/raporty`) i Kreatorze (`/raporty/generuj`). Wymaga `x-data` na
   wspólnym przodku (choćby pustego `x-data="{}"`), żeby Alpine przetwarzało
   `x-ref`/`@scroll` w środku — patrz `reports/_panel_monthly.html`/
   `_panel_rcp.html`. */
.table-header-sticky {
  position: sticky;
  top: var(--planer-navbar-height);
  z-index: 2;
  overflow-x: hidden;
  background-color: var(--planer-surface);
}

.table-scroll-sync {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* Kolejka wniosków: jedna tabela zmienia wiersze w karty na telefonie. */
@media (max-width: 767.98px) {
  .table-cards-sm,
  .table-cards-sm tbody,
  .table-cards-sm tbody tr,
  .table-cards-sm tbody td {
    display: block;
    width: 100%;
  }

  .table-cards-sm thead {
    display: none;
  }

  .table-cards-sm tbody tr {
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    margin-bottom: 0.75rem;
  }

  .table-cards-sm tbody tr:last-child {
    margin-bottom: 0;
  }

  .table-cards-sm tbody td {
    overflow-wrap: anywhere;
    text-align: left !important;
  }

  .table-cards-sm tbody td:last-child {
    border-bottom: 0;
  }

  .table-cards-sm tbody td::before {
    content: attr(data-label);
    display: block;
    font-weight: 700;
    margin-bottom: 0.25rem;
  }

  .table-cards-sm tbody td:last-child > .d-flex {
    flex-direction: column;
    align-items: stretch !important;
  }

  .table-cards-sm tbody td:last-child form,
  .table-cards-sm tbody td:last-child form > .btn {
    width: 100%;
  }

  .table-cards-sm tbody td:last-child .btn,
  .table-cards-sm tbody td:last-child .form-control {
    min-height: 44px;
  }
}

/* `table-layout: fixed` na OBU tabelach (nagłówka i ciała) — bez tego każda
   z dwóch osobnych tabel sama sobie dobiera szerokości kolumn wg własnej
   zawartości, więc nagłówek i dane rozjeżdżają się przy przewijaniu.
   Wyrównanie zapewniają identyczne klasy `.report-col-*` (niżej) powtórzone
   na `<th>` w tabeli nagłówka i `<td>` w każdym wierszu ciała. */
.table-header-sticky table,
.table-scroll-sync table {
  table-layout: fixed;
}

.table-header-sticky thead th {
  background-color: var(--planer-surface);
  white-space: normal;
  /* Ten sam maskujący hairline-prześwit co `.schedule-grid thead th` wyżej. */
  box-shadow: inset 0 1px 0 var(--planer-surface), 0 1px 0 var(--planer-surface);
}

/* Stopniowane szerokości kolumn raportów (zamiast osobnej klasy per
   kolumna) — dobrane pod typowe treści: xs/sm dla liczb, md dla nazw
   działów, lg dla imion i nazwisk (linki), xl dla najdłuższych nagłówków
   (np. "Urlop wykorzystany w {{ year }} (bez ustawowego limitu)"). */
.report-col-xs { width: 75px; }
.report-col-sm { width: 100px; }
.report-col-md { width: 130px; }
.report-col-lg { width: 170px; }
.report-col-xl { width: 210px; }

/* Nagłówek kolumny z sortowaniem klienckim (`app.js`, `.js-sortable-table`)
   — strzałka kierunku doklejona przez `::after`, widoczna dopiero po
   ustawieniu `data-sort-dir` (czyli po pierwszym kliknięciu tej kolumny). */
.sortable-th {
  cursor: pointer;
  user-select: none;
}

.sortable-th::after {
  content: "\2195";
  margin-left: 0.375rem;
  opacity: .35;
  font-size: .8em;
}

.sortable-th[data-sort-dir="asc"]::after {
  content: "\2191";
  opacity: 1;
}

.sortable-th[data-sort-dir="desc"]::after {
  content: "\2193";
  opacity: 1;
}

/* `min-height: 52px` — dopasowany do RZECZYWISTEJ (nie deklarowanej)
   wysokości `.schedule-cell-badge` (patrz niżej): odznaka ze statusem
   renderuje się na 52px mimo własnego `min-height: 44px`, bo ikona +
   padding + line-height pchają ją wyżej niż deklaracja. Puste komórki
   (`.schedule-cell-empty`, ~24px treści) kurczyły się do 44px, więc
   pracownik bez ŻADNEGO wpisu w widocznym zakresie dni miał cały wiersz
   wyraźnie niższy niż wiersze z wpisami (zgłoszenie Jakuba, skrzynka
   zadań poz. 1) — 52px tutaj usuwa różnicę u źródła zamiast łatać
   objaw per-komórka. */
.schedule-cell {
  cursor: pointer;
  min-height: 52px;
  padding: 0.25rem;
  border-radius: 6px;
}

/* Koordynator (podgląd) — komórka bez interakcji, zwykły kursor zamiast
   sugerowania klikalności, której i tak nie ma (patrz _cell_display.html). */
.schedule-cell-readonly {
  cursor: default;
}

@media (max-width: 767.98px) {
  .schedule-cell {
    min-height: 52px;
  }
}

.schedule-cell:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

/* Stan ładowania htmx — subtelny puls zamiast spinnera (czysty CSS). */
.schedule-cell.htmx-request {
  animation: schedule-pulse 0.4s ease-in-out infinite alternate;
}

@keyframes schedule-pulse {
  from {
    opacity: 1;
  }
  to {
    opacity: 0.55;
  }
}

/* `min-height: 44px` + padding 0.25rem (4px) na `.schedule-cell` rodzicu
   (border-box, więc padding liczy się PO obu stronach w te same 8px) =
   52px całej komórki — dokładnie tyle, ile `.schedule-cell` wyżej ma
   ustawione na sztywno wprost. Świadomie NIE 52px tutaj — to podbiłoby
   sumę do 60px (52 + 8 padding), rozjeżdżając z powrotem puste komórki
   (52px) i wypełnione (60px). Patrz `.schedule-cell` wyżej po pełne
   wyjaśnienie. */
.schedule-cell-badge {
  cursor: pointer;
  width: 100%;
  min-height: 44px;
  justify-content: center;
}

.schedule-cell-badge .status-badge-label {
  text-align: center;
}

.schedule-cell-empty {
  display: block;
  min-height: 1.5rem;
}

/* Placeholder w normalnym przepływie dla komórki w trybie edycji — sam
   formularz (`.schedule-cell-editing` niżej) jest `position: absolute`,
   więc bez tego placeholdera komórka w swoim miejscu w tabeli wyglądałaby
   na pustą (jak utracone dane), zamiast sygnalizować "trwa edycja". */
.schedule-cell-editing-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  border: 1px dashed var(--planer-grafit-300);
  border-radius: 6px;
  color: var(--planer-grafit-300);
  cursor: pointer;
}

/* Panel edycji komórki ("click to edit") — pływający panel NAD komórką
   (position: absolute), nie inline. Przy `.schedule-day-col` zwężonej do
   48px (`table-layout: fixed`) formularz (2 selecty + checkbox) fizycznie
   się tam nie mieści — inline zepsułby albo szerokość kolumny (przy
   starym `table-layout: auto`), albo po prostu wyciąłby się bez śladu
   (przy obecnym `fixed`). `position: absolute` wyjmuje panel z przepływu
   tabeli całkowicie — komórka rodzic (`.schedule-day-col`, `position:
   relative` niżej) nie zmienia rozmiaru bez względu na to, co jest w
   panelu. `bottom: 100%` = panel otwiera się NAD klikniętą komórką (nie
   pod nią), zgodnie z życzeniem. */
.schedule-cell-editing {
  position: absolute;
  bottom: 100%;
  left: 0;
  margin-bottom: 0.25rem;
  width: max-content;
  min-width: 200px;
  max-width: 260px;
  z-index: 10;
  background-color: var(--planer-surface);
  border-radius: 0.75rem;
  box-shadow: var(--shadow-md);
  padding: 0.5rem;
  /* `.schedule-grid td` (przodek, patrz wyżej) ustawia `white-space: nowrap`
     dla wąskich kolumn dni — dziedziczy to też ten panel mimo że jest
     `position: absolute` (dziedziczenie idzie po drzewie DOM, nie po layoutcie).
     Bez nadpisania komunikat błędu (np. "Wybierz stanowisko dla wpisu typu
     Praca.") renderuje się w jednej linii i wylewa poza `max-width` panelu,
     nachodząc na sąsiednie komórki (zgłoszenie Jakuba ze zrzutem ekranu). */
  white-space: normal;
}

/* "Flip" dla komórek w pierwszych wierszach siatki — nad nimi nie ma dość
   miejsca WEWNĄTRZ przewijalnego `.schedule-grid-scroll` (które ma
   `overflow-x: auto`, wymuszający COMPUTED `overflow-y: auto` — patrz
   komentarz przy tym selektorze), więc panel otwarty w domyślnym trybie
   `bottom: 100%` zostałby przez ten kontener obcięty, niewidoczny. Klasa
   dodawana przez JS (`app.js`, nasłuch `htmx:afterSwap`) po zmierzeniu
   realnie dostępnego miejsca nad komórką. */
.schedule-cell-editing-below {
  bottom: auto;
  top: 100%;
  margin-bottom: 0;
  margin-top: 0.25rem;
}

/* Ucieczka z `.schedule-grid-scroll`, gdy PRZY ŻADNEJ z dwóch pozycji wyżej
   (nad/pod komórką) panel się nie mieści WEWNĄTRZ samego kontenera —
   dzieje się tak, gdy przefiltrowana siatka ma bardzo mało wierszy (np. 1
   po filtrze działu) i okno przeglądarki jest niskie: kontener przewijania
   jest wtedy sam w sobie niższy niż wysokość panelu, więc ani `bottom: 100%`,
   ani `.schedule-cell-editing-below` nie dają dość miejsca, a wymuszony
   `overflow-y: auto` (patrz komentarz przy `.schedule-grid-scroll`) tnie
   panel i dokłada WEWNĘTRZNY pasek przewijania (zgłoszenie Jakuba ze
   zrzutem ekranu — okno "Praca" ucięte, trzeba było scrollować w małym
   boksie). `position: fixed` liczone jest względem viewportu, nie
   najbliższego pozycjonowanego przodka — żaden przodek panelu (karta,
   `.schedule-grid-scroll`, `<td>`) nie ma `transform`/`filter`/`will-change`,
   więc to naprawdę wyjmuje panel spod przycinania kontenera. JS (`app.js`,
   `htmx:afterSettle`) dokłada tę klasę i liczy `top`/`left` z
   `getBoundingClientRect()` komórki, przycinając do granic okna. */
.schedule-cell-editing.schedule-cell-editing-fixed {
  position: fixed;
  bottom: auto;
  margin: 0;
}

/* Wiersz "dziś" w /moj-grafik — lewy akcent + etykieta "DZIŚ" (osobna
   klasa od `.schedule-day-selected`, żeby lewy border nie trafiał też do
   nagłówka kolumny dnia w siatce /grafik). */
.today-row-accent {
  background-color: var(--planer-primary-subtle);
  border-left: 3px solid var(--planer-primary);
}

/* Mój grafik — wymuszone minimalne cele dotykowe (44px ogólnie, 48px na
   mobile/Zebra), patrz wiki sekcja 2/6/7 pkt 9. */
#my-schedule-desktop-table td {
  min-height: 44px;
  padding-top: 0.625rem;
  padding-bottom: 0.625rem;
}

/* Stałe szerokości dwóch pierwszych kolumn — bez tego badge "DZIŚ"
   (obecny tylko w bieżącym miesiącu i tylko w jednym wierszu) rozpychał
   kolumnę "Data" i przesuwał "Dzień"/"Status" w prawo, więc przejście
   między miesiącami przestawiało całą tabelę. Kolumna "Status" bierze
   resztę. Zgłoszenie Jakuba 2026-08-17. */
.my-schedule-table {
  table-layout: fixed;
}

/* Szerokości PROCENTOWE, nie w rem — pierwsza wersja (12.5rem/9rem)
   stabilizowała tabelę, ale na pełnej szerokości ekranu ściskała obie
   kolumny do lewej krawędzi i zostawiała ~900px pustej kolumny "Status"
   (zgłoszenie Jakuba 2026-08-17, punkt 9 skrzynki). Procenty skalują się
   z szerokością tabeli, więc kolumny rozkładają się na całość i nadal nie
   zależą od tego, czy w miesiącu wypada badge "DZIŚ". */
.my-schedule-col-date {
  width: 22%;
}

.my-schedule-col-weekday {
  width: 18%;
}

.list-group-item {
  min-height: 44px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

@media (max-width: 767.98px) {
  .list-group-item {
    min-height: 48px;
  }
}

/* Sticky mini-nawigacja miesiąca (mobile/Zebra) — potrzebuje solidnego
   tła motywu, żeby treść pod spodem nie prześwitywała przy scrollu. */
.month-nav-sticky-mobile {
  position: sticky;
  top: var(--planer-navbar-height);
  z-index: 5;
  background-color: var(--planer-bg);
  /* Sam background-color nie wystarczał — przy scrollu na mobile wciąż
     było widać hairline-prześwit kafelków listy dni spod górnej krawędzi
     (ten sam rodzaj artefaktu co `.schedule-grid thead th` niżej, tu bez
     tabeli/box-shadow-border, ale subpixel rounding przy sticky robi to
     samo). Ten sam mechanizm maskowania: przemalować szew kolorem tła. */
  box-shadow: 0 1px 0 var(--planer-bg), 0 -1px 0 var(--planer-bg);
}

/* ==========================================================================
   Dashboard "bento" (redesign Miękka, 2026-07-12) — powitanie na
   śliwkowej "aurorze", pasek odpowiedzi "czy wszystko gra?", potem
   asymetryczna siatka kafli: największy odpowiada na najważniejsze
   pytanie (obłożenie), mniejsze wspierają. Wzór: zaakceptowany mockup
   "Poranna zmiana 2.0" (artefakt c65abc2a).
   ========================================================================== */

/* Pas powitania — gradient jak navbar, ale nieprzezroczysty i zaokrąglony;
   dwa radial-gradienty (koral/bursztyn) robią "aurorę" w narożnikach. */
.dashboard-hero {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 1.5rem 1.75rem;
  margin-bottom: 1.25rem;
  border-radius: 1.25rem;
  color: var(--planer-nav-ink);
  background:
    radial-gradient(40rem 12rem at 5% -40%, rgba(var(--planer-coral-rgb), .5), transparent 60%),
    radial-gradient(32rem 11rem at 98% -20%, rgba(var(--planer-amber-rgb), .55), transparent 55%),
    linear-gradient(118deg, var(--planer-primary-dark), var(--planer-primary) 100%);
  box-shadow: var(--shadow-md);
}

.dashboard-hero-date {
  font-family: var(--planer-font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.85;
}

.dashboard-hero h1 {
  color: inherit;
  margin: 0;
}

/* Pasek statusu "czy wszystko gra?" — szklana pigułka na aurorze. */
.dashboard-status {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.9rem;
  font-weight: 600;
  padding: 0.45rem 0.95rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, .14);
  border: 1px solid rgba(255, 255, 255, .22);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.dashboard-status-dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  flex-shrink: 0;
}

.dashboard-status-dot--ok {
  background-color: #55c98a;
  box-shadow: 0 0 10px #55c98a;
}

.dashboard-status-dot--attention {
  background-color: var(--planer-amber);
  box-shadow: 0 0 10px var(--planer-amber);
}

/* Siatka bento — 12 kolumn, kafle deklarują span klasą. Na węższych
   ekranach kafle sklejają się parami, na mobile w jedną kolumnę. */
.bento-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 1rem;
}

.bento-span-3 { grid-column: span 3; }
.bento-span-4 { grid-column: span 4; }
.bento-span-5 { grid-column: span 5; }
.bento-span-12 { grid-column: span 12; }

@media (max-width: 991.98px) {
  .bento-span-3, .bento-span-4, .bento-span-5 { grid-column: span 6; }
}

@media (max-width: 575.98px) {
  .bento-span-3, .bento-span-4, .bento-span-5 { grid-column: span 12; }
}

/* Kafel bento — karta bez akcentu z lewej (inaczej niż .kpi-card:
   hierarchię niesie rozmiar kafla, nie pasek koloru). */
.bento-tile {
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  padding: 1.1rem 1.2rem;
  border-radius: 1.25rem;
  background-color: var(--planer-surface);
  box-shadow: var(--shadow-sm);
}

@media (prefers-reduced-motion: no-preference) {
  .bento-tile {
    transition: transform 0.18s ease, box-shadow 0.18s ease;
  }

  .bento-tile:hover {
    transform: translateY(-3px);
    box-shadow: var(--shadow-md);
  }
}

.tile-label {
  font-family: var(--planer-font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--planer-grafit-700);
}

/* Wartości Figtree (nie Gabarito) — patrz komentarz przy nagłówkach. */
.tile-value {
  font-size: 2.6rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.02em;
}

.tile-value-sm {
  font-size: 2rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.02em;
}

.tile-note {
  font-size: 0.82rem;
  color: var(--planer-grafit-500);
}

/* Miernik obłożenia — wypełnienie śliwkowe na jaśniejszym stopniu tej
   samej rampy (nie szarym), żeby stan czytał się na całej długości. */
.tile-meter {
  height: 10px;
  border-radius: 999px;
  background-color: var(--planer-primary-subtle-2);
  overflow: hidden;
}

.tile-meter-fill {
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--planer-primary-dark), var(--planer-primary-light));
}

/* Obłożenie per dział — mini-paski (etykieta / pasek / %). */
.dept-rows {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.dept-row {
  display: grid;
  grid-template-columns: minmax(6rem, 9rem) 1fr 2.8rem;
  align-items: center;
  gap: 0.7rem;
  font-size: 0.85rem;
}

.dept-row-bar {
  height: 8px;
  border-radius: 999px;
  background-color: var(--planer-primary-subtle-2);
  overflow: hidden;
}

.dept-row-bar span {
  display: block;
  height: 100%;
  border-radius: 999px 4px 4px 999px;
  background-color: var(--planer-primary);
}

.dept-row-pct {
  font-variant-numeric: tabular-nums;
  text-align: right;
  font-weight: 600;
  color: var(--planer-grafit-700);
}

/* Kafel alertu (otwarte wpisy RCP) — jedyna "głośna" karta: gradientowa
   ramka koral→bursztyn (technika dwóch nakładających się background'ów
   z border transparent) + koralowy cień. Renderowana tylko, gdy realnie
   jest co pokazać (warunek w szablonie), nie na stałe. */
.bento-tile--alert {
  border: 1px solid transparent;
  background:
    linear-gradient(var(--planer-surface), var(--planer-surface)) padding-box,
    linear-gradient(135deg, var(--planer-coral), var(--planer-amber)) border-box;
  box-shadow: 0 14px 26px -14px rgba(var(--planer-coral-rgb), .40), 0 3px 10px rgba(var(--planer-amber-rgb), .18);
}

.tile-alert-value {
  color: #c9264d;
}

[data-bs-theme="dark"] .tile-alert-value {
  color: #ff8ba0;
}

/* Szybkie akcje — trzy najczęstsze zadania kierownika pod ręką. */
.tile-actions-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

/* ==========================================================================
   Profil pracownika — panel boczny + treść (`/admin/employees/{id}`,
   wariant "B3" z warsztatu mockupów). Sidebar z tożsamością i kluczowymi
   liczbami przyklejony przy scrollu, treść (grafik/urlopy/RCP) obok jako
   zwykłe karty — reużywa `.bento-tile`/`.tile-meter` z sekcji "Dashboard
   bento" wyżej zamiast duplikować komponent mini-statystyki.
   ========================================================================== */
.employee-profile-layout {
  display: grid;
  grid-template-columns: minmax(280px, 320px) 1fr;
  gap: 1rem;
  align-items: start;
}

@media (max-width: 767.98px) {
  .employee-profile-layout {
    grid-template-columns: 1fr;
  }
}

/* `align-self: start` jest wymagane: bez tego grid item rozciąga się na
   całą wysokość wiersza i `position: sticky` nie ma gdzie się "przykleić".
   Musi zostać bezpośrednim dzieckiem `.employee-profile-layout` w zwykłym
   flow — jeśli wyląduje w elemencie z `overflow-x/y: auto` (patrz pułapka
   przy `.schedule-grid-scroll`), sticky przestaje działać względem strony. */
.employee-profile-sidebar {
  position: sticky;
  top: calc(var(--planer-navbar-height) + 1rem);
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.employee-profile-sidebar .avatar-initials {
  width: 3.5rem;
  height: 3.5rem;
  font-size: 1.35rem;
}

.employee-profile-identity {
  display: flex;
  align-items: center;
  gap: 0.85rem;
}

.employee-profile-content {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  min-width: 0;
}

.employee-profile-week {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 0.5rem;
}

@media (max-width: 575.98px) {
  .employee-profile-week {
    grid-template-columns: repeat(4, 1fr);
  }
}

.employee-profile-day {
  border-radius: 0.85rem;
  background-color: var(--planer-bg);
  padding: 0.6rem 0.4rem;
  text-align: center;
}

.employee-profile-day--today {
  box-shadow: inset 0 0 0 2px var(--planer-primary-dark);
}

.employee-profile-day-label {
  font-family: var(--planer-font-mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  color: var(--planer-grafit-500);
  margin-bottom: 0.4rem;
}

/* Kalendarz roczny "Urlopy i L4" profilu pracownika — wariant "roczna
   mapa" z warsztatu wizualnego 2026-07-22, zastępuje dawną przewijaną
   listę pojedynczych dni (ten sam urlop rozjeżdżał się na kilka wierszy,
   nie było widać zakresu). Dane: `schedule_service.build_leave_year_calendar`. */
.leave-calendar-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.1rem;
  margin: 0 0 1rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--planer-grafit-100);
}

.leave-calendar-chip {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--planer-grafit-700);
}

.leave-calendar-chip-dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 2px;
  flex: none;
}

.leave-calendar-weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 3px;
  max-width: 11rem;
  margin-bottom: 0.4rem;
}

.leave-calendar-weekdays span {
  font-family: var(--planer-font-mono);
  font-size: 0.58rem;
  text-transform: uppercase;
  color: var(--planer-grafit-500);
  text-align: center;
}

/* `overflow-x: auto` — zabezpieczenie: siatka jest elastyczna (fr), ale
   przy bardzo wąskim oknie + zoomie tekstowym nie chcemy, żeby rozpychała
   całą stronę w bok. */
.leave-calendar-grid-scroll {
  overflow-x: auto;
}

.leave-calendar-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.3rem 1rem;
  min-width: 30rem;
}

.leave-calendar-month-name {
  font-family: var(--planer-font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--planer-grafit-700);
  margin-bottom: 0.35rem;
}

.leave-calendar-days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 3px;
}

.leave-calendar-day {
  width: 100%;
  aspect-ratio: 1;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--planer-font-mono);
  font-size: 0.62rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.leave-calendar-day--neutral {
  background-color: var(--planer-grafit-100);
  color: var(--planer-grafit-700);
}

.leave-calendar-day--empty {
  background: transparent;
}

.leave-calendar-day--today {
  box-shadow: inset 0 0 0 2px var(--planer-primary-dark);
}

@media (max-width: 767.98px) {
  .leave-calendar-grid {
    min-width: 0;
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Dzień bez wpisu w grafiku — celowo POZA paletą `--status-*`: brak wpisu
   nie jest równoznaczny z potwierdzonym "Wolne" (może być po prostu
   nieopublikowany), więc nie dostaje koloru statusu, tylko neutralną
   etykietę. */
.status-badge-empty {
  display: inline-block;
  font-size: 0.72rem;
  color: var(--planer-grafit-500);
  border: 1px dashed var(--planer-grafit-300);
  border-radius: 0.4rem;
  padding: 0.15rem 0.4rem;
}

/* ============================================================
   Navbar "Aplikacyjny" (wariant C, warsztat nawigacji 2026-07-20) —
   dok z pigułkami + Panel zarządzania na komputerze, dolny pasek
   zakładek + wysuwana karta "Więcej" na telefonie. Zastępuje dawny
   płaski `.navbar-nav` chowany w hamburgerze pod 768px. Kolory/promienie
   dziedziczone z tokenów wyżej — komponent nie wprowadza nowej palety.
   ============================================================ */

.navbar-app-bar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.navbar-app-left {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.navbar-app-right {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-left: auto;
}

/* Dok z pełnymi etykietami dostaje oddech po obu stronach zamiast siedzieć
   tuż przy marce — `margin-left: auto` wyżej (domyślny, bezpieczny fallback
   gdy wyśrodkowanie niżej nie jest w stanie się zmieścić) wsysa całą wolną
   przestrzeń w JEDNĄ przerwę przed prawym klastrem, więc dok zostaje
   przyklejony do marki. `space-between` rozdziela ją zamiast tego na dwie
   równe przerwy — NIE to samo co prawdziwe wyśrodkowanie doku (patrz
   `.navbar-centered` niżej), tylko krok pośredni, gdy dok ma pełne
   etykiety, ale JS uznał realne wyśrodkowanie za niebezpieczne na tej
   szerokości. Warunek to `.navbar-fits` (JS, `app.js`), NIE stała
   szerokość okna — patrz komentarz przy `.navbar-dock .nav-link span`
   niżej, dlaczego stała px tu regularnie się myliła. */
.navbar-app-bar.navbar-fits .navbar-app-right {
  margin-left: 0;
}

nav.navbar-planer .navbar-app-bar.navbar-fits {
  justify-content: space-between;
}

/* Prawdziwe geometryczne wyśrodkowanie doku (grid `1fr auto 1fr`) — na
   życzenie Jakuba 2026-08-21, PO obejrzeniu podglądu obu wariantów.
   Wcześniej odrzucone jako niebezpieczne (komentarz wyżej, sprzed tej
   zmiany): grid z RÓWNYMI kolumnami bocznymi wymaga tyle samo miejsca po
   stronie marki co po stronie prawego klastra — a te nie są tej samej
   szerokości (marka ma stałą szerokość ~180px, prawy klaster to 85px
   samymi ikonami albo ~260px z "Panel zarządzania"), więc naiwne
   wyśrodkowanie mogło zepchnąć awatar poza ekran dokładnie tak, jak
   ostrzegał stary komentarz. Naprawione NIE rezygnacją z wyśrodkowania,
   tylko warunkiem `.navbar-centered` (JS, `app.js`): dodawany TYLKO gdy
   `szerokość doku + 2× szerszy z dwóch boków <= dostępna szerokość` —
   ten sam wzorzec "zmierz naprawdę, nie zgaduj" co `.navbar-fits`.
   Bezpieczny fallback bez JS/gdy się nie mieści: reguły wyżej
   (`.navbar-fits`/domyślne `margin-left: auto`), nigdy overflow. */
.navbar-app-bar.navbar-centered {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
}

/* `grid-column` jawnie, nie tylko `justify-self`: poniżej 768px
   `.navbar-dock` ma `display: none` (Bootstrap `d-none`) i wypada z
   auto-placement gridu — bez jawnych indeksów pozostałe dwa dzieci
   (`.navbar-app-left`, `.navbar-app-right`) lądowały w kolumnach 1 i 2
   (środkowej "auto"), więc awatar wpadał na środek zamiast trzymać się
   prawej krawędzi (zgłoszenie Jakuba 2026-08-25, zrzut przy 762px). */
.navbar-app-bar.navbar-centered .navbar-app-left {
  grid-column: 1;
  justify-self: start;
}

.navbar-app-bar.navbar-centered .navbar-dock {
  grid-column: 2;
  justify-self: center;
}

.navbar-app-bar.navbar-centered .navbar-app-right {
  grid-column: 3;
  justify-self: end;
  margin-left: 0;
}

.navbar-icon-btn {
  width: 2.25rem;
  height: 2.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  border: none;
  background: transparent;
  color: var(--planer-nav-ink);
  cursor: pointer;
  flex-shrink: 0;
}

.navbar-icon-btn:hover {
  background: rgba(255, 255, 255, 0.14);
}

/* Dok osobistych zakładek — jedna zaokrąglona szyna zamiast płaskiego
   rzędu linków, tak jak w mockupie "Aplikacyjny". */
.navbar-dock {
  align-items: center;
  gap: 0.1rem;
  background: rgba(255, 255, 255, 0.10);
  border-radius: 999px;
  padding: 0.25rem;
}

.navbar-dock .nav-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  white-space: nowrap;
  padding: 0.45rem 0.8rem;
}

/* Pigulka doku: promien na stanie bazowym z tego samego powodu co przy
   `.navbar-planer .nav-link` wyzej — inaczej przy zjezdzie mysza wygasajace
   tlo mruga kwadratem zamiast pigulka. */
.navbar-planer .navbar-dock .nav-link {
  border-radius: 999px;
}

/* Etykiety doku domyślnie schowane (same ikony) — poprzednio sterowane
   stałą szerokością okna w media query, przez trzy sesje z rzędu zgadywaną
   na nowo (1301px→1360px, potem 1495px→1560px) i za każdym razem błędną,
   bo naprawdę potrzebna szerokość zależy nie tylko od okna, ale od TEGO,
   ILE pozycji ma dok u KONKRETNEGO użytkownika: "Mój grafik" tylko przy
   dostępem do Planera, prawy przycisk to "Panel zarządzania"
   (MANAGER/ADMIN, najdłuższy), "Zespół" (COORDINATOR, krótszy) albo nic
   (EMPLOYEE). Jedna stała px nie może być trafna dla wszystkich kombinacji
   naraz — zgłoszenie Jakuba 2026-08-21: przy 7 pozycjach (z "Mój grafik")
   próg 1360px łamał menu na dwie linie, a podniesienie do 1560px dla
   bezpieczeństwa z kolei chowało etykiety NIEPOTRZEBNIE wcześnie jego
   własnemu kontu (6 pozycji, bez "Mój grafik" — mieściłoby się już przy
   ~1360px). Zamiast czwartej zgadywanej stałej: domyślnie zawsze same
   ikony (bezpieczny fallback — nigdy nie łamie się na dwie linie, także
   bez JS), `.navbar-fits` (dodawane przez `app.js` po realnym pomiarze
   `scrollWidth` DOKU TEGO userA względem dostępnej szerokości) przywraca
   pełne etykiety tylko wtedy, gdy naprawdę się mieszczą. */
.navbar-dock .nav-link span,
.navbar-mgmt-btn span {
  display: none;
}

.navbar-fits .navbar-dock .nav-link span,
.navbar-fits .navbar-mgmt-btn span {
  display: inline;
}

/* Wymuszenie pełnych etykiet NA CHWILĘ do pomiaru w `app.js`
   (`.navbar-measuring`) — musi wygrywać z regułą chowającą powyżej,
   inaczej `scrollWidth` mierzyłby ikonki, nie prawdziwą potrzebną
   szerokość. `flex-wrap: nowrap` na samym `.navbar-app-bar` (poniżej) jest
   równie ważne: bez niego przeglądarka "chowa" nadmiar łamiąc na drugą
   linię zamiast przez poziomy overflow, więc `scrollWidth` kontenera
   zrównałby się z `clientWidth` mimo realnego złamania — dokładnie ten
   mechanizm powodował błąd (zobacz komentarz w `app.js`). */
.navbar-measuring .navbar-dock .nav-link span,
.navbar-measuring .navbar-mgmt-btn span {
  display: inline;
}

.navbar-app-bar.navbar-measuring {
  flex-wrap: nowrap;
  /* `width: max-content` zamiast polegać na `scrollWidth`/overflow: przy
     zwykłej szerokości (100% kontenera) dzieci flex z `flex-shrink` != 0
     (domyślne) potrafią się ściskać zamiast przelewać, więc nawet
     wymuszony `nowrap` nie ujawniał prawdziwej potrzebnej szerokości —
     zmierzone (JS w app.js liczyło "mieści się" dla treści, która i tak
     składała się z dwóch linii po zdjęciu wymuszenia). Przy `max-content`
     kontener rośnie do naturalnej szerokości treści, bez ściskania. */
  width: max-content;
  max-width: none;
}

/* Przycisk "Zespół" / "Panel zarządzania" po prawej — osobna, cieplejsza
   pigułka niż dok (rgba jaśniejsze), żeby wizualnie odróżnić "moje" od
   "zarządzam innymi", bez chowania za dodatkowym klikiem. */
.navbar-mgmt-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  white-space: nowrap;
  padding: 0.45rem 0.85rem;
  background: rgba(255, 255, 255, 0.14);
}

/* Selektor z `.navbar-planer`, nie samo `.navbar-mgmt-btn` — przycisk ma w
   HTML rowniez klase `nav-link`, wiec musi wygrac specyficznoscia z
   `.navbar-planer .nav-link { border-radius: 6px }` wyzej, inaczej pigulka
   splaszczylaby sie do 6px. */
.navbar-planer .navbar-mgmt-btn {
  border-radius: 999px;
}

.navbar-planer .navbar-mgmt-btn.active,
.navbar-planer .navbar-mgmt-btn:hover,
.navbar-planer .navbar-mgmt-btn.show {
  background: rgba(255, 255, 255, 0.22);
}

/* Bootstrap 5.3 deklaruje --bs-dropdown-* LOKALNIE na samym .dropdown-menu
   — nadpisanie którejkolwiek z nich tylko w :root (patrz komentarz wyżej)
   nic nie daje, bo potomkowie .dropdown-menu dziedziczą z najbliższego
   przodka, którym jest sam .dropdown-menu z WŁASNĄ deklaracją, nie :root.
   Pierwotnie naprawione tu tylko dla --bs-dropdown-link-active-bg/-color
   (#0d6efd świecący na aktywnych wpisach Panelu zarządzania, zgłoszenie
   Jakuba ze screenami 2026-08-12) — TA SAMA przyczyna zostawiła jednak
   otwarte pozostałe zmienne: tło menu wciąż leciało z domyślnego
   --bs-dropdown-bg (var(--bs-body-bg), czyli kolor STRONY, nie karty —
   menu zlewało się z tabelą pod spodem, czytane jako "przezroczyste"), a
   najechanie na pozycję nie dawało żadnego podświetlenia
   (--bs-dropdown-link-hover-bg). Zgłoszenie Jakuba przy menu akcji wiersza
   na `/admin/poradniki`, ale dotyczy WSZYSTKICH dropdownów w apce (Panel
   zarządzania, menu konta, "Więcej" mobile) — jeden fix na wspólnym
   selektorze. */
.dropdown-menu {
  --bs-dropdown-bg: var(--planer-surface);
  --bs-dropdown-border-color: var(--planer-grafit-100);
  --bs-dropdown-link-hover-bg: var(--planer-primary-subtle);
  --bs-dropdown-link-hover-color: var(--planer-text);
  --bs-dropdown-link-active-bg: var(--planer-primary-dark);
  --bs-dropdown-link-active-color: var(--planer-on-primary);
}

.navbar-mgmt-panel {
  min-width: 560px;
  padding: 0.6rem;
}

@media (max-width: 991.98px) {
  .navbar-mgmt-panel {
    min-width: 320px;
  }
}

.navbar-mgmt-switch {
  background: var(--planer-grafit-100);
  border: 1px solid var(--planer-grafit-300);
  border-radius: 999px;
  padding: 3px;
  margin: 0.2rem 0.3rem 0.6rem;
  flex-wrap: nowrap;
}

.navbar-mgmt-switch .nav-link {
  flex: 1;
  text-align: center;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--planer-text) !important;
  padding: 0.35rem 0.6rem;
}

/* Pełne tło marki zamiast białej pigułki na jasnym tle — biała pigułka na
   --planer-grafit-100 dawała za mało kontrastu (zgłoszenie Jakuba: "prawie
   nie widać panelu zarządzania" na jasnym motywie). Ten sam wzorzec
   aktywnego stanu co .rcp-view-switch (segmentowany przełącznik widoku
   RCP) niżej — jedno źródło prawdy dla tego typu kontrolki. */
.navbar-mgmt-switch .nav-link.active {
  background: var(--planer-primary-dark);
  color: var(--planer-on-primary) !important;
}

/* Bez tego selektora ogólna reguła `.navbar-planer .nav-link:hover/.active`
   (border-radius: 6px, wyżej w pliku) wygrywa specificity z bazowym
   border-radius: 999px przełącznika przy hover/active — pigułka nabierała
   kanciastych rogów odstających od w pełni zaokrąglonego paska w tle
   (zgłoszenie Jakuba: "różne bordery po kliknięciu i odklikaniu"). */
.navbar-planer .navbar-mgmt-switch .nav-link:hover,
.navbar-planer .navbar-mgmt-switch .nav-link.active,
.navbar-planer .navbar-mgmt-switch .nav-link.show {
  border-radius: 999px;
}

.navbar-mgmt-panel .tab-content .dropdown-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* Awatar konta w navbarze — okrągły przycisk bez domyślnych stylów
   przeglądarki, opakowuje istniejące makro `avatar_initials` (te same
   inicjały/kolor co w listach pracowników), żeby nie wprowadzać drugiego
   systemu awatarów. */
.navbar-avatar-btn {
  border: none;
  background: transparent;
  padding: 0;
  border-radius: 50%;
  cursor: pointer;
  line-height: 0;
  flex-shrink: 0;
}

/* Panel konta otwierał się WEWNĄTRZ paska nawigacji — górna krawędź karty
   wchodziła ~15px na fioletowy pas i w ciemnym motywie czytała się jako
   pusty, brudny pasek nad nazwiskiem (zgłoszenie Jakuba 2026-08-17 ze
   zrzutem). Powód: Bootstrap kotwiczy menu do DOLNEJ krawędzi przycisku, a
   awatar ma 28px przy pasku wysokim na 46-62px, więc pod przyciskiem
   zostawało jeszcze kilkanaście pikseli paska.

   Rozwiązanie w dwóch krokach, bez magicznych liczb zależnych od
   szerokości ekranu:
   1. przycisk awatara rozciąga się na całą wysokość paska (jak sąsiednie
      `.nav-link`) — sam medalion nadal wyśrodkowany pionowo,
   2. odstęp menu podniesiony do 0.75rem przez zmienną Bootstrapa.
   Po tych dwóch krokach dolna krawędź przycisku pokrywa się z krawędzią
   `.navbar-app-bar`, a od niej do dołu navbara jest zawsze te same 8px
   paddingu — więc karta wypada 4px PONIŻEJ paska na każdej szerokości. */
.navbar-app-right {
  align-self: stretch;
}

.navbar-app-right > .nav-item.dropdown {
  align-self: stretch;
  display: flex;
  align-items: center;
}

.navbar-avatar-btn {
  align-self: stretch;
  display: flex;
  align-items: center;
}

.navbar-usermenu-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.7rem 0.7rem;
}

.navbar-usermenu-head strong {
  display: block;
  font-size: 0.9rem;
}

.navbar-usermenu-head .badge-role {
  margin-top: 0.2rem;
}

/* Menu użytkownika (avatar w navbarze) — trochę szersze niż domyślny
   dropdown, żeby mini-przełącznik motywu kolorów niżej miał gdzie oddychać
   (przebudowa 2026-08-05, zgłoszenie Jakuba: panel był zbyt ubogi na taką
   szerokość karty). */
.navbar-usermenu {
  min-width: 280px;
  padding-bottom: 0.3rem;
  /* Odstęp od paska nawigacji — patrz komentarz przy `.navbar-avatar-btn`.
     Przez zmienną Bootstrapa, bo `.dropdown-menu` czyta z niej `margin-top`;
     własna reguła `margin-top` o tej samej specyficzności nie zawsze wygrywa
     z kolejnością wczytania bootstrap.css. */
  --bs-dropdown-spacer: 0.75rem;
  /* Zero paddingu NAD nagłówkiem: Bootstrap daje `.dropdown-menu` 0.5rem u
     góry, więc między krawędzią karty a gradientem nagłówka zostawał pas
     gołego tła — w ciemnym motywie czytał się jako pusty pasek nad
     nazwiskiem (zgłoszenie Jakuba 2026-08-17, drugi zrzut, już po
     odsunięciu panelu od navbara). Gradient ma zaczynać się OD krawędzi. */
  padding-top: 0;
  overflow: hidden;
}

/* Nagłówek profilu — delikatny gradient w kolorze marki, żeby karta od
   razu czytała się jako "moje konto", nie jako kolejny wiersz listy.
   Padding górny większy niż był, bo nagłówek przejął pas po paddingu
   dropdownu i bez tego nazwisko kleiłoby się do krawędzi. */
.navbar-usermenu-head {
  background: linear-gradient(180deg, var(--planer-primary-subtle), transparent);
  padding-top: 0.85rem;
}

/* Sekcje "Konto" / "Wygląd" — pogrupowane pod etykietą (eyebrow) +
   cienka linia MIĘDZY sekcjami (nie pod każdą), tak jak panel ustawień,
   zamiast płaskiej listy wszystkiego pod rząd (przebudowa 2026-08-05,
   wariant B wybrany przez Jakuba z 3 makiet). */
.navbar-usermenu-section {
  padding: 0.7rem 1rem 0.8rem;
}

.navbar-usermenu-section + .navbar-usermenu-section {
  border-top: 1px solid var(--planer-grafit-100);
}

.navbar-usermenu-eyebrow {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  opacity: 0.65;
  margin-bottom: 0.5rem;
}

.navbar-usermenu-item {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.3rem 0;
  font-size: 0.87rem;
  font-weight: 600;
  color: var(--planer-text);
  text-decoration: none;
}

.navbar-usermenu-item:hover {
  color: var(--planer-primary-dark);
}

.navbar-usermenu-item .bi,
.navbar-usermenu-row-label .bi {
  color: var(--planer-primary-dark);
}

/* Wiersz "etykieta z lewej, kontrolka z prawej" — wspólny dla przełącznika
   jasny/ciemny i przełącznika koloru motywu, żeby oba wyglądały jak jedna
   spójna grupa "Wygląd", nie jak dwa osobne, niezwiązane elementy. */
.navbar-usermenu-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  padding: 0.3rem 0;
  border: none;
  background: transparent;
  font-size: 0.87rem;
  font-weight: 600;
  color: var(--planer-text);
  text-align: left;
}

button.navbar-usermenu-row {
  cursor: pointer;
}

.navbar-usermenu-row-label {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  flex-shrink: 0;
}

/* Przełącznik jasny/ciemny — suwak zamiast przycisku z długim tekstem,
   pozycja kropki i tło śledzą `data-bs-theme` na <html> (to samo źródło
   prawdy, które ustawia `app.js` po kliknięciu). */
.navbar-usermenu-switch {
  --w: 2.4rem;
  --h: 1.3rem;
  width: var(--w);
  height: var(--h);
  border-radius: 999px;
  background: var(--planer-grafit-300);
  position: relative;
  flex-shrink: 0;
}

.navbar-usermenu-switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: calc(var(--h) - 4px);
  height: calc(var(--h) - 4px);
  border-radius: 50%;
  background: var(--planer-surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  transition: left 0.15s ease;
}

[data-bs-theme="dark"] .navbar-usermenu-switch {
  background: var(--planer-primary-subtle-2);
}

[data-bs-theme="dark"] .navbar-usermenu-switch-knob {
  left: calc(var(--w) - var(--h) + 2px);
}

.navbar-usermenu-swatches {
  display: flex;
  gap: 0.5rem;
}

.navbar-usermenu-swatch {
  cursor: pointer;
  border-radius: 50%;
  line-height: 0;
}

.navbar-usermenu-swatch input[type="radio"] {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

/* Kropka podglądu koloru renderuje się WEWNĄTRZ własnego
   [data-color-theme], niezależnie od aktualnie wybranego motywu strony —
   ten sam wzorzec co `.appearance-swatch-preview` w /moje-dane/wyglad
   (patrz jej komentarz), tylko w miniaturze do paska nawigacji. */
.navbar-usermenu-swatch-dot {
  display: block;
  width: 1.3rem;
  height: 1.3rem;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--planer-primary-dark), var(--planer-primary) 70%);
  box-shadow: 0 0 0 2px var(--planer-surface);
  border: 2px solid transparent;
}

.navbar-usermenu-swatch.is-selected .navbar-usermenu-swatch-dot {
  border-color: var(--planer-primary-dark);
}

.navbar-usermenu-swatch:has(input:focus-visible) .navbar-usermenu-swatch-dot {
  outline: 2px solid var(--planer-primary-dark);
  outline-offset: 2px;
}

/* Dolny pasek zakładek (telefon <768px) — najczęstsze akcje w zasięgu
   kciuka zamiast pod hamburgerem, wzorem natywnych aplikacji. */
.navbar-tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1025;
  display: flex;
  background: var(--planer-surface);
  border-top: 1px solid var(--planer-grafit-300);
  box-shadow: 0 -8px 20px -14px rgba(var(--planer-primary-dark-rgb), 0.45);
  padding: 0.35rem 0.2rem calc(0.35rem + env(safe-area-inset-bottom, 0px));
}

.navbar-tabbar-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  padding: 0.3rem 0.2rem;
  border: none;
  background: transparent;
  color: var(--planer-grafit-500);
  text-decoration: none;
  font-size: 0.68rem;
  font-weight: 600;
  position: relative;
}

.navbar-tabbar-item .bi {
  width: 1.2rem;
  height: 1.2rem;
}

.navbar-tabbar-item.active {
  color: var(--planer-primary-dark);
}

.navbar-tabbar-dot {
  position: absolute;
  top: 0;
  right: calc(50% - 15px);
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--planer-coral);
  border: 2px solid var(--planer-surface);
}

/* Główna treść dostaje dodatkowy zapas na dole na telefonie, żeby dolny
   pasek zakładek nie zasłaniał ostatniego wiersza/przycisku strony.
   `!important` jest tu konieczne: `<main>` ma równocześnie Bootstrapowe
   `py-4`, a utility Bootstrapa niosą `!important` i bez tego wygrywały
   (24px zamiast 5rem) — pasek realnie zasłaniał ostatnią kartę listy RCP,
   zgłoszenie Jakuba 2026-08-06. Zapas = wysokość paska (~3.7rem) + margines
   + safe-area na telefonach z zaokrąglonym dołem ekranu. */
@media (max-width: 767.98px) {
  main.pb-tabbar {
    padding-bottom: calc(5rem + env(safe-area-inset-bottom, 0px)) !important;
  }

  /* Toast błędu htmx (patrz app.js) inaczej ląduje POD paskiem zakładek —
     podnosimy go tyle, ile ma wysokości pasek + bezpieczny margines. */
  .htmx-error-toast {
    bottom: calc(4.25rem + env(safe-area-inset-bottom, 0px));
  }
}

/* Karta "Więcej" (offcanvas Bootstrapa, wysuwana z dołu) — te same
   zaokrąglenia/cienie co reszta "Miękkiej", nie domyślny kwadratowy
   Bootstrap. */
.navbar-more-sheet {
  /* Bootstrap liczy wysokość offcanvas-bottom z `--bs-offcanvas-height`
     (domyślnie 30vh) — za mało na pełną listę Kierownika/Admina (Moje +
     Zespół + Zarządzanie). `auto` + `max-height` niżej dopasowuje wysokość
     do treści, ze scrollem w `.offcanvas-body`, gdy i tak za dużo. */
  --bs-offcanvas-height: auto;
  border-top-left-radius: 1.3rem;
  border-top-right-radius: 1.3rem;
  box-shadow: var(--shadow-md);
  max-height: 80vh;
}

.navbar-more-sheet .offcanvas-header {
  position: relative;
  padding-top: 1.1rem;
  padding-bottom: 0.25rem;
}

.navbar-sheet-handle {
  position: absolute;
  top: 0.5rem;
  left: 50%;
  transform: translateX(-50%);
  width: 36px;
  height: 4px;
  border-radius: 999px;
  background: var(--planer-grafit-300);
}

.navbar-sheet-section-label {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--planer-grafit-500);
  padding: 0.7rem 0.7rem 0.25rem;
  margin: 0;
}

.navbar-more-sheet .dropdown-item {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  border-radius: 0.65rem;
  padding: 0.6rem 0.7rem;
}

/* ==========================================================================
   Panel RCP "Mój czas pracy" (`/rcp`) — dwa layouty wybierane przez
   pracownika i zapamiętywane per karta (`Employee.rcp_panel_view`,
   zgłoszenie Jakuba 2026-07-20, warsztat mockupów warianty D/E). Widok
   zegara reużywa `.tile-meter`/`.tile-meter-fill` i `.dept-row`/
   `.dept-row-bar` z sekcji "Dashboard bento" wyżej (pasek tygodnia i
   pasek "Ostatnie dni" to ten sam wzorzec label/pasek/wartość) zamiast
   duplikować komponent.
   ========================================================================== */

/* Nagłówek panelu — h1, przełącznik trybu widoku (wzajemnie wykluczające
   się Zegar/Tabela) i akcje drugorzędne (Historia/Zgłoś korektę, niezależne
   skróty) to trzy odrębne grupy, nie jeden wspólny rząd pigułek. Na
   telefonie (zgłoszenie Jakuba 2026-07-22) każda dostaje własny pełny
   wiersz zamiast losowego zawijania przez `flex-wrap`. */
.rcp-panel-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.rcp-panel-head h1 {
  margin-right: auto;
}

.rcp-secondary-actions {
  display: flex;
  gap: 0.5rem;
}

.rcp-view-switch {
  display: inline-flex;
  border: 1px solid var(--planer-grafit-300);
  border-radius: 999px;
  padding: 0.2rem;
  gap: 0.2rem;
}

.rcp-view-switch button,
.rcp-view-switch a,
.rcp-view-switch label {
  border: none;
  background: transparent;
  color: var(--planer-grafit-700);
  border-radius: 999px;
  padding: 0.35rem 0.85rem;
  font-size: 0.85rem;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  text-decoration: none;
  cursor: pointer;
  flex: 1;
}

.rcp-view-switch button.active,
.rcp-view-switch a.active,
.rcp-view-switch .chip-input:checked + label {
  background: var(--planer-primary-dark);
  color: var(--planer-on-primary);
}

.rcp-view-switch .chip-input:focus-visible + label {
  outline: 3px solid rgba(var(--planer-primary-rgb), .45);
  outline-offset: 2px;
}

/* <768px: h1/przełącznik/akcje każdy na własnym wierszu. Przełącznik
   trybu zmienia kształt na zakładki z podkreśleniem (bez wypełnienia) —
   wariant B z warsztatu 3 alternatyw dla "pigułki ciętej na pół"
   (zgłoszenie Jakuba 2026-07-22), zamiast rozciągania tej samej kapsuły
   co .navbar-mgmt-switch/.my-profile-tabs na pełną szerokość. */
@media (max-width: 767.98px) {
  .rcp-panel-head h1 {
    flex: 0 0 100%;
    margin-right: 0;
  }

  .rcp-view-switch {
    flex: 0 0 100%;
    border: none;
    border-bottom: 1px solid var(--planer-grafit-300);
    border-radius: 0;
    padding: 0;
    gap: 0;
  }

  .rcp-view-switch form {
    flex: 1;
  }

  .rcp-view-switch button {
    width: 100%;
    justify-content: center;
    border-radius: 0;
    padding: 0.5rem 0 0.6rem;
    color: var(--planer-grafit-500);
    position: relative;
  }

  .rcp-view-switch button.active {
    background: transparent;
    color: var(--planer-primary-dark);
    font-weight: 800;
  }

  .rcp-view-switch button.active::after,
  .rcp-view-switch a.active::after {
    content: "";
    position: absolute;
    left: 20%;
    right: 20%;
    bottom: -1px;
    height: 3px;
    border-radius: 3px 3px 0 0;
    background: var(--planer-primary-dark);
  }

  .rcp-secondary-actions {
    flex: 0 0 100%;
    gap: 0.4rem;
  }

  .rcp-secondary-actions .btn {
    flex: 1;
  }
}

/* ============================================================
   Widok kalendarzowy /rcp/historia?widok=kalendarz (backlog e2a3f9,
   wariant C wybrany przez Jakuba 2026-07-24 po mockupie decyzyjnym) —
   miesięczna siatka dni z mini-listą wpisów RCP wprost w komórce.
   Mobile: pełna siatka 7 kolumn nieczytelna na wąskim ekranie, więc telefon
   dostaje `.rcp-calendar-mobile-list` — ten sam dzień-po-dniu, w pionie,
   jedna kolumna (2026-08-21, zamiast tabelarycznego fallbacku widoku
   "Tabela" sprzed tej daty, patrz komentarz w `history.html`). Oba warianty
   dnia dzielą treść przez `_calendar_day_content.html`, więc style stanów
   (`.rcp-calendar-holiday`, `.rcp-calendar-entries`, `.status-badge`) niżej
   są wspólne dla siatki i listy.
   ============================================================ */

.rcp-calendar-toolbar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

.rcp-calendar-month-label {
  font-family: var(--planer-font-display);
  font-weight: 800;
  font-size: 1.1rem;
  min-width: 11ch;
  text-align: center;
}

.rcp-calendar-weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 0.5rem;
  margin-bottom: 0.4rem;
  min-width: 44rem;
}

.rcp-calendar-weekdays span {
  font-family: var(--planer-font-mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--planer-grafit-500);
  text-align: center;
}

.rcp-calendar-grid-scroll {
  overflow-x: auto;
}

.rcp-calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 0.5rem;
  min-width: 44rem;
}

.rcp-calendar-day {
  min-height: 6.5rem;
  border-radius: 0.9rem;
  border: 1px solid var(--planer-grafit-100);
  background-color: var(--planer-bg);
  padding: 0.4rem 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  overflow: hidden;
}

.rcp-calendar-day--blank {
  border-color: transparent;
  background: transparent;
}

.rcp-calendar-day--today {
  box-shadow: inset 0 0 0 2px var(--planer-primary-dark);
}

.rcp-calendar-day--future {
  opacity: 0.55;
}

.rcp-calendar-day-num {
  font-family: var(--planer-font-mono);
  font-size: 0.72rem;
  color: var(--planer-grafit-500);
  font-variant-numeric: tabular-nums;
}

/* Dzień ustawowo wolny w kalendarzu RCP (`holiday_service`) — świadomie
   NIE plakietka `.status-badge status-holiday` jak w /grafik: w komórce
   często stoi razem z wpisami RCP albo plakietką stanu i pełne bordowe tło
   przeciągałoby na siebie uwagę. Zostaje sam bordowy kolor tekstu, żeby
   znaczenie było wspólne z /grafik. Nazwy bywają długie ("Wniebowzięcie
   NMP / Święto Wojska Polskiego"), stąd przycięcie do jednej linii —
   pełna nazwa jest w `title` na elemencie. */
.rcp-calendar-holiday {
  display: flex;
  align-items: center;
  gap: 0.2rem;
  min-width: 0;
  font-size: 0.68rem;
  line-height: 1.15;
  font-weight: 600;
  color: var(--status-holiday-ink);
}

.rcp-calendar-holiday-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rcp-calendar-entries {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.rcp-calendar-entry {
  display: flex;
  flex-wrap: wrap;
  column-gap: 0.35rem;
  font-size: 0.68rem;
  line-height: 1.3;
}

.rcp-calendar-entry .tnum {
  font-family: var(--planer-font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.rcp-calendar-entry-label {
  color: var(--planer-grafit-700);
}

.rcp-calendar-entry-dur {
  margin-left: auto;
}

/* Jedyny status w tym widoku bez odpowiednika w /grafik — dzień roboczy
   wg grafiku, zero wpisów RCP. Koral (akcent marki), nie recykling
   `--status-*`: te niosą inne znaczenia gdzie indziej w aplikacji. */
.status-anomaly {
  background-color: rgba(var(--planer-coral-rgb), 0.16);
  color: var(--planer-coral);
  border: 1px dashed rgba(var(--planer-coral-rgb), 0.55);
}

/* Lista dni na telefonie — odpowiednik `.rcp-calendar-day` z siatki, tylko
   w pełnej szerokości zamiast komórki grida. Data z lewej (stała szerokość,
   liczby wyrównane), treść dnia z prawej. */
.rcp-calendar-mobile-days {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.rcp-calendar-mobile-day {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  border-radius: 0.9rem;
  border: 1px solid var(--planer-grafit-100);
  background-color: var(--planer-bg);
  padding: 0.6rem 0.75rem;
}

.rcp-calendar-mobile-day--today {
  box-shadow: inset 0 0 0 2px var(--planer-primary-dark);
}

.rcp-calendar-mobile-day--future {
  opacity: 0.55;
}

.rcp-calendar-mobile-day-date {
  flex: 0 0 2.75rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: var(--planer-font-mono);
  font-variant-numeric: tabular-nums;
}

.rcp-calendar-mobile-day-num {
  font-size: 1.1rem;
  font-weight: 700;
}

.rcp-calendar-mobile-day-weekday {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--planer-grafit-500);
}

.rcp-calendar-mobile-day-content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.rcp-calendar-mobile-day .rcp-calendar-holiday-label {
  white-space: normal;
}

/* Próg 992px, nie 768px: `.rcp-calendar-grid`/`.rcp-calendar-weekdays` mają
   min-width 44rem (704px), a `.container` Bootstrapa przy md (768–991px) ma
   max-width tylko 720px — z paddingiem karty to za mało, siatka i tak
   wymuszała scroll w bok w całym zakresie md (zgłoszenie Jakuba
   2026-08-21, ta sama przyczyna co przy `.rcp-table-wrap`). */
@media (min-width: 992px) {
  .rcp-calendar-mobile-list {
    display: none;
  }
}

@media (max-width: 991.98px) {
  .rcp-calendar-mobile-fallback {
    display: none;
  }
}

/* Karta "Trwające" widoku zegara — jeden żywy stoper (albo kilka, przy
   równoległych wpisach, patrz docstring modelu `TimeEntry`). */
.rcp-hero-timer {
  background: var(--planer-surface);
  border-radius: var(--bs-border-radius-lg);
  box-shadow: var(--shadow-md);
  padding: 1.5rem 1.75rem;
  margin-bottom: 1.25rem;
}

.rcp-hero-entry {
  text-align: center;
  padding: 0.75rem 0;
}

.rcp-hero-entry + .rcp-hero-entry {
  border-top: 1px solid var(--planer-grafit-100);
}

.rcp-hero-status {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--planer-font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--planer-grafit-700);
}

.rcp-hero-dot {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  background: #55c98a;
  box-shadow: 0 0 10px #55c98a;
  display: inline-block;
  flex-shrink: 0;
}

@media (prefers-reduced-motion: no-preference) {
  .rcp-hero-dot {
    animation: rcp-pulse 2.2s ease-in-out infinite;
  }
}

@keyframes rcp-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

.rcp-hero-dot--idle {
  background: var(--planer-grafit-300);
  box-shadow: none;
  animation: none !important;
}

.rcp-hero-value {
  font-family: var(--planer-font-body);
  font-weight: 700;
  font-size: 3rem;
  line-height: 1;
  letter-spacing: -0.02em;
  margin: 0.6rem 0 0.3rem;
  font-variant-numeric: tabular-nums;
}

.rcp-hero-sub {
  color: var(--planer-grafit-700);
  font-size: 0.92rem;
  margin-bottom: 1rem;
}

.rcp-hero-actions {
  display: flex;
  justify-content: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.rcp-hero-schedule {
  font-size: 0.88rem;
  color: var(--planer-grafit-700);
  text-align: center;
  padding-top: 1rem;
  margin-top: 0.25rem;
  border-top: 1px solid var(--planer-grafit-100);
}

/* Pasek statystyk (Dziś/Przerwa/Tydzień) — widok zegara. */
.rcp-stat-row {
  display: flex;
  background: var(--planer-surface);
  border-radius: var(--bs-border-radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 1.1rem 1rem;
  margin-bottom: 1.25rem;
}

.rcp-stat {
  flex: 1;
  text-align: center;
  border-left: 1px solid var(--planer-grafit-100);
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.rcp-stat:first-child {
  border-left: none;
}

.rcp-stat-label {
  font-family: var(--planer-font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--planer-grafit-500);
}

.rcp-stat-value {
  font-weight: 700;
  font-size: 1.3rem;
  font-variant-numeric: tabular-nums;
}

.rcp-stat-value small {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--planer-grafit-500);
}

.rcp-stat-meter {
  padding: 0 0.75rem;
}

.rcp-stat-text {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

/* <768px: "Dziś razem"/"Przerwa" zostają dwie kolumny w jednym rzędzie,
   "Tydzień" dostaje własny pełnoszerokościowy wiersz zamiast trzeciej
   wąskiej kolumny — na ciasnym ekranie ułamek "0h 26min / 40h" łamał się
   w środku (zgłoszenie Jakuba 2026-07-22). Pasek postępu przechodzi z
   pionowego układu na poziomy, obok wartości. */
@media (max-width: 767.98px) {
  .rcp-stat-row {
    flex-wrap: wrap;
    row-gap: 0.65rem;
  }

  .rcp-stat--week {
    flex: 0 0 100%;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    text-align: left;
    border-left: none;
    border-top: 1px solid var(--planer-grafit-100);
    padding-top: 0.65rem;
    margin-top: 0.15rem;
  }

  .rcp-stat--week .rcp-stat-meter {
    flex: 1;
    padding: 0;
    margin-left: 0.6rem;
  }
}

/* Oś dnia — plan (grafik) nad rzeczywistymi wpisami, z żywą linią "teraz"
   liczoną w JS (`panel_clock.js`) z realnego `start_time`/`end_time`. */
.rcp-timeline {
  background: var(--planer-surface);
  border-radius: var(--bs-border-radius-lg);
  box-shadow: var(--shadow-md);
  padding: 1.5rem 1.75rem 1.4rem;
  margin-bottom: 1.25rem;
}

.rcp-timeline-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-bottom: 1.1rem;
}

.rcp-timeline-title {
  font-family: var(--planer-font-display);
  font-weight: 800;
  font-size: 1.1rem;
}

.rcp-timeline-ruler {
  position: relative;
  height: 1.1rem;
  margin-bottom: 0.35rem;
  font-family: var(--planer-font-mono);
  font-size: 0.68rem;
  color: var(--planer-grafit-500);
}

.rcp-timeline-ruler span {
  position: absolute;
  transform: translateX(-50%);
}

.rcp-timeline-track {
  position: relative;
  height: 2.4rem;
  background: var(--planer-grafit-100);
  border-radius: 10px;
  margin-bottom: 0.5rem;
}

.rcp-timeline-track--plan {
  height: 1.1rem;
  background: transparent;
}

.rcp-timeline-block {
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: 8px;
  display: flex;
  align-items: center;
  padding: 0 0.6rem;
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  color: #fff;
}

.rcp-timeline-block--plan {
  background: var(--planer-primary-light);
  opacity: 0.85;
  height: 1.1rem;
  top: 0;
  border-radius: 6px;
  font-size: 0.7rem;
}

.rcp-timeline-block--work {
  background: var(--planer-primary);
}

/* Przerwa należy do konkretnego wpisu, więc leży w JEGO pasmie jako niższy
   pasek przy dolnej krawędzi — wcześniej była blokiem tej samej wysokości co
   wpis i wyglądała jak osobna, pływająca pozycja bez związku z niczym
   (zgłoszenie Jakuba 2026-08-18). `z-index` nad wpisem, bo leży w jego
   obrysie. */
.rcp-timeline-block--break {
  background: var(--planer-amber);
  top: 0.25rem;
  bottom: 0.25rem;
  /* Pełna wysokość pasma minus wcięcie, a nie niski pasek przy krawędzi:
     przerwa ma czytać się jako NACIĘCIE w pasku wpisu. Pierwsza wersja
     (0.5rem wysokości u dołu) wyglądała jak przypadkowa plamka położona na
     pasku — zgłoszenie Jakuba 2026-08-18, druga tura. `box-shadow` w kolorze
     tła robi wokół niej szczelinę, przez co pasek wygląda na przerwany. */
  height: auto;
  min-width: 6px;
  padding: 0;
  border-radius: 4px;
  box-shadow: 0 0 0 2px var(--planer-surface);
  z-index: 2;
}

/* Etykieta wpisu — wielokropek zamiast ucięcia w połowie słowa; przy bloku
   węższym niż próg w `panel_clock` chowa się całkiem (pełna treść zostaje w
   dymku `title`). */
.rcp-timeline-block-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rcp-timeline-block.is-narrow {
  padding: 0 0.15rem;
}

.rcp-timeline-block.is-narrow .rcp-timeline-block-label {
  display: none;
}

.rcp-timeline-block--ghost {
  background: var(--planer-grafit-300);
  opacity: 0.55;
  color: var(--planer-grafit-700);
}

.rcp-timeline-block--live {
  background: linear-gradient(90deg, var(--planer-primary-dark), var(--planer-primary-light));
}

@media (prefers-reduced-motion: no-preference) {
  .rcp-timeline-block--live {
    animation: rcp-shimmer 2.6s ease-in-out infinite;
  }
}

@keyframes rcp-shimmer {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.18); }
}

.rcp-timeline-now-wrap {
  position: relative;
}

.rcp-timeline-now {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 0;
  border-left: 2px dashed var(--planer-coral);
}

.rcp-timeline-now span {
  position: absolute;
  top: -1.3rem;
  left: 0.3rem;
  font-family: var(--planer-font-mono);
  font-size: 0.68rem;
  color: var(--planer-coral);
  font-weight: 700;
  white-space: nowrap;
}

.rcp-timeline-legend {
  display: flex;
  gap: 1.1rem;
  flex-wrap: wrap;
  margin-top: 0.9rem;
  font-size: 0.78rem;
  color: var(--planer-grafit-700);
}

.rcp-timeline-legend .sw {
  display: inline-block;
  width: 0.65rem;
  height: 0.65rem;
  border-radius: 3px;
  margin-right: 0.35rem;
  vertical-align: -1px;
}

/* Pasek "Trwające" (widok Tabela) — jeden wiersz na otwarty wpis zamiast
   wspólnego flex-wrap, żeby przy kilku równoległych wpisach (Obecność +
   Praca zdalna naraz) etykieta i jej przyciski trzymały się razem, a nie
   gubiły w zawijanym rzędzie (zgłoszenie Jakuba 2026-07-22). */
.rcp-open-list {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  flex: 1 1 auto;
  min-width: 0;
}

.rcp-open-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.rcp-open-row + .rcp-open-row {
  padding-top: 0.6rem;
  border-top: 1px solid var(--planer-grafit-300);
}

.rcp-open-row-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* Tabela widoku tabeli — nagłówki jak etykiety kafli (mono, uppercase),
   wiersz "na żywo" i wiersz zaległego wpisu odróżnione tłem, akcje wiersza
   schowane pod strzałką (`.rcp-row-menu`). */
.rcp-table-wrap {
  background: var(--planer-surface);
  border-radius: var(--bs-border-radius-lg);
  box-shadow: var(--shadow-sm);
  overflow-x: auto;
}

.rcp-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.87rem;
  min-width: 760px;
}

.rcp-table th {
  text-align: left;
  font-family: var(--planer-font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--planer-grafit-500);
  padding: 0.75rem 0.8rem;
  border-bottom: 1px solid var(--planer-grafit-300);
  white-space: nowrap;
}

.rcp-table td {
  padding: 0.65rem 0.8rem;
  border-bottom: 1px solid var(--planer-grafit-100);
  vertical-align: middle;
  font-variant-numeric: tabular-nums;
}

.rcp-table tbody tr:hover {
  background: var(--planer-primary-subtle);
}

.rcp-table tbody tr.rcp-row-live {
  background: var(--planer-primary-subtle-2);
}

.rcp-table tbody tr.rcp-row-alert {
  background: rgba(var(--planer-coral-rgb), 0.08);
}

.rcp-table tbody tr.rcp-row-alert td:first-child {
  box-shadow: inset 3px 0 0 var(--planer-coral);
}

.rcp-act-dot {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  margin-right: 0.4rem;
}

.rcp-act-dot--attendance { background: #55c98a; }
.rcp-act-dot--overtime { background: var(--planer-amber); }
.rcp-act-dot--remote { background: var(--planer-primary); }
.rcp-act-dot--other { background: var(--planer-grafit-500); }

.rcp-row-menu-cell {
  position: relative;
}

.rcp-chevron {
  background: transparent;
  border: none;
  color: var(--planer-grafit-500);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 6px;
}

.rcp-chevron:hover {
  background: var(--planer-grafit-100);
  color: var(--planer-text);
}

.rcp-row-menu {
  position: absolute;
  right: 0;
  top: 2.3rem;
  background: var(--planer-surface);
  border: 1px solid var(--planer-grafit-300);
  border-radius: 12px;
  box-shadow: var(--shadow-md);
  min-width: 12rem;
  padding: 0.35rem;
  display: none;
  z-index: 30;
  text-align: left;
}

.rcp-row-menu.open {
  display: block;
}

/* Flip w górę, gdy pod przyciskiem nie ma miejsca przed stałym dolnym
   paskiem `.navbar-tabbar` (mobile, z-index 1025 > 30 menu — bez tego
   menu renderuje się pod paskiem, zgłoszenie Jakuba 2026-07-22). Klasę
   dokłada JS w `_panel_table.html` po zmierzeniu realnej przestrzeni.
   `calc(100% - 0.6rem)` mirroruje `top: 0.6rem` przycisku `.rcp-chevron`,
   więc menu kończy się dokładnie tam, gdzie zaczyna się przycisk,
   niezależnie od wysokości karty (notatka wydłuża kartę w dół, nie w górę). */
.rcp-row-menu-above {
  top: auto;
  bottom: calc(100% - 0.6rem);
}

/* Widok tabeli: `.rcp-table-wrap` ma `overflow-x: auto` (tabela min-width
   760px), a przeglądarka wymusza wtedy również `overflow-y: auto` — menu
   pozycjonowane absolutnie było przycinane do wysokości tabeli i dokładało
   pionowy pasek przewijania (zgłoszenie Jakuba 2026-08-06: przy jednym
   wpisie menu prawie w całości wypadało poza kontener). Dlatego tu menu jest
   `fixed`, a współrzędne liczy JS w `_panel_table.html` przy otwarciu.
   Karty mobilne (`.rcp-entry`) nie są w kontenerze przewijania i zostają na
   pozycjonowaniu absolutnym razem z flipem `.rcp-row-menu-above`. */
.rcp-table-wrap .rcp-row-menu {
  position: fixed;
  right: auto;
  top: auto;
}

/* Rejestr dzienny `/rcp/zespol` (redesign 2026-08-19, Faza A kierownika) —
   nagłówek dnia przyklejony przy przewijaniu STRONY (nie zagnieżdżony w
   `.rcp-table-wrap`, więc nie dotyczy go ta sama pułapka co przy
   `.schedule-grid-scroll`, patrz komentarz tamże). Kolejne dni stackują się
   naturalnie: przeglądarka chowa poprzedni nagłówek pod następnym w miarę
   przewijania, bez dodatkowego JS. */
.rcp-day-bar {
  position: sticky;
  top: var(--planer-navbar-height);
  z-index: 2;
}

/* Każdy dzień w rejestrze to OSOBNA `<table>` (grupowanie) — bez wspólnego
   `table-layout: fixed` każda tabela auto-dopasowuje kolumny do WŁASNEJ
   treści, więc np. "Pracownik" wychodzi innej szerokości w dniu z długim
   nazwiskiem niż w dniu bez niego i kolumny wizualnie rozjeżdżają się
   między dniami (zgłoszenie Jakuba 2026-08-20). `fixed` każe przeglądarce
   honorować szerokości z `<th style="width:...">` (te same wartości w
   KAŻDEJ tabeli dnia, patrz `team.html`) zamiast przeliczać je z treści —
   scoped do `.rcp-team-table`, żeby nie zmieniać zachowania jedynej innej
   tabeli reużywającej `.rcp-table` (`_panel_table.html`, tam tylko jedna
   tabela na stronę, więc problem w ogóle nie występuje). */
.rcp-team-table .rcp-table {
  table-layout: fixed;
}

/* ==========================================================================
   Pasek filtrów "chipy + panel na żądanie" — koncepcja B z sesji makietowania
   (Artifact "Koncepcje pasków filtrów", 2026-08-20). Pierwsze wdrożenie na
   `/rcp/zespol` (`team.html`); zamierzony jako WSPÓLNY komponent dla reszty
   widoków listy (pracownicy, klienci, poradniki...), które dziś mają
   identyczną, zawsze ~120px wysoką kartę Bootstrapa (etykieta nad każdym
   polem, "Filtruj"/"Wyczyść filtry" na dole) niezależnie od tego, czy filtr
   jest w ogóle aktywny. Domyślnie widać tylko szukajkę + chipy AKTYWNYCH
   filtrów; reszta pól chowa się w `.filter-bar-panel`, rozwijanym przez
   `.js-filter-toggle` (JS: `static/js/filter-bar.js`). Pinezka
   `.js-filter-pin` trzyma panel rozwinięty na stałe (localStorage per
   widok) — Jakub: "żeby nie trzeba było rozwijać za każdym razem".
   ========================================================================== */
.filter-bar {
  margin-bottom: 1rem;
  background: var(--planer-surface);
  border: 1px solid var(--planer-grafit-100);
  border-radius: var(--bs-border-radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.filter-bar-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  padding: 0.55rem 0.75rem;
}

.filter-bar-search {
  position: relative;
  flex: 1 1 200px;
  max-width: 280px;
}

.filter-bar-search .fb-ico {
  position: absolute;
  left: 0.6rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--planer-grafit-500);
  pointer-events: none;
  display: inline-flex;
}

.filter-bar-search .fb-ico svg {
  width: 0.8rem;
  height: 0.8rem;
  fill: currentColor;
}

.filter-bar-search input {
  width: 100%;
  font-size: 0.85rem;
  border: 1px solid var(--planer-grafit-300);
  border-radius: 999px;
  padding: 0.4rem 0.7rem 0.4rem 1.8rem;
  background: var(--planer-bg);
  color: var(--planer-text);
}

.filter-bar-search input:focus {
  outline: 2px solid var(--planer-primary);
  outline-offset: 1px;
}

.filter-chip {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--planer-primary-dark);
  background: var(--planer-primary-subtle);
  border: 1px solid var(--planer-primary-subtle-2);
  border-radius: 999px;
  padding: 0.3rem 0.5rem 0.3rem 0.7rem;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  text-decoration: none;
}

.filter-chip:hover {
  background: var(--planer-primary-subtle-2);
}

.filter-chip-x {
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  background: var(--planer-primary-subtle-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.62rem;
}

.filter-bar-toggle {
  margin-left: auto;
  font-family: var(--planer-font-body);
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--planer-primary-dark);
  background: var(--planer-surface);
  border: 1px solid var(--planer-grafit-300);
  border-radius: 999px;
  padding: 0.35rem 0.7rem;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  cursor: pointer;
}

.filter-bar-toggle:hover {
  background: var(--planer-primary-subtle);
}

.filter-bar-toggle[aria-expanded="true"] {
  background: var(--planer-primary-dark);
  color: var(--planer-on-primary);
  border-color: var(--planer-primary-dark);
}

.fb-caret {
  font-size: 0.55rem;
  transition: transform 0.15s ease;
}

@media (prefers-reduced-motion: reduce) {
  .fb-caret { transition: none; }
}

.filter-bar-toggle[aria-expanded="true"] .fb-caret {
  transform: rotate(180deg);
}

.filter-bar-pin {
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 0.5rem;
  border: 1px solid var(--planer-grafit-300);
  background: var(--planer-surface);
  font-size: 0.8rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  opacity: 0.55;
  filter: grayscale(1);
}

.filter-bar-pin:hover {
  opacity: 0.85;
}

.filter-bar-pin[aria-pressed="true"] {
  opacity: 1;
  filter: none;
  background: rgba(var(--planer-amber-rgb), 0.15);
  border-color: var(--planer-amber);
}

.filter-bar-panel {
  margin-top: 0;
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  padding: 0 0.9rem;
  background: var(--planer-bg);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.75rem;
  transition: max-height 0.18s ease, opacity 0.15s ease, padding 0.18s ease, margin-top 0.18s ease;
}

@media (prefers-reduced-motion: reduce) {
  .filter-bar-panel { transition: none; }
}

.filter-bar-panel.is-open {
  max-height: 220px;
  opacity: 1;
  padding: 0.9rem;
  border-top: 1px solid var(--planer-grafit-100);
}

.filter-bar-panel .fb-field label {
  display: block;
  font-family: var(--planer-font-mono);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--planer-grafit-500);
  margin-bottom: 0.25rem;
}

.filter-bar-panel .fb-field select,
.filter-bar-panel .fb-field input {
  width: 100%;
  font-size: 0.82rem;
  border: 1px solid var(--planer-grafit-300);
  border-radius: 0.5rem;
  padding: 0.4rem 0.5rem;
  background: var(--planer-surface);
  color: var(--planer-text);
}

/* Wielowybór (dziś: działy na /grafik) — checkboxy jako małe pigułki
   zamiast pojedynczego <select>a, żeby dało się zaznaczyć kilka naraz
   (zgłoszenie Jakuba 2026-08-20). `.fb-field-wide` zajmuje całą szerokość
   panelu (`grid-column: 1 / -1`), bo lista działów bywa dłuższa niż jedna
   kolumna siatki `.filter-bar-panel`. */
.fb-field-wide {
  grid-column: 1 / -1;
}

.fb-checkbox-group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.fb-checkbox-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-family: var(--planer-font-body);
  font-size: 0.8rem;
  color: var(--planer-text);
  background: var(--planer-surface);
  border: 1px solid var(--planer-grafit-300);
  border-radius: 999px;
  padding: 0.3rem 0.7rem;
  cursor: pointer;
}

.fb-checkbox-chip:has(input:checked) {
  background: var(--planer-primary-subtle);
  border-color: var(--planer-primary-subtle-2);
  color: var(--planer-primary-dark);
}

/* Nadpisuje ogólną regułę `.filter-bar-panel .fb-field input { width:
   100% }` wyżej — checkbox nie ma być na całą szerokość wiersza. */
.fb-checkbox-chip input[type="checkbox"] {
  width: auto;
  margin: 0;
}

.filter-bar-actions {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 0.1rem;
}

/* Etykieta "przypięte" siedzi TERAZ od razu przy pinezce w górnym rzędzie
   (patrz `.js-filter-pin` w template), nie na dole rozwiniętego panelu —
   Jakub: "nie będzie się nikomu chciało skakać myszką na koniec". Widoczna
   tylko gdy pinezka aktywna (`aria-pressed="true"`, przełączane w JS). */
.fb-pin-hint-inline {
  display: none;
  font-size: 0.72rem;
  color: var(--planer-grafit-500);
  white-space: nowrap;
}

.filter-bar-pin[aria-pressed="true"] ~ .fb-pin-hint-inline {
  display: inline;
}

/* Widok "Tabela" na telefonie (<768px, patrz `d-none d-md-block` /
   `d-md-none` w _panel_table.html) — jeden wpis = jedna karta zamiast
   wiersza tabeli o min-width:760px wymagającego przewijania w bok
   (zgłoszenie Jakuba 2026-07-22). Menu akcji reużywa `.rcp-row-menu`/
   `.rcp-chevron` z widoku tabeli — ten sam skrypt `.js-rcp-chevron`
   obsługuje oba, więc tylko dopisujemy pozycjonowanie guzika. */
.rcp-entry-list {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

.rcp-entry {
  position: relative;
  background: var(--planer-surface);
  border-radius: var(--bs-border-radius-lg);
  box-shadow: var(--shadow-sm);
  padding: 0.85rem 2.6rem 0.85rem 0.9rem;
}

.rcp-entry-live {
  background: var(--planer-primary-subtle-2);
}

.rcp-entry-alert {
  box-shadow: inset 3px 0 0 var(--planer-coral), var(--shadow-sm);
}

.rcp-entry-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.3rem;
}

.rcp-entry-date {
  font-family: var(--planer-font-mono);
  font-size: 0.72rem;
  color: var(--planer-grafit-500);
}

.rcp-entry-status {
  font-family: var(--planer-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-weight: 700;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  background: var(--planer-grafit-100);
  color: var(--planer-grafit-700);
}

.rcp-entry-status--live {
  background: var(--planer-primary-subtle);
  color: var(--planer-primary-dark);
}

.rcp-entry-time {
  font-weight: 700;
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
  margin-bottom: 0.2rem;
}

.rcp-entry-meta {
  font-size: 0.8rem;
  color: var(--planer-grafit-700);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.rcp-entry-note {
  font-size: 0.8rem;
  color: var(--planer-grafit-500);
  margin-top: 0.3rem;
  font-style: italic;
}

.rcp-entry-menu-btn {
  position: absolute;
  top: 0.6rem;
  right: 0.5rem;
}

/* Wariant karty bez menu kontekstowego (historia /rcp/historia) — bez
   przycisku w prawym górnym rogu zapas 2.6rem po prawej byłby pustą dziurą. */
.rcp-entry--flat {
  padding-right: 0.9rem;
}

.rcp-entry-nextday {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--planer-grafit-500);
}

.rcp-entry-breaks {
  font-size: 0.78rem;
  color: var(--planer-grafit-500);
  margin-top: 0.3rem;
  font-variant-numeric: tabular-nums;
}

/* Zakładki na "Moje dane" (Moje dane / Wygląd) — sam wzorzec pigułki co
   .navbar-mgmt-switch, ale w treści strony, nie w navbarze. */
.my-profile-tabs {
  gap: 0.4rem;
}

.my-profile-tabs .nav-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  border-radius: 999px;
  padding: 0.5rem 1rem;
  font-weight: 700;
  color: var(--planer-text);
  background: var(--planer-grafit-100);
}

.my-profile-tabs .nav-link.active {
  background: var(--planer-primary-dark);
  color: var(--planer-on-primary);
}

.my-profile-tabs .nav-link svg {
  width: 1rem;
  height: 1rem;
}

/* Wybór motywu (Moje dane → Wygląd) — karty-radio, status wyboru zawsze
   kolor + obramowanie + etykieta "Aktualny" (nigdy sam kolor, ta sama
   zasada co status grafiku, patrz wiki/10-projekt-ux.md). Podgląd
   kolorów w każdej karcie renderuje się WEWNĄTRZ własnego
   [data-color-theme], niezależnie od aktualnie wybranego motywu strony —
   dzięki temu widać naraz wszystkie cztery, nie tylko ten wybrany. */
.appearance-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 0.9rem;
  max-width: 760px;
}

.appearance-swatch {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  border: 2px solid var(--planer-grafit-300);
  border-radius: 20px;
  padding: 0.9rem 1rem;
  cursor: pointer;
  background: var(--planer-surface);
  box-shadow: var(--shadow-sm);
  min-height: 44px;
}

.appearance-swatch input[type="radio"] {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

.appearance-swatch:has(input:focus-visible) {
  outline: 2px solid var(--planer-primary-dark);
  outline-offset: 2px;
}

/* Obwódka reaguje na natywny stan zaznaczenia radia — NATYCHMIAST po
   kliknięciu karty, jeszcze przed "Zapisz wygląd" (zgłoszenie Jakuba
   2026-07-21: bez tego wybór nowego motywu nie był w żaden sposób
   oznaczony, dopiero zapisany formularz i przeładowanie strony to
   pokazywały). `.is-selected` (serwerowo, z current_user.color_theme)
   zostaje osobno wyłącznie pod etykietę "Aktualny" — to zapisany stan
   konta, nie to, co akurat zaznaczone w formularzu. */
.appearance-swatch:has(input:checked) {
  border-color: var(--planer-primary-dark);
  box-shadow: var(--shadow-md);
}

.appearance-swatch-preview {
  flex: none;
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 12px;
  background: linear-gradient(135deg, var(--planer-primary-dark), var(--planer-primary) 70%);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
}

.appearance-swatch-preview .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .12);
}

.appearance-swatch-preview .dot-1 { background: var(--planer-on-primary); }
.appearance-swatch-preview .dot-2 { background: var(--planer-coral); }
.appearance-swatch-preview .dot-3 { background: var(--planer-amber); }

.appearance-swatch-body {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  font-size: 0.9rem;
}

.appearance-swatch-body .badge-role {
  align-self: flex-start;
}

.rcp-row-menu button,
.rcp-row-menu a {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  width: 100%;
  background: transparent;
  border: none;
  text-align: left;
  padding: 0.5rem 0.6rem;
  border-radius: 8px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--planer-text);
  text-decoration: none;
}

.rcp-row-menu button:hover,
.rcp-row-menu a:hover {
  background: var(--planer-primary-subtle);
  color: var(--planer-text);
}

.rcp-row-menu button.text-danger:hover {
  background: rgba(242, 99, 123, 0.12);
}

@media (max-width: 700px) {
  .rcp-hero-value {
    font-size: 2.4rem;
  }
}

.navbar-more-sheet .dropdown-item .badge-today {
  margin-left: auto;
}

/* ==========================================================================
   Warsztat raportów (/raporty) i Kreator raportu (/raporty/generuj) —
   redesign 2026-07-27 ("Koncepcja B" z sesji wizualnej u Jakuba). Panel
   filtrów + wynik obok siebie, bez przycisku "Filtruj" — htmx odświeża
   #warsztat-panel przy każdej zmianie filtra (patrz `reports_panel` w
   `reports.py`). Reużywa `.bento-grid`/`.bento-tile` (dashboard) i
   `.rcp-view-switch` (przełącznik Miesięczny/RCP — ten sam wzorzec
   segmentowanej pigułki co widok Tabela/Kalendarz w Historii RCP,
   `time_tracking/history.html`) bez zmian, więc tu tylko to, czego jeszcze
   nie było: układ dwukolumnowy, chipy filtrów i kreator krokowy.
   ========================================================================== */

.reports-workshop {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 1.25rem;
  align-items: start;
}

/* Bez `min-width: 0` domyślna automatyczna minimalna szerokość elementu
   grida to min-content jego zawartości — tu tabeli raportu z `table-layout:
   fixed` (patrz niżej), której min-content to suma szerokości kolumn.
   `.table-scroll-sync` ma `overflow-x: auto`, ale to nie pomaga, bo
   overflow jest ustawiony na potomku, nie na samym elemencie grida — więc
   `<section>` (a za nim cała strona) i tak rozciąga się do szerokości
   tabeli, zamiast pozwolić `.table-scroll-sync` przewijać się we własnym
   zakresie. Efekt: przy 125% skalowania (typowe ~1536px efektywnej
   szerokości CSS na laptopie 1920px) trzeba było przewijać całą stronę w
   poziomie, nie samą tabelę (zgłoszenie z 2026-08-07, zrzut ekranu).
   `min-width: 0` znosi automatyczne minimum i oddaje kontrolę nad
   przewijaniem `.table-scroll-sync`, więc problem znika przy KAŻDEJ
   szerokości okna — media query niżej (901–1400px) zawężająca kolumny
   zostaje jako osobne usprawnienie czytelności, nie jako jedyny mechanizm
   ochronny. */
.reports-workshop > section {
  min-width: 0;
}

.filter-rail {
  position: sticky;
  top: calc(var(--planer-navbar-height) + 1rem);
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}

/* Chip — pod spodem realny checkbox/radio (dostępny, działa bez JS,
   wyzwala `change` który htmx nasłuchuje na całym `<form>`), wizualnie
   ukryty; sąsiadujący <label> przejmuje wygląd pigułki. Ten sam kontrakt
   co `.form-check` gdzie indziej w apce, inny tylko wygląd (pigułka
   zamiast kwadratu) — świadomie osobna klasa zamiast nadpisywać
   `.form-check` globalnie. */
.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
}

.chip-input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  pointer-events: none;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  border: 1px solid var(--planer-grafit-300);
  background: var(--planer-surface);
  color: var(--planer-text);
  border-radius: 999px;
  padding: 0.35rem 0.85rem;
  font-size: 0.82rem;
  cursor: pointer;
  user-select: none;
}

.chip-input:checked + .chip {
  background: var(--planer-primary-dark);
  border-color: var(--planer-primary-dark);
  color: var(--planer-on-primary);
}

.chip-input:focus-visible + .chip {
  outline: 3px solid rgba(var(--planer-primary-rgb), .45);
  outline-offset: 2px;
}

/* Podgląd na żywo — kropka jak `.rcp-hero-dot` (ten sam wzorzec "stanu
   aktywnego" co zegar RCP), tu oznacza "panel aktualizuje się sam, bez
   przycisku Filtruj". */
.live-indicator {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-bottom: 1rem;
  font-size: 0.84rem;
  color: var(--planer-grafit-700);
}

.live-indicator-dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: #55c98a;
  box-shadow: 0 0 8px #55c98a;
  flex-shrink: 0;
}

@media (prefers-reduced-motion: no-preference) {
  .live-indicator-dot {
    animation: reports-live-pulse 2.2s ease-in-out infinite;
  }
}

@keyframes reports-live-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

/* htmx dodaje `.htmx-request` na elemencie z `hx-indicator` w trakcie
   żądania (konwencja htmx, nie nasza) — przygaszenie starej treści, żeby
   było widać że coś się dzieje, bez spinnera przykrywającego cały panel. */
#warsztat-panel.htmx-request,
#wizard-preview.htmx-request {
  opacity: 0.5;
  transition: opacity 0.15s ease;
}

/* Ekran laptopa przy powiększeniu 125% ma zwykle ~1300–1550px efektywnej
   szerokości CSS — za wąsko na sztywny sidebar 300px + tabelę "Pracownicy"
   (12 kolumn, suma szerokości ~1480px), więc strona wymagała przewijania w
   poziomie (zgłoszenie ze skrzynki zadań, 2026-07-27). Sidebar i kolumny
   węższe TYLKO w tym pośrednim przedziale — poniżej 900px układ i tak
   przechodzi na jedną kolumnę (patrz niżej), więc tam oryginalne szerokości
   kolumn zostają (telefon ma i tak całą szerokość ekranu dla samej tabeli). */
@media (min-width: 901px) and (max-width: 1400px) {
  .reports-workshop { grid-template-columns: 240px 1fr; }
  .reports-workshop .report-col-xs { width: 45px; }
  .reports-workshop .report-col-sm { width: 65px; }
  .reports-workshop .report-col-md { width: 90px; }
  .reports-workshop .report-col-lg { width: 115px; }
  .reports-workshop .report-col-xl { width: 145px; }
}

/* Historia RCP (`history.html`, widok "Tabela") — 7 kolumn o pełnych
   szerokościach report-col-* sumuje się do ~1080px, szerzej niż `.container`
   przy lg/xl (960/1140px z paddingiem karty), więc tabela i tak wymuszała
   `.table-scroll-sync` do przewijania w bok mimo że breakpoint karty/tabeli
   jest ustawiony na 992px (zgłoszenie Jakuba 2026-08-21, zrzut przy
   1048px). Węższe kolumny — te same wartości co `.reports-workshop` wyżej —
   permanentnie, nie tylko w przedziale: treść tu jest krótsza (daty,
   godziny, krótkie etykiety) niż w wielokolumnowych raportach, więc pełne
   szerokości nigdy nie są potrzebne, nawet na bardzo szerokim ekranie. */
.rcp-history-table-card .report-col-xs { width: 45px; }
.rcp-history-table-card .report-col-sm { width: 65px; }
.rcp-history-table-card .report-col-md { width: 90px; }
.rcp-history-table-card .report-col-lg { width: 115px; }
.rcp-history-table-card .report-col-xl { width: 145px; }

@media (max-width: 900px) {
  .reports-workshop { grid-template-columns: 1fr; }
  .filter-rail { position: static; }
}

/* --- Kreator raportu (/raporty/generuj) ---------------------------------- */

.wizard-card {
  max-width: 780px;
}

.wizard-stepper {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
  max-width: 660px;
}

.wizard-stepper .step {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  flex: 1;
}

.wizard-stepper .dot {
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 50%;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--planer-font-mono);
  font-size: 0.78rem;
  font-weight: 700;
  background: var(--planer-grafit-100);
  color: var(--planer-grafit-500);
  border: 2px solid transparent;
}

.wizard-stepper .step-label {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--planer-grafit-500);
  white-space: nowrap;
}

.wizard-stepper .line {
  flex: 1;
  height: 2px;
  background: var(--planer-grafit-300);
}

.wizard-stepper .step.done .dot,
.wizard-stepper .step.current .dot {
  border-color: var(--planer-primary-dark);
}

.wizard-stepper .step.done .dot {
  background: var(--planer-primary-dark);
  color: var(--planer-on-primary);
}

.wizard-stepper .step.done .step-label {
  color: var(--planer-text);
}

.wizard-stepper .step.current .dot {
  background: var(--planer-surface);
  color: var(--planer-primary-dark);
  box-shadow: var(--shadow-focus);
}

.wizard-stepper .step.current .step-label {
  color: var(--planer-primary-dark);
}

.wizard-stepper .step.done + .line,
.wizard-stepper .step.current + .line {
  background: var(--planer-primary-dark);
}

/* Pasek "Wstecz / Dalej" kreatora raportów — przyklejony do dołu okna.
   Bez tego płynie z treścią, a krok 3 (podgląd) potrafi mieć kilka ekranów
   wysokości: przy 61 pracownikach przyciski lądowały pod listą pracowników
   I pod tabelą obłożenia stanowisk, więc żeby przejść do kroku 4 trzeba było
   przescrollować cały raport (zgłoszenie Jakuba 2026-08-07). Kroki 1/2/4 są
   krótkie, więc sticky nic tam nie zmienia — pasek stoi tam, gdzie stał.

   `position: sticky` liczy się tu względem STRONY, nie kontenera: pasek jest
   bezpośrednim dzieckiem `.card-body` formularza, poza przewijalnymi
   wrapperami tabel (`.table-scroll-sync` ma `overflow`, więc byłby własnym
   kontenerem przewijania — patrz pułapka opisana przy `.schedule-grid-scroll`).
   Ujemne marginesy boczne rozciągają tło na pełną szerokość karty, żeby przy
   przewijaniu treść nie prześwitywała obok paska. */
.wizard-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  position: sticky;
  bottom: 0;
  z-index: 3;
  margin: 1.5rem -1.25rem -1.25rem;
  padding: 0.9rem 1.25rem calc(0.9rem + env(safe-area-inset-bottom, 0px));
  background: var(--planer-surface);
  border-top: 1px solid var(--planer-grafit-100);
  border-radius: 0 0 var(--bs-card-border-radius, 0.75rem) var(--bs-card-border-radius, 0.75rem);
  box-shadow: 0 -10px 22px -18px rgba(var(--planer-primary-dark-rgb), 0.55);
}

/* Telefon: pasek zakładek jest `position: fixed` przy dolnej krawędzi, więc
   sticky z `bottom: 0` schowałby się pod nim — dokładnie ten sam błąd, który
   naprawialiśmy dla treści strony (c565e01). Podnosimy pasek o wysokość
   zakładek — zmierzone 59,5px (3,72rem) na 2026-08-07, stąd 3,8rem z lekkim
   zapasem; safe-area wnosi już sam tabbar, więc tutaj wraca zwykły padding,
   inaczej odstęp policzyłby się dwa razy. Ujemne marginesy schodzą do 1rem
   razem z `.card-body` (patrz breakpoint przy linii 766). */
@media (max-width: 767.98px) {
  .wizard-actions {
    bottom: calc(3.8rem + env(safe-area-inset-bottom, 0px));
    margin: 1.25rem -1rem -1rem;
    padding: 0.75rem 1rem;
  }
}

.idea-chip {
  font-family: var(--planer-font-mono);
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  background: var(--planer-primary-subtle);
  color: var(--planer-primary-dark);
  padding: 0.15rem 0.45rem;
  border-radius: 4px;
  margin-left: 0.4rem;
}

.export-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

.export-option {
  border: 1px solid var(--planer-grafit-300);
  border-radius: 1rem;
  padding: 1.1rem;
  text-align: center;
}

.export-option .export-icon {
  font-size: 1.6rem;
  display: block;
  margin-bottom: 0.5rem;
}

@media (max-width: 720px) {
  .export-grid { grid-template-columns: 1fr; }
  .wizard-stepper .step-label { display: none; }
}

/* ==========================================================================
   Formularz poradnika (/admin/poradniki/…) — „wariant B", 2026-08-17.
   Układ (siatka 300px + szyna) to `.reports-workshop` / `.filter-rail` wyżej,
   świadomie reużyte bez zmian. Tutaj tylko dwie rzeczy, których nigdzie
   indziej nie było.
   ========================================================================== */

/* Tytuł ma wyglądać na nagłówek, nie na pole formularza — treść jest na tym
   ekranie ważniejsza od ustawień. Nadal zwykły `<input>`: kreskowana linia i
   brak ramki to jedyna różnica, focus wraca do zwykłego obramowania Bootstrapa,
   żeby nie zgubić widocznego wskazania klawiaturowego. */
.guide-title-input.form-control {
  font-family: var(--planer-font-display);
  font-weight: 800;
  font-size: 1.6rem;
  line-height: 1.25;
  padding-left: 0;
  padding-right: 0;
  border: 0;
  border-bottom: 2px dashed var(--planer-grafit-300);
  border-radius: 0;
  background-color: transparent;
}

.guide-title-input.form-control:focus {
  border-bottom-style: solid;
  box-shadow: none;
}

/* Lista nazwisk pod licznikiem odbiorców — przy materiale ogólnofirmowym ma
   ich być ~100, więc własne przewijanie zamiast wypychania przycisku zapisu
   poza ekran. */
.guide-recipient-list {
  max-height: 12rem;
  overflow-y: auto;
}

/* Wiersz bloku treści wraca do układu poziomego: globalna reguła
   `.list-group-item { flex-direction: column }` (wyżej, dla list dotykowych)
   zwijała nazwę pliku i strzałki kolejności do dwóch linii, przez co lista
   dziesięciu bloków robiła się dwa razy dłuższa niż musi. Zawężone do tej
   listy, żeby nie ruszać pozostałych ekranów. */
.guide-block-list .list-group-item {
  flex-direction: row;
  align-items: flex-start;
}

/* ===================================================================
   Poradniki — biblioteka pracownika i panel zarządzania
   (redesign 2026-08-17, warianty P3 + A2/A3)
   =================================================================== */

/* Pas nagłówka z paskiem postępu po prawej. Postęp trzyma stałą szerokość,
   żeby nie skakał przy zmianie liczby materiałów. */
.guides-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: 1rem;
}

.guides-progress {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  min-width: 12rem;
  flex: 0 1 16rem;
}

.guides-filters {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.65rem 0.75rem;
  margin-bottom: 1rem;
  background-color: var(--planer-surface);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  box-shadow: var(--shadow-sm);
}

.guides-filters input[type="search"] {
  flex: 1 1 14rem;
  min-width: 10rem;
}

/* Szyna rodzajów + lista. Poniżej `md` szyna ląduje nad listą jako zwykły
   blok — sticky kolumna na telefonie zjadałaby pół ekranu. */
.guides-layout {
  display: grid;
  grid-template-columns: 13rem minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}

@media (max-width: 767.98px) {
  .guides-layout {
    grid-template-columns: minmax(0, 1fr);
  }
}

.guides-rail {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.5rem;
  background-color: var(--planer-surface);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius);
  box-shadow: var(--shadow-sm);
  position: sticky;
  /* Pasek nawigacji jest `sticky-top`, więc szyna musi zaczynać się pod nim. */
  top: calc(var(--planer-navbar-height) + 1rem);
}

@media (max-width: 767.98px) {
  .guides-rail {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
  }
}

.guides-rail-title {
  padding: 0.25rem 0.5rem;
}

@media (max-width: 767.98px) {
  .guides-rail-title {
    width: 100%;
  }
}

.guides-rail-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  padding: 0.35rem 0.5rem;
  border-radius: var(--bs-border-radius-sm);
  color: var(--planer-grafit-700);
  text-decoration: none;
  font-size: 0.9rem;
}

.guides-rail-item:hover,
.guides-rail-item:focus-visible {
  background-color: var(--planer-primary-subtle);
  color: var(--planer-text);
}

.guides-rail-item.is-active {
  background-color: var(--planer-primary-subtle);
  color: var(--planer-text);
  font-weight: 600;
}

.guides-rail-count {
  font-variant-numeric: tabular-nums;
  color: var(--planer-grafit-500);
}

.guides-section-title {
  margin: 0 0 0.5rem;
}

.guides-list > .guides-section-title:not(:first-child) {
  margin-top: 1.25rem;
}

/* Wiersz materiału. `.list-group-item` jest globalnie kolumnowy (listy
   dotykowe), więc medalion i plakietki wymagają jawnego powrotu do wiersza. */
.guide-row {
  flex-direction: row;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  transition: background-color 0.15s ease-in-out;
}

.guide-row:hover {
  background-color: var(--planer-primary-subtle);
}

/* Lewy akcent tylko przy nieotwartych — to jedyne rozróżnienie, które
   pracownik skanuje wzrokiem, zanim zacznie czytać tytuły.

   `box-shadow: inset`, nie `border-left`: Bootstrap zeruje obramowania przez
   `.list-group-flush > .list-group-item { border-width: 0 0 1px }`, więc
   `border-left` w ogóle się nie rysował, a przebicie go specyficznością
   przesuwałoby treść nieotwartych wierszy o 3px w prawo. Cień wewnętrzny
   nie zajmuje miejsca w układzie. */
.guide-row--unviewed {
  box-shadow: inset 3px 0 0 var(--planer-primary);
}

.guide-row-main {
  flex: 1 1 12rem;
  min-width: 0;
}

/* Nad `stretched-link` tytułu, żeby plakietki dało się zaznaczyć i żeby
   klik w chip nie zachowywał się inaczej niż klik w wiersz. */
.guide-row-meta {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  flex-wrap: wrap;
}

.guide-medal {
  flex: 0 0 auto;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--bs-border-radius-sm);
  display: grid;
  place-items: center;
  color: #ffffff;
}

.guide-medal .bi {
  width: 1.1rem;
  height: 1.1rem;
  margin: 0;
}

.guide-medal--pdf { background-color: var(--planer-coral); }
.guide-medal--film { background-color: var(--planer-amber); }
.guide-medal--mix { background-color: var(--planer-primary); }
.guide-medal--text { background-color: var(--planer-grafit-500); }

.guide-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  padding: 0.05rem 0.45rem;
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-sm);
  font-size: 0.78rem;
  color: var(--planer-grafit-700);
  white-space: nowrap;
}

.guide-chip--muted {
  background-color: var(--planer-grafit-100);
  color: var(--planer-grafit-500);
  white-space: normal;
}

/* --- panel zarządzania: kolumna zasięgu --- */

.guide-reach-col {
  min-width: 9rem;
}

.guide-reach-cell {
  min-width: 9rem;
}

/* Kolumna zasięgu jest szeroka (tabela ma tylko cztery kolumny), więc sam
   miernik trzyma własną szerokość — inaczej „5 / 12" i „42%" rozjeżdżały się
   na pół ekranu i przestawały czytać się jako jedna informacja. */
.guide-reach {
  display: block;
  max-width: 11rem;
  color: inherit;
}

.guide-reach-numbers {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  color: var(--planer-grafit-700);
  margin-bottom: 0.15rem;
}

.guide-reach-pct {
  font-weight: 600;
  color: var(--planer-text);
}

/* Koral rezerwujemy dla realnego problemu: materiał, który nie dotarł nawet
   do co czwartej osoby. Kolor NIE zastępuje liczby — procent stoi obok. */
.guide-reach-pct.is-low {
  color: var(--planer-coral);
}

.guide-reach .tile-meter {
  height: 6px;
}

.tile-meter-fill.is-low {
  background: linear-gradient(90deg, var(--planer-coral), var(--planer-amber));
}

.guide-group-row td {
  background-color: var(--planer-grafit-100);
  padding-top: 0.4rem;
  padding-bottom: 0.4rem;
}

.guide-sort {
  color: inherit;
  text-decoration: none;
}

.guide-sort:hover {
  text-decoration: underline;
}

.guide-sort.is-active::after {
  content: " ↑";
  color: var(--planer-primary);
}

/* -------------------------------------------------------------------
   Strona poradnika — strumień bloków treści
   (zgłoszenie Jakuba 2026-08-17: tekst ginął obok osadzonego czytnika
   PDF na 60vh i filmu na 70vh — czytał się jak podpis pod nimi, a nie
   jak równorzędna część materiału)
   ------------------------------------------------------------------- */

.guide-block {
  margin-bottom: 1.75rem;
}

/* Blok tekstowy jako karta z akcentem: dostaje własne tło, wcięcie i
   lewą krawędź w kolorze marki, więc ma taką samą "wagę" na stronie co
   sąsiadujące osadzone media. Miara wiersza ograniczona do 68ch —
   przy pełnej szerokości ekranu wiersz robił się nieczytelnie długi. */
.guide-block-text {
  white-space: pre-line;
  max-width: 68ch;
  padding: 1.15rem 1.35rem;
  border-radius: 0.75rem;
  border-left: 4px solid var(--planer-primary);
  background: var(--planer-primary-subtle);
  color: var(--planer-text);
  font-size: 1.02rem;
  line-height: 1.75;
}

/* Etykieta nad blokiem medialnym — ta sama typografia co eyebrow w menu
   użytkownika, żeby czytnik PDF/film czytał się jako JEDNA z części
   materiału, a nie jako cała strona. */
.guide-block-eyebrow {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--planer-grafit-700);
  margin-bottom: 0.4rem;
}

.guide-block-eyebrow .bi {
  color: var(--planer-primary);
}

/* Ramka mediów zamiast bootstrapowego `border rounded`: cieńsza i w
   kolorze tła strony, więc prostokąt czytnika przestaje krzyczeć. */
.guide-block-media {
  border: 1px solid var(--planer-grafit-300);
  border-radius: 0.75rem;
  overflow: hidden;
}

/* -------------------------------------------------------------------
   Obecność dziś (`/obecnosc-dzis`) — wariant C makiety, wybór Jakuba
   2026-08-17. Ekran przestał być czterema zwijanymi kartami z osobnymi
   tabelami: jest jedna lista, a kategoria schodzi do roli kolorowej
   kolumny "Stan", licznikowego filtra nad nią i paska "co wymaga
   reakcji" na samej górze.

   Cztery tony stanu są semantyką DANYCH, nie akcentem marki — dlatego
   nie używają `--planer-primary` i nie zmieniają się z motywem
   kolorystycznym konta (tak samo jak kolory statusów grafiku, patrz
   sekcja `--status-*`). W trybie ciemnym ta sama rola przechodzi na
   drugą stronę skali jasności: kolor tekstu się rozjaśnia, tło stanu
   ciemnieje.
   ------------------------------------------------------------------- */

.attendance-tone-missing {
  --attendance-tone: #c62828;
  --attendance-tone-bg: #fdeef1;
}

.attendance-tone-unplanned {
  --attendance-tone: #b5651d;
  --attendance-tone-bg: #fdf0e3;
}

.attendance-tone-present {
  --attendance-tone: #1f7a4d;
  --attendance-tone-bg: #e7f4ed;
}

.attendance-tone-away {
  --attendance-tone: var(--planer-grafit-500);
  --attendance-tone-bg: var(--planer-grafit-100);
}

[data-bs-theme="dark"] .attendance-tone-missing {
  --attendance-tone: #f2637b;
  --attendance-tone-bg: #3d2431;
}

[data-bs-theme="dark"] .attendance-tone-unplanned {
  --attendance-tone: #f2914a;
  --attendance-tone-bg: #3b2d21;
}

[data-bs-theme="dark"] .attendance-tone-present {
  --attendance-tone: #4fbf88;
  --attendance-tone-bg: #1e3a2c;
}

/* Pasek "co wymaga reakcji" nad listą — odpowiada na pytanie kierownika
   ("czy dziś coś się sypie?") zanim spojrzy na nazwiska. Zastępuje dawny
   `alert-success` pokazywany tylko przy zerze rozbieżności: teraz pasek
   jest zawsze, zmienia się tylko jego wymowa. */
.attendance-alert {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.8rem 1.15rem;
  margin-bottom: 1rem;
  border-radius: 1rem;
  border: 1px solid var(--attendance-tone-bg);
  background: var(--attendance-tone-bg);
  color: var(--planer-text);
  line-height: 1.45;
}

.attendance-alert--attention {
  --attendance-tone: #c62828;
  --attendance-tone-bg: #fdeef1;
  border-color: rgba(198, 40, 40, .35);
}

.attendance-alert--ok {
  --attendance-tone: #1f7a4d;
  --attendance-tone-bg: #e7f4ed;
  border-color: rgba(31, 122, 77, .3);
}

[data-bs-theme="dark"] .attendance-alert--attention {
  --attendance-tone: #f2637b;
  --attendance-tone-bg: #3d2431;
  border-color: rgba(242, 99, 123, .4);
}

[data-bs-theme="dark"] .attendance-alert--ok {
  --attendance-tone: #4fbf88;
  --attendance-tone-bg: #1e3a2c;
  border-color: rgba(79, 191, 136, .4);
}

.attendance-alert-icon {
  display: inline-flex;
  flex: none;
  color: var(--attendance-tone);
}

.attendance-alert-icon .bi {
  width: 1.15rem;
  height: 1.15rem;
}

.attendance-alert strong {
  color: var(--attendance-tone);
}

/* Filtr po stanie — pigułki w tej samej karcie co `.filter-bar` (dział),
   dopisane jako trzecia sekcja shellu 2026-08-20 (dwie osobno stylizowane
   białe karty wyglądały niespójnie — zgłoszenie Jakuba). Oddzielone
   hairlinem, bo to DRUGI, niezależny filtr (dział leci na serwer
   formularzem, stan zawęża wiersze po stronie przeglądarki). */
.attendance-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.75rem;
  border-top: 1px solid var(--planer-grafit-100);
}

.attendance-chip {
  --attendance-tone: var(--planer-grafit-500);
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
  border: 1px solid var(--planer-grafit-300);
  background: var(--planer-surface);
  color: var(--planer-grafit-700);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.3;
  cursor: pointer;
}

.attendance-chip:hover {
  border-color: var(--planer-primary);
  color: var(--planer-text);
}

.attendance-chip:focus-visible {
  outline: none;
  box-shadow: var(--shadow-focus);
}

.attendance-chip.is-on {
  background: var(--planer-primary-dark);
  border-color: var(--planer-primary-dark);
  color: var(--planer-on-primary);
}

.attendance-chip-dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  flex: none;
  background: var(--attendance-tone);
}

/* Kropka na wciśniętej pigułce leży na śliwkowym tle — token stanu
   (ciemna czerwień/zieleń) zlałby się z nim, więc na czas zaznaczenia
   przechodzi na kolor tekstu przycisku. */
.attendance-chip.is-on .attendance-chip-dot {
  background: currentColor;
}

.attendance-chip-count {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--planer-grafit-500);
}

.attendance-chip.is-on .attendance-chip-count {
  color: inherit;
  opacity: .75;
}

/* Kolumna "Stan" + pasek stanu przy krawędzi wiersza. Kolor NIE jest
   jedynym nośnikiem informacji (WCAG 1.4.1) — obok kropki zawsze stoi
   pełna etykieta tekstowa. */
.attendance-state {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-weight: 600;
  font-size: 0.875rem;
  color: var(--attendance-tone);
  white-space: nowrap;
}

.attendance-state-dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  flex: none;
  background: var(--attendance-tone);
}

.attendance-row > td:first-child {
  box-shadow: inset 3px 0 0 var(--attendance-tone);
}
