/* ============================================================================
   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;

    /* Powierzchnie CIEMNE w obu motywach: dok terminala i lightbox. Terminal jest
       ciemny niezaleznie od motywu, wiec jego kolory nie sa tokenami motywu. */
    --dk-bg: #1d211b;      --dk-raised: #2a2f27;  --dk-black: #000000;
    --dk-fg: #e7e9e2;      --dk-fg-soft: #cfd4c8; --dk-muted: #9aa392;
    --dk-ok: #93b06a;      --dk-warn: #e0963c;    --dk-down: #e5484d;  --dk-danger: #b3261e;
    --dk-link: #cfe3b0;    --dk-ok-fg: #eef5e4;   --dk-warn-fg: #fbe8cf; --dk-white: #ffffff;

    /* 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;
}

/* --- motywy: Pracownia (jasny) i Sterownia (ciemny) --------------------------
   Decyzja 2026-10-11 (tozsamosc-wizualna/PLAN-motywy-2026-10-11.md). Wybor:
   atrybut data-theme na <html> ustawia base.html.twig (user.theme / cookie
   opsownia_theme) albo, gdy brak wyboru, skrypt w <head> wg prefers-color-scheme.
   Bez atrybutu (np. bez JS) obowiazuje Pracownia.
   Kontrasty (WCAG AA, tekst 4,5:1) zmierzone dla par tekst/tlo:
     Pracownia: ink #1E2A3A / #F4EFE6 12,4; muted #5B6676 / #F4EFE6 5,0;
       akcent #B3452F (przyciemniony terakota #C8553D, ktory dawal 3,8:1) / biel 5,5;
       ok #4F6B50 / biel 5,9 (szalwia #8FA68E tylko dekoracyjnie: --ui-ok-solid).
     Sterownia: ink #E8EAED / #14161A 15,5; muted #9AA1AB / #14161A 7,0;
       akcent #FFB020 / #14161A 10,2; down #E05252 / #14161A 5,2.
   Specyficznosc: Pico trzyma palete pod :root:not([data-theme=dark]) (0,2,0),
   wiec nasze selektory tez maja 0,2,0 i stoja pozniej. */

@font-face {
    font-family: "Inter"; font-style: normal; font-weight: 100 900; font-display: swap;
    src: url("../fonts/inter-latin-ext-1Br77OV.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: "Inter"; font-style: normal; font-weight: 100 900; font-display: swap;
    src: url("../fonts/inter-latin-Gz7AlRl.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: "JetBrains Mono"; font-style: normal; font-weight: 100 800; font-display: swap;
    src: url("../fonts/jbmono-latin-ext-5t87yUl.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: "JetBrains Mono"; font-style: normal; font-weight: 100 800; font-display: swap;
    src: url("../fonts/jbmono-latin-qI6BF9f.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: "DM Serif Display"; font-style: normal; font-weight: 400; font-display: swap;
    src: url("../fonts/dmserif-latin-ext-1O_ye5p.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: "DM Serif Display"; font-style: normal; font-weight: 400; font-display: swap;
    src: url("../fonts/dmserif-latin-U8_QxHi.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Pracownia — jasny (domyslny) */
:root:not([data-theme="sterownia"]), :root[data-theme="pracownia"] {
    color-scheme: light;
    --ui-ink: #1E2A3A;
    --ui-ink-soft: #2B3849;
    --ui-muted: #5B6676;
    --ui-line: #E3DCCF;
    --ui-line-strong: #CFC6B5;
    --ui-surface: #FFFFFF;
    --ui-surface-2: #F4EFE6;
    --ui-surface-3: #ECE5D8;
    --ui-accent: #B3452F;
    --ui-accent-hover: #963826;
    --ui-accent-fg: #FFFFFF;
    --ui-accent-soft: #FBEAE4;
    --ui-accent-line: #EBC3B7;
    --ui-ok: #4F6B50;        --ui-ok-bg: #E6EEE3;      --ui-ok-line: #BFD0BC;
    --ui-warn: #8A5A00;      --ui-warn-bg: #FBF0D9;    --ui-warn-line: #EBD3A0;
    --ui-down: #A63421;      --ui-down-bg: #FBE6E1;    --ui-down-line: #EBB9AF;
    --ui-pending: #6B4FA3;   --ui-pending-bg: #EFEAF7; --ui-pending-line: #D3C8E8;
    --ui-tone-blue: #3A5A8C; --ui-tone-blue-bg: #E8EEF7; --ui-tone-blue-line: #C3D1E6;
    --ui-inverse-bg: #1E2A3A; --ui-inverse-fg: #F4EFE6; --ui-inverse-accent: #F0A08C;
    --ui-brand-ring: #1E2A3A; --ui-brand-dot: #C8553D;
    --ui-ok-solid: #8FA68E;
    --ui-cat-blue: #1d4ed8; --ui-cat-green: #15803d; --ui-cat-teal: #0e7490;
    --ui-cat-violet: #6d28d9; --ui-cat-brown: #92400e; --ui-cat-pink: #be185d;
    --ui-input-bg: #FFFFFF;
    --ui-overlay: rgb(30 42 58 / 0.45);
    --ui-glass: rgb(255 255 255 / 0.82);
    --ui-shadow: 0 1px 2px rgb(30 42 58 / 0.06), 0 4px 10px -6px rgb(30 42 58 / 0.12);
    --ui-font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Roboto, Arial, sans-serif;
    --ui-font-head: "DM Serif Display", Georgia, "Times New Roman", serif;
    --ui-head-weight: 400;
    --ui-font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* Sterownia — ciemny */
:root[data-theme="sterownia"] {
    color-scheme: dark;
    --ui-ink: #E8EAED;
    --ui-ink-soft: #D5D8DD;
    --ui-muted: #9AA1AB;
    --ui-line: #2A2F36;
    --ui-line-strong: #3B414B;
    --ui-surface: #1B1E23;
    --ui-surface-2: #14161A;
    --ui-surface-3: #2A2F36;
    --ui-accent: #FFB020;
    --ui-accent-hover: #FFC555;
    --ui-accent-fg: #14161A;
    --ui-accent-soft: #2B2312;
    --ui-accent-line: #6B4E10;
    --ui-ok: #3FD0B6;        --ui-ok-bg: #0F2B28;      --ui-ok-line: #1D5A50;
    --ui-warn: #FFB020;      --ui-warn-bg: #2B2312;    --ui-warn-line: #6B4E10;
    --ui-down: #E65A5A;      --ui-down-bg: #2D1517;    --ui-down-line: #7A2B2B;
    --ui-pending: #B9A2F5;   --ui-pending-bg: #231D3A; --ui-pending-line: #4B3F7A;
    --ui-tone-blue: #8DB4F0; --ui-tone-blue-bg: #17243A; --ui-tone-blue-line: #2F4A75;
    --ui-inverse-bg: #2A2F36; --ui-inverse-fg: #E8EAED; --ui-inverse-accent: #FFB020;
    --ui-brand-ring: #FFB020; --ui-brand-dot: #FFB020;
    --ui-ok-solid: #3FD0B6;
    --ui-cat-blue: #8DB4F0; --ui-cat-green: #6FD08C; --ui-cat-teal: #5CC8D8;
    --ui-cat-violet: #B9A2F5; --ui-cat-brown: #E0A86A; --ui-cat-pink: #F08DB5;
    --ui-input-bg: #101216;
    --ui-overlay: rgb(0 0 0 / 0.6);
    --ui-glass: rgb(30 34 40 / 0.85);
    --ui-shadow: 0 1px 2px rgb(0 0 0 / 0.5), 0 6px 14px -8px rgb(0 0 0 / 0.6);
    --ui-font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Roboto, Arial, sans-serif;
    --ui-font-head: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Roboto, Arial, sans-serif;
    --ui-head-weight: 650;
    --ui-font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* Wspolne mapowanie Pico + starych tokenow app.css na tokeny motywu. */
:root:not([data-theme="sterownia"]), :root[data-theme="sterownia"] {
    --pico-font-family: var(--ui-font-body);
    --pico-font-family-monospace: var(--ui-font-mono);
    --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-input-bg);
    --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-form-element-active-background-color: var(--ui-input-bg);
    --pico-form-element-focus-color: var(--ui-accent);
    --pico-table-border-color: var(--ui-line);
    --pico-code-background-color: var(--ui-surface-3);
    --pico-code-color: var(--ui-ink-soft);
    --pico-blockquote-border-color: var(--ui-line);
    --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: 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: color-mix(in srgb, var(--ui-accent) 40%, transparent);
    --pico-primary-focus: color-mix(in srgb, var(--ui-accent) 30%, transparent);
    --pico-secondary: var(--ui-ink-soft);
    --pico-secondary-background: var(--ui-surface-3);
    --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-strong);
    --pico-dropdown-background-color: var(--ui-surface);
    --pico-dropdown-color: var(--ui-ink-soft);
    --pico-dropdown-hover-background-color: var(--ui-surface-3);
    --pico-modal-overlay-background-color: var(--ui-overlay);
    --pico-box-shadow: var(--ui-shadow);
    --pico-del-color: var(--ui-down);
    --pico-ins-color: var(--ui-ok);

    /* Stare tokeny app.css (warstwa przejsciowa) — wygenerowane z listy
       :root w app.css; kazdy wskazuje na token motywu. */
    --ink: var(--ui-ink);
    --ink-soft: var(--ui-ink-soft);
    --muted: var(--ui-muted);
    --muted-bg: color-mix(in srgb, var(--ui-ink) 6%, var(--ui-surface));
    --line: var(--ui-line);
    --line-strong: var(--ui-line-strong);
    --paper: var(--ui-surface-2);
    --panel: var(--ui-surface);
    --wash: var(--ui-surface-2);
    --accent: var(--ui-accent);
    --accent-strong: var(--ui-accent-hover);
    --blue: var(--ui-tone-blue);
    --amber: var(--ui-warn);
    --rose: var(--ui-down);
    --violet: var(--ui-pending);
    --status-ok-border: var(--ui-ok-line);
    --status-ok-bg: var(--ui-ok-bg);
    --status-ok-text: var(--ui-ok);
    --status-warning-border: var(--ui-warn-line);
    --status-warning-bg: var(--ui-warn-bg);
    --status-warning-text: var(--ui-warn);
    --status-down-border: var(--ui-down-line);
    --status-down-bg: var(--ui-down-bg);
    --status-down-text: var(--ui-down);
    --status-pending-border: var(--ui-pending-line);
    --status-pending-bg: var(--ui-pending-bg);
    --status-pending-text: var(--ui-pending);
    --tone-blue-bg: var(--ui-tone-blue-bg);
    --tone-violet-bg: var(--ui-pending-bg);
    --tone-amber-bg: var(--ui-warn-bg);
    --tile-green-bg: var(--ui-ok-bg);
    --tile-green-text: var(--ui-ok);
    --tile-blue-border: var(--ui-tone-blue-line);
    --tile-blue-text: var(--ui-tone-blue);
    --tile-violet-border: var(--ui-pending-line);
    --tile-violet-text: var(--ui-pending);
    --tile-amber-border: var(--ui-warn-line);
    --tile-amber-text: var(--ui-warn);
    --tile-rose-bg: var(--ui-down-bg);
    --tile-rose-border: var(--ui-down-line);
    --tile-rose-text: var(--ui-down);
    --tile-teal-bg: var(--ui-ok-bg);
    --tile-teal-border: var(--ui-ok-line);
    --tile-teal-text: var(--ui-ok);
    --tile-slate-bg: color-mix(in srgb, var(--ui-ink) 5%, var(--ui-surface));
    --tile-slate-border: var(--ui-line);
    --tile-slate-text: var(--ui-muted);
    --sidebar-toggle-text: var(--ui-ink);
    --brand-subtitle-text: var(--ui-muted);
    --app-sidebar-text: var(--ui-muted);
    --panel-head-border-bottom: var(--ui-line);
    --module-mark-bg: var(--ui-accent-soft);
    --module-mark-text: var(--ui-accent);
    --module-mark-tone-blue-bg: var(--ui-tone-blue-bg);
    --module-mark-tone-blue-text: var(--ui-tone-blue);
    --module-mark-tone-violet-bg: var(--ui-pending-bg);
    --module-mark-tone-violet-text: var(--ui-pending);
    --module-mark-tone-amber-bg: var(--ui-warn-bg);
    --module-mark-tone-amber-text: var(--ui-warn);
    --module-mark-tone-rose-bg: var(--ui-down-bg);
    --module-mark-tone-rose-text: var(--ui-down);
    --change-log-card-mark-bg: var(--ui-ink);
    --change-log-meta-bg: color-mix(in srgb, var(--ui-ink) 5%, var(--ui-surface));
    --change-log-copy-text: var(--ui-muted);
    --change-log-instruction-text: var(--ui-ink-soft);
    --btn-primary-bg: var(--ui-accent);
    --btn-muted-bg: color-mix(in srgb, var(--ui-ink) 7%, var(--ui-surface));
    --status-down-bg-2: var(--ui-down-bg);
    --status-down-text-2: var(--ui-down);
    --table-toolbar-border-bottom: var(--ui-line);
    --table-toolbar-bg: color-mix(in srgb, var(--ui-ink) 5%, var(--ui-surface));
    --list-table-header-bg: color-mix(in srgb, var(--ui-ink) 5%, var(--ui-surface));
    --sort-header-link-text: var(--ui-ink-soft);
    --monitor-board-status-text: var(--ui-muted);
    --monitor-board-status-is-error-text: var(--ui-down);
    --monitor-breadcrumbs-text: var(--ui-muted);
    --monitor-breadcrumbs-text-2: var(--ui-ink-soft);
    --monitor-folder-card-border: var(--ui-line);
    --monitor-folder-card-border-top: var(--ui-line-strong);
    --monitor-parent-card-border: var(--ui-line);
    --monitor-parent-card-border-top: var(--ui-line-strong);
    --monitor-parent-card-bg: color-mix(in srgb, var(--ui-ink) 5%, var(--ui-surface));
    --monitor-parent-card-border-2: var(--ui-line-strong);
    --monitor-parent-card-bg-2: color-mix(in srgb, var(--ui-ink) 7%, var(--ui-surface));
    --monitor-folder-card-is-ok-border-top: var(--ui-ok);
    --monitor-folder-card-is-warning-border-top: var(--ui-warn);
    --monitor-folder-card-is-down-border-top: var(--ui-down);
    --monitor-parent-icon-bg: color-mix(in srgb, var(--ui-ink) 5%, var(--ui-surface));
    --monitor-parent-icon-text: var(--ui-ink-soft);
    --monitor-inline-edit-border: var(--ui-line);
    --monitor-inline-edit-border-2: var(--ui-line-strong);
    --monitor-inline-edit-is-danger-text: var(--ui-down);
    --monitor-folder-meta-bg: color-mix(in srgb, var(--ui-ink) 5%, var(--ui-surface));
    --monitor-refresh-control-text: var(--ui-ink-soft);
    --monitor-refresh-control-text-2: var(--ui-muted);
    --monitor-drop-zone-is-drop-target-outline: var(--ui-accent);
    --monitor-card-facts-text: var(--ui-muted);
    --monitor-drop-empty-border: var(--ui-line);
    --notion-import-groups-is-nested-bg: color-mix(in srgb, var(--ui-ink) 5%, var(--ui-surface));
    --notion-para-group-head-border-bottom: var(--ui-line);
    --notion-para-group-head-bg: color-mix(in srgb, var(--ui-ink) 5%, var(--ui-surface));
    --notion-para-group-head-bg-2: color-mix(in srgb, var(--ui-ink) 7%, var(--ui-surface));
    --notion-para-group-head-bg-3: color-mix(in srgb, var(--ui-ink) 9%, var(--ui-surface));
    --capture-drop-zone-border: var(--ui-line-strong);
    --capture-drop-zone-is-active-border: var(--ui-line-strong);
    --help-dot-bg: color-mix(in srgb, var(--ui-ink) 5%, var(--ui-surface));
    --help-dot-text: var(--ui-ink-soft);
    --help-dot-text-2: var(--ui-ink-soft);
    --project-summary-chip-border: var(--ui-line);
    --project-preview-link-border: var(--ui-line);
    --project-preview-link-text: var(--ui-tone-blue);
    --project-preview-asset-bg: color-mix(in srgb, var(--ui-ink) 5%, var(--ui-surface));
    --project-sidebar-item-is-active-bg: var(--ui-accent-soft);
    --icon-button-danger-border: var(--ui-down-line);
    --pin-button-text: var(--ui-muted);
    --pin-button-border: var(--ui-accent);
    --iframe-blocked-state-border: var(--ui-line);
    --dashboard-widget-resize-handle-border: var(--ui-line-strong);
    --drag-handle-bg: color-mix(in srgb, var(--ui-ink) 5%, var(--ui-surface));
    --drag-handle-text: var(--ui-ink-soft);
    --dashboard-widget-chosen-border: var(--ui-line-strong);
    --connector-countdown-bg: var(--ui-ok-bg);
    --connector-countdown-text: var(--ui-ok);
    --connector-countdown-border: var(--ui-ok-line);
    --source-service-icon-tone-amber-text: var(--ui-warn);
    --source-service-icon-tone-violet-bg: var(--ui-pending-bg);
    --source-service-icon-tone-violet-text: var(--ui-pending);
    --source-service-icon-tone-rose-bg: var(--ui-down-bg);
    --source-service-icon-tone-rose-text: var(--ui-down);
    --login-shell-bg: color-mix(in srgb, var(--ui-ink) 5%, var(--ui-surface));
    --issue-selection-rect-border: var(--ui-accent);
    --nav-link-bg: var(--ui-muted);
    --para-select-filter-input-is-open-border: var(--ui-accent);
    --para-select-option-text: var(--ui-ink);
    --para-select-option-bg: var(--ui-accent-soft);
    --digest-card--pending-border: var(--ui-warn);
    --digest-card--pending-shadow: var(--ui-warn-bg);
    --digest-divider: var(--ui-line);
    --digest-drag-icon-text: var(--ui-muted);
    --digest-placeholder-meta-text: var(--ui-muted);
    --digest-menu-item--approve-text: var(--ui-ok);
}

/* Typografia motywu: naglowki (Pracownia: DM Serif Display), dane i kod w mono. */
/* Szeryfowy naglowek tylko dla tytulow stron i paneli (h1, h2) — nizsze poziomy zostaja w Inter, zeby nie poszerzac kart i tabel. */
h1, h2 { font-family: var(--ui-font-head); }
:root:not([data-theme="sterownia"]) :is(h1, h2) { font-weight: 400; }
code, kbd, samp, pre, .mono, .num, time, .pill-count { font-family: var(--ui-font-mono); }
:root[data-theme="sterownia"] h1, :root[data-theme="sterownia"] h2, :root[data-theme="sterownia"] h3 { letter-spacing: -0.01em; }


/* --------------------------------------------------- 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;
}

/* Wysokość: reguła `button { height: var(--ui-btn-h) }` (39 px, niżej) łapała też
   przyciski doku terminala (nazwa zakładki, ×, ▾, uchwyt „Terminale") i rozpychała
   belkę zakładek z ~28 do ~40 px — na telefonie to prawie 10% ekranu zabrane
   terminalowi (zgłoszenie 2026-09-02). Dok ma własne, gęste wymiary. */
.terminal-dock button:not(.btn):not(.icon-btn) {
    height: auto;
    min-height: 0;
}


/* 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); }

/* Stan „pracuje" dla .icon-btn z data-icon (initConnectorRefresh w app.js
   dokłada .is-busy, patrz .icon-button.is-busy w app.css) — duplikat animacji,
   bo panel Pliki (Drive) przeszedł ze starego .icon-button na .icon-btn
   (zgłoszenie Marcina 2026-09-03, punkt 4: różne rozmiary przycisków-glifów
   w nagłówkach paneli bocznych). */
.icon-btn.is-busy {
    cursor: progress;
}

.icon-btn.is-busy .ico {
    animation: icon-spin 900ms linear infinite;
}

@media (prefers-reduced-motion: reduce) {
    .icon-btn.is-busy .ico {
        animation: none;
        opacity: 0.5;
    }
}

/* #334: strefa niebezpieczna (usuwanie projektu) — wariant is-danger dla
   zwykłego przycisku Pico (nie tylko .icon-btn), i ramka karty-ostrzeżenia. */
button.is-danger, [type="submit"].is-danger {
    --pico-background-color: var(--ui-down);
    --pico-border-color: var(--ui-down);
}
button.is-danger.outline, [type="submit"].is-danger.outline {
    --pico-color: var(--ui-down);
    --pico-border-color: var(--ui-down-line);
}
button.is-danger.outline:hover, [type="submit"].is-danger.outline:hover {
    background: var(--ui-down-bg);
    border-color: var(--ui-down);
}
.border-danger { border-color: var(--ui-down-line); }

/* --- 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: var(--ui-cat-blue); background: color-mix(in srgb, var(--ui-cat-blue) 8%, var(--ui-surface)); border-color: color-mix(in srgb, var(--ui-cat-blue) 20%, var(--ui-line)); }
.file-mark.is-sheet  { color: var(--ui-cat-green); background: color-mix(in srgb, var(--ui-cat-green) 8%, var(--ui-surface)); border-color: color-mix(in srgb, var(--ui-cat-green) 20%, var(--ui-line)); }
.file-mark.is-media  { color: var(--ui-cat-teal); background: color-mix(in srgb, var(--ui-cat-teal) 8%, var(--ui-surface)); border-color: color-mix(in srgb, var(--ui-cat-teal) 20%, var(--ui-line)); }
.file-mark.is-code   { color: var(--ui-cat-violet); background: color-mix(in srgb, var(--ui-cat-violet) 8%, var(--ui-surface)); border-color: color-mix(in srgb, var(--ui-cat-violet) 20%, var(--ui-line)); }
.file-mark.is-archive{ color: var(--ui-cat-brown); background: color-mix(in srgb, var(--ui-cat-brown) 8%, var(--ui-surface)); border-color: color-mix(in srgb, var(--ui-cat-brown) 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);
    color: color-mix(in srgb, currentColor 70%, var(--ui-ink));
    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 {
    width: 2rem;
    height: 2rem;
    flex: 0 0 auto;
}
.brand-mark .ring { stroke: var(--ui-brand-ring); }
.brand-mark .dot { fill: var(--ui-brand-dot); }

.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;
}

/* #329: wrapper wiersza — link (nazwa/kropka/liczniki) + przycisk odepnij jako
   SIBLING, nie potomek linku (patrz komentarz w layout.html.twig przy tym
   znaczniku o tym, czemu button-w-<a> tu nie wchodzi w grę). */
.nav-pinned-item {
    display: flex;
    align-items: center;
    gap: 1px;
    border-radius: var(--ui-radius-sm);
}

.nav-pinned-item:hover, .nav-pinned-item:focus-within { background: var(--ui-surface-2); }

.nav-pinned-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex: 1 1 auto;
    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;
}

/* Przycisk odepnij — niewidoczny, dopóki wiersz nie ma hover/focus: 14 pozycji
   z widocznym drugim przyciskiem na stałe zaśmiecałoby listę i konkurowało
   wzrokowo z plakietkami `wisi`/`czeka` (zgłoszenie właściciela 2026-09-27).
   `visibility`, nie `display`, żeby focus klawiaturą (Tab) mógł na niego trafić
   nawet zanim JS zdąży cokolwiek przełączyć. */
.nav-pinned-unpin { flex: 0 0 auto; }

.nav-pinned-unpin-btn {
    width: 1.4rem;
    height: 1.4rem;
    visibility: hidden;
}

.nav-pinned-item:hover .nav-pinned-unpin-btn,
.nav-pinned-item:focus-within .nav-pinned-unpin-btn {
    visibility: visible;
}

.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); }

/* Nazwa projektu musi móc się skurczyć (Tailwind .truncate sam nie ustawia
   flex/min-width) — bez tego doszedłby licznik i wąska kolumna sidebaru
   rozpychałaby się na jego rzecz zamiast obcinać nazwę wielokropkiem. */
.nav-pinned-row .truncate {
    flex: 1 1 auto;
    min-width: 0;
}

/* Licznik otwartych zadań: zwykła cyfra, nie pigułka .count — ta ma padding
   i obrys, w wąskim sidebarze zjadałaby miejsce nazwie projektu (zgłoszenie
   właściciela 2026-09-10, patrz komentarz w layout.html.twig). */
/* Wyglad bierze z `.count` (ta sama pigulka co liczby na dashboardach i w PARA,
   prosba wlasciciela 2026-09-14) — tutaj zostaje WYLACZNIE ulozenie w wierszu
   i wezsza geometria, zeby pigulki nie rozpychaly waskiej kolumny sidebaru. */
/* SIATKA o stalych kolumnach, nie flex. Przy flexie szerokosc slotu zalezala od
   liczby w nim ("17" szersze niz "3") i od tego, czy druga pigulka w ogole jest —
   przez co liczniki w kolejnych wierszach ladowaly na roznych wysokosciach w poziomie
   i kolumna sie lamala (zgloszenie wlasciciela 2026-09-14, zrzut 11:51). Dwie stale
   kolumny daja jedna pionowa os dla calej listy, niezaleznie od wartosci. */
.nav-pinned-counts {
    flex: 0 0 auto;
    margin-left: auto;
    display: grid;
    grid-template-columns: 1.75rem 1.75rem;
    gap: 3px;
    align-items: center;
}

.nav-pinned-count {
    min-width: 0;
    width: 100%;
    padding: 0 0.2rem;
    font-variant-numeric: tabular-nums;
}

/* Zero NIE znika z ukladu, tylko z widoku: usuniecie wezla zabieralo slot i
   sasiednia pigulka przeskakiwala pod cudza kolumne. visibility trzyma miejsce. */
.nav-pinned-count.is-zero { visibility: hidden; }

/* Czerwien dostaje pigulka WYMAGAJACA REAKCJI (doprecyzowanie wlasciciela
   2026-09-14: „te ktore wymagaja jakiejs reakcji"), czyli zadania bez terminu
   albo z terminem dzis/przeterminowanym. Tu czerwien znaczy to samo co wszedzie
   w tej appce: zajmij sie tym. */
/* 15.09.2026: ten sam kod koloru dziala teraz na KAZDEJ pigulce `.count`
   (lista projektow ma osobne kolumny „wisi" i „czeka"), nie tylko w menu —
   czerwone/szare ma znaczyc to samo w calej appce. */
.count.is-action {
    border-color: var(--ui-down-line);
    background: var(--ui-down-bg);
    color: var(--ui-down);
}

/* „Czeka na termin w przyszlosci" — wygaszona i obrysowana przerywana linia.
   Ma byc widoczne, ze to NIE jest na teraz, bez czytania liczby. */
.count.is-waiting {
    border-style: dashed;
    background: transparent;
    opacity: 0.6;
    font-weight: 500;
}

/* --- 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 -------------------------------------------------------- */

/* #325: jeden zwarty wiersz (avatar + nazwa + wyjście) zamiast karty na dwa
   wiersze z powtórzoną nazwą (workspace.name i userLabel osobno) i danymi
   nieistotnymi na co dzień (plan/tryb/licznik sesji) — to zjadało ~160px na
   768px wysokości paska (zgłoszenie właściciela 2026-09-27). Szczegóły nie
   znikają: `title` na całym wierszu pokazuje je jako natywny tooltip po
   najechaniu — bez JS-owego popovera, którego to nie jest warte. */
.user-box {
    margin-top: auto;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    border-top: 1px solid var(--ui-line);
    padding-top: 0.6rem;
    font-size: var(--ui-meta-size);
    cursor: default;
}

.user-box-mark {
    display: grid;
    place-items: center;
    width: 1.6rem;
    height: 1.6rem;
    flex: 0 0 auto;
    border-radius: 999px;
    background: var(--ui-surface-2);
    color: var(--ui-ink-soft);
    font-weight: 600;
    font-size: 12px;
}

.user-box-name {
    flex: 1 1 auto;
    min-width: 0;
    font-weight: 600;
}

.user-box-logout {
    flex: 0 0 auto;
    font-size: var(--ui-eyebrow-size);
}

/* --- 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-inverse-bg);
    color: var(--ui-inverse-fg);
}

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

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

.surface-ink .text-ink, .surface-ink a { color: var(--ui-inverse-fg); }
.surface-ink .brand-subtitle { color: color-mix(in srgb, var(--ui-inverse-fg) 70%, transparent); }
.surface-ink .text-accent { color: var(--ui-inverse-accent); }

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

/* Skrot dla klas w stylu utility, ktore szablony nadal niosa (Tailwind zniknal
   z buildu) — wartosci z tokenow, wiec oba motywy dzialaja. */
.bg-white { background: var(--ui-surface); }
.bg-zinc-100 { background: var(--ui-surface-3); }
.text-white { color: var(--ui-inverse-fg); }
.text-red-600, .text-red-700 { color: var(--ui-down); }
.text-zinc-400, .text-zinc-500 { color: var(--ui-muted); }
.surface-accent .text-ink, .surface-accent.text-ink { color: var(--ui-accent-fg) !important; }  /* !important: linki dostaja kolor z lancucha :not() o duzej specyficznosci */

.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;
}

/* Pusty stos musi byc CELEM upuszczenia. Bez tego ma zerowa wysokosc, wiec
   Sortable nie ma w co trafic i boxa nie da sie przeniesc do pustej kolumny
   (zgloszenie wlasciciela 16.09.2026: „nie da sie przenosic tych boxow miedzy
   kolumnami" — projekt z wymuszonymi 3 kolumnami i pustym trzecim stosem).
   Kreskowana ramka mowi wprost, ze to miejsce na box, zamiast wygladac na
   dziure w ukladzie.
   2026-09-26: bylo `:empty`, ktore NIGDY nie trafialo — Twig zostawia w pustym
   divie newline z wcieciem, a `:empty` odpada przy jakimkolwiek tekscie.
   `:not(:has(*))` patrzy na elementy potomne, wiec jest odporne na formatowanie
   szablonu (dotyczy obu widokow: project_show i dashboard). */
.project-side-stack:not(:has(*)) {
    min-height: 140px;
    border: 1px dashed var(--ui-border);
    border-radius: var(--ui-radius, 8px);
    opacity: 0.55;
}

.project-side-stack:not(:has(*))::after {
    content: "Przeciagnij tu box";
    display: block;
    padding: 1rem;
    font-size: 0.85em;
    text-align: center;
    color: var(--ui-muted);
}

/* 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;
}

/* Dossier (Osint): identyfikatory (NIP/REGON/KRS) w nagłówku karty, zamiast
   osobnego boksu na jedną wartość (zgłoszenie Marcina 27.09.2026) — kopiowanie
   i link do rejestru zostają, tylko skurczone do rozmiaru pigułki, żeby nie
   rozpychały nagłówka wyższego niż sąsiednie plakietki ról. */
.osint-id-chip {
    gap: 0.4em;
}

.osint-id-chip .icon-btn {
    width: 1.4em;
    height: 1.4em;
    font-size: 11px;
    border-radius: 999px;
}

/* Dossier (Osint): zdjęcie podmiotu albo awatar-inicjały wygenerowany CSS-em
   (bez zewnętrznej usługi — zamówienie 2026-09-27). Kolor liczony z id % 6,
   deterministyczny, żeby ten sam podmiot zawsze dostawał ten sam kolor. */
/* Pełna treść artykułu (box "W mediach") — szerokość wiersza ograniczona do
   wygodnej do czytania (zgłoszenie Marcina 27.09.2026: "ma się czytać jak
   artykuł, nie jak zrzut z bazy"); kolumna Podglądu bywa szersza niż to. */
.osint-artykul-tresc {
    max-width: 68ch;
}

.osint-avatar-media {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 4rem;
    height: 4rem;
    border-radius: 999px;
    overflow: hidden;
    flex-shrink: 0;
    border: 1px solid var(--ui-line);
    font-weight: 700;
    font-size: 1.25rem;
}

.osint-avatar-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.osint-avatar-initials.osint-avatar-c0 { background: color-mix(in srgb, var(--ui-cat-blue) 16%, var(--ui-surface)); color: var(--ui-cat-blue); }
.osint-avatar-initials.osint-avatar-c1 { background: color-mix(in srgb, var(--ui-cat-green) 16%, var(--ui-surface)); color: var(--ui-cat-green); }
.osint-avatar-initials.osint-avatar-c2 { background: color-mix(in srgb, var(--ui-cat-brown) 16%, var(--ui-surface)); color: var(--ui-cat-brown); }
.osint-avatar-initials.osint-avatar-c3 { background: color-mix(in srgb, var(--ui-cat-violet) 16%, var(--ui-surface)); color: var(--ui-cat-violet); }
.osint-avatar-initials.osint-avatar-c4 { background: color-mix(in srgb, var(--ui-cat-pink) 16%, var(--ui-surface)); color: var(--ui-cat-pink); }
.osint-avatar-initials.osint-avatar-c5 { background: color-mix(in srgb, var(--ui-cat-teal) 16%, var(--ui-surface)); color: var(--ui-cat-teal); }

/* #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);
}

/* 2026-10-04 (zamowienie wlasciciela): dok po PRAWEJ jest szuflada NAD trescia
   (position:fixed), a nie kolumna obok. Zmiana jego szerokosci nie rusza ukladu
   pod spodem - zadnego padding-right na .app-frame, szerokosc boxow stala. */
/* Zwiniety dok po prawej to staly pasek 22 px - nie zaslania krawedzi tresci. */
body:has(.terminal-dock.is-right.is-collapsed) .app-frame {
    padding-right: 30px;
}

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

/* #325 (zgłoszenie właściciela 2026-09-27, mały laptop ~270px paska): DWA
   niezależne obszary przewijania (nav-pinned własnym max-height:34vh, nav-list
   flex:1 z własnym overflow) — oba krótkie, oba z widocznym suwakiem, Osint/
   Dossier na końcu nav-list ucięty pod scrollem, którego nie było widać że
   trzeba przewinąć. Zmierzone Playwright 1366×768, 8 przypiętych: nav-list
   pokazywał 156 z 423px (Dashboardy ledwie, Triaż/Osint/Publikator/PARA/
   Zgłoszenia/Gabaryty/Techniczne całkiem poza kadrem).

   Pierwsza naprawa (nav-list order:1 flex:0 0 auto + nav-pinned order:2
   flex:1 1 auto) usunęła DRUGI scroll, ale zostawiła oba bloki w tym samym
   flex-column walczące o tę samą przestrzeń: z rozwiniętymi grupami
   (Dashboardy + Osint, czyli u właściciela na co dzień) nav-list rośnie,
   nav-pinned (flex:1, min-height:0) się ściska do wąskiego paska — a
   ponieważ ma WŁASNY overflow-y:auto z twardą wysokością, ostatni widoczny
   wiersz ucina się w połowie (zrzut właściciela 2026-09-27 18:59,
   `zrzut-20260927-185958-4fcd3e4a.png`: „[SPO]…” widoczne tylko górną
   krawędzią). Dwa flex-y o zmiennej wysokości rywalizujące o t samo miejsce
   to zawsze ryzyko takiego cięcia w środku wiersza, niezależnie od tego,
   który akurat się ściska.

   Druga naprawa — właściciel wybrał wariant „jeden wspólny scroll”: usuwamy
   OBU blokom własny overflow/flex-grow, całą resztę paska (nav-list +
   nav-pinned) scrolluje WYŁĄCZNIE .app-sidebar (już ma overflow-y:auto,
   patrz reguła bazowa wyżej w pliku) — sticky zostaje tylko na szukajce
   (u góry) i stopce konta (na dole), dokładnie jak poprosił. Rzeczywisty
   koniec listy (dół .app-sidebar, tuż nad sticky stopką) to jedyne możliwe
   miejsce cięcia — naturalna krawędź scrolla, nie sztuczne pudełko ze
   zmienną wysokością — i dostaje delikatne zanikanie (.user-box::before)
   jako sygnał "dalej coś jest", zamiast twardej krawędzi w środku niczego. */
.app-sidebar .nav-list {
    order: 1;
    flex: 0 0 auto;
    overflow: visible;
}

.app-sidebar .nav-pinned {
    order: 2;
    flex: 0 0 auto;
    padding-top: 0.6rem;
    padding-bottom: 0.6rem;
    margin-bottom: 0;
    border-top: 1px solid var(--ui-line);
}

/* Sticky tylko na szukajce (góra) i stopce konta (dół) — reszta (logo, nav,
   przypięte) to JEDNA przewijana kolumna wewnątrz .app-sidebar. */
.app-sidebar .nav-search {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--ui-surface);
    margin-inline: -0.75rem;
    padding-inline: 0.75rem;
}

.app-sidebar .user-box {
    order: 3;
    position: sticky;
    bottom: 0;
    z-index: 1;
    background: var(--ui-surface);
    margin-inline: -0.75rem;
    padding-inline: 0.75rem;
}

/* Zanikanie tuż nad sticky stopką: jedyne miejsce, gdzie lista może się
   "urwać" w naturalnej krawędzi scrolla — to jest sygnał, że jest jej
   dalszy ciąg, nie usterka. */
.app-sidebar .user-box::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: -14px;
    height: 14px;
    background: linear-gradient(to bottom, transparent, var(--ui-surface));
    pointer-events: none;
}

/* Menu to nawigacja aplikacji, przypiete to skroty do projektow — rozne rzeczy,
   wiec rozna waga. Pozycje menu zostaja mocniejsze, przypiete lzejsze. */
.app-sidebar .nav-pinned-row {
    font-size: 12.5px;
    padding-left: 0.35rem;
}

/* ═══════════════════════════════════════════════════════════════════════════
   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;
}

/* 1100, nie 1340: przy doku terminala na pół ekranu treść ma ~1100–1200 px
   i mają już być trzy kolumny (zrzut usera 2026-08-26). */
@container workspace (min-width: 1100px) {
    .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 byl 48rem = 768 px, ale po dzisiejszym #333
   (kolumna boczna dostala track 2fr zamiast sztywnego floora 49rem —
   app.css/app-ui.css, commit 08fb89b) kolumna boczna realnie ma ~380-460 px
   przy 1000-1440 px viewportu — próg 768 px byl NIEOSIAGALNY, wiec select
   „Kolumny" zapisywal wybor (widac po klasie dash-cols-N i w bazie), ale
   siatka nigdy sie nie wlaczala: user widzial tylko mrugniecie ramki, bez
   zadnej zmiany ukladu (zgloszenie Marcina 2026-09-03, #200 regresja od
   #333). Zmierzone Playwright: 380 px @1000, 451 px @1440. Nowy prog 20rem
   (320 px) miesci sie w obu, wiec 2 kolumny faktycznie wystapia. 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;
}

/* #347 (trzecia runda #335): auto/wymuszone — user ma wygrywać z kontenerem
   w obie strony ("UI ustawia automatycznie 2, a ja chcę na przykład 1, albo
   na siłę 3", zgłoszenie właściciela 2026-09-14). Poniższe reguły (próg
   "panele" 20rem NIŻEJ i próg "workspace" 900px DALEJ) sterują liczbą kolumn
   wyłącznie w trybie AUTO (.is-auto — DashboardController::resolveSidePanelColumnMode
   zwraca 'auto', gdy user nic nie wymusił; szablon dokłada wtedy klasę
   is-auto i buduje KOMPLET 3 stosów, żeby było czym sterować). W trybie
   wymuszonym (.is-forced) te reguły NIE MOGĄ obowiązywać — stąd .is-auto
   w każdym selektorze zamiast zostawiać je gołe. .is-forced dostaje WŁASNY,
   bezwarunkowy komplet reguł niżej (poza @container), żeby wymuszona liczba
   kolumn działała NIEZALEŻNIE od szerokości. Obie klasy się wykluczają, więc
   żadna z tych reguł nie musi bić się z drugą przez !important. */
@container panele (min-width: 20rem) {
    .project-side-column > turbo-frame#project-side-panels.is-auto {
        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. Domyślnie (brak klasy dash-cols-N,
           albo dash-cols-2 — #335) dwie kolumny; warianty 1/3 nadpisują
           grid-template-columns niżej. */
        grid-template-columns:
            minmax(0, var(--dash-col-1, 1fr)) auto
            minmax(0, var(--dash-col-2, 1fr));
        /* Przerwa między stosami MA równać się przerwie podgląd↔boxy (16px,
           --space-md na .project-workspace): 5 + uchwyt 6 + 5 = 16. Wcześniej
           28+6+28 = 62 px vs 16 px z lewej — nierówne kolumny (zrzut usera
           2026-08-26). Wizualną granicę robi uchwyt, nie sama pustka. */
        row-gap: 28px;
        column-gap: 5px;
        align-items: start;
    }

    /* #335: 1 kolumna wybrana przez usera — pojedynczy stos, bez siatki
       (i bez uchwytów, których i tak nie ma w DOM, bo jest tylko jeden
       .project-side-stack — patrz resolveVisibleSidePanelStacks()). */
    .project-side-column > turbo-frame#project-side-panels.is-auto.dash-cols-1 {
        display: block;
    }

    /* #335: 3 kolumny — jak dashboard (DASHBOARD_COLUMN_WIDTHS_DEFAULT), ale
       z podłogą 300 px na panel (zgłoszenie #335: przy trzech kolumnach panel
       nie może się zapaść poniżej czytelnej szerokości). Przy wąskim
       kontenerze grid po prostu przekroczy szerokość rodzica (poziomy scroll
       w obrębie ramki) — to świadomy floor, nie breakpoint. */
    .project-side-column > turbo-frame#project-side-panels.is-auto.dash-cols-3 {
        grid-template-columns:
            minmax(300px, var(--dash-col-1, 1fr)) auto
            minmax(300px, var(--dash-col-2, 1fr)) auto
            minmax(300px, var(--dash-col-3, 1fr));
    }

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

    .project-side-column > turbo-frame#project-side-panels.is-auto.dash-cols-1 > .dash-col-resize {
        display: none;
    }
}

/* #335: ≤900 px TREŚCI — zawsze jedna kolumna, niezależnie od wyboru usera (na tej
   szerokości nawet 2×300 px już się nie mieści obok podglądu/paneli). MUSI być
   @container, nie @media: dok terminala jest fixed i zabiera miejsce przez
   padding-right na .app-frame — szerokość OKNA się nie zmienia, zmienia się
   tylko szerokość .content-wrap. @media (max-width: 900px) na szerokim oknie
   (np. 1920px) z dokiem otwartym nigdy się nie odpala, więc siatka 3×minmax(300px,…)
   przekracza wąski kontener i panele na siebie nachodzą, zamiast się złożyć do
   jednej kolumny (zgłoszenie właściciela 2026-09-14). Wygrywa nad regułami
   @container powyżej, bo jest zdefiniowana PO nich i celuje wprost w klasy
   dash-cols-2/3 (ta sama specyficzność, kolejność w źródle rozstrzyga kaskadę).
   TYLKO auto (#347) — .is-forced ma prawo zostać przy 2/3 kolumnach nawet tu. */
@container workspace (max-width: 900px) {
    .project-side-column > turbo-frame#project-side-panels.is-auto.dash-cols-2,
    .project-side-column > turbo-frame#project-side-panels.is-auto.dash-cols-3 {
        display: block;
        grid-template-columns: none;
    }

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

/* #347: TRYB WYMUSZONY — te same siatki co powyżej, ale BEZ żadnego @container:
   user wybrał "na siłę 3", więc 3 kolumny stoją zawsze, choćby kontener miał
   20px. Przy braku miejsca podłoga 300px×3 (patrz .is-auto.dash-cols-3 wyżej)
   przekroczy szerokość .project-side-column — ten sam, już istniejący
   "świadomy floor" (#335): kolumna boczna dostaje poziomy scroll (patrz
   overflow-x auto-obliczone z overflow-y na .project-side-column, app.css/
   app-ui.css @container workspace 580px), NIE nachodzenie i NIE scroll całej
   strony. Zero !important: .is-auto i .is-forced się wykluczają. */
.project-side-column > turbo-frame#project-side-panels.is-forced {
    display: grid;
    grid-template-columns:
        minmax(0, var(--dash-col-1, 1fr)) auto
        minmax(0, var(--dash-col-2, 1fr));
    row-gap: 28px;
    column-gap: 5px;
    align-items: start;
}

.project-side-column > turbo-frame#project-side-panels.is-forced.dash-cols-1 {
    display: block;
}

.project-side-column > turbo-frame#project-side-panels.is-forced.dash-cols-3 {
    grid-template-columns:
        minmax(300px, var(--dash-col-1, 1fr)) auto
        minmax(300px, var(--dash-col-2, 1fr)) auto
        minmax(300px, var(--dash-col-3, 1fr));
}

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

.project-side-column > turbo-frame#project-side-panels.is-forced.dash-cols-1 > .dash-col-resize {
    display: none;
}

/* Telefon: wymuszone 2/3 kolumny (300px floor) wypychały panele poza ekran
   (+558px) — na wąskim oknie zawsze jeden słupek, uchwyt zbędny. */
@media (max-width: 640px) {
    .project-side-column > turbo-frame#project-side-panels.is-forced {
        display: block;
    }

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

    .project-side-column > turbo-frame#project-side-panels.is-forced .project-side-stack + .project-side-stack {
        margin-top: 28px;
    }
}

/* Telefon: długie tokeny (nazwy testów, komunikaty), nawigacja platformy,
   tabele wzorcowni — zawijanie / przewijanie we własnym kontenerze. */
@media (max-width: 640px) {
    .test-tree-node .font-mono,
    .test-tree-message { min-width: 0; max-width: 100%; overflow-wrap: anywhere; }
    .platform-nav { flex-wrap: wrap; }
    .test-tree-children { padding-left: 10px; }
    .kit-demo { overflow-x: auto; }
    time.meta { white-space: normal; }
    .para-select-filter-wrap { min-width: 0; max-width: 100%; }
    .para-select-filter-input { min-width: 0; max-width: 100%; box-sizing: border-box; }
}

/* 2. PRÓG 1340 px DZIAŁAŁ NA ODWRÓT — a ten nadpis też, w drugą stronę.
   Historycznie ten blok dawał aside floor 49rem (784px) przy main minmax(0, 1fr),
   czyli main mógł zapaść się do 0, a aside miał gwarantowane 784px — dokładnie
   odwrotnie niż zgłoszenie Marcina 2026-09-03 (2/3): "Podgląd ma być zawsze w
   najszerszej kolumnie, nigdy węższy od paneli". Zmierzone na /app/projekty/22
   @1440 z dokiem is-right otwartym (kontener 1176px): main=344px, aside=784px —
   aside ponad DWA razy szerszy od main. Skok był też nieciągły: poniżej 1100px
   aside miał sufit 23rem (368px), a od 1100px floor 784px — podwojenie szerokości
   panelu w jednej klatce media query, stąd "boxy się psują przy rozciąganiu doku".
   Naprawa: usunięty ten nadpis. Track 3fr:2fr z app.css (@container workspace
   580px) ma main.min(20rem) ≥ aside.min(15rem) i main.fr(3) ≥ aside.fr(2), więc
   main jest szerszy od aside przy KAŻDEJ szerokości kontenera ≥580px, ciągle,
   bez osobnego progu na 1100px. Dwa stosy w panelach bocznych (próg "panele"
   48rem = 768px, reguła niżej) nadal się pojawiają — dopiero przy większej
   szerokości ekranu niż wcześniej, bo aside rośnie wolniej niż main z założenia. */

/* 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));
    }
}

/* Przy trzech kolumnach (dwa stosy) kolumna boczna NIE jest własnym scrollboksem
   — scrolluje się z całą stroną. Sticky+overflow z reguły 580px dokładał drugi
   suwak tuż obok suwaka strony ("czy tych suwaków nie jest za dużo", zrzut
   2026-08-26); sens miał przy jednym wąskim słupku, nie przy panelach szerokich
   na pół ekranu. Musi stać PO regule wyżej — ta sama specyficzność. */
@container workspace (min-width: 1100px) {
    .project-side-column {
        position: static;
        max-height: none;
        overflow: visible;
    }
}

/* 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;
}

/* Główna ramka treści: zawsze block. Domyślnie turbo-frame jest inline, a reguła
   [busy] wyżej (app.css) przełączała ją na block tylko na czas ładowania — cały
   widok przeskakiwał przy każdym kliknięciu (zgłoszenie 04.10.2026, lista Dossier).
   Spinner przypięty do okna, nie do środka wysokiej ramki (bywał poza ekranem). */
turbo-frame#app-content { display: block; }
turbo-frame#app-content[busy],
turbo-frame#app-content[aria-busy="true"] { min-height: 0; opacity: 1; }
turbo-frame#app-content[busy]::after,
turbo-frame#app-content[aria-busy="true"]::after { position: fixed; z-index: 50; }

/* Pico rysuje przy [aria-busy=true] własny spinner jako ::before W PRZEPŁYWIE —
   na ramce Turbo to było kółko w lewym górnym rogu i spychanie treści o ~23 px
   na każdym widoku. Wskaźnikiem jest nasz ::after wyżej. */
turbo-frame[aria-busy="true"]::before { content: none; display: none; }

/* Komunikaty flash: pływający stos w rogu, poza przepływem (layout.html.twig). */
/* sticky + height:0: przykleja się do górnej krawędzi KOLUMNY TREŚCI (fixed do
   okna lądował na docku terminali), a zerowa wysokość nie zajmuje miejsca. */
.flash-stack {
    position: sticky; top: 1rem; z-index: 60; height: 0;
    display: grid; gap: 0.5rem; justify-items: end;
}
.flash-stack > .toast { max-width: min(28rem, 100%); }
.flash-stack > .toast { margin: 0; box-shadow: 0 10px 28px rgb(0 0 0 / 0.14); }

/* Lista Dossier: stałe szerokości kolumn, żeby zmiana sortowania (inne nazwy
   na stronie) nie przesuwała tabeli. Nazwa bierze resztę i się zawija. */
.osint-list-table { table-layout: fixed; width: 100%; min-width: 52rem; }
.osint-list-table td:first-child { overflow-wrap: anywhere; }

@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;
}

/* Zgłoszenie Marcina 2026-09-03: belka rozbita na trzy grupy (maszyny / akcje
   treści / widok — patrz templates/app/section/project_show.html.twig), każda
   własnym flex-wrap. Zaleta nad jednym płaskim rzędem: przy zwężaniu kontenera
   grupa wpada na nowy wiersz W CAŁOŚCI (jest jednym elementem flex w
   .page-header-actions), więc nic nie odpada pojedynczo — przedtem "Boxy▾"
   potrafiło spaść samotnie w drugi wiersz z prawej, a select "Kolumny" wisiał
   nierówno (zrzut usera 2026-09-03). Gap równy między grupami i wewnątrz nich. */
.page-header-group {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
}

/* Przy wąskim kontenerze (dok terminala na pół ekranu) grupy dociśnięte do
   prawej zostawiają brzydką dziurę z lewej, gdy wpadają w kolejny wiersz —
   od tego progu belka rośnie na pełną szerokość i grupy startują od lewej.
   Container query po .content-wrap-fluid (container: workspace / inline-size,
   app.css), NIE media query po oknie — dok zwęża kontener treści, nie okno. */
/* ── LISTA PARA W WASKIM KONTENERZE ──────────────────────────────────────────
   Dok terminala przy prawej krawedzi zwezal kontener listy do ~340 px, a tabela
   dalej miala ~680 px: kolumna akcji lezala POZA kadrem i przyciski po prostu
   znikaly (zmierzone 2026-09-06). Dwa kroki, w tej kolejnosci:

   1. w waskim kontenerze chowamy kolumny drugorzedne (zrodlo, aktualizacja) —
      wtedy tabela sie MIESCI i nie ma czego przewijac;
   2. gdyby i tak nie starczylo, kolumna akcji zostaje przyklejona do prawej
      krawedzi przewijania, wiec przyciski sa widoczne zawsze. */
@container workspace (max-width: 48rem) {
    .col-drugorzedna {
        display: none;
    }
}

.col-akcje {
    position: sticky;
    right: 0;
    background: var(--ui-surface);
}

/* Wyrownanie belki rozstrzyga TYLKO tutaj. Domyslnie od lewej: poszarpana
   prawa krawedz czyta sie normalnie, poszarpana lewa nie. Do prawej dopiero
   gdy kontener jest na tyle szeroki, ze belka i tak wchodzi w jeden rzad. */
.page-header-actions {
    justify-content: flex-start;
}

@container workspace (min-width: 48rem) {
    .page-header-actions {
        justify-content: flex-end;
    }
}

@container workspace (max-width: 48rem) {
    .page-header-actions {
        width: 100%;
    }

    /* W waskiej kolumnie grupowanie szkodzi: kazda grupa jest JEDNYM elementem
       flex, wiec kazda zaczyna wlasny wiersz i belka rosnie do czterech rzedow
       z poszarpana krawedzia (zrzut usera 2026-09-06), mimo ze przyciski zmiescilyby
       sie po dwa-trzy w rzedzie. `display: contents` zdejmuje same pudelka grup,
       zostawiajac przyciski bezposrednimi dziecmi belki — zawijaja sie wtedy
       ciasno, jeden za drugim. Grupowanie zostaje tam, gdzie ma sens: przy
       szerokim kontenerze, gdzie chroni przed odpadaniem pojedynczych przyciskow. */
    .page-header-group {
        display: contents;
    }

    /* Zapas na plywajacy klaster narzedzi ma sens tylko wtedy, gdy belka
       faktycznie dochodzi do prawej krawedzi okna. W waskiej kolumnie (dok
       terminala na pol ekranu) zabieral 3,25rem z ~34rem i wypychal przyciski
       do kolejnego wiersza — a klaster i tak wisi nad czyms innym. */
    .page-header-actions {
        padding-right: 0;
    }
}

/* Select "Kolumny": etykieta INLINE obok selecta, nigdy nad nim (zgłoszenie
   Marcina 2026-09-03 — poprzednio <label class="flex ..."> zależał od klas
   Tailwinda, które w tym miejscu bywały przykryte resetem formularzy i
   etykieta wisiała nad kontrolką). Własna klasa, żeby układ nie zależał od
   tego, co akurat wygeneruje skan Tailwinda. */
.dash-column-field {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    white-space: nowrap;
    font-size: var(--ui-eyebrow-size, 11px);
    font-weight: 600;
    color: var(--ui-muted);
}

.dash-column-select {
    /* auto: szersze niż gołe "1"/"2"/"3", żeby zmieściło etykietę opcji
       "auto (N)" bez ucinania (#347) — select i tak pokazuje tylko WYBRANĄ
       opcję, więc szerokość rośnie tylko w trybie auto. */
    width: auto;
    min-width: 3.4rem;
    height: var(--ui-btn-h);
}

/* #200 poprawka 2026-09-03, znaczenie rozszerzone #347 (2026-09-14): select
   w trybie 'auto' (liczbę kolumn dobiera kontener, patrz initProjectSidePanelEffectiveColumns
   w app.js) sygnalizujemy delikatnie — bez alarmującego koloru stanu (§5
   docs/UI_ZASADY.md rezerwuje kolor wyłącznie na stan). W trybie wymuszonym
   (1/2/3) klasa NIE jest dokładana — to świadomy, stały wybór usera. */
.dash-column-select.is-effective {
    border-style: dashed;
    color: var(--ui-muted);
}

/* ═══════════════════════════════════════════════════════════════════════════
   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;
}

/* WYJĄTEK: drzewo plików. Reguła wyżej łapała KAŻDE <ul> w boxie, także
   węzły jsTree — w trybie „3 ostatnie" katalog z 55 zdjęciami pokazywał
   001–003.jpg, a katalog projektu trzy pozycje z czternastu, bez żadnego
   śladu, że coś ucięto. Dla drzewa „3 ostatnie" nie ma sensu (to nie jest
   lista chronologiczna), a user brał to za zgubione pliki (zgłoszenie
   2026-09-02, [PRO] OW Sudety). Tryb gęstości steruje tu tylko zwijaniem
   całego boxu (is-density-0), nie liczbą węzłów. */
.panel.is-density-1 .tree ul > li:nth-child(n+4),
.panel.is-density-1 .jstree-children > li:nth-child(n+4) { display: block; }


/* 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.
   row-gap, NIE skrót gap: skrót ustawiał też column-gap i (później w pliku)
   nadpisywał 5px z reguły progowej — przerwa między stosami rosła do 62 px. */
.project-side-column > turbo-frame#project-side-panels {
    display: grid;
    row-gap: 28px;
    align-items: start;
}

/* Nagłówek sortowalnej kolumny — komponent do reużycia (lista PARA, lista
   Dossier). Zgłoszenie 27.09.2026: strzałka sortowania (↑↓↕) była za drobna,
   nie było widać która kolumna jest aktywna ani w którą stronę sortuje.
   Znacznik: templates/ui/_table_sort.html.twig. */
.table-sort-link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: inherit;
    text-decoration: none;
}

.table-sort-icon {
    font-size: 1rem;
    line-height: 1;
    color: var(--ui-muted);
    opacity: 0.4;
    transition: opacity 0.15s ease, color 0.15s ease;
}

.table-sort-link:hover .table-sort-icon,
.table-sort-link:focus-visible .table-sort-icon {
    opacity: 0.85;
}

/* Kolumna aktywna: pogrubiony nagłówek + strzałka w kolorze akcentu, większa
   niż reszta tekstu — ma być jednoznaczne, która kolumna sortuje. */
.table-sort-link.is-active {
    color: var(--ui-ink);
    font-weight: 700;
}

.table-sort-link.is-active .table-sort-icon {
    color: var(--ui-accent);
    opacity: 1;
    font-size: 1.15rem;
    font-weight: 700;
}

/* Filtr listy przeniesiony NAD kolumnę Nazwa (zgłoszenie 27.09.2026) — stał
   w prawym górnym rogu belki, daleko od kolumny, którą filtruje. Siedzi w tym
   samym <th>, więc wyrównanie do kolumny jest gwarantowane bez liczenia
   szerokości. */
.table-sort-link-row {
    margin-bottom: 0.35rem;
}

.table-filter-input {
    display: block;
    width: 100%;
    max-width: 16rem;
    font-size: 0.8rem;
    font-weight: 400;
    text-transform: none;
    padding: 0.3rem 0.6rem;
}

/* Kolumny Millera (#172) — miller_controller.js */
.miller-toggle { margin: 0 1rem .5rem; padding: .15rem .6rem; font-size: .8rem; width: auto; }
.miller-crumbs { display: flex; flex-wrap: wrap; gap: .25rem; padding: 0 1rem .5rem; }
.miller-crumbs button { all: unset; cursor: pointer; font-size: .8rem; color: var(--ui-muted, inherit); }
.miller-crumbs button + button::before { content: '/'; margin-right: .25rem; }
.miller-body { display: flex; overflow-x: auto; border-top: 1px solid var(--ui-border); }
.miller-col { list-style: none; margin: 0; padding: 0; min-width: 11rem; max-height: 22rem; overflow-y: auto; border-right: 1px solid var(--ui-border); flex: 0 0 auto; }
.miller-item { all: unset; display: block; box-sizing: border-box; width: 100%; padding: .3rem .6rem; font-size: .85rem; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.miller-item:hover { background: var(--ui-surface-2); }
.miller-item.is-active { background: var(--ui-surface-2); font-weight: 600; }
.miller-preview { width: 100%; height: 24rem; border: 0; }
@media (max-width: 640px) {
    .miller-col:not(:last-child) { display: none; }
    .miller-col { min-width: 100%; }
}

/* ── Triaż zadań: belka przewijania + kompaktowe akcje (2026-10-01) ───────── */
.triage-bar {
    display: grid;
    grid-template-columns: 2.75rem 1fr 2.75rem;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.6rem;
}
.triage-count { text-align: center; font-size: var(--ui-meta-size); font-weight: 600; color: var(--ui-ink-soft); }
.triage-arrow {
    display: grid;
    height: 2.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.5rem;
    line-height: 1;
    text-decoration: none;
}
.triage-arrow:hover { border-color: var(--ui-accent); color: var(--ui-accent); }
.triage-arrow.is-disabled { opacity: 0.35; pointer-events: none; }
.triage-card { padding: 1rem 1.1rem; }
/* Na szerokim ekranie karta rozciągała przyciski na 1500px — czyta się jak kartka, nie baner. */
.triage-bar, .triage-card { max-width: 52rem; margin-inline: auto; }
.triage-card ul { list-style: none; padding-left: 0; margin-bottom: 0; }
.triage-card ul li { margin-bottom: 0; }
.triage-actions :is(button, input[type="date"]) {
    margin: 0;
    min-height: 2.5rem;
    padding: 0.35rem 0.5rem;
    font-size: 14px;
}
.triage-actions form { margin: 0; }
.mobile-nav-dropdown .mobile-header-navgrid { padding-bottom: 0.5rem; }
.triage-row { display: grid; gap: 0.5rem; margin-top: 0.5rem; }
.triage-row-2 { grid-template-columns: 1fr 1fr; margin-top: 0; }
.triage-row-3 { grid-template-columns: repeat(3, 1fr); }
.triage-row-4 { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.triage-row-4 > button { flex: 1 1 4.5rem; }
.triage-row-4 > .triage-date { flex: 2 1 9rem; }
.triage-row-4 > .triage-label { flex: 0 0 auto; min-width: 3.5rem; }
.triage-row :is(button, input[type="date"]) { width: 100%; }
.triage-label { font-size: 12px; font-weight: 600; color: var(--ui-muted); }
/* Menu pod hamburgerem: dwie kolumny, niskie wiersze — lista nie zjada kilku ekranów. */
.mobile-nav-dropdown:not([hidden]) { display: grid; grid-template-columns: 1fr 1fr; gap: 0.3rem; }
.mobile-nav-dropdown .mobile-header-navgrid { grid-column: 1 / -1; }
.mobile-nav-dropdown .mobile-nav-link { margin: 0; padding: 0.4rem 0.5rem; min-height: 0; font-size: 14px; text-align: left; }
.triage-actions .triage-row > form { display: block; width: 100%; }
.triage-actions .triage-row > form > button { display: block; width: 100% !important; } /* app.css ma width:auto z wyższą specyficznością */

/* Pigułki ok/warning/down przy „Monitoring" + tooltip z nazwami żółtych i czerwonych.
   Tooltip pod pozycją (sidebar ma overflow-x: hidden, więc nie wychodzi w bok);
   tylko :hover — na telefonie link działa jak zwykle. */
.nav-link:has(.nav-mon) { position: relative; }
.nav-mon { margin-left: auto; display: contents; }
.nav-mon-pills { margin-left: auto; display: inline-flex; gap: 0.25rem; }
.nav-mon-pill {
    min-width: 1.4rem; padding: 0.05rem 0.4rem; text-align: center;
    border-radius: 999px; border: 1px solid; font-size: var(--ui-eyebrow-size); font-weight: 600;
}
.nav-mon-pill.is-ok      { border-color: var(--ui-ok-line);   background: var(--ui-ok-bg);   color: var(--ui-ok); }
.nav-mon-pill.is-warning { border-color: var(--ui-warn-line); background: var(--ui-warn-bg); color: var(--ui-warn); }
.nav-mon-pill.is-down    { border-color: var(--ui-down-line); background: var(--ui-down-bg); color: var(--ui-down); }
.nav-triage-pill { border-color: var(--ui-line); color: var(--ui-ink); }
.nav-triage-pill.is-zero { opacity: 0.4; }
.nav-mon-tip {
    display: none; position: absolute; top: calc(100% + 4px); left: 0.25rem; z-index: 30;
    right: 0.25rem; /* menu boczne przycina wszystko poza swoja krawedzia — dlugie nazwy sie zawijaja */
    max-height: 18rem; overflow-y: auto; padding: 0.35rem; border-radius: 0.6rem;
    background: var(--ui-surface); border: 1px solid var(--ui-line);
    box-shadow: 0 10px 28px rgb(0 0 0 / 0.14), 0 2px 6px rgb(0 0 0 / 0.06);
    font-size: 0.8rem; line-height: 1.3; color: var(--ui-ink);
}
.nav-link:hover .nav-mon-tip { display: block; }
.nav-mon-tip-head {
    display: flex; justify-content: space-between; align-items: center;
    padding: 0.35rem 0.45rem 0.2rem; font-size: 0.68rem; font-weight: 700;
    letter-spacing: 0.06em; text-transform: uppercase;
}
.nav-mon-tip-head:not(:first-child) { margin-top: 0.25rem; border-top: 1px solid var(--ui-line); padding-top: 0.5rem; }
.nav-mon-tip-head b { min-width: 1.4rem; padding: 0 0.35rem; border-radius: 999px; text-align: center; font-size: 0.7rem; }
.nav-mon-tip-head.is-down    { color: var(--ui-down); }
.nav-mon-tip-head.is-down b  { background: var(--ui-down-bg); }
.nav-mon-tip-head.is-warning   { color: var(--ui-warn); }
.nav-mon-tip-head.is-warning b { background: var(--ui-warn-bg); }
.nav-mon-tip-row {
    display: flex; align-items: baseline; gap: 0.5rem; padding: 0.3rem 0.45rem; border-radius: 0.4rem;
    overflow-wrap: anywhere;
}
.nav-mon-tip-row:hover { background: var(--ui-surface-2); }
.nav-mon-tip-row i { flex: 0 0 auto; width: 0.5rem; height: 0.5rem; border-radius: 50%; transform: translateY(-0.05rem); }
.nav-mon-tip-row.is-down i    { background: var(--ui-down); box-shadow: 0 0 0 3px var(--ui-down-bg); }
.nav-mon-tip-row.is-warning i { background: var(--ui-warn); box-shadow: 0 0 0 3px var(--ui-warn-bg); }
.nav-mon-tip-row small { color: var(--ui-muted); font-size: 0.72rem; }

/* Czekam: wiersz w jednej linii (długie teksty z ellipsis, pełna treść w title). */
.czekam-table td { white-space: nowrap; vertical-align: middle; }
.czekam-clip { display: block; max-width: 13rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.czekam-table td.col-akcje { width: 1%; }
.czekam-table .inline-action-panel { display: grid; white-space: normal; }
/* Menu „⋯” jest absolutne — na desktopie wrapper nie może go przycinać (overflow-x robi z y też auto). */
@media (min-width: 1100px) { .czekam-wrap { overflow: visible !important; } }

/* Czekam: ikonki akcji pełnej szerokości (były ściśnięte do ~12 px), filtry nie zjadają kolumn. */
.czekam-table .row-actions { gap: 0.25rem; }
.czekam-table .row-actions .icon-btn,
.czekam-table .row-actions summary.icon-btn { width: 2rem; min-width: 2rem; height: 2rem; padding: 0; display: inline-grid; place-items: center; }
.czekam-table .table-filter-input { width: 100%; min-width: 7rem; max-width: 14rem; }

/* Do zgody w menu: trzy plakietki (czeka / Twoja część / w trakcie) i lista Twoja część. */
.
.count.is-hand { border-color: var(--ui-warn-line); background: var(--ui-warn-bg); color: var(--ui-warn); }
.dz-twoja summary { cursor: pointer; display: flex; flex-wrap: wrap; gap: .4rem .6rem; align-items: baseline; }
.dz-twoja .dz-skrot { flex: 1 1 100%; color: var(--ui-ink-soft, inherit); }
.dz-twoja li { display: block; }

/* ── Karta triażu backlogu: projekt na górze, potem meta, tytuł, opis ─────── */
.triage-project {
    margin: -0.25rem 0 1.25rem;
    padding: 0.85rem 1rem;
    border: 1px solid var(--ui-accent-line);
    border-left: 4px solid var(--ui-accent);
    border-radius: var(--ui-radius);
    background: var(--ui-accent-soft);
}
.triage-project-eyebrow {
    margin: 0;
    font-size: var(--ui-eyebrow-size);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ui-accent);
}
.triage-project-name {
    display: inline-block;
    margin-top: 0.15rem;
    font-size: 1.2rem;
    font-weight: 700;
    color: var(--ui-ink);
    text-decoration: none;
}
.triage-project-name:hover { color: var(--ui-accent); }
.triage-project-summary {
    display: -webkit-box;
    margin: 0.3rem 0 0;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    font-size: var(--ui-meta-size);
    line-height: 1.5;
    color: var(--ui-ink-soft);
}
.triage-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; }
.triage-title {
    margin: 0.9rem 0 0;
    font-size: var(--ui-title-size);
    font-weight: 650;
    line-height: 1.3;
    color: var(--ui-ink);
}
.triage-body {
    margin-top: 1rem;
    padding: 1rem 1.15rem;
    border-radius: var(--ui-radius);
    background: var(--ui-surface-2);
    font-size: var(--ui-body-size);
    line-height: 1.65;
    color: var(--ui-ink-soft);
}
.triage-title { font-size: clamp(1.15rem, 1rem + 0.5vw, 1.4rem); }
.triage-body > :first-child { margin-top: 0; }
.triage-body > :last-child { margin-bottom: 0; }
.triage-body a { word-break: break-all; }
.triage-session {
    margin-top: 1rem;
    padding: 0.75rem 1rem;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius);
}
.triage-session > summary { cursor: pointer; }
.backlog-actions {
    position: sticky;
    bottom: 0;
    z-index: 2;
    display: grid;
    gap: 0.6rem;
    margin: 1.25rem -1rem -1rem;
    padding: 0.85rem 1rem;
    border-top: 1px solid var(--ui-line);
    background: var(--ui-surface);
}
.backlog-actions form, .backlog-actions button { margin: 0; }
@container (min-width: 44rem) {
    .backlog-actions { grid-template-columns: 2fr 1fr 1fr; }
}

/* ── UX tri ── */
.triage-row-3 { grid-template-columns: repeat(2, 1fr); }
@media (min-width: 44rem) { .triage-row-3 { grid-template-columns: repeat(4, 1fr); } }
.ux-tri-wiecej > summary { cursor: pointer; margin-top: 0.5rem; }
.ux-tri-utknelo { font-weight: 700; }
.ux-tri-partia input[type="checkbox"], .dz-wiersze .dz-chk input { width: 22px; height: 22px; }
.dz-wiersze .dz-chk { flex-basis: 1.7rem; }
@container dz (max-width: 819px) {
    .ux-tri-pusty { display: none; }
    .ux-tri-partia { flex-direction: column; align-items: stretch; flex-wrap: wrap; }
    .ux-tri-partia > button { width: 100%; white-space: normal; }
}

/* ── UX not ── */
.ux-not-error { margin-top: 0.4rem; font-size: 0.875rem; color: var(--amber); }
.ux-not-error > summary { cursor: pointer; font-weight: 600; }
.ux-not-error-short {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
    vertical-align: top;
}
.ux-not-error[open] .ux-not-error-short { display: none; }
.ux-not-error-full {
    margin: 0.4rem 0 0;
    max-height: 16rem;
    overflow: auto;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    font-size: 0.75rem;
    color: var(--ui-ink-soft);
}
.ux-not-channels { font-size: 0.8125rem; font-weight: 600; color: var(--ui-muted); }
.ux-not-tile { min-height: auto; overflow: visible; padding-block: 0.8rem; }
.ux-not-tile .tile-label, .ux-not-tile .tile-hint { overflow-wrap: anywhere; }
.ux-not-filebtn {
    display: inline-block;
    margin-top: 0.75rem;
    padding: 0.45rem 0.9rem;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius);
    background: var(--ui-surface);
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
}
.ux-not-filebtn:hover { border-color: var(--ui-accent); }
.ux-not-iframe { height: max(480px, calc(100svh - 220px)); border: 0; }
@media (max-width: 640px) {
    .ux-not-iframe { height: 70svh; min-height: 480px; }
}
@container workspace (max-width: 640px) {
    .digest-board { grid-auto-rows: auto; row-gap: var(--space-md); }
    .digest-card-wrap { grid-row: auto; }
}

/* ── UX prj ── */
/* Czekam / Projekty: rodzic flex/grid nie rozpycha dokumentu, przewija się tylko tabela. */
.ux-prj-panel { min-width: 0; max-width: 100%; }
.ux-prj-panel .czekam-wrap, .ux-prj-panel > div[style*="overflow-x"] { max-width: 100%; }
@media (min-width: 1100px) { .czekam-clip { max-width: 9rem; } }
.ux-prj-kamp td:first-child, .ux-prj-kamp th:first-child { min-width: 9rem; overflow-wrap: anywhere; }
.ux-prj-name { min-width: 10rem; }
@media (max-width: 639px) {
    .ux-prj-hide-sm { display: none; }
}
/* Widok projektu: menu „⋯” z narzędziami drugorzędnymi. */
.ux-prj-more-panel { display: grid; gap: .6rem; min-width: 14rem; max-width: calc(100vw - 2rem); }
.ux-prj-more-panel .page-header-group { align-items: center; }

/* ── UX tech ── */
.ux-tech-scroll { overflow-x: auto; max-width: 100%; }
.ux-tech-features { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; min-width: 0; }
.ux-tech-features > * { min-width: 0; }
.ux-tech-facts { display: flex; flex-wrap: wrap; gap: 6px; }
.ux-tech-facts > div, .monitor-card-facts.ux-tech-facts > div:last-child { display: block; flex: 1 1 auto; min-width: 0; }
.ux-tech-facts dt { white-space: normal; letter-spacing: 0.04em; }
.ux-tech-facts dd { white-space: normal; overflow: visible; }
.monitor-folder-meta.ux-tech-meta span { background: none; padding: 0; border-radius: 0; }
.ux-tech-form { max-width: 640px; }
.ux-tech-form-grid { display: grid; gap: 16px; grid-template-columns: minmax(0, 1fr); }
.ux-tech-nowrap { white-space: nowrap; }
.ux-tech-card { display: flex; flex-direction: column; }
.ux-tech-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: auto; padding-top: 20px; }
.ux-tech-steps > summary, .ux-tech-more > summary { cursor: pointer; }

/* ── Dok terminala po prawej: treść kończy się przy terminalu (Marcin 2026-10-09) ──
   Wraca odsunięcie treści, zdjęte 4.10 („szuflada nad treścią”) z powodu mulenia
   przy przeciąganiu. Od 2a32d38 rozmiar wchodzi dopiero po puszczeniu uchwytu
   (linia-duch), więc układ przelicza się raz, a container queries (.content-wrap,
   kolumny boxów w trybie auto) widzą realnie widoczną szerokość — boxy nie chowają
   się pod terminalem. Przewijaczem jest .content-wrap, żeby pasek przewijania stał
   przy treści, a nie za dokiem. */
body:has(.terminal-dock.is-right:not(.is-collapsed)) .app-frame {
    /* Treść nie schodzi poniżej ~480 px (zrzut 2026-10-09: tytuł litera pod literą
       przy terminalu na całość) — szerszy terminal przykrywa resztę jak szuflada. */
    padding-right: min(var(--terminal-w, 520px), max(0px, 100vw - 16.5rem - 480px));
    /* Odstęp na dok na DOLE nie dotyczy doku po prawej. */
    padding-bottom: 34px;
}
body:has(.terminal-dock.is-right:not(.is-collapsed)) {
    overflow: hidden;
}
body:has(.terminal-dock.is-right:not(.is-collapsed)) .content-wrap {
    max-height: 100dvh;
    overflow-y: auto;
    box-sizing: border-box;
}

/* Większe przyciski ½ ⇤ » i szerszy uchwyt doku po prawej (Marcin 2026-10-09). */
.terminal-dock.is-right .terminal-dock-resize { width: 32px; }
.terminal-dock.is-right .terminal-dock-bar { padding-left: 36px; }
.terminal-dock.is-right .terminal-dock-body { margin-left: 32px; }
.terminal-dock.is-right .terminal-dock-presets { gap: 6px; }
.terminal-dock .terminal-dock-presets button:not(.btn):not(.icon-btn) {
    width: 28px;
    height: 26px;
    border-radius: 6px;
    font-size: 15px;
}
.terminal-dock.is-right .terminal-dock-presets button:not(.btn):not(.icon-btn) {
    width: 26px;
    height: 28px;
}
/* Zwinięty pasek tej samej szerokości co uchwyt (32 px) — przyciski i chwyt
   w jednej osi, nic nie wystaje (zrzut Marcina 2026-10-09). */
.terminal-dock.is-right.is-collapsed { width: 32px; }
body:has(.terminal-dock.is-right.is-collapsed) .app-frame { padding-right: 40px; }
/* Zwinięty: „zwiń” nie ma sensu, klik w pasek rozkłada. */
.terminal-dock.is-right.is-collapsed .terminal-dock-presets button[data-terminal-preset="hide"] { display: none; }
/* Rozróżnienie kolorem: ⇤ powiększ (zielony), » zwiń (bursztyn), ½ neutralny. */
.terminal-dock .terminal-dock-presets button[data-terminal-preset="sidebar"]:not(.btn):not(.icon-btn) {
    background: color-mix(in srgb, var(--dk-ok) 38%, transparent);
    color: var(--dk-ok-fg);
}
.terminal-dock .terminal-dock-presets button[data-terminal-preset="hide"]:not(.btn):not(.icon-btn) {
    background: color-mix(in srgb, var(--dk-warn) 38%, transparent);
    color: var(--dk-warn-fg);
}
.terminal-dock .terminal-dock-presets button[data-terminal-preset="sidebar"]:not(.btn):not(.icon-btn):hover { background: var(--dk-ok); color: var(--dk-bg); }
.terminal-dock .terminal-dock-presets button[data-terminal-preset="hide"]:not(.btn):not(.icon-btn):hover { background: var(--dk-warn); color: var(--dk-bg); }

/* Zakładki terminali: prefiks drobniej, nazwa widoczna także na nieaktywnych
   (wcześniej ścisk zostawiał samo „[PRY] …”), aktywna nie zjada całego pasa. */
.terminal-dock .terminal-dock-tab-name {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
}
/* Zakładka nie zwęża się poniżej treści (nachodziły na siebie) — nadmiar
   przewija pas zakładek (kółko myszy, gaśnięcie krawędzi has-more-*). */
/* Zakładka bierze pełną nazwę, gdy jest miejsce, i zwęża się dopiero przy pełnym
   pasie — ale nie poniżej 7.5rem (wtedy pas się przewija; wcześniej zakładki
   nachodziły na siebie, potem były ucięte mimo wolnego miejsca, zrzut 2026-10-10). */
.terminal-dock .terminal-dock-tab { flex: 0 1 auto; min-width: 7.5rem; }
.terminal-dock .terminal-dock-tab .terminal-dock-tab-name { max-width: 16rem; flex: 1 1 auto; }
.terminal-dock .tdt-prefix {
    flex: 0 0 auto;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.02em;
    opacity: 0.6;
}
.terminal-dock .tdt-nazwa {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Stan sesji: kropka przed nazwą (initTerminalState w app.js). Brak danych = brak kropki. */
.tdt-stan { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; display: none; }
.tdt-stan[data-stan] { display: inline-block; }
.tdt-stan[data-stan="pracuje"], .nav-pinned-dot[data-stan="pracuje"] {
    background: var(--dk-warn); box-shadow: 0 0 0 0 color-mix(in srgb, var(--dk-warn) 70%, transparent); animation: tdt-puls 1.4s ease-out infinite;
}
.tdt-stan[data-stan="czeka"], .nav-pinned-dot[data-stan="czeka"] { background: var(--dk-down); }
.tdt-stan[data-stan="gotowe"], .nav-pinned-dot[data-stan="gotowe"] { background: var(--dk-ok); }
@keyframes tdt-puls {
    0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--dk-warn) 70%, transparent); }
    70% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--dk-warn) 0%, transparent); }
    100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--dk-warn) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) { .tdt-stan[data-stan="pracuje"], .nav-pinned-dot[data-stan="pracuje"] { animation: none; } }
/* Klaster narzędzi nie wchodzi na zwinięty pasek doku (32 px). */
body:has(.terminal-dock.is-right.is-collapsed) .issue-toolbox:not(.has-custom-position) { right: 50px; }
/* Przypięte Narzędzia przy zwiniętym doku: wyżej i z odstępem — na wysokości ½ ⇤
   wyglądały jak część paska terminala (zrzut Marcina 2026-10-09). */
html[data-toolbox-variant="pinned"] body:has(.terminal-dock.is-right) .issue-toolbox { top: 22% !important; }
html[data-toolbox-variant="pinned"] body:has(.terminal-dock.is-right.is-collapsed) .issue-toolbox { right: 44px !important; }

/* „Scal z…” w okienku Przenieś (2026-10-09). */
.ux-merge { margin: 0.75rem 0; padding: 0.6rem 0.8rem; border: 1px solid var(--ui-line); border-radius: var(--ui-radius); }
.ux-merge > summary { cursor: pointer; font-weight: 600; }
.ux-merge[open] > summary { margin-bottom: 0.6rem; }
.ux-merge-keep { margin: 0.6rem 0 0; padding: 0; border: 0; }
.ux-merge-keep legend { font-size: var(--ui-meta-size); color: var(--ui-muted); margin-bottom: 0.3rem; }
.ux-merge-keep label { display: flex; align-items: center; gap: 0.4rem; margin: 0.2rem 0; }

/* Menu „⋯” w widoku projektu: sekcje z nagłówkami, wszystko do lewej (2026-10-09). */
.ux-prj-more-panel { display: flex; flex-direction: column; align-items: stretch; gap: .35rem; padding: .8rem .9rem; min-width: 16rem; }
.ux-prj-more-panel .page-header-group { display: flex; flex-wrap: wrap; justify-content: flex-start; align-items: center; gap: .4rem; width: auto; margin: 0; }
.ux-prj-more-panel .page-header-group > *, .ux-prj-more-panel .terminal-machine-buttons > * { width: auto; flex: 0 0 auto; margin: 0; }
.ux-prj-more-panel .terminal-machine-buttons { display: flex; flex-wrap: wrap; gap: .4rem; }
.ux-prj-more-h { margin: .5rem 0 0; font-size: var(--ui-eyebrow-size); font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ui-muted); }
.ux-prj-more-h:first-child { margin-top: 0; }
.ux-prj-more-panel .terminal-machine-buttons button { border: 1px solid var(--ui-line-strong); border-radius: var(--ui-radius-sm); padding: .35rem .8rem; }

/* Widok projektu bez „sieczki” (Marcin 2026-10-09): zero się nie pokazuje (UI_ZASADY
   zasada 4), pusty box to sam nagłówek z „+”, bez akapitu „Brak…”. */
.panel-head .count.is-zero { display: none; }
.project-workspace .panel:has(> p.empty, > details > summary.empty) > p.empty,
.project-workspace .panel:has(> p.empty, > details > summary.empty) > details:has(> summary.empty) { display: none; }
.project-workspace .panel:has(> p.empty, > details > summary.empty) { padding-block: .55rem; }
.project-workspace .panel:has(> p.empty, > details > summary.empty) > .panel-head { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; }
.project-workspace .panel:has(> p.empty, > details > summary.empty) > .panel-head h2 { color: var(--ui-muted); font-size: 1rem; }

/* ── Gęstość całej Opsowni (Marcin 2026-10-09: „mało kompaktowy wygląd w całej
   Opsowni”). Jedno miejsce: skala odstępów app.css ścięta o ~25%, niższe
   przyciski, mniej powietrza między boxami. Wartości wyżej w plikach zostają —
   ten blok je nadpisuje, więc cofnięcie = usunięcie bloku. */
:root {
    --space-3xs: 3px;
    --space-2xs: 6px;
    --space-xs: 8px;
    --space-sm: 10px;
    --space-md: 12px;
    --space-lg: 16px;
    --space-xl: 24px;
    --space-2xl: 30px;
    --space-3xl: 40px;
    --ui-btn-h: 34px;
    --pico-spacing: 0.6rem;
    --pico-block-spacing-vertical: 0.7rem;
    --pico-block-spacing-horizontal: 0.8rem;
    --pico-form-element-spacing-vertical: 0.4rem;
    --pico-form-element-spacing-horizontal: 0.6rem;
    --pico-typography-spacing-vertical: 0.6rem;
}
.panel { margin-bottom: 0.7rem; }
.panel-head { margin-bottom: 0.4rem; }
.space-y-6 > :not([hidden]) ~ :not([hidden]) { margin-top: 0.7rem; }
.project-workspace main > * { margin-bottom: 0.7rem; }
/* Podgląd notatki: tytuł z akcjami w jednym rzędzie, treść tuż pod nim. */
.ux-prev-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: .5rem 1rem; margin: .6rem 0 .5rem; }
.ux-prev-head h3 { margin: 0; flex: 1 1 16rem; min-width: 0; }
.ux-prev-actions { display: flex; gap: .4rem; flex: 0 0 auto; }
.ux-prev-actions form { margin: 0; }
.ux-prev-actions button { height: 30px; padding: 0 .7rem; font-size: 13px; margin: 0; }
.ux-prev-del:hover { border-color: var(--ui-down); color: var(--ui-down); }
.project-preview-body { margin-top: .4rem; }

/* Narzędzia (⋮ 🔍 + 💬 ⇄) zlewały się z treścią pod spodem (zrzut 2026-10-10):
   każdy przycisk to wyraźny krążek z tłem, obramowaniem i cieniem. */
/* Podwojona klasa: reset :is(...) button:not(.btn):not(.icon-btn) ma wagę 0,3,1. */
.issue-toolbox .issue-toolbox-toggle.issue-toolbox-toggle.issue-toolbox-toggle {
    background: var(--ui-surface);
    border: 1px solid var(--ui-line-strong);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.16);
    color: var(--ui-ink);
}
.issue-toolbox .issue-toolbox-toggle.issue-toolbox-toggle.issue-toolbox-toggle:hover {
    border-color: var(--ui-accent);
    color: var(--ui-accent);
}

/* Szybkie dodanie: panel mieści się w ekranie (przewija się w środku), przełącznik
   Notatka / Zadanie wygląda jak przełącznik, nie jak dwa gołe napisy (2026-10-10). */
.issue-toolbox .issue-toolbox-panel { max-height: calc(100dvh - 5rem); overflow-y: auto; }
.issue-toolbox .issue-toolbox-panel button[data-qt-tab][data-qt-tab] {
    height: 32px;
    margin: 0;
    border: 1px solid var(--ui-line-strong);
    border-radius: var(--ui-radius-sm);
    background: var(--ui-surface);
    color: var(--ui-ink-soft);
    font-weight: 600;
}
.issue-toolbox .issue-toolbox-panel button[data-qt-tab][data-qt-tab]:not(.secondary) {
    border-color: var(--ui-accent);
    background: var(--ui-accent);
    color: var(--ui-accent-fg);
}

/* Pasek doku po prawej: bez „▼ Terminale” (zwija »), link „→ Projekt” (2026-10-10). */
.terminal-dock.is-right .terminal-dock-handle { display: none; }
.terminal-dock .ux-dock-projekt { color: var(--dk-link); font-weight: 600; text-decoration: none; white-space: nowrap; }
.terminal-dock .ux-dock-projekt:hover { color: var(--dk-white); text-decoration: underline; }
.issue-toolbox.ux-schowane { display: none !important; }
/* Pasek zakładek po prawej: cała szerokość na zakładki (bez ⤓ i linków), niższy (2026-10-10). */
.terminal-dock.is-right .terminal-dock-tools { display: none; }
.terminal-dock .terminal-dock-tab-name.terminal-dock-tab-name { padding-top: 3px; padding-bottom: 3px; height: auto; }
.terminal-dock .terminal-dock-tab-close.terminal-dock-tab-close, .terminal-dock .terminal-dock-tab-menu.terminal-dock-tab-menu { padding-top: 3px; padding-bottom: 3px; height: auto; }
/* Wysokość jak wcześniej, napisy trochę większe i wyśrodkowane w pionie (2026-10-10). */
.terminal-dock .terminal-dock-bar { min-height: 40px; }
.terminal-dock .terminal-dock-tab { height: 36px; align-self: flex-end; font-size: 14px; }
.terminal-dock .terminal-dock-tab > button.terminal-dock-tab-name,
.terminal-dock .terminal-dock-tab > button.terminal-dock-tab-close,
.terminal-dock .terminal-dock-tab > button.terminal-dock-tab-menu {
    display: inline-flex; align-items: center; height: 100%; margin: 0; padding-top: 0; padding-bottom: 0; line-height: 1;
}
.terminal-dock .tdt-prefix { font-size: 11.5px; }
.terminal-dock .tdt-stan { width: 9px; height: 9px; }

/* ── UX qy ── */
.ux-qy-card { aspect-ratio: auto; overflow: visible; }
.ux-qy-card > p { display: block; -webkit-line-clamp: unset; }
.ux-qy-od { padding: .7rem .9rem; border: 2px solid var(--pico-primary); border-radius: .4rem; }
.ux-qy-od > p:first-child { color: inherit !important; }
.ux-qy-od-late { border-color: var(--pico-del-color); }
.ux-qy-od-late > p:first-child { color: var(--pico-del-color) !important; }
@media (max-width: 767px) {
    .backlog-actions { bottom: 44px; grid-template-columns: 1fr 1fr; }
    .backlog-actions > form:first-child { grid-column: 1 / -1; }
}

/* ── UX qx ── */
/* #3: przycisk dziedziczy stałe height: var(--ui-btn-h) — kafel trybu musi rosnąć z treścią. */
.tile.ux-qx-tile { height: auto; min-height: 4.5rem; overflow: visible; margin-bottom: 0.25rem; }
.ux-qx-tile .tile-icon.is-mark { position: static; flex: none; }
.tiles:has(.ux-qx-tile) { grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); margin-bottom: 1rem; }
/* #22: kontrast */
.tile.is-active .tile-hint { color: var(--ui-ink-soft); opacity: 1; }
.ux-qx-ink { color: var(--ui-ink-soft); }
/* #23: Głos — pole + przycisk w jednym wierszu bez ucinania */
.ux-qx-voice-row > input { flex: 1 1 0; min-width: 0; }
.ux-qx-voice-row > button { flex: 0 0 auto; }
/* #4: przycisk zapisu nad paskiem Terminale (wąski ekran) */
@media (max-width: 1023px) {
    body:has(.terminal-dock) .capture-mobile-submit-bar { bottom: calc(var(--capture-keyboard-offset, 0px) + 44px + env(safe-area-inset-bottom, 0px)); }
    body:has(.terminal-dock) .capture-mobile-shell { padding-bottom: calc(44px + env(safe-area-inset-bottom, 0px)); }
}
/* Kafle trybu „Zapisz do notatki” — trzecie podejście, zmierzone (2026-10-10): kafel
   ma sztywne 72 px, a znaczek 44 px z ujemnym marginesem wystawał nad ramkę, podpis
   pod nią. Kolumna flex, mniejszy znaczek, wysokość z treści. */
.tiles .tile.ux-qx-tile {
    display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
    gap: 4px; height: auto !important; min-height: 0; padding: 10px 8px;
}
.tiles .tile.ux-qx-tile .tile-icon.is-mark { width: 28px; height: 28px; margin: 0; font-size: 13px; }
.tiles .tile.ux-qx-tile .tile-hint { line-height: 1.3; text-align: center; }
/* Publikator: wiersz liczony od szerokości LISTY (container query), nie okna — przy
   terminalu ½ tytuł był słupkiem 80 px, a „Ponów publikację” ucięte (audyt 2026-10-10). */
:has(> .ux-pub-row) { container-type: inline-size; }
.ux-pub-row { grid-template-columns: 1fr; }
.ux-pub-row button, .ux-pub-row [role="button"] { white-space: nowrap; height: auto; }
@container (min-width: 46rem) {
    .ux-pub-row.ux-pub-row { grid-template-columns: minmax(0, 1fr) 150px 120px auto; align-items: center; }
}

/* ── UX kar ── */
/* Tabela jako karty, gdy KONTENER (nie okno) ma < 40rem: Logi, Czekam, Kampanie.
   Kontener = bezpośredni rodzic tabeli. Podwojona klasa podbija specyfikę ponad
   .czekam-table td i reguły Pico (cascade już raz zablokował poprawkę). */
:has(> table.ux-kar-tabela) { container-type: inline-size; }
@container (max-width: 40rem) {
    .ux-kar-tabela.ux-kar-tabela, .ux-kar-tabela.ux-kar-tabela tbody { display: block; width: 100%; }
    /* Nagłówek zostaje tylko jako pasek sortowania/filtrów (th z linkiem lub polem);
       reszta th znika, więc nic nie ginie z funkcji. */
    .ux-kar-tabela.ux-kar-tabela thead, .ux-kar-tabela.ux-kar-tabela thead tr { display: flex; flex-wrap: wrap; gap: .4rem .8rem; }
    .ux-kar-tabela.ux-kar-tabela thead th { display: none; border: 0; padding: .2rem 0; }
    .ux-kar-tabela.ux-kar-tabela thead th:has(a, input) { display: block; }
    .ux-kar-tabela.ux-kar-tabela thead th:has(input) { flex: 1 1 100%; }
    .ux-kar-tabela.ux-kar-tabela thead th input { width: 100%; max-width: none; margin: .2rem 0 0; }
    .ux-kar-tabela.ux-kar-tabela tbody tr {
        display: flex; flex-direction: column; gap: .35rem;
        border: 1px solid var(--pico-muted-border-color);
        border-radius: .5rem; padding: .6rem .75rem; margin-top: .6rem;
    }
    .ux-kar-tabela.ux-kar-tabela td {
        display: flex; align-items: baseline; gap: .5rem; border: 0; padding: 0;
        white-space: normal; overflow-wrap: anywhere; width: auto; min-width: 0;
    }
    .ux-kar-tabela.ux-kar-tabela td[data-label]::before {
        content: attr(data-label); flex: 0 0 5.5rem;
        font-size: .72rem; opacity: .7; text-transform: uppercase; letter-spacing: .03em;
    }
    .ux-kar-tabela.ux-kar-tabela td.ux-kar-first { order: -1; }
    .ux-kar-tabela.ux-kar-tabela td.ux-kar-first::before { display: none; }
    .ux-kar-tabela.ux-kar-tabela td.ux-kar-first > * { min-width: 0; }
    /* Czekam: Temat i Stan widoczne mimo .ux-prj-hide-sm (to reguła okna, nie kontenera). */
    .ux-kar-tabela.ux-kar-tabela td.ux-kar-show { display: flex; }
    .ux-kar-tabela.ux-kar-tabela td.ux-prj-hide-sm:not(.ux-kar-show) { display: none; }
    .ux-kar-tabela.ux-kar-tabela .czekam-clip { max-width: none; white-space: normal; }
    .ux-kar-tabela.ux-kar-tabela td.col-akcje { order: 99; width: auto; justify-content: flex-start; padding-top: .3rem; border-top: 1px solid var(--pico-muted-border-color); }
    .ux-kar-tabela.ux-kar-tabela .row-actions { flex-wrap: wrap; }
    .ux-kar-tabela.ux-kar-tabela .inline-action-panel { position: static; margin-top: .4rem; }
    .ux-kar-tabela.ux-kar-tabela .row-actions details[open] { flex: 1 1 100%; }
}
/* Konektory: wiersz konta zawija się i nie rozpycha strony (poziomy scroll 813 px na telefonie). */
.ux-kar-konto { min-width: 0; max-width: 100%; }
.ux-kar-konto > * { min-width: 0; }
.ux-kar-ident { overflow-wrap: anywhere; word-break: break-word; }
.ux-kar-akcje { max-width: 100%; }

/* ── UX prl ── */
/* Widok projektu na telefonie: drzewo plików (jsTree, white-space: nowrap) rozpychało
   kolumnę gridu do 417 px. Dzieci gridu mogą się zwęzić, nazwy plików się zawijają. */
.project-workspace > *,
.project-side-column,
.project-side-stack > * { min-width: 0; }
.project-side-column .jstree-default .jstree-anchor {
    white-space: normal;
    overflow-wrap: anywhere;
    height: auto;
    max-width: calc(100% - 1.5rem);
}

/* ── UX nav ── */
/* #12c: Triaż — zera nie pokazujemy (liczba zostaje w DOM-ie dla pollingu, który przełącza .is-zero). */
.nav-triage-pill.is-zero { display: none; }
/* #8: pusty panel podglądu = jedna linia zamiast pustej karty. */
.dz-panel .ux-tri-pusty { padding-block: 0.55rem; }
.ux-nav-pusty-linia { margin: 0; }
/* Karta konektora: kolumna (przyciski pod treścią), nie rząd — na telefonie rząd
   wypychał „Konfiguruj / Pliki / Usuń” do 813 px (audyt 2026-10-10). */
.module-card.ux-tech-card.ux-tech-card { flex-direction: column; flex-wrap: nowrap; height: auto; align-items: stretch; }
.ux-tech-card .ux-tech-actions { flex-wrap: wrap; }
.ux-tech-card p { overflow-wrap: anywhere; }

/* ── UX r2b ── */
.ux-r2b-id { margin-left: 0.4rem; font-size: 0.7rem; color: var(--ui-muted); background: none; padding: 0; }
a.ux-r2b-pub { text-decoration: none; }
a.ux-r2b-pub .ux-r2b-pub-title { text-decoration: underline; text-decoration-color: var(--ui-line-strong); }
.ux-r2b-maszyna { min-height: 40px; padding-block: 0.4rem; cursor: pointer; }
.digest-product-media img { object-fit: contain; }

/* ── UX r2a ── */
/* Jeden wzorzec filtrów: pole szukania + selecty w jednym rzędzie, na wąskim ekranie pole zajmuje całą linię. */
.ux-r2a-filtry { align-items: center; }
.ux-r2a-filtry > input[type="search"] { flex: 1 1 14rem; max-width: 26rem; }
.ux-r2a-filtry > .icon-btn { margin: 0; }
.ux-r2a-wiecej > summary { cursor: pointer; padding: 0.4rem 0.25rem; }
.ux-r2a-wiecej > .toolbar { margin-top: 0.4rem; }
.ux-r2a-chipy .pill { min-height: 32px; display: inline-flex; align-items: center; }
/* Menu „⋯” grupy w kafelku: panel w układzie (kafelek ma overflow:hidden, popover byłby ucięty). */
.ux-r2a-menu > summary { list-style: none; cursor: pointer; }
.ux-r2a-menu > summary::-webkit-details-marker { display: none; }
.ux-r2a-menu .inline-action-panel { position: static; width: auto; margin-top: 0.3rem; box-shadow: none; }
/* Triaż: długa notatka zadania zwinięta do 6 linii, klik rozwija (audyt 2026-10-10). */
.ux-r3-notatka { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 6; overflow: hidden; white-space: pre-line; cursor: pointer; }
.ux-r3-notatka.is-open { -webkit-line-clamp: unset; display: block; }
/* Przycisk lupy w filtrach: kwadrat, nie pasek na całą szerokość. */
.ux-r2a-filtry { display: flex; flex-wrap: wrap; gap: 0.5rem; }
button.icon-btn[aria-label="Szukaj"][aria-label="Szukaj"] { width: var(--ui-btn-h); min-width: var(--ui-btn-h); height: var(--ui-btn-h); flex: 0 0 auto; padding: 0; margin: 0; font-size: 16px; }
.row-list.ux-r3-pelna { max-height: none; overflow: visible; }
/* Kontrast metadanych na kolorowych kafelkach Monitoringu/Testów i w digest (axe, audyt 2026-10-10). */
.monitor-folder-meta span, .monitor-tile-foot, .monitor-card .font-mono, .monitor-status-ok .font-mono,
.monitor-card .text-muted, .monitor-group-card .text-muted, .digest-card-wrap span[title] { color: var(--ui-ink-soft); }
.monitor-tile-foot .meta, .monitor-tile-foot dt, .monitor-tile-foot dd, .monitor-tile-foot span, .digest-board span[title] { color: var(--ui-ink-soft); }
/* Do zgody w jednej kolumnie (terminal obok, telefon): wybrany podgląd NA GÓRZE, nie pod
   wszystkimi listami — kliknięcie „nic nie robiło”, bo podgląd był poza ekranem (2026-10-10). */
@container dz (max-width: 819px) {
    .dz-grid > .dz-panel:not(.ux-tri-pusty):has(article:not(.ux-tri-pusty)) { order: -1; }
}
/* Panel decyzji dla zadania, które utknęło (Do zgody, 2026-10-10). */
.ux-utk { margin-bottom: 1rem; padding: .75rem .9rem; border: 2px solid var(--ui-down-line); border-radius: var(--ui-radius); background: var(--ui-down-bg); }
.ux-utk-h { margin: 0 0 .35rem; font-weight: 700; color: var(--ui-down); }
.ux-utk-powod { margin: 0 0 .6rem; white-space: pre-line; color: var(--ui-ink); }
.ux-utk-label { font-size: var(--ui-meta-size); color: var(--ui-ink-soft); }
.ux-utk-akcje { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .4rem; }
.ux-utk-akcje button { margin: 0; }
.ux-utk-link { font-weight: 600; white-space: nowrap; }

/* ═══ TOZSAMOSC MOTYWOW 2026-10-11 (plansze A-sterownia / B-pracownia) ═══════
   Tylko tokeny. Sterownia = centrum dowodzenia (grafit, bursztyn, mono, kropki ze
   swiatlem). Pracownia = warsztat (papier, granat, terakota, szeryf, miekkie tagi). */

/* --- wspolne: wyrownanie kontrolek w jednym rzedzie i uchwyt kafelka --- */
.toolbar, .panel-head-actions, .page-header-actions, [data-monitor-stats-bar] { align-items: center; }
:is(.toolbar, .panel-head-actions, .page-header-actions, .row-actions, [data-monitor-stats-bar])
  :is(button:not(.icon-btn), a[role="button"], [type="submit"], summary[role="button"]) {
    box-sizing: border-box; height: var(--ui-btn-h); min-height: var(--ui-btn-h);
    display: inline-flex; align-items: center; justify-content: center;
    padding-block: 0; margin: 0; font-size: 14px; line-height: 1.2; white-space: nowrap;
}
[data-monitor-stats-bar] select, .monitor-refresh-control { margin: 0; }
/* menu ⋯ grupy dostaje swoja komorke siatki (wczesniej auto-flow wpychal je pod uchwyt) */
.monitor-folder-card-top > details { grid-area: delete; position: relative; margin: 0; }
.monitor-folder-card-top > details > summary { margin: 0; }
.monitor-folder-card-top { grid-template-columns: 18px minmax(0, 1fr) 28px 28px; }
.monitor-card-handle { margin: 0; }
.monitor-tile-bar { overflow: visible; }

/* kafelki monitoringu: karta z cienka linia statusu, nie zalany kolorem klocek */
.monitor-folder-card[class*="monitor-status-"], .monitor-card[class*="monitor-status-"] {
    background: var(--ui-surface); border-color: var(--ui-line); border-top-width: 3px;
}
.monitor-folder-card[class*="monitor-status-"] .monitor-tile-bar,
.monitor-card[class*="monitor-status-"] .monitor-tile-bar { background: transparent; border-bottom-color: var(--ui-line); }
.monitor-card.monitor-status-ok { border-top-color: var(--ui-ok-solid); }
.monitor-card.monitor-status-warning { border-top-color: var(--ui-warn); }
.monitor-card.monitor-status-down { border-top-color: var(--ui-down); }
.monitor-card.monitor-status-pending { border-top-color: var(--ui-pending); }

/* ─────────────── STEROWNIA ─────────────── */
:root[data-theme="sterownia"] body { background: var(--ui-surface-2); }
:root[data-theme="sterownia"] .app-sidebar { background: var(--ui-surface-2); border-right-color: var(--ui-line); }
:root[data-theme="sterownia"] .panel,
:root[data-theme="sterownia"] article { background: var(--ui-surface); border: 1px solid var(--ui-line); box-shadow: none; border-radius: 8px; }
:root[data-theme="sterownia"] .panel:hover { border-color: var(--ui-line-strong); }
/* etykiety sekcji: male kapitaliki mono w bursztynie */
:root[data-theme="sterownia"] :is(.eyebrow, .nav-pinned-title, .panel-head .eyebrow, th) {
    font-family: var(--ui-font-mono); font-size: 10.5px; font-weight: 500; letter-spacing: 0.14em; text-transform: uppercase;
}
:root[data-theme="sterownia"] :is(.eyebrow, .nav-pinned-title) { color: var(--ui-accent); }
/* liczby, odznaki, znaczniki czasu, slugi: mono */
:root[data-theme="sterownia"] :is(.nav-badge, .count, .pill, .meta, .nav-link-sub .nav-badge, time, td.num, .monitor-card-facts, .monitor-tile-foot, .monitor-folder-meta, .ux-tech-meta, .heartbeat-pill) {
    font-family: var(--ui-font-mono); letter-spacing: 0;
}
:root[data-theme="sterownia"] :is(.nav-badge, .count, .pill) { border-radius: 4px; font-weight: 500; }
:root[data-theme="sterownia"] .brand-name { font-family: var(--ui-font-mono); font-size: 17px; font-weight: 500; letter-spacing: 0.04em; }
:root[data-theme="sterownia"] .brand-subtitle { font-family: var(--ui-font-mono); font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; }
/* nawigacja: aktywna pozycja w bursztynie */
:root[data-theme="sterownia"] .nav-link { color: var(--ui-muted); border-radius: 6px; }
:root[data-theme="sterownia"] .nav-link:hover { background: var(--ui-surface-3); color: var(--ui-ink); }
:root[data-theme="sterownia"] .nav-link.is-active, :root[data-theme="sterownia"] .nav-link[aria-current="page"] {
    background: var(--ui-accent-soft); color: var(--ui-accent); box-shadow: inset 2px 0 0 var(--ui-accent);
}
/* kropki stanu ze swiatlem */
:root[data-theme="sterownia"] .nav-dot, :root[data-theme="sterownia"] .heartbeat-pill > span:first-child {
    box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 0%, transparent), 0 0 7px 1px color-mix(in srgb, var(--ui-line-strong) 60%, transparent);
}
:root[data-theme="sterownia"] .nav-link[data-nav-tone="ok"] .nav-dot { box-shadow: 0 0 8px 1px color-mix(in srgb, var(--ui-ok) 70%, transparent); }
:root[data-theme="sterownia"] .nav-link[data-nav-tone="warning"] .nav-dot { box-shadow: 0 0 8px 1px color-mix(in srgb, var(--ui-warn) 70%, transparent); }
:root[data-theme="sterownia"] .nav-link[data-nav-tone="down"] .nav-dot { box-shadow: 0 0 8px 1px color-mix(in srgb, var(--ui-down) 70%, transparent); }
:root[data-theme="sterownia"] .monitor-card.monitor-status-ok, :root[data-theme="sterownia"] .monitor-folder-card.is-ok { border-top-color: var(--ui-ok); box-shadow: 0 -1px 10px -4px color-mix(in srgb, var(--ui-ok) 60%, transparent); }
:root[data-theme="sterownia"] .monitor-folder-card.is-warning { border-top-color: var(--ui-warn); box-shadow: 0 -1px 10px -4px color-mix(in srgb, var(--ui-warn) 60%, transparent); }
:root[data-theme="sterownia"] .monitor-folder-card.is-down { border-top-color: var(--ui-down); }
/* przyciski: glowny bursztyn z ciemnym tekstem; reszta grafit z cienka ramka */
:root[data-theme="sterownia"] :is(button:not(.secondary):not(.outline):not(.icon-btn):not(.contrast):not(.monitor-inline-edit):not(.sidebar-toggle):not(.sidebar-restore):not(.drag-handle):not(.pin-button):not(.help-dot):not(.icon-button):not([class*="terminal-dock"]):not([class*="handle"]):not([class*="toggle"]):not(.nav-link), [type="submit"]:not(.secondary):not(.outline), a[role="button"]:not(.secondary):not(.outline)) {
    background: var(--ui-accent); border-color: var(--ui-accent); color: var(--ui-accent-fg); font-weight: 600; border-radius: 6px;
}
:root[data-theme="sterownia"] :is(button, [role="button"], [type="submit"]):is(.secondary, .outline):not(.icon-btn) {
    background: transparent; border: 1px solid var(--ui-line-strong); color: var(--ui-ink); border-radius: 6px;
}
:root[data-theme="sterownia"] :is(button, [role="button"]):is(.secondary, .outline):not(.icon-btn):hover { border-color: var(--ui-accent); color: var(--ui-accent); }
:root[data-theme="sterownia"] :is(input, select, textarea):focus, :root[data-theme="sterownia"] :focus-visible {
    outline: 2px solid var(--ui-accent); outline-offset: 1px; box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-accent) 25%, transparent);
}
:root[data-theme="sterownia"] h1 { font-weight: 700; }
:root[data-theme="sterownia"] .ux-r2a-menu button, :root[data-theme="sterownia"] .inline-action-panel button { color: var(--ui-ink); }

/* ─────────────── PRACOWNIA ─────────────── */
:root:not([data-theme="sterownia"]) body { background: var(--ui-surface-2); }
:root:not([data-theme="sterownia"]) .app-sidebar { background: var(--ui-surface-2); border-right: 1px solid var(--ui-line); }
:root:not([data-theme="sterownia"]) .panel,
:root:not([data-theme="sterownia"]) article { background: var(--ui-surface); border: 1px solid var(--ui-line); border-radius: 12px; box-shadow: var(--ui-shadow); }
:root:not([data-theme="sterownia"]) h1 { font-size: clamp(1.7rem, 1.3rem + 1vw, 2.2rem); line-height: 1.15; letter-spacing: 0; }
:root:not([data-theme="sterownia"]) .panel-head h2, :root:not([data-theme="sterownia"]) .panel h2 { font-size: 1.25rem; line-height: 1.2; }
:root:not([data-theme="sterownia"]) .brand-name { font-family: var(--ui-font-head); font-size: 1.3rem; font-weight: 400; }
:root:not([data-theme="sterownia"]) :is(.eyebrow, .nav-pinned-title) { letter-spacing: 0.16em; font-weight: 500; color: var(--ui-muted); }
:root:not([data-theme="sterownia"]) .nav-link { border-radius: 8px; }
:root:not([data-theme="sterownia"]) .nav-link:hover { background: var(--ui-surface-3); }
:root:not([data-theme="sterownia"]) .nav-link.is-active, :root:not([data-theme="sterownia"]) .nav-link[aria-current="page"] {
    background: var(--ui-surface-3); color: var(--ui-ink); box-shadow: inset 3px 0 0 var(--ui-accent);
}
:root:not([data-theme="sterownia"]) .nav-dot { background: var(--ui-line-strong); }
:root:not([data-theme="sterownia"]) .nav-link[data-nav-tone="ok"] .nav-dot { background: var(--ui-ok-solid); }
/* miekkie tagi jak na planszy B */
:root:not([data-theme="sterownia"]) :is(.pill, .count, .nav-badge) {
    border-radius: 6px; border-color: transparent; font-size: 10.5px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
}
:root:not([data-theme="sterownia"]) .pill:not(.is-ok):not(.is-warn):not(.is-down):not(.is-pending) { background: var(--ui-tone-blue-bg); color: var(--ui-tone-blue); }
:root:not([data-theme="sterownia"]) .nav-badge { border-radius: 999px; text-transform: none; letter-spacing: 0; }
:root:not([data-theme="sterownia"]) :is(button:not(.secondary):not(.outline):not(.icon-btn):not(.contrast):not(.monitor-inline-edit):not(.sidebar-toggle):not(.sidebar-restore):not(.drag-handle):not(.pin-button):not(.help-dot):not(.icon-button):not([class*="terminal-dock"]):not([class*="handle"]):not([class*="toggle"]):not(.nav-link), [type="submit"]:not(.secondary):not(.outline), a[role="button"]:not(.secondary):not(.outline)) {
    background: var(--ui-accent); border-color: var(--ui-accent); color: var(--ui-accent-fg); border-radius: 8px;
}
:root:not([data-theme="sterownia"]) :is(button, [role="button"]):is(.secondary, .outline):not(.icon-btn) {
    background: var(--ui-surface); border: 1px solid var(--ui-line-strong); color: var(--ui-ink); border-radius: 8px;
}
:root:not([data-theme="sterownia"]) :is(button, [role="button"]):is(.secondary, .outline):not(.icon-btn):hover { border-color: var(--ui-accent); color: var(--ui-accent); }
:root:not([data-theme="sterownia"]) hr, :root:not([data-theme="sterownia"]) .panel-head { border-color: var(--ui-line); }

.monitor-folder-card-top summary::after, .monitor-folder-card-top summary::marker { display: none !important; content: none; }
.monitor-inline-edit, summary.monitor-inline-edit { box-sizing: border-box; padding: 0; line-height: 1; }
.issue-toolbox-toggle, .quick-tools-toggle { background: var(--ui-surface); border: 1px solid var(--ui-line-strong); color: var(--ui-ink-soft); }
/* naglowek panelu w jednej linii z trescia pod nim (Pico dodaje <header> w <article> poziomy padding) */
.panel > .panel-head, article.panel > header, .panel-head { padding-inline: 0; margin-inline: 0; }
@media (max-width: 640px) {
    .monitor-folder-card, .monitor-card { aspect-ratio: auto; min-height: 11rem; align-self: stretch; }
}
