/* ============================================================================
   Opsownia — warstwa własna na Pico CSS.  Etap 0 migracji (docs/UI_PICO_MIGRACJA.md)

   Ładowana ZAWSZE po pico.min.css. Zawiera dokładnie trzy rzeczy:
     1) tokeny (kolory, typografia, gęstość) — przestawienie zmiennych Pico,
     2) warstwę zgodności — to, co Pico robi inaczej niż potrzebuje panel operacyjny,
     3) 30 klas komponentów, których Pico nie ma.

   Zasada: jeśli coś da się opisać semantyką HTML (article, table, details, dialog,
   nav, form), NIE dostaje klasy. Klasa powstaje wyłącznie wtedy, gdy komponent
   pojawia się w co najmniej dwóch widokach — inwentarz z 2026-08-18 pokazał
   385 klas własnych, z czego ogon (digest 83 klasy na 99 użyć) to były ubrania
   jednorazowe, nie komponenty.
   ============================================================================ */

/* ---------------------------------------------------------------- 1. TOKENY */

/* UWAGA na kolejność i specyficzność: Pico definiuje swoją paletę pod selektorem
   `:root:not([data-theme=dark]), [data-theme=light]` (0,2,0). Zwykłe `:root` (0,1,0)
   PRZEGRYWA i nadpisanie po cichu nie działa — sprawdzone pomiarem 2026-08-18:
   na wzorcowni wychodziło 14 kolorów tekstu, w tym niebieski link i sześć różnych
   szarości nagłówków prosto z Pico. Dlatego mapowanie zmiennych Pico siedzi pod
   TYMI SAMYMI selektorami, a nasze własne tokeny (--ui-*) pod zwykłym :root. */

:root {
    --ui-radius: 8px;
    --ui-radius-sm: 6px;

    /* Jedna skala wysokości kontrolek jednowierszowych — pomiar 2026-08-20 na
       /app/projekty/{id} znalazł RÓŻNE wysokości w jednym rzędzie mimo
       identycznego paddingu/obramowania: przycisk .secondary/select 39px,
       input[type=search] 42px (sam UA line-height, nie CSS), .icon-btn 32px.
       --ui-btn-h ujednolica przyciski tekstowe/select/input; --ui-btn-h-sm
       zostaje dla kwadratowego przycisku-glifu. */
    /* 39px = padding 8px+8px + border 1px+1px + line-height 21px — dokładnie to,
       co Pico już liczy poprawnie dla przycisku/selecta. px, nie rem: to jest
       dopasowanie do istniejącej geometrii, nie nowa skala. */
    --ui-btn-h: 39px;
    --ui-btn-h-sm: 2rem;

    /* Cztery tiery typografii — tyle, ile dopuszcza UI_ZASADY. Pomiar 2026-08-17
       znalazł ich 13–24 na widok, przez co metadane albo krzyczały wersalikami,
       albo zlewały się z treścią. */
    --ui-eyebrow-size: 11px;
    --ui-meta-size: 12.5px;
    --ui-body-size: 15px;
    --ui-title-size: clamp(1.25rem, 1rem + 0.9vw, 1.7rem);

    /* Pico jest domyślnie luźne (strona treściowa). Panel operacyjny potrzebuje
       gęstości: skala odstępów i pola formularzy ścięte o ~25%. */
    --pico-spacing: 0.75rem;
    --pico-block-spacing-vertical: 0.9rem;
    --pico-block-spacing-horizontal: 1rem;
    --pico-form-element-spacing-vertical: 0.5rem;
    --pico-form-element-spacing-horizontal: 0.7rem;
    --pico-typography-spacing-vertical: 0.75rem;
    --pico-border-radius: var(--ui-radius);
    --pico-font-size: 100%;
    --pico-line-height: 1.5;

    /* Aplikacja nie deklarowała ŻADNEGO kroju dla treści — każdy system podstawiał
       swój. Stos systemowy, bez pliku z sieci (asset-mapper, brak CDN). */
    --pico-font-family: "Inter var", Inter, -apple-system, BlinkMacSystemFont,
        "Segoe UI", "Noto Sans", Roboto, "Helvetica Neue", Arial, sans-serif;
}

/* --- motyw jasny ------------------------------------------------------------ */

:root:not([data-theme="dark"]), [data-theme="light"] {
    /* Jedna rodzina szarości (zinc). Dotąd były dwie naraz — chłodna z tokenów
       i ciepła oliwka wpisana w szablony — i to, a nie czcionki, dawało wrażenie
       "rozmywania się" interfejsu (docs/UI_ZASADY.md, pomiar 2026-08-13). */
    --ui-ink: #09090b;
    --ui-ink-soft: #27272a;
    --ui-muted: #71717a;
    --ui-line: #e4e4e7;
    --ui-line-strong: #d4d4d8;
    --ui-surface: #ffffff;
    --ui-surface-2: #fafafa;

    /* Kolor WYŁĄCZNIE dla stanu. Cztery stany: tekst + tło + ramka. */
    /* Akcent = pomarańcz (wybór właściciela 2026-08-18). Odcień 700, nie 600:
       zmierzony kontrast 5,18:1 działa W OBIE STRONY — tekst na bieli i biały tekst
       na akcencie. Orange-600 daje 3,56:1, czyli za mało na tekst (AA wymaga 4,5). */
    --ui-accent: #c2410c;
    --ui-accent-hover: #9a3412;
    --ui-accent-fg: #ffffff;
    --ui-accent-soft: #fff7ed;
    --ui-accent-line: #fed7aa;

    /* Kolizja rozwiązana świadomie: skoro pomarańcz zaczął znaczyć "kliknij", stan
       "wymaga Ciebie" nie może być pomarańczowy — przesunięty w żółcień (4,75:1 na
       swoim tle). Rozróżnia je też kontekst: akcent występuje wyłącznie jako pełny
       przycisk, link i pierścień fokusu, a stan wyłącznie jako pigułka. */
    --ui-ok: #047857;        --ui-ok-bg: #ecfdf5;      --ui-ok-line: #bbf7d0;
    --ui-warn: #a16207;      --ui-warn-bg: #fefce8;    --ui-warn-line: #fde68a;
    --ui-down: #b91c1c;      --ui-down-bg: #fef2f2;    --ui-down-line: #fecaca;
    --ui-pending: #7e22ce;   --ui-pending-bg: #faf5ff; --ui-pending-line: #e9d5ff;

    --pico-color: var(--ui-ink-soft);
    --pico-muted-color: var(--ui-muted);
    --pico-muted-border-color: var(--ui-line);
    --pico-background-color: var(--ui-surface-2);
    --pico-card-background-color: var(--ui-surface);
    --pico-card-border-color: var(--ui-line);
    --pico-card-sectioning-background-color: var(--ui-surface);
    --pico-form-element-background-color: var(--ui-surface);
    --pico-form-element-border-color: var(--ui-line-strong);
    --pico-form-element-color: var(--ui-ink);
    --pico-form-element-placeholder-color: var(--ui-muted);
    --pico-table-border-color: var(--ui-line);
    --pico-code-background-color: var(--ui-surface-2);
    --pico-code-color: var(--ui-ink-soft);
    --pico-blockquote-border-color: var(--ui-line);

    /* Pico daje KAŻDEMU poziomowi nagłówka inny odcień (sześć szarości). Jedna. */
    --pico-h1-color: var(--ui-ink);
    --pico-h2-color: var(--ui-ink);
    --pico-h3-color: var(--ui-ink);
    --pico-h4-color: var(--ui-ink);
    --pico-h5-color: var(--ui-ink);
    --pico-h6-color: var(--ui-ink);

    /* Akcent zamiast niebieskiego Pico (#0172ad): pomarańcz na przyciskach głównych,
       linkach i pierścieniu fokusu. */
    --pico-primary: var(--ui-accent);
    --pico-primary-background: var(--ui-accent);
    --pico-primary-hover: var(--ui-accent-hover);
    --pico-primary-hover-background: var(--ui-accent-hover);
    --pico-primary-border: var(--ui-accent);
    --pico-primary-inverse: var(--ui-accent-fg);
    --pico-primary-underline: rgba(194, 65, 12, 0.35);
    --pico-primary-focus: rgba(194, 65, 12, 0.25);
    --pico-secondary: var(--ui-ink-soft);
    --pico-secondary-background: var(--ui-surface-2);
    --pico-secondary-border: var(--ui-line-strong);
    --pico-secondary-inverse: var(--ui-ink);
    --pico-secondary-hover: var(--ui-ink);
    --pico-secondary-hover-background: var(--ui-line);
}

/* --- motyw ciemny ----------------------------------------------------------- */

/* Dok terminala jest ciemny, więc ciemna aplikacja przestaje się z nim gryźć.
   Własne komponenty MUSZĄ znać oba warianty — inaczej ciemny motyw rozjeżdża się
   dokładnie na tych 31 klasach, a nie na Pico. */
@media only screen and (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --ui-ink: #fafafa;
        --ui-ink-soft: #e4e4e7;
        --ui-muted: #a1a1aa;
        --ui-line: #27272a;
        --ui-line-strong: #3f3f46;
        --ui-surface: #18181b;
        --ui-surface-2: #09090b;
        --ui-accent: #fb923c;
        --ui-accent-hover: #fdba74;
        --ui-accent-fg: #18181b;
        --ui-accent-soft: #2a1206;
        --ui-accent-line: #7c2d12;
        --ui-ok: #6ee7b7;      --ui-ok-bg: #052e22;      --ui-ok-line: #14532d;
        --ui-warn: #fcd34d;    --ui-warn-bg: #2a2206;    --ui-warn-line: #854d0e;
        --ui-down: #fca5a5;    --ui-down-bg: #2a0a0a;    --ui-down-line: #7f1d1d;
        --ui-pending: #d8b4fe; --ui-pending-bg: #1e1030; --ui-pending-line: #4c1d95;

        --pico-color: var(--ui-ink-soft);
        --pico-muted-color: var(--ui-muted);
        --pico-muted-border-color: var(--ui-line);
        --pico-background-color: var(--ui-surface-2);
        --pico-card-background-color: var(--ui-surface);
        --pico-card-border-color: var(--ui-line);
        --pico-card-sectioning-background-color: var(--ui-surface);
        --pico-form-element-background-color: #111113;
        --pico-form-element-border-color: var(--ui-line-strong);
        --pico-form-element-color: var(--ui-ink);
        --pico-table-border-color: var(--ui-line);
        --pico-code-background-color: #111113;
        --pico-code-color: var(--ui-ink-soft);
        --pico-h1-color: var(--ui-ink);
        --pico-h2-color: var(--ui-ink);
        --pico-h3-color: var(--ui-ink);
        --pico-h4-color: var(--ui-ink);
        --pico-h5-color: var(--ui-ink);
        --pico-h6-color: var(--ui-ink);
        --pico-primary: var(--ui-accent);
        --pico-primary-background: var(--ui-accent);
        --pico-primary-hover-background: var(--ui-accent-hover);
        --pico-primary-border: var(--ui-accent);
        --pico-primary-inverse: var(--ui-accent-fg);
        --pico-primary-underline: rgba(251, 146, 60, 0.4);
        --pico-secondary: var(--ui-ink-soft);
        --pico-secondary-background: #1f1f23;
        --pico-secondary-border: var(--ui-line-strong);
        --pico-secondary-inverse: var(--ui-ink);
    }
}

[data-theme="dark"] {
    color-scheme: dark;
    --ui-ink: #fafafa;
    --ui-ink-soft: #e4e4e7;
    --ui-muted: #a1a1aa;
    --ui-line: #27272a;
    --ui-line-strong: #3f3f46;
    --ui-surface: #18181b;
    --ui-surface-2: #09090b;
    --ui-accent: #fb923c;
    --ui-accent-hover: #fdba74;
    --ui-accent-fg: #18181b;
    --ui-accent-soft: #2a1206;
    --ui-accent-line: #7c2d12;
    --ui-ok: #6ee7b7;      --ui-ok-bg: #052e22;      --ui-ok-line: #14532d;
    --ui-warn: #fcd34d;    --ui-warn-bg: #2a2206;    --ui-warn-line: #854d0e;
    --ui-down: #fca5a5;    --ui-down-bg: #2a0a0a;    --ui-down-line: #7f1d1d;
    --ui-pending: #d8b4fe; --ui-pending-bg: #1e1030; --ui-pending-line: #4c1d95;

    --pico-color: var(--ui-ink-soft);
    --pico-muted-color: var(--ui-muted);
    --pico-muted-border-color: var(--ui-line);
    --pico-background-color: var(--ui-surface-2);
    --pico-card-background-color: var(--ui-surface);
    --pico-card-border-color: var(--ui-line);
    --pico-card-sectioning-background-color: var(--ui-surface);
    --pico-form-element-background-color: #111113;
    --pico-form-element-border-color: var(--ui-line-strong);
    --pico-form-element-color: var(--ui-ink);
    --pico-table-border-color: var(--ui-line);
    --pico-code-background-color: #111113;
    --pico-code-color: var(--ui-ink-soft);
    --pico-h1-color: var(--ui-ink);
    --pico-h2-color: var(--ui-ink);
    --pico-h3-color: var(--ui-ink);
    --pico-h4-color: var(--ui-ink);
    --pico-h5-color: var(--ui-ink);
    --pico-h6-color: var(--ui-ink);
    --pico-primary: var(--ui-accent);
    --pico-primary-background: var(--ui-accent);
    --pico-primary-hover-background: var(--ui-accent-hover);
    --pico-primary-border: var(--ui-accent);
    --pico-primary-inverse: var(--ui-accent-fg);
    --pico-primary-underline: rgba(251, 146, 60, 0.4);
    --pico-secondary: var(--ui-ink-soft);
    --pico-secondary-background: #1f1f23;
    --pico-secondary-border: var(--ui-line-strong);
    --pico-secondary-inverse: var(--ui-ink);
}

/* --------------------------------------------------- 2. WARSTWA ZGODNOŚCI */

/* Pico daje przyciskom width:100% (sensowne na stronie treściowej, nie w panelu).
   Bez tego nagłówek widoku rozjeżdża się na pół ekranu — dokładnie to zobaczyliśmy
   w pilotażu 2026-08-17. `:not(.icon-btn)` na KAŻDEJ gałęzi listy jest obowiązkowe:
   `<button type="button" class="icon-btn">` dopasowywał się przez `[type="button"]`
   nawet gdy sama gałąź `button` miała wyjątek — .icon-btn zwężał się do ~10px
   (min-content glifu) w każdym panelu bocznym, stąd "poupychane" ikony w Linkach
   (zmierzone 2026-08-20: szerokość 10px, wysokość 39px zamiast kwadratu 32px). */
button:not(.btn-block):not(.icon-btn), [type="submit"]:not(.btn-block):not(.icon-btn),
[type="button"]:not(.btn-block):not(.icon-btn),
[type="reset"]:not(.btn-block):not(.icon-btn), [role="button"]:not(.btn-block):not(.icon-btn) {
    width: auto;
}

/* Pico koloruje KAŻDY link akcentem. W panelu operacyjnym lista 50 plików robi
   się wtedy ścianą pomarańczu i kolor przestaje cokolwiek znaczyć. Link ma kolor
   tekstu i podkreślenie; akcent zostaje dla akcji głównej, fokusu i pozycji, na
   której właśnie jesteś — czyli dla "kliknij", a nie dla "to jest tekst". */
a:not([role="button"]):not(.nav-link):not(.icon-btn):not(.btn):not(.pill):not([class*="-btn"]):not([class*="btn-"]) {
    color: inherit;
    /* Pico ustawia na linku ZMIENNĄ --pico-color (nie tylko `color`), a potomny
       akapit koloruje się przez var(--pico-color). Bez tego wiersza tytuły zadań
       w panelach dashboardu były pomarańczowe mimo poprawnego koloru samego <a>
       (pomiar CDP 2026-08-18). Akcent ma znaczyć "kliknij", nie "to jest tekst". */
    --pico-color: inherit;
    text-decoration-color: var(--ui-line-strong);
}

a:not([role="button"]):not(.nav-link):not(.icon-btn):not(.btn):not(.pill):not([class*="-btn"]):not([class*="btn-"]):hover {
    color: var(--ui-accent);
    text-decoration-color: currentColor;
}

/* ---- WARSTWA PRZEJŚCIOWA (kasowana razem z app.css, widok po widoku) --------
   Pico maluje KAŻDY <button> na kolor akcentu. Stare komponenty aplikacji mają
   własny wygląd i przyjmują gołe <button> jako element sterujący (uchwyt doku,
   zakładki terminali, przyciski ikonowe, paski narzędzi) — bez tego resetu robią
   się pomarańczowymi klockami. Pomiar 2026-08-18: pasek doku terminala.
   Każda pozycja z tej listy znika, gdy odpowiadający jej widok zostanie przepisany. */
:is(.terminal-dock, .quick-tools, .issue-toolbox, .app-sidebar, .mobile-header,
    .dashboard-widget, .digest-card-wrap, .monitor-card, .inline-action, .app-modal,
    .para-select, .capture-mobile-navgrid, .table-toolbar) button:not(.btn):not(.icon-btn),
button.icon-button, button.drag-handle, button.pin-button, button.help-dot,
button.sidebar-toggle, button.sidebar-restore, button.terminal-dock-tab-name,
button.terminal-dock-tab-close, button.terminal-dock-tab-menu {
    background: transparent;
    border-color: transparent;
    color: inherit;
    box-shadow: none;
}


/* Pico ma WŁASNĄ klasę `.grid` i od 992 px rozkłada nią dzieci w KOLUMNY
   (repeat(auto-fit, minmax(0,1fr))). Szablony używają `grid` w znaczeniu
   tailwindowym — "po prostu display:grid", czyli jeden element pod drugim.
   Skutek: `class="grid gap-2"` w formularzach robiło na desktopie poziomy rządek
   pól (zgłoszenie: formatka „nowe zadanie" w szufladzie rozjechana). Miejsca,
   które naprawdę chcą kolumn, deklarują je jawnie przez grid-cols-* i są tu
   wyłączone. */
.grid:not([class*="grid-cols-"]) {
    grid-template-columns: minmax(0, 1fr);
}

/* Cyfry w interfejsie danych nie mogą "tańczyć" przy zmianie wartości. */
table, .meta, .count, .stat, .pill, .nav-badge, time {
    font-variant-numeric: tabular-nums;
}

/* Pico ma własną skalę nagłówków (32/28/25,6/22,4/19,2/16 px) — sześć rozmiarów tam,
   gdzie UI_ZASADY dopuszcza cztery tiery w całej aplikacji. Przycinamy ją do trzech
   poziomów tytułu, bo panel operacyjny nie jest artykułem: tytuł strony, tytuł panelu,
   tytuł pozycji. Reszta hierarchii idzie wagą i kolorem, nie kolejnym rozmiarem. */
body { font-size: var(--ui-body-size); }
h1 { font-size: var(--ui-title-size); font-weight: 650; letter-spacing: -0.01em; }
h2, h3, h4, h5, h6 { font-size: 1rem; font-weight: 650; }

/* Waga 700 to czwarta waga w systemie, w którym mają być trzy (400/500/600).
   Pico daje ją `strong`/`b`/`th`; sprowadzamy do 600 — hierarchię robi rozmiar
   i kolor, nie kolejny stopień grubości. */
strong, b, th { font-weight: 600; }

/* `code` w Pico jest względne (0,8em), więc w metadanych schodziło do 10 px,
   a w nagłówku puchło do 22 px — dwa dodatkowe tiery z jednego elementu. */
code, kbd, samp { font-size: var(--ui-meta-size); }

/* Kontrolki i tabele: jeden rozmiar (14 px/600 dla akcji, 15 px dla treści tabeli),
   zamiast domyślnych 16 px, które w gęstym panelu robią z każdego przycisku klocek. */
button, [type="submit"], [type="button"], [type="reset"], [role="button"], select, input, textarea {
    font-size: 14px;
}

button, [type="submit"], [type="button"], [type="reset"], [role="button"] {
    font-weight: 600;
}

table { font-size: var(--ui-body-size); }
th { font-size: var(--ui-meta-size); color: var(--ui-muted); }
small { font-size: var(--ui-meta-size); }

/* Tytuły bez sierot; treść czytana nigdy szersza niż 72 znaki (reguła 1). */
h1, h2, h3, h4, p {
    text-wrap: pretty;
}

.prose {
    max-width: 72ch;
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ------------------------------------------------------- 3. KOMPONENTY (30) */

/* --- typografia pomocnicza (3 tiery, których Pico nie nazywa) --- */

.eyebrow {
    color: var(--ui-muted);
    font-size: var(--ui-eyebrow-size);
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

/* `hgroup > *:last-child` w Pico ma wyższą specyficzność niż sama klasa, więc
   podtytuł w nagłówku panelu wracał do 16px — stąd drugi selektor. */
.meta,
hgroup > .meta {
    color: var(--ui-muted);
    font-size: var(--ui-meta-size);
    font-weight: 500;
}

/* Licznik przy nagłówku panelu. ZERO nie pokazuje się wcale — pusty panel to
   jedna linia, nie karta z zerem (UI_ZASADY, reguła 4). */
.count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.5rem;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    background: var(--ui-surface-2);
    border: 1px solid var(--ui-line);
    color: var(--ui-muted);
    font-size: var(--ui-eyebrow-size);
    font-weight: 600;
}

/* Licznik przejmuje ton, gdy liczba NIESIE stan (np. monitory w awarii,
   zgłoszenia czekające na Ciebie) — wtedy sama liczba mówi, że trzeba zajrzeć. */
.count.is-ok   { border-color: var(--ui-ok-line);   background: var(--ui-ok-bg);   color: var(--ui-ok); }
.count.is-warn { border-color: var(--ui-warn-line); background: var(--ui-warn-bg); color: var(--ui-warn); }
.count.is-down { border-color: var(--ui-down-line); background: var(--ui-down-bg); color: var(--ui-down); }

/* --- pigułka: WYŁĄCZNIE stan --- */

/* Pomiar 2026-08-17: 730 pigułek na widoku wdrożeń, 507 na logach, 199 na liście
   projektów — w 1–5 wzorach graficznych. Kształt znaczył cztery różne rzeczy naraz
   (stan, typ, data, licznik), więc oko przestawało go czytać. Tu znaczy jedno. */
/* Przełącznik segmentowy (filtr „kto dodał zadanie" na dashboardzie): rząd
   przycisków bez fantomowych marginesów Pico i bez własnej geometrii — wysokość
   bierze z .icon-btn/button, jak .toolbar. */
.segmented {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.segmented > button,
.segmented > .count {
    margin: 0;
}

.segmented > button.is-active {
    border-color: var(--ui-accent);
    background: var(--ui-accent);
    color: var(--ui-accent-fg);
    font-weight: 600;
}

.pill {
    display: inline-flex;
    align-items: center;
    gap: 0.35em;
    border: 1px solid var(--ui-line);
    border-radius: 999px;
    padding: 0.15em 0.6em;
    background: var(--ui-surface-2);
    color: var(--ui-muted);
    font-size: var(--ui-eyebrow-size);
    font-weight: 600;
    letter-spacing: 0.02em;
    white-space: nowrap;
}

.pill.is-ok      { border-color: var(--ui-ok-line);      background: var(--ui-ok-bg);      color: var(--ui-ok); }
.pill.is-warn    { border-color: var(--ui-warn-line);    background: var(--ui-warn-bg);    color: var(--ui-warn); }
.pill.is-down    { border-color: var(--ui-down-line);    background: var(--ui-down-bg);    color: var(--ui-down); }
.pill.is-pending { border-color: var(--ui-pending-line); background: var(--ui-pending-bg); color: var(--ui-pending); }

/* Pigułka jako przełącznik (katalog roboczy, filtr): bieżący wybór bierze akcent,
   bo to "tu jesteś", a nie stan obiektu. */
.pill[aria-current="page"] {
    border-color: var(--ui-accent);
    background: var(--ui-accent-soft);
    color: var(--ui-accent);
}

/* --- nagłówek panelu: tytuł + licznik + akcje --- */

/* flex-wrap jest tu obowiązkowy: dok terminala potrafi zabrać 900px szerokości,
   a wtedy przyciski wchodziły na tytuł (pomiar 2026-08-17, @1440 + dok 900). */
.panel-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem 1rem;
    margin-bottom: 0.75rem;
}

/* Nagłówek z podtytułem: <hgroup> ma zostać kolumną, nie wpaść w rządek
   ustawiany dla zwykłego tytułu obok pigułek. */
.panel-head > hgroup:first-child { display: block; }

.panel-head > :first-child {
    min-width: 0;
}

/* Flex-baseline jest dla wzorca "tytuł + pigułka w jednym rzędzie"
   (np. <div><h2>Zadania</h2><span class="count">3</span></div>) — TYLKO gdy
   pierwsze dziecko nie ma .eyebrow. Bez :not(:has()) ta sama reguła łapała
   też wzorzec eyebrow+tytuł+podtytuł (3 elementy z natury pod sobą, nie obok
   siebie) używany w większości widoków przez _page_header.html.twig — na
   Pixelu 8 Pro (412px) łamała tytuł "Do zrobienia dziś" na cztery linie po
   jednym-dwóch znakach, bo trzy elementy dzieliły jeden wąski rząd zamiast
   stać w kolumnie (zmierzone na żywo 2026-08-19). */
/* :not(hgroup) — bez tego ta reguła bije regułę `.panel-head > hgroup:first-child
   { display: block }` kilka linii wyżej (0,3,0 kontra 0,2,1) i <hgroup> wpadał
   w rządek razem z podtytułem. Zmierzone na Pixelu 8 Pro (2026-08-23): tytuł
   "Udostępnione do Opsowni" łamał się na trzy linie po kilka znaków, bo dzielił
   wąski rząd z akapitem .meta. To ta sama klasa błędu, co opisana wyżej dla
   wzorca eyebrow+tytuł+podtytuł. */
.panel-head > :first-child:not(hgroup):not(:has(.eyebrow)) {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
}

.panel-head h1, .panel-head h2, .panel-head h3 {
    margin: 0;
    /* Tytuł potrafi zawierać długi ciąg bez spacji (nazwa konta w nawiasie,
       adres e-mail) — bez tego rozpychał nagłówek poza ekran na telefonie:
       123px poziomego przewijania na /app/konektory/13/konfiguracja (2026-08-18). */
    overflow-wrap: anywhere;
}

.page-subtitle, .panel-head .meta {
    overflow-wrap: anywhere;
}

.panel-head-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
}

/* --- lista pozycji: JEDEN wzorzec dla zadań, notatek, linków, plików, monitorów --- */

.row-list {
    display: flex;
    flex-direction: column;
    /* akcje schodzą pod tytuł, gdy lista jest wąska — próg liczony od szerokości
       LISTY, nie okna, bo szerokość zabiera dok terminala, a nie rozdzielczość */
    container-type: inline-size;
    margin: 0;
    padding: 0;
    list-style: none;
    /* Zadania nie mają limitu po stronie serwera (materiały/notatki tak, patrz
       DashboardController::getProjectCaptureItems limit=8) — długa lista
       rozpychała panel i zsuwała sąsiednie panele w dół (2026-08-20). Panel
       sam nie scrolluje (strona przewija się normalnie), więc to jeden poziom
       scrolla, nie scroll w scrollu. Próg na oko: ~6-7 pozycji widocznych. */
    max-height: min(48vh, 420px);
    overflow-y: auto;
}

.row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: start;
    gap: 0.5rem 0.75rem;
    padding: 0.6rem 0;
    border-top: 1px solid var(--ui-line);
}

.row-list > .row:first-child { border-top: 0; }
.row.is-active { background: var(--ui-surface-2); }

.row-main { min-width: 0; }

/* Wiersz bywa zbudowany dwojako: link W ŚRODKU (.row-main > a) albo cały wiersz
   JEST linkiem (a.row-main) — tak robi dashboard. Oba warianty muszą wyglądać
   tak samo, inaczej te same listy różnią się podkreśleniem. */
a.row-main,
.row-main a, .row-main > strong {
    font-size: var(--ui-body-size);
    font-weight: 600;
    color: inherit;
    text-decoration: none;
}

.row-main a:hover { text-decoration: underline; }

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

/* Próg dobrany pomiarowo: panel w kolumnie bocznej ma ~300 px, a tytuł + dwa
   glify mieszczą się obok siebie do ~15 rem. Wyżej ustawiony próg zwijał akcje
   pod tytuł tam, gdzie było na nie miejsce (wzorcownia, 2026-08-18). */
@container (max-width: 15rem) {
    .row { grid-template-columns: minmax(0, 1fr); }
    .row-actions { justify-content: flex-start; }
}

/* --- karta źródła (układ zaakceptowany 2026-08-17: ikona + nazwa + licznik) --- */

.chip {
    display: inline-grid;
    place-items: center;
    min-width: 1.6rem;
    height: 1.6rem;
    padding-inline: 0.35rem;
    border-radius: var(--ui-radius-sm);
    background: var(--ui-surface-2);
    border: 1px solid var(--ui-line);
    color: var(--ui-ink-soft);
    font-size: var(--ui-eyebrow-size);
    font-weight: 600;
}

.source {
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius);
    padding: 0.7rem 0.85rem;
    margin-top: 0.6rem;
}

.source-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.source-head > strong {
    flex: 1;
    min-width: 0;
    overflow-wrap: anywhere;
    font-size: var(--ui-body-size);
}

.filelist {
    margin: 0.4rem 0 0;
    padding: 0;
    list-style: none;
}

.filelist > li {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.75rem;
    padding: 0.3rem 0;
    border-top: 1px solid var(--ui-line);
}

/* --- pasek liczb w nagłówku widoku --- */

.stat-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 1.75rem;
    margin-top: 0.6rem;
}

.stat {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    color: var(--ui-muted);
    font-size: var(--ui-meta-size);
    font-weight: 500;
}

.stat > strong {
    color: var(--ui-ink-soft);
    font-size: var(--ui-meta-size);
    font-weight: 600;
}

/* --- pasek narzędzi nad listą (filtry, sortowanie, akcje zbiorcze) --- */

.toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
}

/* Pola w pasku narzędzi mają szerokość treści, nie 100% (to drugie jest domyślne
   w Pico i sensowne w formularzu, nie w filtrze nad listą). */
.toolbar > input, .toolbar > select {
    margin: 0;
    width: auto;
    min-width: 8rem;
}

.toolbar > input[type="search"] {
    min-width: 14rem;
}

.toolbar > .spacer { margin-left: auto; }

/* Pico rozciąga przyciski wewnątrz <form> na całą szerokość. W formularzu treściowym
   to bywa sensowne, w panelu operacyjnym nie: przycisk "Zapisz monitor" miał 1140px.
   Szerokość z treści; pełną szerokość wymusza się świadomie klasą .btn-block —
   stąd `:not()`, bo `form [type="submit"]` (0,1,1) samo w sobie bije `.btn-block` (0,1,0). */
form button:not(.btn-block):not(.icon-btn), form [type="submit"]:not(.btn-block):not(.icon-btn),
form [type="button"]:not(.btn-block):not(.icon-btn), form [role="button"]:not(.btn-block):not(.icon-btn),
.toolbar button:not(.btn-block):not(.icon-btn), .toolbar [type="submit"]:not(.btn-block):not(.icon-btn),
.panel-head button:not(.btn-block):not(.icon-btn) {
    width: auto;
}

/* Data i godzina nigdy nie zawijają się na trzy linie w kolumnie tabeli. */
time { white-space: nowrap; }

/* W komórce tabeli akcje zostają w jednej linii (inaczej pinezka spada pod
   pozostałe ikony), a nagłówek sortowania nie łamie się na strzałce. */
td .row-actions { flex-wrap: nowrap; }
th a { white-space: nowrap; }

/* Pico rozciąga elementy `role="group"` na całą szerokość (sensowne przy polu
   z przyciskiem w formularzu, bez sensu w pasku narzędzi i w nagłówku panelu). */
.toolbar [role="group"], .panel-head [role="group"] {
    width: auto;
}

.toolbar [role="group"] > *, .panel-head [role="group"] > * {
    flex: 0 0 auto;
}

/* --- pusty stan: jedna linia, nie karta --- */

.empty {
    color: var(--ui-muted);
    font-size: var(--ui-meta-size);
    padding: 0.5rem 0;
}

/* --- siatki: liczba kolumn dobiera się sama, kolumna ma minimum --- */

/* Reguła 2 z UI_ZASADY: wąsko = jedna kolumna, szeroko = kilka; nigdy ściskanie
   kolumny poniżej progu. Container query, bo szerokość zabiera dok terminala. */
.panels {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
}

.board {
    display: grid;
    gap: 0.75rem;
    grid-auto-flow: column;
    grid-auto-columns: minmax(18rem, 1fr);
    overflow-x: auto;
    padding-bottom: 0.5rem;
}

.board-col {
    min-width: 0;
}

/* --- kontrolki jednowierszowe: jedna wysokość --- */

/* Wewnątrz wysuwanych popoverów (.inline-action-panel, np. "Dodaj link",
   "Wybierz zdjęcia z Google Photos") etykieta bywa dłuższa niż wąski popover
   (23rem, węziej w kolumnie bocznej) i przycisk MUSI móc zawinąć się do dwóch
   linii — sztywna wysokość by go obcięła. Dlatego reguła niżej ich nie łapie,
   patrz wyjątek `.inline-action-panel button` pod spodem. */
button:not(.icon-btn):not(.btn-block),
[type="submit"]:not(.btn-block):not(.icon-btn), [type="button"]:not(.btn-block):not(.icon-btn),
[type="reset"]:not(.btn-block):not(.icon-btn),
a[role="button"]:not(.icon-btn), summary[role="button"]:not(.icon-btn),
select,
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="hidden"]):not([type="file"]) {
    height: var(--ui-btn-h);
}

/* Wyjątek: przycisk w popoverze rośnie z treścią (patrz komentarz wyżej). */
.inline-action-panel button:not(.icon-btn):not(.btn-block) {
    height: auto;
    min-height: var(--ui-btn-h);
}

/* --- przycisk-glif --- */

.icon-btn {
    display: inline-grid;
    place-items: center;
    width: var(--ui-btn-h-sm);
    height: var(--ui-btn-h-sm);
    padding: 0;
    margin: 0;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius-sm);
    background: var(--ui-surface);
    color: var(--ui-ink-soft);
    font-size: 14px;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
}

/* Pico daje KAŻDEMU przyciskowi/polu margin-bottom: var(--pico-spacing) (12px) —
   sensowne w stosie pól formularza, fatalne w poziomym rzędzie akcji: dokładało
   niewidzialne 12px pod spodem, więc .panel-head-actions/.row-actions/.toolbar
   wychodziły wyższe niż ich własna zawartość (zmierzone: kontener 44px przy
   przycisku 32px — stąd wrażenie "poupychanych" akcji, 2026-08-20 runda 2). */
.panel-head-actions :is(button, [type="submit"], [type="button"], [type="reset"], select, input, details),
.row-actions :is(button, [type="submit"], [type="button"], [type="reset"], select, input, details),
.toolbar :is(button, [type="submit"], [type="button"], [type="reset"], select, input, details) {
    margin: 0;
}

.icon-btn:hover { border-color: var(--ui-line-strong); background: var(--ui-surface-2); }
.icon-btn.is-danger:hover { border-color: var(--ui-down-line); color: var(--ui-down); }

/* --- ucinanie tekstu --- */

/* Tytuł zawija się do dwóch linii; nie ucinamy do nieczytelności (reguła 3).
   Pomiar: tytuły zadań bywały widoczne w 19%. */
.clamp-2 {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
}

/* Ścieżki ucinamy OD PRZODU — znaczenie jest na końcu. Treść musi być w <bdi>. */
.trunc-start {
    display: block;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    direction: rtl;
    text-align: left;
}

/* --- powłoka aplikacji --- */

.shell {
    display: flex;
    min-height: 100dvh;
}

.shell-nav {
    position: sticky;
    top: 0;
    flex: 0 0 16rem;
    height: 100dvh;
    overflow-y: auto;
    border-right: 1px solid var(--ui-line);
    background: var(--ui-surface);
    padding: 0.75rem;
}

.shell-main {
    flex: 1;
    min-width: 0;
    /* własny kontekst kontenera: panele wewnątrz reagują na szerokość TREŚCI,
       a nie okna — to jest jedyny sposób, żeby układ widział dok terminala */
    container-type: inline-size;
    padding: 1rem;
}

/* Pico układa `nav` poziomo (menu strony). Nawigacja powłoki jest pionowa —
   deklarujemy to na kontenerze, zamiast dokładać klasę do każdego linku. */
nav:has(> .nav-link) {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    /* MUSI być jawne: stary `.nav-list` ma w app.css `justify-content: space-between`,
       które przy siatce nic nie robiło, ale po zmianie na flex rozrzuciło pozycje
       menu po całej wysokości paska (pomiar 2026-08-18: 146px odstępu między
       pozycjami zamiast 2px). Klasyczny koszt migracji "po kawałku": reguła, która
       spała, budzi się przy zmianie sąsiada. */
    justify-content: flex-start;
    gap: 2px;
}

.nav-link {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.6rem;
    border-radius: var(--ui-radius-sm);
    color: var(--ui-ink-soft);
    font-size: 14px;
    font-weight: 500;
    text-decoration: none;
}

.nav-link:hover { background: var(--ui-surface-2); }
.nav-link[aria-current="page"] { background: var(--ui-surface-2); color: var(--ui-ink); font-weight: 600; }

.nav-badge {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.5rem;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    background: var(--ui-surface-2);
    border: 1px solid var(--ui-line);
    color: var(--ui-muted);
    font-size: var(--ui-eyebrow-size);
    font-weight: 600;
}

.nav-link.is-ok   .nav-badge { border-color: var(--ui-ok-line);   background: var(--ui-ok-bg);   color: var(--ui-ok); }
.nav-link.is-warn .nav-badge { border-color: var(--ui-warn-line); background: var(--ui-warn-bg); color: var(--ui-warn); }
.nav-link.is-down .nav-badge { border-color: var(--ui-down-line); background: var(--ui-down-bg); color: var(--ui-down); }

/* Pozycja aktywna: pasek akcentu z lewej zamiast samego tła — kolor akcentu
   znaczy "tu jesteś / to klikalne", a nie "stan". */
.nav-link[aria-current="page"] {
    box-shadow: inset 2px 0 0 var(--ui-accent);
}

/* --- komunikaty --- */

.toast {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    border: 1px solid var(--ui-line);
    border-left-width: 3px;
    border-radius: var(--ui-radius);
    padding: 0.6rem 0.8rem;
    background: var(--ui-surface);
    font-size: var(--ui-meta-size);
}

.toast.is-ok   { border-left-color: var(--ui-ok);   background: var(--ui-ok-bg);   color: var(--ui-ok); }
.toast.is-warn { border-left-color: var(--ui-warn); background: var(--ui-warn-bg); color: var(--ui-warn); }
.toast.is-down { border-left-color: var(--ui-down); background: var(--ui-down-bg); color: var(--ui-down); }

/* ------------------------------------------------- 4. PLIKI: DRZEWO I TYPY */

/* Drzewo katalogów na natywnych <details> — bez biblioteki. Powody, nie lenistwo:
   (1) działa wewnątrz ramek Turbo, więc podkatalog może się doładować z serwera
   zamiast siedzieć w pamięci przeglądarki, (2) `summary` jest fokusowalne i
   obsługiwane z klawiatury bez ani jednej linii JS, (3) rozwinięcie przeżywa
   nawigację Turbo, bo jest stanem DOM, a nie stanem komponentu.
   Rozważony i odrzucony: Shoelace <sl-tree> — dobry, ale dokłada runtime web
   components i własny motyw, który trzeba by godzić z tokenami Pico. */

.tree {
    --tree-indent: 1.15rem;
    font-size: var(--ui-body-size);
}

.tree ul {
    margin: 0;
    padding-left: var(--tree-indent);
    list-style: none;
    /* pionowa prowadnica poziomu — czytelniejsza niż same wcięcia przy 5+ poziomach */
    border-left: 1px solid var(--ui-line);
}

.tree li { padding: 0; }

.tree-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.25rem 0.4rem;
    border-radius: var(--ui-radius-sm);
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}

.tree-row:hover { background: var(--ui-surface-2); }
.tree-row.is-active { background: var(--ui-accent-soft); box-shadow: inset 2px 0 0 var(--ui-accent); }

.tree-row > .name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tree-row > .meta { flex: 0 0 auto; }

/* Strzałka rozwinięcia: obracamy glif, zamiast trzymać dwie ikony.
   Pico rysuje własny znacznik `summary` jako obrazek tła po PRAWEJ stronie
   (sensowne w akordeonie, mylące w drzewie, gdzie strzałka należy do wcięcia). */
.tree summary { list-style: none; }
.tree summary::-webkit-details-marker { display: none; }
.tree summary::after { display: none !important; }
.tree summary { background-image: none; padding-right: 0.4rem; }

/* Pico stylizuje znaczniki `li`; w drzewie i listach pozycji nie ma po nich śladu. */
.tree li, .tree ul, .row-list, .row-list > li, .filelist, .filelist > li {
    list-style: none;
}

.tree li::marker, .row-list > li::marker, .filelist > li::marker { content: none; }

.tree summary .ico-chevron {
    flex: 0 0 auto;
    width: 0.9rem;
    height: 0.9rem;
    transition: transform 120ms ease;
    color: var(--ui-muted);
}

.tree details[open] > summary .ico-chevron { transform: rotate(90deg); }

/* Pasek akcji gałęzi: „Dodaj plik" / „Dodaj katalog" dla katalogu, który
   właśnie oglądasz (drzewo plików konektora). Wchodzi w każdą rozwiniętą
   gałąź, więc musi być cichy — to nie jest główna akcja strony. */
.tree-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    padding: 0.2rem 0 0.35rem 0.4rem;
}

.tree-actions form { margin: 0; }
.tree-action {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    margin: 0;
    padding: 0.15rem 0.5rem;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius-sm);
    background: none;
    color: var(--ui-muted);
    font-size: 0.8rem;
    line-height: 1.6;
    cursor: pointer;
}

.tree-action:hover { background: var(--ui-surface-2); color: var(--ui-ink); }

/* Katalog wskazany jako cel wklejania zrzutu (Ctrl+V). */

/* --- znacznik typu pliku ---------------------------------------------------- */

/* Kolor rodziny plików to WYJĄTEK od zasady "kolor tylko dla stanu" — i jedyny.
   Uzasadnienie: to tożsamość, nie stan, występuje w skali 2 rem i jest wyciszona
   (tło na 8–10% nasycenia), więc nie konkuruje z pigułkami stanu. Rodzin jest
   sześć, nie dwadzieścia — po to, żeby dało się je zapamiętać. */

.ico {
    width: 1.05em;
    height: 1.05em;
    flex: 0 0 auto;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.file-mark {
    position: relative;
    display: inline-grid;
    place-items: center;
    width: 2rem;
    height: 2rem;
    flex: 0 0 auto;
    border-radius: var(--ui-radius-sm);
    border: 1px solid var(--ui-line);
    background: var(--ui-surface-2);
    color: var(--ui-muted);
    overflow: hidden;
}

.file-mark .ico { width: 1rem; height: 1rem; }

/* Prawdziwa miniatura wygrywa z ikoną wszędzie, gdzie ją mamy (załączniki NOTATEK,
   indeksy Photos/Drive). Ikona jest fallbackiem, nie domyślnym rozwiązaniem. */
.file-mark img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.file-mark.is-folder { color: var(--ui-ink-soft); background: color-mix(in srgb, var(--ui-ink-soft) 8%, var(--ui-surface)); }
.file-mark.is-doc    { color: #1d4ed8; background: color-mix(in srgb, #1d4ed8 8%, var(--ui-surface)); border-color: color-mix(in srgb, #1d4ed8 20%, var(--ui-line)); }
.file-mark.is-sheet  { color: #15803d; background: color-mix(in srgb, #15803d 8%, var(--ui-surface)); border-color: color-mix(in srgb, #15803d 20%, var(--ui-line)); }
.file-mark.is-media  { color: #0e7490; background: color-mix(in srgb, #0e7490 8%, var(--ui-surface)); border-color: color-mix(in srgb, #0e7490 20%, var(--ui-line)); }
.file-mark.is-code   { color: #6d28d9; background: color-mix(in srgb, #6d28d9 8%, var(--ui-surface)); border-color: color-mix(in srgb, #6d28d9 20%, var(--ui-line)); }
.file-mark.is-archive{ color: #92400e; background: color-mix(in srgb, #92400e 8%, var(--ui-surface)); border-color: color-mix(in srgb, #92400e 20%, var(--ui-line)); }

/* Rozszerzenie pod ikoną tylko tam, gdzie typ naprawdę trzeba odczytać wprost
   (lista plików projektu). W drzewie wystarczy sama ikona — nazwa i tak je niesie. */
.file-ext {
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    padding-block: 1px;
    background: color-mix(in srgb, currentColor 12%, transparent);
    font-size: 8px;
    font-weight: 700;
    letter-spacing: 0.02em;
    line-height: 1;
    text-align: center;
    text-transform: uppercase;
}

/* ============================================================== 5. POWŁOKA
   Etap 1b migracji. Zastępuje 86 reguł (~1000 linii) rozsianych po app.css:
   pasek boczny, marka, nawigacja, przypięte projekty, box użytkownika, nagłówek
   mobilny i stan zwinięty.

   Nazwy klas ZOSTAJĄ bez zmian — zależy od nich JS (initSidebarToggle przez
   [data-sidebar-shell] i klasę .sidebar-collapsed, initNavStatePolling, które
   TWORZY .nav-badge w locie i ustawia data-nav-tone, initMobileMenu, galeria).
   Migrujemy warstwę wizualną, nie strukturę — inaczej trzeba by przepisać
   jednocześnie 4409 linii JS-a.
   ============================================================================ */

.app-shell { min-height: 100dvh; background: var(--ui-surface-2); }

.app-frame { display: flex; min-height: 100dvh; }

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

/* Kontener zapytań: panele w treści reagują na szerokość TREŚCI, nie okna —
   to jedyny sposób, żeby układ widział dok terminala (patrz §4 dokumentu). */
.content-wrap {
    container-type: inline-size;
    width: 100%;
    max-width: 96rem;
    margin-inline: auto;
    padding: 1rem;
}

.content-wrap-fluid { max-width: none; }

/* --- pasek boczny ----------------------------------------------------------- */

.app-sidebar {
    position: sticky;
    top: 0;
    display: none;
    height: 100dvh;
    flex: 0 0 16.5rem;
    width: 16.5rem;
    flex-direction: column;
    gap: 0.75rem;
    overflow-y: auto;
    overflow-x: hidden;
    border-right: 1px solid var(--ui-line);
    background: var(--ui-surface);
    padding: 0.75rem;
    transition: width 160ms ease, flex-basis 160ms ease;
}

@media (min-width: 1024px) {
    .app-sidebar { display: flex; }
}

.sidebar-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}

.brand-lockup {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-width: 0;
    flex: 1;
    padding: 0.2rem;
    border-radius: var(--ui-radius-sm);
    color: inherit;
    text-decoration: none;
}

.brand-mark {
    display: grid;
    place-items: center;
    width: 2rem;
    height: 2rem;
    flex: 0 0 auto;
    border-radius: var(--ui-radius-sm);
    background: var(--ui-accent);
    color: var(--ui-accent-fg);
    font-size: var(--ui-meta-size);
    font-weight: 600;
}

.brand-name { display: block; font-size: 14px; font-weight: 600; line-height: 1.2; }
.brand-subtitle { display: block; color: var(--ui-muted); font-size: var(--ui-eyebrow-size); }

.sidebar-toggle, .sidebar-restore {
    display: none;
    place-items: center;
    width: 2rem;
    height: 2rem;
    flex: 0 0 auto;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius-sm);
    background: var(--ui-surface);
    color: var(--ui-muted);
    cursor: pointer;
}

@media (min-width: 1024px) {
    .sidebar-toggle { display: inline-grid; }
}

.sidebar-toggle:hover, .sidebar-restore:hover { border-color: var(--ui-line-strong); color: var(--ui-ink); }

/* Znak "panel boczny" rysowany CSS-em — jedna ikona nie jest warta pliku. */
.sidebar-toggle-symbol {
    position: relative;
    display: block;
    width: 1rem;
    height: 0.85rem;
    border: 1.5px solid currentColor;
    border-radius: 3px;
}

.sidebar-toggle-symbol::before {
    position: absolute;
    content: "";
    top: 0;
    bottom: 0;
    left: 4px;
    width: 1.5px;
    background: currentColor;
}

/* --- stan zwinięty ---------------------------------------------------------- */

@media (min-width: 1024px) {
    .app-shell.sidebar-collapsed .app-sidebar {
        width: 3.5rem;
        flex-basis: 3.5rem;
        padding-inline: 0.4rem;
    }

    .app-shell.sidebar-collapsed .sidebar-top { display: grid; justify-items: center; gap: 0.4rem; }
    .app-shell.sidebar-collapsed .brand-lockup { justify-content: center; padding: 0; gap: 0; }

    .app-shell.sidebar-collapsed .brand-name,
    .app-shell.sidebar-collapsed .brand-subtitle,
    .app-shell.sidebar-collapsed .sidebar-card,
    .app-shell.sidebar-collapsed .nav-pinned,
    .app-shell.sidebar-collapsed .nav-list,
    .app-shell.sidebar-collapsed .user-box {
        display: none;
    }

    .app-shell.sidebar-collapsed .sidebar-restore { display: inline-grid; }
}

.sidebar-restore {
    position: fixed;
    left: 0.5rem;
    bottom: 3rem;
    z-index: var(--z-panel-float);
}

/* --- przypięte projekty ----------------------------------------------------- */

.nav-pinned { display: flex; flex-direction: column; gap: 1px; }
.nav-pinned:not(:has(.nav-pinned-row)) { display: none; }

.nav-pinned-title {
    margin: 0 0 0.2rem;
    color: var(--ui-muted);
    font-size: var(--ui-eyebrow-size);
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

.nav-pinned-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
    padding: 0.3rem 0.5rem;
    border-radius: var(--ui-radius-sm);
    color: var(--ui-ink-soft);
    font-size: 13px;
    text-decoration: none;
}

.nav-pinned-row:hover { background: var(--ui-surface-2); }

.nav-pinned-dot {
    width: 0.4rem;
    height: 0.4rem;
    flex: 0 0 auto;
    border-radius: 999px;
    background: var(--ui-line-strong);
}

/* Zielona kropka = projekt ma ŻYWĄ sesję terminala. To stan, więc kolor. */
.nav-pinned-dot.is-live { background: var(--ui-ok); }

/* --- nawigacja -------------------------------------------------------------- */

.nav-list { display: flex; flex-direction: column; gap: 1px; }

.nav-link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.4rem 0.5rem;
    border-radius: var(--ui-radius-sm);
    color: var(--ui-ink-soft);
    font-size: 14px;
    font-weight: 500;
    text-decoration: none;
    cursor: pointer;
}

.nav-link:hover { background: var(--ui-surface-2); }

.nav-link.is-active {
    background: var(--ui-surface-2);
    color: var(--ui-ink);
    font-weight: 600;
    box-shadow: inset 2px 0 0 var(--ui-accent);
}

.nav-link-main { display: flex; align-items: center; gap: 0.55rem; min-width: 0; }

/* Kropka stanu przy pozycji menu: szara = spokój, kolor = coś czeka.
   Ton ustawia JS przez data-nav-tone (initNavStatePolling), nie klasa. */
.nav-dot {
    width: 0.45rem;
    height: 0.45rem;
    flex: 0 0 auto;
    border-radius: 999px;
    background: var(--ui-line-strong);
}

.nav-link[data-nav-tone="ok"] .nav-dot { background: var(--ui-ok); }
.nav-link[data-nav-tone="warning"] .nav-dot { background: var(--ui-warn); }
.nav-link[data-nav-tone="down"] .nav-dot { background: var(--ui-down); }
.nav-link[data-nav-tone="warning"] .nav-badge { border-color: var(--ui-warn-line); background: var(--ui-warn-bg); color: var(--ui-warn); }
.nav-link[data-nav-tone="down"] .nav-badge { border-color: var(--ui-down-line); background: var(--ui-down-bg); color: var(--ui-down); }

.nav-group { border: 0; }
.nav-group > summary { list-style: none; }
.nav-group > summary::-webkit-details-marker { display: none; }
.nav-group > summary::after { display: none !important; }
.nav-group-caret { color: var(--ui-muted); font-size: 10px; transition: transform 120ms ease; }
.nav-group[open] .nav-group-caret { transform: rotate(180deg); }
.nav-group-items { display: flex; flex-direction: column; gap: 1px; padding-left: 0.9rem; }
.nav-link-sub { font-size: 13px; }

/* --- box użytkownika -------------------------------------------------------- */

.user-box {
    margin-top: auto;
    border-top: 1px solid var(--ui-line);
    padding-top: 0.6rem;
    font-size: var(--ui-meta-size);
}

.user-box .rounded-md { background: var(--ui-surface-2); border-radius: var(--ui-radius); }

/* --- nagłówek mobilny ------------------------------------------------------- */

.mobile-header {
    display: grid;
    gap: 0.6rem;
    position: sticky;
    top: 0;
    z-index: var(--z-panel-float);
    border-bottom: 1px solid var(--ui-line);
    background: var(--ui-surface);
    padding: 0.6rem 0.75rem;
}

@media (min-width: 1024px) {
    .mobile-header { display: none; }
}

.mobile-menu-toggle {
    display: grid;
    gap: 3px;
    width: 2.25rem;
    height: 2.25rem;
    place-content: center;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius-sm);
    background: var(--ui-surface);
    cursor: pointer;
}

.mobile-menu-toggle > span {
    display: block;
    width: 1rem;
    height: 1.5px;
    background: var(--ui-ink-soft);
}

.mobile-nav-list {
    display: flex;
    flex-direction: column;
    gap: 1px;
    border-top: 1px solid var(--ui-line);
    padding-top: 0.5rem;
}

.mobile-nav-link {
    padding: 0.55rem 0.6rem;
    border-radius: var(--ui-radius-sm);
    color: var(--ui-ink-soft);
    font-size: 14px;
    text-decoration: none;
}

.mobile-nav-link.is-active { background: var(--ui-surface-2); color: var(--ui-ink); font-weight: 600; }

/* --- nagłówek widoku (wspólny partial) -------------------------------------- */

.page-header-panel { margin-bottom: 1rem; }
.page-subtitle { color: var(--ui-muted); font-size: var(--ui-meta-size); max-width: 72ch; }
.label-caps {
    margin: 0;
    color: var(--ui-muted);
    font-size: var(--ui-eyebrow-size);
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

/* --- kurczliwość dzieci siatek (przyczyna poziomego przewijania) ------------- */

/* Dziecko grida/flexa ma domyślnie `min-width: auto`, więc długi nieprzerywalny
   tekst (URL endpointu konektora) rozpycha kolumnę ponad szerokość ekranu —
   pomiar 2026-08-18: karta 577px w oknie 390px, 203px poziomego przewijania na
   /app/konektory. `truncate`/ellipsis NIE zadziała, dopóki element nie może się
   skurczyć. To jedna z tych rzeczy, które trzeba naprawić raz, w siatce, a nie
   w każdym widoku z osobna. */
.panels > *, .board > *, .module-grid > *, .metrics-grid > *, .section-grid > * {
    min-width: 0;
}

.module-card > *, .module-card p, .row-main > *, .source-head > * {
    min-width: 0;
}

/* Długi adres w <code> (token heartbeatu, endpoint webhooka) rozpychał stronę
   na telefonie o 10px — kod ma się łamać, a nie wypychać layoutu.
   Pomiar 2026-08-18: /app/ustawienia @390. */
code, kbd, samp, pre {
    overflow-wrap: anywhere;
}

pre {
    overflow-x: auto;
}

/* Wiersz listy plików/pozycji źródła. Osobna klasa, bo po zdjęciu starej
   pozycje zostawały gołym <article>, a Pico stylizuje article jako KARTĘ —
   lista 50 plików robiła się 50 kafelkami (pomiar 2026-08-18). */
.filelist-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.75rem;
    padding: 0.35rem 0;
    border-top: 1px solid var(--ui-line);
    background: none;
    box-shadow: none;
    border-radius: 0;
}

.filelist > .filelist-row:first-child { border-top: 0; }
.filelist-row > *:first-child { min-width: 0; }

/* --- klasy zastępujące kolory wpisywane w szablonach ------------------------ */

/* Etap 6b: 356 arbitralnych klas kolorów Tailwinda (`text-[#697263]` i podobne)
   w 46 szablonach zastąpione klasami semantycznymi. Powód nie jest kosmetyczny:
   te wartości wymuszały w app.css sześć selektorów dopasowujących DOSŁOWNĄ nazwę
   klasy (`[class~="text-[#697263]"] { color: … !important }`). Taki selektor
   przestaje pasować po każdej zmianie klasy w szablonie i NIE zgłasza błędu —
   regres widać dopiero w `getComputedStyle` (docs/UI_ZASADY.md, pułapka z §4). */

.text-ink { color: var(--ui-ink); }
.text-muted { color: var(--ui-muted); }
.text-down { color: var(--ui-down); }  /* tekst ostrzegawczy przy polu formularza, np. licznik znaków po przekroczeniu limitu */
.text-soft { color: var(--ui-ink-soft); }
.surface-soft { background: var(--ui-surface-2); }
.border-soft { border-color: var(--ui-line); }

/* Ciemne sekcje (strony publiczne: RODO, regulamin, o serwisie) mają własną,
   odwróconą parę: tło atramentowe + jasny tekst. Nagłówki i .text-muted MUSZĄ
   być tu nadpisane — inaczej dziedziczą ciemny kolor i znikają na ciemnym tle
   (regres z zamiany klas, złapany zrzutem 2026-08-18: tytuł "Polityka
   prywatności" był czarny na czarnym). */
.surface-ink {
    background: var(--ui-ink);
    color: var(--ui-surface);
}

.surface-ink h1, .surface-ink h2, .surface-ink h3, .surface-ink strong {
    color: var(--ui-surface);
}

.surface-ink .text-muted, .surface-ink.text-muted {
    color: color-mix(in srgb, var(--ui-surface) 70%, transparent);
}

.surface-ink .text-ink {
    color: var(--ui-ink);
}

.text-accent { color: var(--ui-accent); }

.surface-accent {
    background: var(--ui-accent);
    color: var(--ui-accent-fg);
}

/* --- panel (karta) --------------------------------------------------------- */

/* Klasa ZOSTAJE, mimo że wizualnie to zwykła karta: JS pyta o nią w pięciu
   miejscach przez closest('.panel') (sortowanie widżetów, przycisk dodania grupy
   monitorów, filtr projektów). Zmiana na goły <article> urwałaby te funkcje —
   migrujemy wygląd, nie strukturę. */
.panel {
    display: block;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius);
    background: var(--ui-surface);
    padding: 1rem;
    margin-bottom: 1rem;
    min-width: 0;
}

/* Panel w kolumnie bocznej projektu i w tablicach nie dubluje odstępu — układ
   nadaje go siatką (gap), nie marginesem karty. */
.project-side-column > .panel,
.project-side-stack > .panel,
.panels > .panel,
.board > .panel {
    margin-bottom: 0;
}

/* Podgląd na dashboardzie osadza panel w panelu — wewnętrzny traci ramkę,
   żeby nie było karty w karcie (dawniej osobna reguła w app.css). */
.dash-preview-panel .dash-preview-body > turbo-frame > .panel {
    border: 0;
    padding: 0;
    background: transparent;
}

/* Wysuwany panel akcji musi móc wyjść poza kartę, gdy jest otwarty. */
.project-workspace .panel:has(.inline-action[open]) {
    overflow: visible;
    z-index: var(--z-panel-float);
}

/* Przycisk ikonowy w stanie nieaktywnym (nawigacja wpisów w karcie digestu). */
.icon-btn.is-disabled {
    color: var(--ui-line-strong);
    cursor: default;
    pointer-events: none;
}

/* ── Kafelki ────────────────────────────────────────────────────────────────
   Jeden wzorzec zamiast trzech osobnych: karty trybu w capture na desktopie,
   kafelki nawigacji mobilnej i kafelki wyboru pliku miały własne klasy o
   praktycznie identycznym wyglądzie (kwadrat, ikona, podpis, stan aktywny).
   `auto-fit` sam decyduje o liczbie kolumn — dok terminala zabiera szerokość,
   a media query tego nie widzi. */
.tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(var(--tiles-min, 6rem), 1fr));
    gap: 0.5rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Pasek nawigacji mobilnej: pięć skrótów zawsze w jednym rzędzie — zwijanie
   ich do dwóch rzędów zjadałoby ekran nad polem notatki. */
.tiles-fixed {
    grid-template-columns: repeat(var(--tiles-cols, 3), minmax(0, 1fr));
}

.tile {
    position: relative;
    display: grid;
    align-content: center;
    justify-items: center;
    gap: 0.4rem;
    min-height: 4.5rem;
    padding: 0.7rem 0.4rem;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius);
    background: var(--ui-surface);
    color: var(--ui-ink);
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    overflow: hidden;
    transition: border-color 120ms ease, background 120ms ease;
}

.tile:hover,
.tile:focus-visible,
.tile:focus-within {
    border-color: var(--ui-accent);
    background: var(--ui-surface-2);
}

.tile.is-active {
    border-color: var(--ui-accent);
    background: var(--ui-accent-soft);
    color: var(--ui-accent);
}

/* Kafelek „wybierz plik": input przykrywa całą powierzchnię, żeby dotyk w
   dowolnym miejscu otwierał wybór pliku (mobile). */
.tile-input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

.tile-icon {
    display: grid;
    place-items: center;
    color: inherit;
}

.tile-icon svg {
    width: 20px;
    height: 20px;
}

.tile-icon.is-mark {
    width: 2.75rem;
    height: 2.75rem;
    border-radius: var(--ui-radius);
    background: var(--ui-surface-2);
    color: var(--ui-ink-soft);
}

.tile-icon.is-mark svg {
    width: 22px;
    height: 22px;
}

.tile.is-active .tile-icon.is-mark {
    background: var(--ui-surface);
}

.tile-label {
    font-size: var(--ui-meta-size);
    font-weight: 650;
    line-height: 1.2;
}

.tile-hint {
    color: var(--ui-muted);
    font-size: var(--ui-eyebrow-size);
    font-weight: 500;
    line-height: 1.3;
}

.tile.is-active .tile-hint {
    color: inherit;
    opacity: 0.85;
}

/* ── Zakładki ───────────────────────────────────────────────────────────── */
.tabs {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: 0.25rem;
    padding: 0.25rem;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius);
    background: var(--ui-surface-2);
}

.tab {
    min-height: 2.5rem;
    padding: 0.4rem 0.6rem;
    border: 0;
    border-radius: calc(var(--ui-radius) - 0.2rem);
    background: transparent;
    color: var(--ui-ink-soft);
    font-size: var(--ui-body-size);
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
}

.tab.is-active {
    background: var(--ui-surface);
    color: var(--ui-ink);
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.06);
}

/* ── Pole formularza ────────────────────────────────────────────────────────
   Pico daje `label`/`input` marginesy pionowe; `.field` zbiera etykietę,
   pole i podpowiedź w jedną odstępowaną całość, także gdy elementem jest
   `<details>` albo `<label>` z `<span>` w środku. */
.field {
    display: grid;
    gap: 0.35rem;
    margin-bottom: 1rem;
}

.field > input,
.field > select,
.field > textarea,
.field > * > input,
.field > * > select {
    margin-bottom: 0;
}

/* Przycisk na pełną szerokość, wysokość pod kciuk (paski zapisu na mobile). */
.btn-block {
    width: 100%;
    min-height: 3.25rem;
    justify-content: center;
}

/* ── Przegląd notatek (inbox/review) ────────────────────────────────────────
   Karta na środku, strzałki po bokach. Próg liczony kontenerowo: dok terminala
   zabiera szerokość treści, więc media query pokazywałaby strzałki tam, gdzie
   nie ma już na nie miejsca. */
.review-shell {
    container-type: inline-size;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    justify-content: center;
    align-items: center;
    gap: 0.75rem;
}

@container (min-width: 44rem) {
    .review-shell {
        grid-template-columns: 3rem minmax(0, min(92cqw, 68rem)) 3rem;
    }
}

.review-nav {
    display: none;
    place-items: center;
}

@container (min-width: 44rem) {
    .review-nav { display: grid; }
}

.review-arrow {
    display: grid;
    width: 3rem;
    height: 4.5rem;
    place-items: center;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius);
    background: var(--ui-surface);
    color: var(--ui-ink-soft);
    font-size: 1.75rem;
    line-height: 1;
    text-decoration: none;
}

.review-arrow:hover { border-color: var(--ui-accent); color: var(--ui-accent); }
.review-arrow.is-disabled { opacity: 0.35; pointer-events: none; }

.quick-create-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-items: end;
    gap: 0.75rem;
}

@media (min-width: 40rem) {
    .quick-create-grid { grid-template-columns: 11rem minmax(0, 1fr); }
}

/* Osadzony odtwarzacz (YouTube itp.) — proporcje pilnuje aspect-ratio, nie padding-hack. */
.embed-16x9 {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border-radius: var(--ui-radius);
    background: var(--ui-surface-2);
}

.embed-16x9 iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* Przycisk z ikoną: Pico daje `display:inline-block`, więc `<svg>` + tekst
   łamały się na dwie linie zamiast stanąć obok siebie. */
button:has(> .ico), [role="button"]:has(> .ico) {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

/* Kafelek z podglądem załącznika: obraz wypełnia szerokość, nazwa pliku łamie
   się na dwie linie zamiast ucinać w połowie (pomiar: „IMG-2026…" widoczne w 44%). */
.tile > img {
    width: 100%;
    aspect-ratio: 4 / 3;
    border-radius: var(--ui-radius-sm);
    object-fit: cover;
}

/* Stan monitoringu w pasku statystyk projektu — kolor niesie sama liczba/etykieta,
   bez ramki i tła (to metadane nagłówka, nie pigułka stanu). */
.stat.is-ok > strong { color: var(--ui-ok); }
.stat.is-warning > strong { color: var(--ui-warn); }
.stat.is-down > strong { color: var(--ui-down); }

/* Załącznik w podglądzie materiału: obraz w pełnej szerokości karty, klikalny
   do galerii. Nie kafelek — tu liczy się czytelność obrazu, nie siatka. */
.preview-asset {
    display: grid;
    gap: 0.6rem;
    padding: 0.75rem;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius);
    background: var(--ui-surface);
    color: inherit;
    text-decoration: none;
}

.preview-asset img {
    width: 100%;
    max-height: 70vh;
    border-radius: var(--ui-radius-sm);
    object-fit: contain;
    background: var(--ui-surface-2);
    cursor: zoom-in;
}

/* #213: podgląd PDF (eksport z Google Drive) renderuje natywny viewer przeglądarki. */
.embed-pdf {
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius);
    overflow: hidden;
}

.embed-pdf iframe {
    display: block;
    width: 100%;
    height: 70vh;
    border: 0;
}

/* Wiersz z miniaturą: ikona/zdjęcie po lewej, tytuł i metadane po prawej.
   Ten sam układ powtarzał się w NOTATKACH, materiałach projektu i wzorcowni
   jako inline `style` — jedna klasa zamiast pięciu kopii. */
.row-media {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
}

.row-media > .file-mark {
    width: 2.5rem;
    height: 2.5rem;
}

/* Większa miniatura tam, gdzie zdjęcie samo w sobie jest treścią wiersza (NOTATKI). */
.file-mark.is-lg {
    width: 3rem;
    height: 3rem;
}

.row-media > :last-child {
    min-width: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
   UKŁAD: dok terminala odzyskuje miejsce dla treści
   ═══════════════════════════════════════════════════════════════════════════
   Commit 2febd90 ("Etap 1b: 90 reguł mniej w app.css") usunął te reguły,
   zostawiając w app.css:2740 i 5015-5027 same komentarze, które je opisują.
   Zamiennik nigdy nie powstał. Skutek zmierzony 2026-08-19 na 2560x950:
   dok po prawej (900px) zakrywał 884 px treści, bo jest position:fixed,
   a .app-frame nie dostawał już padding-right. Dodatkowo container query
   .content-wrap nigdy nie widziało zwężenia, więc układ zachowywał się tak,
   jakby dok nie istniał.
   --terminal-w / --terminal-h ustawia JS (applySize w assets/app.js). */

.app-frame { padding-bottom: 34px; }

body:has(.terminal-dock:not(.is-collapsed)) .app-frame {
    padding-bottom: calc(var(--terminal-h, 38dvh) + 40px);
}

body:has(.terminal-dock:not(.is-collapsed)) .app-sidebar {
    height: calc(100dvh - var(--terminal-h, 38dvh) - 40px);
}

body:has(.terminal-dock.is-right:not(.is-collapsed)) .app-frame {
    padding-bottom: 34px;
    padding-right: var(--terminal-w, 520px);
}

body:has(.terminal-dock.is-right:not(.is-collapsed)) .app-sidebar {
    height: 100dvh;
}

/* Gdy dok zabrał wysokość, dolne pozycje menu muszą pozostać osiągalne. */
.app-sidebar .nav-list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

.app-sidebar .nav-pinned {
    flex: 0 0 auto;
    max-height: 34vh;
    overflow-y: auto;
}

/* ═══════════════════════════════════════════════════════════════════════════
   WIDOK PROJEKTU: trzecia kolumna wraca, próg przestaje działać na odwrót
   ═══════════════════════════════════════════════════════════════════════════ */

/* Ile pionu zabiera dok przyklejony do dołu. Zero, gdy zamknięty albo po prawej —
   dzięki temu wszystkie wysokości liczone poniżej działają w każdym stanie doku. */
body:has(.terminal-dock:not(.is-collapsed):not(.is-right)) {
    --dok-zajmuje: calc(var(--terminal-h, 38dvh) + 40px);
}

/* 1. TRZECIA KOLUMNA.
   Zamysł z commita 3369fc6 (#124/#141): kolumna główna + DWA niezależne stosy.
   Siatka dwukolumnowa siedzi na .project-side-column (app.css:2776), ale jej
   jedynym dzieckiem jest <turbo-frame id="project-side-panels"> — oba stosy są
   w środku ramki. Grid z jednym dzieckiem zostawia drugą komórkę pustą:
   zmierzone 361 px martwego pola na całą wysokość, a stosy jeden pod drugim
   (oba na x=1840). Siatka musi trafić tam, gdzie faktycznie są dzieci.
   turbo-frame jest domyślnie display:inline — stąd jawne display. */
.project-side-column > turbo-frame#project-side-panels {
    display: block;
    min-width: 0;
}

@container workspace (min-width: 1340px) {
    .project-side-column {
        display: block;
        width: auto;
    }
}

/* Dwa stosy zaleza od szerokosci SAMEJ kolumny bocznej, nie calego widoku —
   inaczej przy otwartym doku dostawalismy dwa slupki po ~264 px, czyli gorzej
   niz dzisiejszy jeden. Prog 48rem = 768 px daje stosy po 375 px, wiec kazdy
   jest szerszy od obecnych 343 px. Ponizej progu zostaje jeden, ale pelnej
   szerokosci kolumny. */
.project-side-column {
    /* justify-self: stretch MUSI tu byc przed container-type. app.css:1074 ustawia
       justify-self:end, a wtedy blokowy element w gridzie ma szerokosc z tresci —
       w polaczeniu z inline-size containment (ktory tresci nie widzi) kolumna
       zapada sie do ZERA. Zmierzone: side=0px, stosow=0. Track ma definitywna
       szerokosc, wiec przy stretch containment jest bezpieczny. */
    justify-self: stretch;
    width: auto;
    container: panele / inline-size;
}

/* Uchwyt szerokości kolumn (#333) istnieje tylko wtedy, gdy stosy faktycznie
   stoją obok siebie — poniżej progu ramka jest jednym słupkiem. Container query
   nie ma „else", więc domyślnie ukryty, a pokazany w regule progowej niżej. */
.project-side-column > turbo-frame#project-side-panels > .dash-col-resize {
    display: none;
}

@container panele (min-width: 48rem) {
    .project-side-column > turbo-frame#project-side-panels {
        display: grid;
        /* #333: szerokości sterowane tymi samymi zmiennymi co dashboard
           (--dash-col-N, initDashColumnResize w app.js) — jeden mechanizm,
           jedna pamięć per szerokość ekranu, zamiast drugiej implementacji.
           Środkowy tor `auto` to uchwyt. */
        grid-template-columns:
            minmax(0, var(--dash-col-1, 1fr)) auto
            minmax(0, var(--dash-col-2, 1fr));
        /* Ten sam odstęp co WEWNĄTRZ stosu (28px). Przy 18px granica między
           stosami czytała się jako zlepione boxy — dwa boxy z różnych kolumn
           stały bliżej siebie niż dwa z tej samej (zgłoszenie 2026-08-20).
           Z uchwytem 6px w środku: 11 + 6 + 11 = te same 28px. */
        row-gap: 28px;
        column-gap: 11px;
        align-items: start;
    }

    .project-side-column > turbo-frame#project-side-panels > .dash-col-resize {
        display: block;
    }
}

/* 2. PRÓG 1340 px DZIAŁAŁ NA ODWRÓT.
   app.css:2773 dawał drugiemu trackowi sztywne 44rem. Zmierzony skutek:
   poszerzenie kontenera z 1324 na 1344 px SKRACAŁO kolumnę główną z 940 do
   624 px (-34%). Track ma rosnąć razem z ekranem, ale mieć sufit — inaczej
   nadmiar szerokości ląduje w panelach zamiast w treści.
   56rem = 896 px, czyli dwa stosy po ~439 px: przy tej szerokości tytuły
   zadań przestają się łamać na dwie linie (przy 343 px łamało się 97%). */
@container workspace (min-width: 1340px) {
    .project-workspace {
        /* Proporcja idzie za TRESCIA, nie za intuicja "glowna = najszersza".
           Zmierzone na projekcie 41: kolumna glowna ma 658 px tresci, boczna
           ~4000 px. Wczesniej boczna dostawala 40% szerokosci na 86% tresci.
           Polowa na polowe daje stosy po ~557 px zamiast 343 px, a kolumna
           glowna i tak ogranicza proze do 72ch (600 px), wiec nic nie traci. */
        grid-template-columns: minmax(0, 1fr) minmax(22rem, min(72rem, 50%));
    }
}

/* 3. PANELE BOCZNE MAJĄ WŁASNY SCROLLBOX — musi wiedzieć o doku.
   app.css:2766 ustawia max-height: calc(100vh - 32px). Przy doku na dole
   dolne 439 px tego scrollboksa chowa się pod dokiem: zmierzone 3146 px
   treści nieosiągalnej, mimo poprawnego padding-bottom na .app-frame. */
@container workspace (min-width: 580px) {
    .project-side-column {
        max-height: calc(100dvh - 32px - var(--dok-zajmuje, 0px));
    }
}

/* Na waskim ekranie kolejnosc z DOM (podglad -> panele) jest odwrotna do
   potrzeby: podglad ma sens dopiero po wybraniu materialu, a na telefonie
   wchodzi sie po zadania i notatki. Zmierzone przed zmiana: pierwsze zadanie
   dopiero 3,8 ekranu w dol. Kolejnosc w DOM zostaje bez zmian (czytniki ekranu
   i Turbo dostaja to samo), zmienia sie tylko kolejnosc wizualna. */
@container workspace (max-width: 579.98px) {
    .project-workspace > main { order: 2; }
    .project-side-column { order: 1; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   AKCJE W WIERSZU I GĘSTOŚĆ PANELU — poprawka po zrzucie właściciela 2026-08-20
   ═══════════════════════════════════════════════════════════════════════════ */

/* Pico rysuje znacznik `summary` jako obrazek tła po prawej. Dla summary
   udającego przycisk-glif (popover akcji w wierszu) dawało to DRUGI element
   obok ⋮ — użytkownik widział w wierszu cztery kontrolki zamiast trzech.
   Wyciszone globalnie, nie tylko w .tree/.nav-group jak wcześniej. */
summary.icon-btn,
summary.icon-btn[role="button"] {
    list-style: none;
    background-image: none;
    padding-right: 0;
}
summary.icon-btn::-webkit-details-marker { display: none; }
summary.icon-btn::after { display: none !important; }

/* Trzy glify w wierszu to jeden rządek, nigdy słupek: zawijanie w wąskiej
   kolumnie bocznej ustawiało je pionowo i podwajało wysokość wiersza. */
.row-actions { flex-wrap: nowrap; }

/* Nagłówek panelu zabierał ~70 px pionu na jedną linijkę tekstu. Tytuł panelu
   to etykieta, nie nagłówek strony — skala jak w liście, nie jak w h2 Pico. */
.panel { padding: 0.75rem; }
.panel-head { margin-bottom: 0.4rem; }
.panel-head h2, .panel-head h3 {
    font-size: 1rem;
    line-height: 1.25;
}

/* ═══════════════════════════════════════════════════════════════════════════
   LOADER RAMKI — wyszarzenie CAŁEGO boxa + spinner na środku (2026-08-20)
   ═══════════════════════════════════════════════════════════════════════════ */

/* Wcześniej ramka po prostu gasła do opacity .45 i nic się nie działo, więc
   przy dłuższej odpowiedzi nie było widać, czy cokolwiek leci. Teraz gaśnie
   treść, a nad nią stoi spinner — właściciel ma widzieć, KTÓRY box się podmienia. */
turbo-frame[busy],
turbo-frame[aria-busy="true"] {
    position: relative;
    opacity: 1;
}

turbo-frame[busy] > *,
turbo-frame[aria-busy="true"] > * {
    opacity: 0.3;
    transition: opacity 150ms ease;
}

turbo-frame[busy]::after,
turbo-frame[aria-busy="true"]::after {
    content: "";
    position: absolute;
    inset: 0;
    margin: auto;
    width: 2.75rem;
    height: 2.75rem;
    border: 3px solid var(--ui-line);
    border-top-color: var(--ui-accent);
    border-radius: 50%;
    animation: ui-frame-spin 0.8s linear infinite;
}

@keyframes ui-frame-spin {
    to { transform: rotate(360deg); }
}

/* Ruch wyłączony na życzenie systemu — zostaje sam pierścień jako znak „czekaj". */
@media (prefers-reduced-motion: reduce) {
    turbo-frame[busy]::after,
    turbo-frame[aria-busy="true"]::after { animation: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   BELKA AKCJI W NAGŁÓWKU STRONY — jedna wysokość, jedna linia bazowa
   ═══════════════════════════════════════════════════════════════════════════ */

/* Zrzut właściciela 2026-08-20: "Odpal sesję AI"/"Dodaj materiał" (pełne tło)
   były wyższe niż "Edytuj"/"Lista PARA" (obrys), a szpilka miała jeszcze inny
   rozmiar, bo używa klasy .icon-button, nie .icon-btn. Klaster w nagłówku musi
   stać w jednym rządku o jednej wysokości — niezależnie od klasy i od tego, czy
   element siedzi w <form>. */
.page-header-actions :is(a[role="button"], button, [type="submit"], [type="button"], summary[role="button"]) {
    height: var(--ui-btn-h);
    min-height: var(--ui-btn-h);
    padding-top: 0;
    padding-bottom: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 0;
}

/* Przyciski-glify (szpilka) zostają kwadratem tej samej wysokości. */
.page-header-actions :is(.icon-button, .icon-btn, .pin-button) {
    width: var(--ui-btn-h);
    padding-left: 0;
    padding-right: 0;
}

/* Formularz z jednym przyciskiem (szpilka) nie dodaje własnego pionu. */
.page-header-actions form {
    display: flex;
    margin: 0;
}

/* "Odpal sesję AI" przy kilku maszynach to <details class="inline-action"> z
   <summary role="button">, a nie przycisk — dlatego siedziało wyżej niż reszta
   klastra (zgłoszenie właściciela 2026-08-20, mój pierwszy pomiar go nie objął,
   bo szukałem tylko a/button). Wrapper nie może wnosić własnej wysokości. */
.page-header-actions > details.inline-action {
    display: flex;
    align-items: center;
    margin: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
   TRZY TRYBY ROZWIJANIA BOXA (initPanelDensity w app.js) — 2026-08-20
   ═══════════════════════════════════════════════════════════════════════════ */

/* Zwinięty: zostaje sam nagłówek z licznikiem i przyciskami. Selektor po
   rodzeństwie nagłówka, bo panele mają różną strukturę — Dziennik trzyma treść
   w <turbo-frame> razem z nagłówkiem, pozostałe wprost w <section>. */
.panel.is-density-0 .panel-head ~ * { display: none; }
.panel.is-density-0 .panel-head { margin-bottom: 0; }

/* Trzy ostatnie pozycje: reszta ukryta, więc scroll listy jest zbędny. */
.panel.is-density-1 :is(.row-list, ol, ul) > li:nth-child(n+4) { display: none; }
.panel.is-density-1 .row-list {
    max-height: none;
    overflow: visible;
}


/* Autoodświeżanie ma być WIDOCZNE, gdy naprawdę coś się zmieniło — inaczej
   właściciel nie wie, czy box się dociągnął (zgłoszenie 2026-08-20). Klasa
   zdejmowana z JS po animacji; brak zmiany = brak błysku. */
turbo-frame.is-frame-updated {
    animation: ui-frame-updated 1.1s ease-out;
}

@keyframes ui-frame-updated {
    0%   { box-shadow: inset 0 0 0 2px var(--ui-accent); }
    60%  { box-shadow: inset 0 0 0 2px var(--ui-accent); }
    100% { box-shadow: inset 0 0 0 2px transparent; }
}

@media (prefers-reduced-motion: reduce) {
    turbo-frame.is-frame-updated { animation: none; }
}


/* Karty czytały się jako jeden zlepiony blok mimo odstępu 28 px (zgłoszenie
   2026-08-20) — sam odstęp nie wystarcza, gdy ramka jest tak jasna jak linie
   wewnątrz panelu. Delikatny cień odkleja kartę od tła bez zabierania miejsca. */
.panel {
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.06), 0 4px 10px -6px rgb(0 0 0 / 0.10);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .panel {
        box-shadow: 0 1px 2px rgb(0 0 0 / 0.5), 0 6px 14px -8px rgb(0 0 0 / 0.6);
    }
}

:root[data-theme="dark"] .panel {
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.5), 0 6px 14px -8px rgb(0 0 0 / 0.6);
}

/* Pinezka w kolorze akcentu — tym samym, co przyciski główne (życzenie
   właściciela 2026-08-20). Stan przypięty zostaje wypełniony, odpięty ma
   sam obrys w tym samym kolorze. */
.pin-button .pin-icon {
    fill: var(--ui-accent);
    stroke: var(--ui-accent);
}

.pin-button:not(.is-active) .pin-icon {
    fill: none;
    stroke-width: 1.5;
}


/* Zwinięty box pokazywał jeszcze linię pod nagłówkiem i pas pustego pionu —
   czyli wyglądał jak box z uciętą treścią, nie jak zwinięty (zgłoszenie
   właściciela 2026-08-20). */
.panel.is-density-0 .panel-head {
    border-bottom: 0;
    padding-bottom: 0;
    margin-bottom: 0;
}

/* Licznik przy tytule boxa: jeden wygląd wszędzie, zielony jak plakietka OWNER
   w ustawieniach. Wcześniej Źródła miały go po prawej, przy akcjach. */
.panel-head .count {
    border-color: var(--ui-ok-line);
    background: var(--ui-ok-bg);
    color: var(--ui-ok);
}

/* Belka boxa ma tę samą wysokość niezależnie od tego, ile ma przycisków —
   Źródła (bez "+") stały niżej niż Notatki (z "+"). */
.panel > .panel-head,
.panel turbo-frame > .panel-head {
    min-height: calc(var(--ui-btn-h-sm) + 0.8rem);
}


/* Poniżej progu dwóch stosów ramka paneli jest jednym blokiem: bez tego odstęp
   między OSTATNIM boxem pierwszego stosu i PIERWSZYM boxem drugiego brał się
   wyłącznie z marginesu karty (0) — stąd "Zadania" i "Linki" na styk. */
.project-side-column > turbo-frame#project-side-panels {
    display: grid;
    gap: 28px;
    align-items: start;
}
