/* Universal DataTable molecules (wydzielone z tasks.css 2026-05-23).
   Klasy: .data-table-* (cols/pager/per-page/saved), .tbl + .tbl-*
   (rows/cells/head/foot/sort/title/expand/subrows), .query-bar*, .query-chip*,
   .query-clear-all, .query-suggest*, .filter-chip(s)/pop*, .iconbtn-*,
   .field-saved, .chip-mini, .xsearch*, .products-{header,query-row,
   tools-slot,simple-*,body,loadmore*}, .subtask-badge.
   --------------------------------------------------------------------- */



.xsearch {
  display: inline-flex; align-items: center;
  height: 34px;
  padding: 0 12px;
  border-radius: 10px;
  border: 1px solid var(--line-2);
  background: var(--bg-elev);
  width: 240px;
}

.xsearch-input {
  flex: 1; border: 0; background: transparent; outline: none;
  font-size: 13px;
}

.query-bar {
  display: flex; align-items: center; gap: 8px;
}

.query-bar-input-wrap {
  position: relative;
  flex: 1;
  display: flex; align-items: center; gap: 8px;
  height: 36px;
  padding: 0 10px;
  border: 1px solid var(--line-2);
  background: var(--bg-elev);
  border-radius: 10px;
  transition: border-color .15s, box-shadow .15s;
}

.query-bar-input-wrap > svg { color: var(--ink-4); flex: none; }

.query-bar-input-wrap:focus-within {
  border-color: var(--ink-3);
  box-shadow: 0 0 0 3px var(--y-wash);
}

.query-bar-input {
  flex: 1; border: 0; background: transparent; outline: 0;
  font-family: var(--font-mono); font-size: 13px;
  color: var(--ink);
}

.query-bar-input::placeholder { color: var(--ink-4); font-family: var(--font-sans); }

.query-bar-clear {
  border: 0; background: var(--bg-sunk); color: var(--ink-3);
  width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-items: center; cursor: pointer;
}

.query-bar-clear:hover { background: var(--line); color: var(--ink); }



.query-bar-filters { position: relative; }

.query-bar-saved { position: relative; }



/* Filtry popover (chip groups) */

.filter-pop {
  position: absolute;
  top: calc(100% + 6px); left: 0;
  width: 380px; max-height: 480px;
  background: var(--bg-elev);
  border: 1px solid var(--line-2);
  border-radius: 12px;
  box-shadow: var(--sh-3);
  z-index: 50;
  display: flex; flex-direction: column;
  overflow: hidden;
  animation: pop-in .12s ease;
}

.filter-pop-group {
  padding: 8px 12px 4px;
  border-bottom: 1px solid var(--line);
}

.filter-pop-group:last-of-type { border-bottom: 0; }

.filter-pop-h {
  font-size: 10.5px; font-weight: 600;
  color: var(--ink-4);
  letter-spacing: .06em; text-transform: uppercase;
  margin-bottom: 6px;
}

.filter-chips {
  display: flex; flex-wrap: wrap; gap: 4px;
  padding-bottom: 6px;
}

.filter-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px;
  border: 1px solid var(--line-2);
  background: var(--bg-elev);
  border-radius: 999px;
  font-size: 12px;
  color: var(--ink-2);
  cursor: pointer;
  transition: background .12s, border-color .12s;
}

.filter-chip:hover { background: var(--bg-sunk); border-color: var(--line-3); }

.filter-chip[data-on="true"] {
  background: var(--ink);
  color: white;
  border-color: var(--ink);
}

.filter-chip[data-on="true"] .dot { box-shadow: 0 0 0 1px rgba(255,255,255,.35); }

.filter-chip .dot {
  width: 6px; height: 6px; border-radius: 50%;
  display: inline-block;
}

.filter-pop-foot {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 10px;
  border-top: 1px solid var(--line);
  background: var(--bg);
}



/* Active count badge on the Filtry button */

.iconbtn-badge {
  background: var(--accent);
  color: var(--ink);
  font-size: 10px; font-weight: 700;
  padding: 0 5px;
  border-radius: 999px;
  min-width: 16px; height: 16px;
  display: inline-grid; place-items: center;
  letter-spacing: 0;
  border: 1px solid var(--accent-deep);
}



/* Query autocomplete — visually attached to the input itself so it reads

   as a combobox extension rather than a separate dropdown. */

.query-bar-input-wrap:has(.query-suggest-pop:not([style*="display: none"])) {
  border-bottom-left-radius: 0; border-bottom-right-radius: 0;
}

.query-suggest-pop {
  position: absolute;
  top: 100%; left: -1px; right: -1px;
  max-height: 280px;
  overflow-y: auto;
  background: var(--bg-elev);
  border: 1px solid var(--ink-3);
  border-top: 0;
  border-radius: 0 0 10px 10px;
  box-shadow: 0 4px 14px rgba(0,0,0,.08), 0 0 0 3px var(--y-wash);
  z-index: 60;
  padding: 4px;
  animation: pop-in .08s ease;
}

.query-suggest-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  background: transparent;
  border-radius: 6px;
  text-align: left;
  cursor: pointer;
  font-size: 13px;
  color: var(--ink-2);
}

.query-suggest-item:hover,
.query-suggest-item[data-active="true"] {
  background: var(--bg-sunk);
  color: var(--ink);
}

.query-suggest-icon {
  width: 18px;
  font-family: var(--font-mono);
  color: var(--ink-4);
  text-align: center;
}

.query-suggest-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.query-suggest-label b { font-family: var(--font-mono); color: var(--accent-deep); font-weight: 600; }

.query-suggest-hint { font-size: 11px; color: var(--ink-4); flex: none; }

.query-suggest-label .qs-q { font-family: var(--font-mono); font-size: 11px; color: var(--ink-4); margin-left: 6px; }

.query-bar-saved-pop {
  position: absolute;
  top: calc(100% + 6px); right: 0;
  width: 380px; max-height: 460px;
  background: var(--bg-elev);
  border: 1px solid var(--line-2);
  border-radius: 12px;
  box-shadow: var(--sh-3);
  z-index: 50;
  display: flex; flex-direction: column;
  animation: pop-in .12s ease;
}

.query-bar-saved-h {
  padding: 10px 14px 6px;
  font-size: 11px; font-weight: 600;
  color: var(--ink-4);
  letter-spacing: .06em; text-transform: uppercase;
}

.query-bar-saved-body {
  flex: 1; overflow-y: auto;
  padding: 4px;
}

.query-bar-saved-loading,
.query-bar-saved-empty {
  padding: 16px;
  text-align: center;
  font-size: 12.5px;
  color: var(--ink-4);
}

.query-bar-saved-list {
  display: flex; flex-direction: column; gap: 2px;
}

.query-bar-saved-row {
  display: flex; align-items: stretch; gap: 4px;
  padding: 2px;
  border-radius: 8px;
}

.query-bar-saved-row:hover { background: var(--bg-sunk); }

.query-bar-saved-apply {
  flex: 1;
  display: flex; flex-direction: column; gap: 2px;
  padding: 7px 10px;
  border: 0; background: transparent;
  text-align: left;
  cursor: pointer;
  border-radius: 6px;
}

.query-bar-saved-label {
  font-size: 13px; font-weight: 500;
  color: var(--ink);
}

.query-bar-saved-q {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-4);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.query-bar-saved-edit,
.query-bar-saved-del {
  border: 0; background: transparent;
  color: var(--ink-4);
  padding: 4px 8px;
  border-radius: 6px;
  cursor: pointer;
}

.query-bar-saved-edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}

.query-bar-saved-edit:hover { background: var(--bg-sunk); color: var(--ink); }
.query-bar-saved-del:hover { background: var(--bad-soft); color: var(--bad); }



.query-bar-save-form {
  display: flex; gap: 6px;
  padding: 8px;
  border-top: 1px solid var(--line);
  background: var(--bg);
  border-radius: 0 0 12px 12px;
}

.query-bar-save-form input[type="text"] {
  flex: 1;
  height: 30px; padding: 0 10px;
  border: 1px solid var(--line-2);
  background: var(--bg-elev);
  border-radius: 7px;
  font-size: 12.5px;
}



.query-bar-chips {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
}

.query-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 4px 3px 8px;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 11.5px;
  color: var(--ink-2);
}

.query-chip-key {
  color: var(--ink-4);
  font-family: var(--font-mono);
  font-size: 11px;
}

.query-chip-val { color: var(--ink); font-weight: 500; }

.query-chip-x {
  width: 16px; height: 16px;
  border: 0; background: transparent;
  color: var(--ink-4);
  border-radius: 50%;
  cursor: pointer;
  font-size: 12px; line-height: 1;
  padding: 0;
}

.query-chip-x:hover { background: var(--line); color: var(--ink); }

/* Chip negacji (`-key:value` lub `NOT key:value`). Czerwony border'em
   sygnalizuje wykluczenie; prefix `−` (U+2212 minus) wizualnie spójny
   z `-` w syntax, ale lepiej kerned. */
.query-chip-negate {
  border-color: rgba(239, 68, 68, 0.4);
  background: rgba(239, 68, 68, 0.06);
}
.query-chip-negate .query-chip-key { color: rgb(220, 38, 38); }
.query-chip-prefix {
  color: rgb(220, 38, 38);
  font-weight: 700;
  font-size: 12px;
  line-height: 1;
  margin-right: 2px;
}

.query-clear-all {
  border: 0; background: transparent;
  color: var(--ink-4);
  font-size: 11.5px;
  cursor: pointer;
  padding: 0 4px;
  text-decoration: underline;
}

.query-clear-all:hover { color: var(--ink); }



/* ── Iconbtn-pill (filter, view, saved-filters trigger) ─────── */

.iconbtn-pill {
  display: inline-flex; align-items: center; gap: 4px;
  height: 30px;
  padding: 0 10px;
  border: 1px solid var(--line-2);
  background: var(--bg-elev);
  color: var(--ink-2);
  border-radius: 8px;
  cursor: pointer;
  position: relative;
  /* white-space+flex-shrink:0 — bez tego, gdy rodzic-toolbar zwęża się
     (np. margin-right push otwartego DrawerRight na Products — 480px
     zjada szerokość i pigułki nie mieszczą się w rzędzie), flex domyślnie
     kurczy pigułkę PONIŻEJ szerokości jej tekstu zamiast zostawić jej
     naturalny rozmiar — label łamie się na 2 linie w sztywnej wysokości
     30px i dotyka górnej krawędzi (zgłoszone na żywo: "Dane analityczne"/
     "Stare produkty"/"Frazy Google"/"Kolumny" wyglądały nieprofesjonalnie
     przy otwartym drawerze). Rodzic-rząd dostaje flex-wrap:wrap (patrz
     products.templ) żeby całe pigułki, nie ich tekst, schodziły do
     nowej linii gdy zabraknie miejsca. */
  white-space: nowrap;
  flex-shrink: 0;
  /* font-size on the shared class (not scattered per-caller inline
     styles) — some toolbar buttons used atoms.Btn WITHOUT Bare:true,
     which also applies the base .btn class (height:36px, padding:0 14px,
     font-size:13px/weight:500 — components.css) on top of this one,
     visibly mismatching size/weight against callers that only carry
     .iconbtn-pill (reported live: "Dane analityczne"/"Kolumny" looked
     bigger/heavier than CSV/Stare produkty/Frazy Google in the same
     toolbar row). Baking font-size here is a partial mitigation for any
     future .iconbtn-pill caller that forgets Bare:true; the real fix is
     Bare:true on every atoms.Btn({Class:"iconbtn-pill"}) call (done for
     the Products toolbar — see products.templ), since .btn's height/
     padding/border-radius still conflict even with a matching font-size. */
  font-size: 12px;
}

.iconbtn-pill:hover { background: var(--bg-sunk); color: var(--ink); }

.iconbtn-pill[data-active="true"] {
  background: var(--accent-wash);
  border-color: var(--accent-soft);
  color: var(--ink);
}

/* Kebab menu (toolbar + per-row) — <details>/<summary> natywne, zero JS.
   Przeniesione z feeds.css (2026-08-31): wzorzec przestał być
   feeds-specific, Products toolbar go reużywa jako icon-only kebab
   (grupuje CSV/Stare produkty/Frazy Google — zgłoszone na żywo: zbyt
   wiele pigułek naraz ściśniętych obok siebie, gdy prawy drawer zwęża
   rząd; label "Więcej" widoczny obok ikony zajmował za dużo miejsca —
   sam kebab wystarczy, tytuł/aria-label niosą semantykę). data_table
   jest w baseline (każda strona), feeds nie — generyczny wzorzec musiał
   się przenieść żeby Products (Stylesheets:[]) mógł go użyć. */
.row-menu {
  position: relative;
  display: inline-block;
}

.row-menu > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 8px;
  color: var(--ink-3);
  background: transparent;
  border: 1px solid transparent;
  transition: background 100ms, color 100ms, border-color 100ms;
}

.row-menu > summary::-webkit-details-marker { display: none; }
.row-menu > summary::marker { content: ""; }
.row-menu > summary:hover {
  background: var(--bg-sunk, rgba(127, 127, 127, 0.08));
  color: var(--ink);
  border-color: var(--line);
}

.row-menu[open] > summary {
  background: var(--bg-sunk, rgba(127, 127, 127, 0.08));
  color: var(--ink);
  border-color: var(--line);
}

.row-menu-list {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  min-width: 180px;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
  padding: 4px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  z-index: 50;
}

.row-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  background: transparent;
  border-radius: 7px;
  font-size: 13px;
  color: var(--ink-2);
  cursor: pointer;
  text-align: left;
  text-decoration: none;
  white-space: nowrap;
}

.row-menu-item:hover {
  background: var(--bg-sunk, rgba(127, 127, 127, 0.1));
  color: var(--ink);
}

.row-menu-item-icon {
  display: inline-flex;
  color: var(--ink-4);
  flex: none;
}

.row-menu-item:hover .row-menu-item-icon { color: var(--ink); }

.row-menu-item-danger { color: var(--danger, #c0392b); }
.row-menu-item-danger:hover {
  background: rgba(220, 60, 60, 0.08);
  color: var(--danger, #c0392b);
}
.row-menu-item-danger .row-menu-item-icon { color: var(--danger, #c0392b); }

.row-menu-divider {
  height: 1px;
  background: var(--line);
  margin: 4px -4px;
}

.row-menu-form {
  margin: 0;
  display: block;
}

.tbl {
  display: grid;
  /* Reverted width:max-content (tried live, two rounds of back-and-forth
     — see git history if curious): that made columns grow to show full
     header text, but is FUNDAMENTALLY INCOMPATIBLE with "table always
     fills full width regardless of column count", which the user
     confirmed as the priority when told the two can't coexist. `fr`
     tracks (title's minmax(280px, 1fr) below) only distribute LEFTOVER
     space within an already-DEFINITE container width — with
     width:max-content the container has no "leftover" to hand out
     (it shrinks to exactly its content), so 1fr silently stops
     filling. Plain block-default width (fills .tasks-table-wrap) is
     required for title's 1fr to actually absorb freed space when
     other columns are removed. Long analytics/identifier headers go
     back to a fixed width (proxy_sembot's products.go
     productAnalyticsColDefaultWidth) instead of max-content — full
     text available via the .tbl-col-label title= tooltip on hover. */
}

.tbl-head { display: contents; }

.tbl-row  { display: contents; }

.tbl-row-cells { display: contents; }

.tbl-head .tbl-cell {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-4);
  padding: 12px 14px;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  /* Left-aligned (justify-content default = flex-start). align-items:center
     here is vertical centering only for row-direction headers (most
     columns) — .tbl-title overrides it below since its column-direction
     header would otherwise inherit this as HORIZONTAL centering on the
     cross axis (that mismatch was the original 71px title-offset bug). */
  display: flex; align-items: center; gap: 4px;
}

/* .tbl-title (any column with a two-line body, e.g. title+id) switches
   flex-direction to column — the align-items:center above would then
   center on the CROSS axis (horizontal), misaligning the header from
   the body's flex-start-aligned text below it. Match the body cell. */
.tbl-head .tbl-cell.tbl-title { align-items: flex-start; }

.tbl-cell {
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  /* Column boundary — vertical divider matching sembot-angular's AG Grid
     table (--ag-cell-horizontal-border: 1px solid var(--monochrome-300)
     there, same idea here with this codebase's own --line divider token
     already used for border-bottom). Requested live: with resizable/
     variable-width columns it wasn't clear where one column ended and
     the next began, especially several fanned-out analytics columns in
     a row with near-identical header text. Applies to both header
     (.tbl-head .tbl-cell inherits this, same class) and body cells. */
  border-right: 1px solid var(--line);
  display: flex; align-items: center; gap: 6px;
  min-width: 0;
  font-size: 13px;
  white-space: nowrap;
  background: var(--bg-elev);
}

/* Wypełniacz na końcu wiersza (products.go buildProductColumnVMs, key
   "filler") — sam absorbuje wolne miejsce w gridzie, nie ma treści, więc
   zerujemy padding/border żeby nie było widać pustej "szufladki" ani
   podwójnej linii tuż przed prawą krawędzią tabeli. */
.tbl-filler { padding: 0; border-right: none; }

/* Kolumna TUŻ PRZED fillerem traci swój prawy border-right z generycznej
   reguły .tbl-cell. Bez tego pionowa kreska dzieląca kolumny (dodana dla
   czytelności przy resize) + prawy border .tasks-table-wrap otaczały pusty
   filler z dwóch stron, więc czytał się jako osobna "kolumna bez
   nagłówka" (zgłoszone na żywo) zamiast zwykłej wolnej przestrzeni po
   prawej. :has(+ .tbl-filler) zamiast nth-last-child(2) — Tasks współdzieli
   ten CSS ale NIE ma kolumny filler (patrz data_table.templ: filler to
   coś co products.go buildProductColumnVMs dokleja świadomie, Tasks tego
   nie robi), więc nth-last-child(2) obcinałby border prawdziwej,
   widocznej kolumnie tam. :has() dopasowuje tylko gdy filler faktycznie
   jest następnym rodzeństwem — samoskopujące się, nic do zrobienia gdy
   filler nie istnieje. */
.tbl-cell:has(+ .tbl-filler) {
  border-right: none;
}

.tbl-row:hover .tbl-cell { background: var(--bg-sunk); }



.tbl-sort {
  border: 0; background: transparent; padding: 0;
  display: inline-flex; align-items: center; gap: 4px;
  color: inherit;
  font: inherit; letter-spacing: inherit; text-transform: inherit;
  cursor: pointer;
  /* min-width:0 lets the flex item shrink below its label's natural width —
     without it .tbl-col-label's ellipsis never engages inside a narrow
     column (flex items default to min-width:auto = content size). */
  min-width: 0;
  max-width: 100%;
}

/* Header label truncation (SB-301206 "kierunku" follow-up — long
   per-connection analytics labels like "Wsp. konwersji — GAds ·
   name@domain #38" had NO clipping anywhere in this chain: they simply
   overflowed ~250px past their 170px column, invisibly masked by the
   NEXT column repainting over the tail — visible as garbled overlapping
   text only at the last column, which has nothing after it to mask the
   bleed. Applies to every DataTable consumer (Tasks + Products), not
   just the column that exposed it. */
.tbl-col-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.tbl-sort:hover { color: var(--ink-2); }

.tbl-sort[data-sort="asc"],
.tbl-sort[data-sort="desc"] {
  color: var(--ink);
  background: var(--accent-wash);
  padding: 2px 6px;
  margin: -2px -6px;
  border-radius: 6px;
}

.tbl-sort-chev { display: inline-grid; place-items: center; line-height: 0; transition: transform .15s; }

.tbl-sort-chev-inactive { opacity: .35; color: var(--ink-4); }

.tbl-sort[data-sort="asc"]  .tbl-sort-chev { transform: rotate(180deg); color: var(--accent-deep); }

.tbl-sort[data-sort="desc"] .tbl-sort-chev { color: var(--accent-deep); }

.tbl-sort:hover .tbl-sort-chev-inactive { opacity: .65; }



.tbl-title { flex-direction: column; align-items: flex-start; gap: 2px; white-space: normal; }

.tbl-titleline {
  display: flex; align-items: center; gap: 8px;
  min-width: 0; width: 100%;
}

/* Linijka rodzica nad tytułem — pokazywana gdy task jest sub-taskiem
   (TaskRowVM.ParentTitle). Wcięta pod spacer chevrona expandu (30px = 11px
   ikona + 8px gap + ~11px), żeby wyrównać z tekstem tytułu. */
.tbl-parentline {
  display: flex; align-items: center; gap: 5px;
  font-size: 11px;
  color: var(--ink-4);
  line-height: 1.25;
  padding-left: 30px;
  min-width: 0;
}
.tbl-parentline span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0;
}
.tbl-parentline svg { flex: none; }

.tbl-titletext {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink); font-weight: 500;
  letter-spacing: -.005em;
  min-width: 0;
}

.tbl-titletext-done {
  color: var(--ink-4); text-decoration: line-through;
}

.tbl-id {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--ink-4);
  padding-left: 14px;
  white-space: nowrap;
}

.tbl-status-dot {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
}

.tbl-expand,
.tbl-expand-spacer {
  width: 18px; height: 18px;
  flex: none;
  display: inline-grid; place-items: center;
}

.tbl-expand {
  border: 0; background: transparent;
  color: var(--ink-4);
  border-radius: 4px;
  cursor: pointer;
  transition: transform .15s, background .15s;
}

.tbl-expand:hover { background: var(--bg-sunk); color: var(--ink); }

.tbl-expand[data-open="true"] { transform: rotate(90deg); color: var(--ink); }

.subtask-badge {
  display: inline-flex; align-items: center; gap: 3px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--ink-3);
  padding: 1px 6px;
  background: var(--bg-sunk);
  border-radius: 999px;
  letter-spacing: 0;
  text-transform: none;
  flex: none;
}



.tbl-tags { gap: 4px; flex-wrap: wrap; }

.tbl-due { font-family: var(--font-mono); font-size: 12px; color: var(--ink-3); }

.tbl-due-sub { font-size: 11px; color: var(--ink-4); }



.tbl-assignee {
  font-size: 12.5px; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis;
}



.tbl-foot {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px;
  border-top: 1px solid var(--line);
  background: var(--bg);
  font-size: 12px;
  color: var(--ink-3);
}

.tbl-foot-slot { display: inline-flex; align-items: center; }

.tbl-foot .grow { flex: 1; }



/* Column resize handle — thin draggable strip on the right edge of

 * each header cell. Sits flush with the column border; the hover

 * state widens it visually so the affordance is obvious. */

.tbl-head .tbl-cell { position: relative; }

.tbl-col-resizer {
  position: absolute;
  top: 6px; bottom: 6px;
  right: -3px;
  width: 7px;
  cursor: col-resize;
  z-index: 2;
  background: transparent;
  user-select: none;
}

.tbl-col-resizer::after {
  content: ""; display: block;
  position: absolute; top: 0; bottom: 0; left: 50%;
  width: 1px;
  background: transparent;
  transition: background .12s ease;
}

.tbl-col-resizer:hover::after,
.tbl-col-resizer.is-dragging::after {
  background: var(--accent);
  left: calc(50% - 1px); width: 3px;
}



/* Pagination — classic numbered pager rendered in the table foot. */

.data-table-pager {
  display: inline-flex; align-items: center; gap: 2px;
}

.data-table-pager-step,
.data-table-pager-page {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 28px; height: 28px;
  padding: 0 8px;
  font-size: 12.5px;
  color: var(--ink-2);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  cursor: pointer; text-decoration: none;
  user-select: none;
}

.data-table-pager-step:hover,
.data-table-pager-page:hover {
  background: var(--bg-sunk);
  color: var(--ink);
}

.data-table-pager-page.is-active {
  background: var(--accent);
  color: var(--bg);
  cursor: default;
}

.data-table-pager-step.is-disabled {
  color: var(--ink-4);
  cursor: not-allowed;
  pointer-events: none;
}

.data-table-pager-ellipsis {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 28px;
  color: var(--ink-4);
  font-size: 13px;
  user-select: none;
}

.tbl-foot-selnum { font-weight: 500; color: var(--ink); }

.tbl-foot-divider { width: 1px; height: 16px; background: var(--line-2); }

.tbl-foot-menu { position: relative; display: inline-block; }

.tbl-foot-pop {
  position: absolute;
  bottom: calc(100% + 4px); left: 0;
  min-width: 160px;
  background: var(--bg-elev);
  border: 1px solid var(--line-2);
  border-radius: 8px;
  box-shadow: var(--sh-3);
  z-index: 60;
  padding: 4px;
  display: flex; flex-direction: column; gap: 1px;
  animation: pop-in .1s ease;
}

.tbl-foot-pop-item {
  display: block; width: 100%;
  text-align: left;
  border: 0; background: transparent;
  padding: 6px 10px;
  border-radius: 6px;
  font-size: 12.5px; color: var(--ink-2);
  cursor: pointer;
}

.tbl-foot-pop-item:hover { background: var(--bg-sunk); color: var(--ink); }

.tbl-foot-selected .grow { flex: 1; }

.tbl-foot-selected .btn-danger {
  background: var(--bad-soft);
  border-color: var(--bad-soft);
  color: var(--bad);
}

.tbl-foot-selected .btn-danger:hover { background: var(--bad); color: white; border-color: var(--bad); }



/* DataTableColsPicker — column-visibility dropdown reused by every

 * surface that hosts the shared DataTable. The <details> element is

 * the disclosure container; we suppress the native marker and style

 * the summary as a pill button so it sits naturally next to a search

 * field or QueryBar. */

.data-table-cols { position: relative; }

.data-table-cols > summary { list-style: none; }

.data-table-cols > summary::-webkit-details-marker { display: none; }

.data-table-cols-trigger {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px;
  font-size: 12.5px;
  color: var(--ink-2);
  background: var(--bg-elev);
  border: 1px solid var(--line-2);
  border-radius: 8px;
  cursor: pointer;
  user-select: none;
}

.data-table-cols-trigger:hover { color: var(--ink); }

.data-table-cols[open] .data-table-cols-trigger { background: var(--bg-sunk); }

.data-table-cols-count {
  font-size: 11px; color: var(--ink-3);
  padding-left: 4px;
  border-left: 1px solid var(--line-2);
  margin-left: 2px;
}

.data-table-cols-pop {
  position: absolute;
  top: calc(100% + 4px); right: 0;
  min-width: 200px;
  background: var(--bg-elev);
  border: 1px solid var(--line-2);
  border-radius: 8px;
  box-shadow: var(--sh-3);
  z-index: 60;
  padding: 4px;
  animation: pop-in .1s ease;
}

.data-table-cols-list { list-style: none; margin: 0; padding: 0; }

.data-table-cols-item {
  margin: 0;
  display: flex; align-items: center;
  border-radius: 6px;
}

.data-table-cols-item.is-dragging {
  background: var(--bg-sunk);
  opacity: 0.6;
}

.data-table-cols-grip {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 26px;
  margin-left: 2px;
  color: var(--ink-4);
  font-size: 10px;
  letter-spacing: -1px;
  cursor: grab; user-select: none;
}

.data-table-cols-grip:active { cursor: grabbing; }

.data-table-cols-item:hover .data-table-cols-grip { color: var(--ink-3); }

.data-table-cols-link {
  flex: 1;
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px;
  border-radius: 6px;
  font-size: 12.5px;
  color: var(--ink-2);
  cursor: pointer;
  text-decoration: none;
}

.data-table-cols-link:hover { background: var(--bg-sunk); color: var(--ink); }

.data-table-cols-check {
  width: 14px; height: 14px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-2);
  border-radius: 3px;
  color: var(--ink-3);
  background: var(--bg);
}

.data-table-cols-check.is-on {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
}



/* Per-page picker — same disclosure shape as the cols picker, just

 * narrower and anchored from the right (pop opens upward from the

 * footer). */

.data-table-per-page { position: relative; display: inline-block; }

.data-table-per-page > summary { list-style: none; }

.data-table-per-page > summary::-webkit-details-marker { display: none; }

.data-table-per-page-trigger {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 8px;
  font-size: 12px;
  color: var(--ink-3);
  background: transparent;
  border: 1px solid var(--line-2);
  border-radius: 6px;
  cursor: pointer; user-select: none;
}

.data-table-per-page-trigger:hover { color: var(--ink); }

.data-table-per-page[open] .data-table-per-page-trigger { background: var(--bg-sunk); color: var(--ink); }

.data-table-per-page-current { color: var(--ink); font-weight: 500; }

.data-table-per-page-pop {
  position: absolute;
  bottom: calc(100% + 4px); right: 0;
  min-width: 80px;
  background: var(--bg-elev);
  border: 1px solid var(--line-2);
  border-radius: 8px;
  box-shadow: var(--sh-3);
  z-index: 60;
  padding: 4px;
  animation: pop-in .1s ease;
}

.data-table-per-page-list { list-style: none; margin: 0; padding: 0; }

.data-table-per-page-item {
  display: block; padding: 5px 12px;
  text-align: right;
  border-radius: 4px;
  font-size: 12.5px;
  color: var(--ink-2);
  cursor: pointer; text-decoration: none;
}

.data-table-per-page-item:hover { background: var(--bg-sunk); color: var(--ink); }

.data-table-per-page-item.is-on { color: var(--accent-deep); font-weight: 500; }



/* Saved-filter star + dropdown — mirrors the QueryBar's saved-filter

 * pop styling so the affordance feels identical across simple and

 * advanced filter modes. Sits to the right of the simple-filter form

 * via flex auto-margin. */

.data-table-saved { position: relative; display: inline-block; }

.data-table-saved-pop {
  position: absolute;
  top: calc(100% + 4px); right: 0;
  width: 280px;
  background: var(--bg-elev);
  border: 1px solid var(--line-2);
  border-radius: 10px;
  box-shadow: var(--sh-3);
  z-index: 60;
  padding: 8px;
  animation: pop-in .1s ease;
}

.data-table-saved-h {
  font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-4); padding: 2px 4px 8px;
}

.data-table-saved-body { min-height: 32px; }

.data-table-saved-loading {
  padding: 8px; font-size: 12px; color: var(--ink-4); text-align: center;
}

.data-table-saved-form {
  display: flex; gap: 6px;
  margin-top: 6px; padding-top: 8px;
  border-top: 1px solid var(--line);
}

.data-table-saved-form input[name="label"] {
  flex: 1; height: 30px;
  padding: 0 8px;
  font-size: 12.5px;
  border: 1px solid var(--line-2);
  border-radius: 6px;
  background: var(--bg);
}

.data-table-saved-form input[name="label"]:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-wash);
}



/* Products header — same skeleton as .tasks-header: a flex row with

 * title on the left, grow spacer, then the filter toggle pill. */

.products-header { padding-top: 18px; padding-bottom: 18px; display: flex; align-items: center; gap: 12px; }

.products-tools-slot { display: inline-flex; align-items: center; }



/* Products body — owns its own scroll surface inside main.view's

 * overflow:hidden container. The table-wrap inside uses

 * `overflow:hidden` to clip its border-radius, which combined with

 * a flex-column parent would have constrained the wrap's height and

 * hidden the load-more sentinel. Keeping the body as a normal block

 * lets the wrap auto-size to its content, and the body's

 * `overflow:auto` scrolls the whole thing within main.view. */

.products-body {
  flex: 1; min-height: 0;
  overflow: auto;
  padding: 14px var(--pad-x) 32px;
}



/* Simple filter form — row of compact inputs/selects rendered when the

 * advanced QueryBar is gated off. Each control behaves like a small

 * pill matching the cols/per-page picker styling. */

/* Toolbar row grid: kolumna 1 (1fr) = inputs/QueryBar; kolumna 2
   (auto) = saved-filter star + cols picker. Chips strip (z QueryBar)
   wrap'uje na 2 wiersz spanning full width. Tools group żyje w
   .dt-toolbar-right regardless of filter mode. */
.products-query-row,
.tasks-query-row {
  padding: 10px var(--pad-x) 0;
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: 10px;
  row-gap: 8px;
}
.products-query-row > .query-bar,
.products-query-row > .products-simple-filters,
.tasks-query-row > .query-bar,
.tasks-query-row > .products-simple-filters {
  grid-column: 1; grid-row: 1;
}
.products-query-row > .dt-toolbar-right,
.tasks-query-row > .dt-toolbar-right {
  grid-column: 2; grid-row: 1;
  display: inline-flex; align-items: center; gap: 6px;
}
.products-query-row > .query-bar-chips,
.tasks-query-row > .query-bar-chips {
  grid-column: 1 / -1; grid-row: 2;
}

/* flex-wrap:wrap zmienione na poziomy scroll (zgłoszone na żywo, Products:
   gdy prawy drawer zwęża tę kolumnę grida, 4 inputy o min-width:160px nie
   mieściły się w rzędzie i każdy łamał się na WŁASNY pełnoszerokościowy
   wiersz — wyglądało jakby pola "urosły", choć to tylko efekt wrapa.
   overflow-x:auto trzyma rząd zawsze w jednej linii o stałej wysokości,
   niezależnie od dostępnej szerokości — spójne z tym samym fixem na
   .products-tools-slot (products.templ) po prawej stronie tego samego
   grida. */
.products-simple-filters {
  grid-column: 1; grid-row: 1;
  display: flex; align-items: center; gap: 8px;
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
}

/* Filtra-row inputy — kompaktowy variant (32px high, mniejszy padding,
   mniejszy radius). Match z .iconbtn-pill i innymi toolbar elementami
   po prawej (cols picker, saved filters). Override globalnego
   width:100% z .form-control — w flex row inputy mają auto width
   z min-width żeby nie były za wąskie. flex-shrink:0 — bez tego przy
   overflow-x:auto input i tak próbuje się skurczyć przed scrollowaniem,
   dając niespójną szerokość zależnie od tego ile miejsca akurat zostało. */
/* width: jawna, nie min-width — `width:auto` na <input>/<select> renderuje
   się na natywną szerokość przeglądarki (~190-210px dla input, jeszcze
   więcej dla <select> z długimi opcjami jak "Widoczny jeśli w feedzie
   wejściowym" — zmierzone 268px), więc min-width:160px nigdy nie było
   realnym ograniczeniem górnym, tylko podłogą. Zgłoszone na żywo: 4 inputy
   + select w tej naturalnej szerokości nie mieściły się w zwężonym
   (drawer otwarty) rzędzie nawet z overflow-x:auto. */
.products-simple-filters .form-control {
  width: 118px;
  flex: 0 0 auto;
  height: 32px;
  padding: 0 10px;
  font-size: 13px;
  border-radius: 10px;
}
.products-simple-filters .form-select {
  width: 150px;
  padding-right: 28px;
  background-position: right 8px center;
  text-overflow: ellipsis;
}



/* Load-more sentinel — sits at the end of the rows, the intersect

 * observer fires the GET when it scrolls into view. `grid-column:

 * 1 / -1` makes the block span every column of the parent .tbl grid;

 * `display: block` overrides the .tbl-row trick (display:contents)

 * so IntersectionObserver actually sees a positive-sized box. */

.products-loadmore {
  display: block;
  grid-column: 1 / -1;
  padding: 14px 16px;
  text-align: center;
  color: var(--ink-3);
  font-size: 12px;
  background: var(--bg);
  border-top: 1px solid var(--line);
}

.products-loadmore-label::before {
  content: ""; display: inline-block;
  width: 10px; height: 10px;
  border: 2px solid var(--ink-4);
  border-top-color: var(--ink-2);
  border-radius: 50%;
  margin-right: 8px;
  animation: spin .6s linear infinite;
  vertical-align: -2px;
}



/* Subtask rows */

.tbl-subrows { display: contents; }

.tbl-row-sub .tbl-cell {
  background: var(--bg-sunk);
  font-size: 12.5px;
}

.tbl-row-sub:hover .tbl-cell { background: color-mix(in srgb, var(--bg-sunk) 90%, var(--ink) 4%); }

.tbl-titleline-sub { padding-left: 22px; position: relative; }

.tbl-sub-curve {
  position: absolute;
  left: 8px; top: -8px;
  width: 10px; height: 14px;
  border-left: 1px solid var(--line-2);
  border-bottom: 1px solid var(--line-2);
  border-bottom-left-radius: 6px;
}

.tbl-sub-empty {
  grid-column: 1 / -1;
  padding: 10px 14px;
  text-align: center;
  font-size: 12px;
  color: var(--ink-4);
  background: var(--bg-sunk);
}



/* Mini chip used inside sub rows */

.chip-mini {
  font-size: 10px;
  padding: 1px 6px;
  height: auto;
  display: inline-flex; align-items: center; gap: 3px;
  border-radius: 4px;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  color: var(--ink-2);
}

.chip-mini .dot {
  width: 6px; height: 6px; border-radius: 50%; display: inline-block;
}



/* Field-saved → źródło prawdy: components.css (zielony inset right
   przez 2s). Wcześniej była tu duplikacja z innym kolorem/shadowem
   nadpisująca components.css; usunięta. */

/* ── Catalog pages (products family: brands / categories / products) ──
   Style dla zakładek produktowych proxy: tab-bar rodziny, inline edit
   w komórkach, chipy keywordów, ikonki akcji wiersza, drzewo kategorii. */

.pfam-tabs {
  display: flex;
  gap: 4px;
  margin: 0 0 18px;
  padding: 0 var(--pad-x, 24px);
  border-bottom: 1px solid var(--line);
}
/* Pierwszy tab bez lewego paddingu — tekst zaczyna się równo z tytułem
   headera (taby są wizualnie częścią headera). */
.pfam-tab:first-child { padding-left: 0; }
.pfam-tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: 10px 14px;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-3);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: color 120ms, border-color 120ms;
  margin-bottom: -1px;
}
.pfam-tab:hover { color: var(--ink); }
.pfam-tab[data-active="true"] {
  color: var(--ink);
  border-bottom-color: var(--accent, #d4af37);
  font-weight: 600;
}

/* Inline edit — wygląda jak tekst, ramka dopiero na hover/focus. */
.tbl-inline-input,
input.tbl-inline-input.form-control {
  width: 100%;
  min-width: 0;
  height: 30px;
  padding: 4px 8px;
  font-size: 13px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  transition: border-color .12s ease, background .12s ease;
}
.tbl-row:hover .tbl-inline-input { border-color: var(--line-2); background: var(--bg-elev); }
.tbl-inline-input:focus {
  border-color: var(--accent, #d4af37);
  background: var(--bg-elev);
  outline: none;
}

/* Chipy (keywords itp.) */
.badge-soft {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  font-size: 11px;
  line-height: 1.5;
  color: var(--ink-2);
  background: var(--bg-sunk);
  border: 1px solid var(--line);
  border-radius: 999px;
  white-space: nowrap;
}
.badge-neutral {
  display: inline-flex;
  align-items: center;
  min-width: 26px;
  justify-content: center;
  padding: 2px 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-2);
  background: var(--bg-sunk);
  border: 1px solid var(--line);
  border-radius: 999px;
  text-decoration: none;
}
a.badge-neutral:hover { color: var(--accent, #d4af37); border-color: var(--accent, #d4af37); }

.tbl-muted { color: var(--ink-4); font-size: 12px; }

/* Ikonki akcji wiersza — bez ramek, przygaszone domyślnie, pełna
   widoczność na hover wiersza. Wcześniej opacity:0 (całkowicie
   niewidoczne bez najechania) — zgłoszone na żywo: przy dodanym
   nagłówku "AKCJE"/"Akcje" nad tą kolumną user nie miał żadnej
   wskazówki że trzeba najechać, żeby cokolwiek się pojawiło. 0.55 zamiast
   0/1 — widoczne od razu (discoverability), a hover dalej daje wyraźny
   sygnał "to jest interaktywne" zamiast płaskiego zawsze-identycznie. */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--ink-4);
  border-radius: 6px;
  cursor: pointer;
  opacity: 0.55;
  transition: opacity .12s ease, background .12s ease, color .12s ease;
}
.tbl-row:hover .btn-icon,
.btn-icon:focus-visible,
#brand-kw-list .btn-icon,
#category-kw-list .btn-icon { opacity: 1; }
.btn-icon:hover { background: var(--bg-sunk); color: var(--ink); }
.btn-icon-danger:hover { color: var(--danger, #c0392b); background: color-mix(in srgb, var(--danger, #c0392b) 10%, transparent); }

/* Kopiuj-wartość przy komórce (np. token/domena w kolumnie tabeli) —
   CELOWO INNY wzorzec niż .btn-icon wyżej. .btn-icon to PRIMARY akcja
   całego wiersza (kolumna "Akcje") — stąd opacity:0.55 domyślnie, żeby
   nie powtórzyć incydentu discoverability opisanego wyżej. Tu chodzi o
   SECONDARY, incydentalną afordancję przy pojedynczej wartości tekstowej
   w komórce (jak kopiowanie commit SHA na GitHubie albo API key w
   Stripe) — użytkownik i tak widzi i czyta wartość tekstu bez najechania,
   ikonka to tylko skrót do schowka, więc pełne opacity:0 → 1 na hover
   SAMEJ KOMÓRKI (nie całego wiersza) jest tu uzasadnione. */
.cell-copy {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  max-width: 100%;
}
.cell-copy-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--ink-4);
  border-radius: 6px;
  cursor: pointer;
  opacity: 0;
  flex: 0 0 auto;
  transition: opacity .12s ease, background .12s ease, color .12s ease;
}
.cell-copy:hover .cell-copy-btn,
.cell-copy-btn:focus-visible { opacity: 1; }
.cell-copy-btn:hover { background: var(--bg-sunk); color: var(--ink); }

/* Flash "skopiowano" na .cell-copy-btn — dwie stałe ikonki (kopiuj/check)
   przełączane Alpine `x-show`/`x-cloak` (patrz cellCopy w comparators.templ,
   ten sam idiom co IconEye/IconEyeOff w molecules.PasswordInput). Alpine
   sam toggle'uje `display`, więc tu tylko stylujemy kolor stanu "skopiowano"
   — --ok reużyty z .field-saved wyżej w tym pliku, ten sam duch "zapisano /
   potwierdzone" co reszta tabeli. NIE dodawaj tu opacity/position:absolute
   crossfade'u ani klasy is-copied — to martwy kod z poprzedniej (odrzuconej)
   wersji opartej o czysty JS/CSS, koliduje z x-show zamiast go uzupełniać. */
.cell-copy-icon-copied { color: var(--ok, #2e9e5b); }

/* Drzewo kategorii */
/* Drzewo kategorii — karta z wierszami jak w tabeli: hover, chevron
   rotate, badge liczby produktów dosunięty do prawej, guide-line na
   poziomach zagnieżdżenia. */
.cat-tree {
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--bg-elev);
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.cat-tree details { min-width: 0; }
.cat-tree details > summary { list-style: none; }
.cat-tree details > summary::-webkit-details-marker { display: none; }
.cat-tree-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 8px;
  font-size: 13px;
  min-width: 0;
  cursor: default;
}
summary.cat-tree-row { cursor: pointer; }
.cat-tree-row:hover { background: var(--bg-sunk); }
.cat-tree-chev {
  flex: 0 0 14px;
  width: 14px;
  color: var(--ink-3);
  font-size: 12px;
  line-height: 1;
  text-align: center;
  transition: transform .12s ease;
}
details[open] > .cat-tree-row > .cat-tree-chev { transform: rotate(90deg); }
.cat-tree-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink);
}
summary.cat-tree-row .cat-tree-name { font-weight: 500; }
.cat-tree-count {
  margin-left: auto;
  flex: 0 0 auto;
}
.cat-tree-children {
  margin-left: 16px;
  padding-left: 8px;
  border-left: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 1px;
}

/* Puste stany i loading list — spójne z .feeds-empty (dashed box). */
.tbl-empty, .tbl-loading, .products-empty {
  padding: 32px 20px;
  text-align: center;
  color: var(--ink-4);
  font-size: 13px;
  font-style: italic;
  border: 1px dashed var(--line);
  border-radius: 12px;
}
.tbl-loading { border-style: none; }

/* Body shellu listy — ten sam padding co .tasks-body (kanon: tabela
   Zadań); bez tego strony na shellu renderują tabelę full-bleed. */
.dt-shell-body {
  padding: 14px 24px 32px;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* Karta tabeli — kanon z tasks.css (tabela Zadań = wzorzec). Żyje w
   baseline data_table.css żeby KAŻDA strona na DataTableWith miała
   identyczną kartę bez ładowania tasks.css. Duplikat definicji w
   tasks.css jest identyczny (ostatnia wygrywa, ten sam efekt). */
.tasks-table-wrap {
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: var(--sh-1);
  overflow-x: auto;
  overflow-y: hidden;
  /* Reverted width:fit-content (tried live, see .tbl's comment above for
     the full back-and-forth) — that paired with .tbl's own
     width:max-content, both since reverted in favor of "table always
     fills full width" as the confirmed priority. Plain block-default
     width (100% of this element's own container) is what lets .tbl's
     title column (minmax(280px, 1fr)) fill it edge-to-edge regardless
     of how many other columns are visible. */
}

/* Wiersz nieaktywny (produkt nie trafia do feeda: hidden albo
   visible-if-in-feed poza feedem) — wyszarzenie jak w Angularze. */
.tbl-row-inactive .tbl-cell { opacity: .55; }
.tbl-row-inactive:hover .tbl-cell { opacity: .8; }

/* Kompaktowy edytor inline listy produktów — cichy jak .tbl-inline-input:
   wygląda jak tekst, ramka dopiero na hover wiersza / focus. Autosave na
   blur/Enter (input) i change (select) robi HTMX — patrz productInlineEdit. */
/* Form edytora rośnie z komórką (flex item w .tbl-cell) — bez flex:1
   zwijał się do naturalnej szerokości inputa i resize kolumny nie
   poszerzał pola. width:100% dla kolumny tytułu (flex-direction:column). */
.tbl-edit-form {
  display: flex; align-items: center; gap: 4px;
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
}

/* .product-id-checking (products.templ productInlineEdit — "Sprawdzanie…"
   spinner, hx-indicator explicitly bound to it, see productEditIndicator())
   opts OUT of htmx's generic `.htmx-request .htmx-indicator` DESCENDANT
   rule (htmx's own auto-injected default stylesheet). That rule matches
   ANY .htmx-indicator under ANY .htmx-request-marked ancestor — including
   completely unrelated ones. #products-body (the whole table's reload
   target/source for column resize/reorder/filter-apply, see
   data_table.js) legitimately gets .htmx-request during those actions,
   and since every row's "Sprawdzanie…" span lives inside #products-body,
   ALL 25 rows' spinners lit up together for the resize's own unrelated
   ~100ms fragment reload (reported live — looked like a false save,
   confirmed via network log it wasn't: zero POSTs fired). Fix: match
   ONLY when .htmx-request is on THIS element directly (which htmx does
   toggle correctly for its own real per-row edit request, since
   hx-indicator explicitly targets it) — ignore ancestor .htmx-request
   entirely. Higher specificity (4 classes) than the generic 2-class
   descendant rule wins without needing !important. */
.tbl-edit-form .product-id-checking.htmx-indicator {
  opacity: 0;
}
.tbl-edit-form .product-id-checking.htmx-indicator.htmx-request {
  opacity: 1;
}
.tbl-edit-input,
input.tbl-edit-input.form-control,
select.tbl-edit-input.form-control {
  width: 100%;
  min-width: 0;
  height: 26px;
  padding: 2px 7px;
  font-size: 12.5px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 7px;
  box-shadow: none;
}
select.tbl-edit-input.form-control {
  padding-right: 22px;
  background-position: right 6px center;
}
.tbl-row:hover .tbl-edit-input {
  border-color: var(--line-2);
  background: var(--bg-elev);
}
.tbl-edit-input:focus {
  border-color: var(--accent, #d4af37);
  background: var(--bg-elev);
  box-shadow: 0 0 0 2px var(--accent-wash, rgba(212,175,55,.15));
}
.tbl-edit-input.field-saved {
  border-color: var(--ok, #2e9e5b);
  box-shadow: 0 0 0 2px rgba(46,158,91,.15);
}

/* Skeleton komórki (dwuetapowe ładowanie metryk analitycznych). */
.tbl-skel {
  display: inline-block;
  width: 48px;
  height: 12px;
  border-radius: 6px;
  background: linear-gradient(90deg, var(--line) 25%, var(--bg-sunk) 50%, var(--line) 75%);
  background-size: 200% 100%;
  animation: tbl-skel-shimmer 1.2s ease-in-out infinite;
}
@keyframes tbl-skel-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Skeleton całej tabeli — pierwsza klatka ładowania listy (initial
   hydrate). Wygląda jak karta tabeli z migoczącymi wierszami. */
.tbl-skel-table {
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: var(--sh-1);
  padding: 6px 14px;
}
.tbl-skel-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 15px 0;
  border-bottom: 1px solid var(--line);
}
.tbl-skel-row:last-child { border-bottom: none; }
.tbl-skel-row .tbl-skel:nth-child(1) { width: 36px; height: 36px; border-radius: 8px; flex: 0 0 36px; }
.tbl-skel-row .tbl-skel:nth-child(2) { flex: 1 1 auto; max-width: 380px; }
.tbl-skel-row .tbl-skel:nth-child(n+3) { width: 90px; }

/* Pasek zaznaczenia (bulk) — wyróżniony akcentem, jeden rząd. */
.products-bulk-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 8px 14px;
  margin-bottom: 10px;
  border: 1px solid var(--accent-soft, rgba(212,175,55,.35));
  background: var(--accent-wash, rgba(212,175,55,.10));
  border-radius: 10px;
  font-size: 13px;
}
.products-bulk-bar .form-check { white-space: nowrap; }
.products-bulk-count {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 600;
  white-space: nowrap;
}

/* Host [data-dt-shell] gdy owija scrollowalne body (products/connections/
   team): musi kontynuować flex-column łańcuch .view, inaczej
   `.products-body { flex:1; overflow:auto }` traci wysokość i strona
   nie scrolluje. */
.dt-shell-host {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* Akcentowy wariant (i) InfoEvent (DataTableFilters.InfoPrimary) —
   przyciąga uwagę do modala promo (np. filtry zaawansowane na products). */
.query-bar-help-primary,
button.iconbtn-pill.query-bar-help-primary {
  background: var(--accent);
  border-color: var(--accent-deep, var(--accent));
  color: #14130F;
}
.query-bar-help-primary:hover,
button.iconbtn-pill.query-bar-help-primary:hover {
  background: var(--accent-deep, var(--accent));
  color: #14130F;
}
