@font-face{font-family:'Manrope';src:url('/static/fonts/Manrope-Regular.ttf') format('truetype');font-weight:400;font-display:swap}
@font-face{font-family:'Manrope';src:url('/static/fonts/Manrope-Medium.ttf') format('truetype');font-weight:500;font-display:swap}
@font-face{font-family:'Manrope';src:url('/static/fonts/Manrope-SemiBold.ttf') format('truetype');font-weight:600;font-display:swap}
@font-face{font-family:'Manrope';src:url('/static/fonts/Manrope-Bold.ttf') format('truetype');font-weight:700;font-display:swap}

:root{--ink:#0f0f0f;--paper:#fff;--surface:#f6f7f9;--line:#e5e8ec;--muted:#6b7480;--muted-2:#9aa2ab;
  --cyan:#1ebde2;--petrol:#004e6d;--ok:#12a150;--warn:#c98a00;--danger:#d64545;--r:8px;--r-sm:6px;--maxw:1200px;--pad:24px}
*{box-sizing:border-box}
/* Резерв под полосу прокрутки: без этого её появление/исчезновение (открытие панели
   меняет высоту страницы) двигало весь layout по горизонтали. */
html{scrollbar-gutter:stable}
html,body{margin:0}
body{font-family:'Manrope',system-ui,sans-serif;background:var(--surface);color:var(--ink);font-size:14px;line-height:1.4;-webkit-font-smoothing:antialiased}
a{color:var(--petrol)}
:focus-visible{outline:2px solid var(--cyan);outline-offset:2px}

/* Header */
.hdr{background:var(--ink);color:#fff;height:54px;display:flex;align-items:center;padding:0 var(--pad);gap:24px}
.hdr .logo{height:22px;display:block;cursor:pointer}
.nav{display:flex;align-items:center;gap:22px;margin-left:8px}
.nav a{color:rgba(255,255,255,.68);text-decoration:none;font-weight:500;font-size:14px;padding:4px 0;position:relative;transition:color .15s}
.nav a:hover{color:#fff}.nav a.active{color:#fff}
.nav a.active::after{content:'';position:absolute;left:0;right:0;bottom:-17px;height:2px;background:var(--cyan)}
.hdr .spacer{flex:1}.hdr .user{color:rgba(255,255,255,.55);font-size:13px;font-weight:500;text-decoration:none}
.hdr .user:hover{color:rgba(255,255,255,.85)}
.rate{display:inline-flex;align-items:center;gap:8px}
.rate .val{color:rgba(255,255,255,.7);font-size:13px;font-weight:500;font-variant-numeric:tabular-nums}
.iconbtn{width:32px;height:32px;border:1px solid rgba(255,255,255,.16);border-radius:var(--r-sm);background:transparent;color:rgba(255,255,255,.7);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:.15s;padding:0}
.iconbtn:hover{background:rgba(255,255,255,.08);color:#fff;border-color:rgba(255,255,255,.3)}

.wrap{max-width:var(--maxw);margin:0 auto;padding:22px var(--pad) 60px}
.h1{font-size:20px;font-weight:700;margin:0 0 16px}

/* Buttons */
.btn{min-width:104px;height:42px;border:none;border-radius:var(--r);background:var(--ink);color:#fff;font:inherit;font-weight:600;font-size:14px;padding:0 22px;cursor:pointer;transition:.15s;white-space:nowrap;display:inline-flex;align-items:center;justify-content:center;gap:8px;text-decoration:none}
.btn:hover{background:var(--petrol)}
.btn-link{background:none;border:none;color:var(--muted);font:inherit;font-weight:500;font-size:14px;cursor:pointer;padding:0}
.btn-link:hover{color:var(--ink)}
.create{height:38px;padding:0 16px;border:none;border-radius:var(--r-sm);background:var(--ink);color:#fff;font:inherit;font-weight:600;font-size:13px;cursor:pointer;transition:.12s;display:inline-flex;align-items:center;gap:7px;text-decoration:none}
.create:hover{background:var(--petrol)}

/* Search form */
.searchblock{width:580px;max-width:100%}
.toolbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;gap:16px}
.chips{display:flex;gap:8px}
.chip{font-size:13px;font-weight:500;padding:5px 13px;border:1px solid var(--line);border-radius:999px;background:#fff;color:var(--muted);cursor:pointer;user-select:none;transition:.12s}
.chip:hover{border-color:#cfd4da}
.chip.on{background:var(--cyan);border-color:var(--cyan);color:#08252c;font-weight:600}
.searchrow{display:flex;gap:10px;align-items:center}
.searchrow .field{position:relative;flex:1;margin-bottom:0}
.searchrow input.q{width:100%;height:42px;border:1px solid var(--line);border-radius:var(--r);padding:0 14px 0 40px;font:inherit;font-size:14px;background:#fff;outline:none;transition:.12s}
.searchrow input.q:focus{border-color:var(--cyan);box-shadow:0 0 0 3px rgba(30,189,226,.18)}
.searchrow .field .lens{position:absolute;left:13px;top:50%;transform:translateY(-50%);color:var(--muted-2)}
.btn.loading{background:var(--petrol);cursor:default}
.btn .spin{display:none}.btn.loading .spin{display:inline-flex;animation:spin .7s linear infinite}
.btn.loading .lbl{opacity:.9}

/* Search history overlay: всё под классом .searchhist — оверлей общий для формы
   Поиска (.searchhist) и поисковых полей инлайн-панелей заказа (.rsearch .searchhist).
   Изоляция — по явной разметке контейнера, а не по id одной страницы. */
.searchhist{position:absolute;left:0;right:0;top:calc(100% + 6px);z-index:20;background:#fff;border:1px solid var(--line);border-radius:var(--r);box-shadow:0 16px 38px -14px rgba(15,15,15,.28);overflow:hidden;opacity:0;transform:translateY(-6px);pointer-events:none;transition:opacity .15s ease,transform .15s ease}
.searchhist.sh-open{opacity:1;transform:translateY(0);pointer-events:auto}
.searchhist .sh-head{display:flex;justify-content:space-between;align-items:baseline;padding:9px 13px 6px;font-size:11px;letter-spacing:.04em;text-transform:uppercase;color:var(--muted-2);font-weight:600}
.searchhist .sh-cnt{letter-spacing:0;text-transform:none;font-weight:500}
.searchhist .sh-list{max-height:230px;overflow-y:auto}
.searchhist .sh-row{display:flex;align-items:center;gap:11px;padding:9px 13px;cursor:pointer;max-height:46px;overflow:hidden;transition:background .1s,max-height .18s ease,opacity .18s ease,padding .18s ease}
.searchhist .sh-row:hover{background:rgba(30,189,226,.06)}
.searchhist .sh-row.is-active{background:rgba(30,189,226,.1)}
.searchhist .sh-row.is-removing{max-height:0;opacity:0;padding-top:0;padding-bottom:0}
.searchhist .sh-clk{color:var(--muted-2);display:flex;flex:none}
.searchhist .sh-txt{flex:1;font-size:13.5px;color:var(--ink);font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.searchhist mark{background:rgba(30,189,226,.28);color:inherit;border-radius:2px}
.searchhist .sh-del{color:#c7ccd2;display:flex;flex:none;border:none;background:none;padding:4px;margin:-4px -2px -4px 0;cursor:pointer;border-radius:5px;line-height:0;transition:color .12s,background .12s}
.searchhist .sh-del:hover{color:var(--danger);background:rgba(214,69,69,.1)}
.searchhist .sh-empty{padding:16px 13px;color:var(--muted-2);font-size:13px;text-align:center}
@media (prefers-reduced-motion:reduce){.searchhist,.searchhist .sh-row{transition:none}}

/* Пикер фильтра шапки (спека §5.3) — визуальная копия оверлея истории
   поиска выше (.searchhist): та же тень, тот же радиус, та же шапка со
   счётчиком, тот же скроллируемый список с подсветкой строки.
   §5.3.1: пикер живёт СИБЛИНГОМ .tbl-scroll внутри #results, а не в <th> —
   overflow:hidden у .tbl (нужен для скруглённых углов при border-collapse)
   обрезал бы абсолютного потомка. #results{position:relative} — точка отсчёта
   для left/top, которые считает search_filters.js по getBoundingClientRect(). */
#results{position:relative}
/* Хост скоупа фильтров в панелях заказа: система координат для абсолютно
   спозиционированного пикера. У Поиска ту же роль играет #results, и у него
   position:relative уже стоит своим правилом — не сливаем их в один селектор, чтобы
   правка панелей не могла задеть Поиск. */
.pickhost{position:relative}
.pick{position:absolute;z-index:30;width:246px;background:#fff;border:1px solid var(--line);
      border-radius:var(--r);box-shadow:0 16px 38px -14px rgba(15,15,15,.28);overflow:hidden;text-align:left}
.pick .ph{display:flex;justify-content:space-between;align-items:baseline;gap:8px;padding:9px 12px 6px;
          font-size:11px;letter-spacing:.04em;text-transform:uppercase;color:var(--muted-2);font-weight:600}
.pick .pf{padding:0 10px 8px}
.pick .pf input{width:100%;height:32px;border:1px solid var(--line);border-radius:var(--r-sm);padding:0 10px;
                font:inherit;font-size:13px;outline:none;background:#fff}
.pick .pf input:focus{border-color:var(--cyan);box-shadow:0 0 0 3px rgba(30,189,226,.18)}
.pick .pl{max-height:214px;overflow-y:auto;border-top:1px solid var(--line)}
/* Одиночный пикер (наличие, сортировка) идёт без поля фильтра — своя граница
   списка встала бы вплотную к границе шапки двойной линией. */
.pick .pl.flush{border-top:none}
.pick .pr{display:flex;align-items:center;gap:9px;padding:7px 12px;cursor:pointer;transition:background .1s}
/* display:flex у .pr перебивает UA-правило [hidden]{display:none} — без этого
   поле «Фильтр по названию…» не даёт НИЧЕГО: search_filters.js прячет непопавшие
   строки атрибутом hidden, а они остаются на экране. Тот же случай, что у
   .rfr[hidden] и .pmod-val[hidden] ниже по файлу. */
.pick .pr[hidden]{display:none}
.pick .pr:hover{background:rgba(30,189,226,.06)}
.pick .pr.is-active{background:rgba(30,189,226,.1)}
.pick .pr input{width:15px;height:15px;accent-color:var(--cyan);cursor:pointer;flex:none;margin:0}
.pick .pr .nm{flex:1;font-size:13.5px;font-weight:500;color:var(--ink);white-space:nowrap;
              overflow:hidden;text-overflow:ellipsis;margin:0;line-height:1.3}
.pick .pr .n{font-size:12px;color:var(--muted-2);font-variant-numeric:tabular-nums;flex:none}
.pick .pr.picked .nm{color:var(--petrol);font-weight:600}
/* Выбранное значение, у которого под другими фильтрами не осталось строк — см. §11.2 */
.pick .pr.dead .nm{color:var(--muted-2)}
.pick .pr.dead .n{color:var(--warn);font-weight:600}
.pick .pfoot{border-top:1px solid var(--line);background:var(--surface);padding:8px 10px;
             display:flex;justify-content:space-between;align-items:center;gap:8px}
.pick .pfoot .sel{font-size:11.5px;color:var(--muted);font-weight:500}
.pick .pfoot .sel b{color:var(--petrol)}
.pk-ap{height:24px;padding:0 11px;border:none;border-radius:var(--r-sm);background:var(--ink);color:#fff;
       font:inherit;font-weight:600;font-size:11.5px;cursor:pointer;transition:.12s;white-space:nowrap}
.pk-ap:hover{background:var(--petrol)}
.pk-rs{background:none;border:none;font:inherit;font-size:11.5px;font-weight:600;color:var(--petrol);
       letter-spacing:0;text-transform:none;cursor:pointer;padding:0;white-space:nowrap}
.pk-rs:hover{color:var(--cyan)}
.pk-rs:disabled{color:var(--muted-2);cursor:default}

/* Футер таблицы (Поиск + Заказы) — счётчик «Показано N из M» и кнопка «Показать ещё»
   ПОД таблицей (не фиктивными <tr> внутри неё, см. _tblfoot.html). Раньше .count
   объявлялся дважды (здесь и в секции Заказов), и второе объявление тихо выигрывало
   каскад для ОБЕИХ страниц (тот же селектор, тот же вес) — держим одно объявление. */
.tblfoot{display:flex;flex-direction:column;align-items:center;gap:10px;margin-top:16px}
.count{font-size:12px;color:var(--muted-2);text-align:center;margin-top:8px}.count b{color:var(--ink);font-weight:700}
.searching{font-size:13px;color:var(--muted);display:inline-flex;align-items:center;gap:8px}
.searching .s{width:12px;height:12px;color:var(--cyan);display:inline-flex;animation:spin .7s linear infinite}
.empty{color:var(--muted-2);font-size:14px;text-align:center;padding:48px 4px}

/* Table (generic, fixed layout via colgroup per-page) */
.tbl{width:100%;table-layout:fixed;border-collapse:collapse;background:#fff;border:1px solid var(--line);border-radius:var(--r);overflow:hidden}
.tbl thead th{background:var(--surface);text-align:left;font-size:11px;letter-spacing:.04em;text-transform:uppercase;color:var(--muted);font-weight:600;padding:9px 12px;border-bottom:1px solid var(--line)}
.tbl tbody td{padding:9px 12px;border-bottom:1px solid var(--line);vertical-align:middle}
.tbl tbody tr:last-child td{border-bottom:none}
.tbl tbody tr:hover{background:#fafbfc}
/* Строка итогов. Правил для tfoot не было вовсе: паддинг и границы задаёт селектор
   `.tbl tbody td`, поэтому ячейки итога шли вплотную к левому краю таблицы (текст
   не совпадал по вертикали ни с одной колонкой), без разделителя сверху и без
   фона — строка читалась как случайно вывалившаяся из таблицы. Фон как у шапки
   замыкает белое поле снизу; `tbody tr:last-child td` уже гасит свою нижнюю
   границу, так что линия между последней строкой и итогом ровно одна. */
.tbl tfoot td{padding:9px 12px;border-top:1px solid var(--line);background:var(--surface);font-weight:600;color:var(--ink)}
.tbl thead th.c-mid,.tbl tbody td.c-mid,.tbl tfoot td.c-mid{text-align:center}
.tbl thead th.c-price{text-align:right}
.tbl tbody td.c-price{text-align:right;white-space:nowrap}
/* Два разных случая, один цвет. В списке заказов (orders/_list_rows.html) класс
   ставится по o.missing_price и значит «в заказе есть позиции без продажной
   цены». В выдаче Поиска — «цена устарела» (старше двух суток). Имя `stale` для
   первого случая неудачное, но переименование — чужая задача. */
.tbl tbody td.c-price.stale .price{color:var(--warn)}
/* Продавцы агрегатора идут вложенно под своим владельцем: отступ и приглушённый
   тон отделяют их от обычных поставщиков, у которых в этих колонках органы
   управления, а у продавца — ничего. */
.tbl tr.vendor-row td{color:var(--muted)}
.tbl tr.vendor-row td.vendor-name{padding-left:28px}
.tbl tr.vendor-row td.vendor-note{font-size:12px}

/* Заголовок-фильтр в шапке Поиска (спека §5.2). В ПОКОЕ выглядит ровно
   как обычный <th>: цвет/вес/размер приходят через font:inherit/color:inherit,
   добавляются только пунктир (тот же приём, что .fedit у правки по месту в
   заказах/пользователях) и шевронка. Капс снимается ТОЛЬКО у выбранного
   значения (.thf.on) — иначе значение читалось бы как подпись колонки.

   text-transform/letter-spacing НАСЛЕДУЮТСЯ ЯВНО, и это не украшательство:
   в шорткат `font` они не входят, а UA-таблица гасит их у form controls
   собственным правилом (input,textarea,select,button{text-transform:none;
   letter-spacing:normal}), которое бьёт наследование. Без этих двух строк
   .thf-кнопки шли строчными, а соседние заголовки той же шапки — капсом.
   Четвёртый случай ловушки «UA-таблица бьёт авторское ожидание» в этом файле
   (см. .rfr[hidden], .pmod-val[hidden], .pick .pr[hidden]). */
.thf{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;
     text-transform:inherit;letter-spacing:inherit;
     display:inline-flex;align-items:center;gap:5px;max-width:100%;
     text-decoration:underline dotted #c7ccd2;text-underline-offset:4px;text-decoration-thickness:1px}
.thf:hover{color:var(--ink);text-decoration-color:var(--cyan)}
.thf .car{color:var(--muted-2);flex:none;display:flex}
.thf:hover .car{color:var(--cyan)}

/* Выбрано: значение встаёт вместо подписи колонки. Обёртка (span, не button —
   внутри лежат ДВЕ настоящие кнопки, .thl и .clr, а кнопку в кнопку валидно не
   вложить) держит на себе весь вид чипа — фон/скругление/паддинг/капс/цвет
   унаследованы отсюда же через .thf выше. Кнопки внутри сброшены до нейтральных
   (.thl/.clr делят один и тот же reset — фон/рамка/паддинг/шрифт/цвет/курсор
   плюс text-transform/letter-spacing: без него UA-стили <button> съели бы
   унаследованные размер, цвет и начертание). */
.thf.on{color:var(--petrol);text-transform:none;letter-spacing:0;font-size:12px;font-weight:700;
        text-decoration:none;background:rgba(30,189,226,.12);border-radius:5px;padding:2px 4px 2px 7px;gap:4px}
.thf.on .thl,.thf.on .clr{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;
                          text-transform:inherit;letter-spacing:inherit}
/* .thl несёт то же inline-flex/gap, что раньше держала целиком .thf.on: val/more/srt
   переехали на один уровень глубже (внутрь .thl), поэтому их взаимный отступ теперь
   считает сама .thl, а не обёртка. min-width:0 — иначе флекс-айтем .thl не даёт
   .val сжаться до эллипсиса на длинных значениях (тот же трюк, что и раньше, просто
   на уровень ниже). */
.thf.on .thl{display:inline-flex;align-items:center;gap:4px;min-width:0}
.thf.on .val{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.thf.on .more{background:var(--petrol);color:#fff;border-radius:4px;font-size:10.5px;font-weight:700;
              padding:1px 4px;flex:none;font-variant-numeric:tabular-nums}
.thf.on .clr{display:flex;color:var(--petrol);opacity:.55;border-radius:4px;padding:1px}
.thf.on .clr:hover{opacity:1;background:rgba(0,78,109,.14)}
.thf .srt{color:var(--petrol);flex:none;display:flex}
.c-price .thf,.rtbl th.r .thf{justify-content:flex-end}
/* x-cloak снимается Alpine после инициализации — до неё строки группы не мигают. */
[x-cloak]{display:none}
.grp{display:inline-flex;align-items:center;gap:5px;margin-top:3px;padding:0;border:none;background:none;color:var(--muted);font:inherit;font-size:12px;cursor:pointer}
.grp:hover{color:var(--ink)}
.grp svg{transition:transform .15s}
.grp.on svg{transform:rotate(90deg)}
.quota{color:var(--muted);margin:0 0 10px}
.quota.err{color:var(--danger)}
.catadd{display:flex;align-items:flex-start;gap:12px;margin-top:12px}
.catadd textarea{flex:1;min-height:76px;padding:10px 12px;border:1px solid var(--line);border-radius:var(--r);background:#fff;color:var(--ink);font:inherit;font-size:13px;resize:vertical}

/* Search cells */
/* Free-text cells wrap long unbroken PN/spec/article tokens instead of overflowing the
   fixed column. .rbp/.rname/.rsup — те же ячейки в выдаче панелей заказа (.rtbl,
   orders/_panel_row.html): таблица тоже table-layout:fixed, и длинный PN без этого
   правила наезжал на колонку «Наименование» (баг живого smoke №4).
   .np/.nm/.sline — текстовые блоки строки ЗАКАЗА (orders/_item_row.html): та же
   table-layout:fixed, но у .otbl намеренно overflow:visible (иначе обрезается оверлей
   истории поиска в панелях), поэтому неразрывный токен вроде списка совместимости
   картриджа «916c/920/920c/…» не обрезался краем таблицы, а ложился поверх соседних
   колонок. overflow-wrap наследуется, так что PN внутри .cp-wrap и артикул внутри
   .sline .a накрыты этими же селекторами. .nm — общий класс, тем же правилом заодно
   накрыты имя поставщика в _supplier_row.html (раздел «Поставщики») и .sup .nm в
   _result_row.html (выдача поиска): оно там безвредно, потому что имена поставщиков —
   тот же свободный текст без гарантированных пробелов. */
.bp,.name,.sup,.rbp,.rname,.rsup,.np,.nm,.sline{overflow-wrap:anywhere}
.dot{display:inline-block;width:9px;height:9px;border-radius:50%;flex:none}
.dot.ok{background:var(--ok)}.dot.no{background:transparent;border:1.5px solid #c7ccd2}
.dot.warn{background:var(--warn)}
.bp .brand{font-size:11.5px;color:var(--muted);font-weight:500;line-height:1.2}
.bp .pn{font-size:12.5px;color:var(--ink);font-weight:600;line-height:1.25;font-variant-numeric:tabular-nums}
.name{font-size:13.5px;font-weight:500;color:var(--ink)}
.price{font-size:14px;font-weight:700;font-variant-numeric:tabular-nums}.price .cur{color:var(--muted);font-weight:600}
.stkrow{display:flex;align-items:center;gap:8px}
.stk .n{font-size:13px;font-weight:600;font-variant-numeric:tabular-nums}.stk .n.zero{color:var(--muted-2);font-weight:500}
.stk .transit{font-size:11.5px;color:var(--muted);line-height:1.3;margin-top:1px}
.sup .nm{font-size:13px;font-weight:600;line-height:1.25}
.sup .art{font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums;line-height:1.3;margin-top:1px}
/* Чипы направлений: свой margin-top (при 1px они слипались с названием) и flex-gap
   вместо margin-right у .dtag — тот не даёт вертикального зазора, когда чипы
   переносятся на вторую строку (узкий экран). */
.sup .dir{font-size:11.5px;color:var(--muted-2);margin-top:5px;display:flex;flex-wrap:wrap;gap:5px}
.sup .dir .dtag{margin-right:0}
.tbl td.updcell{padding:9px 2px;text-align:center}
/* Дата под ценой. Устаревание меняет ТОЛЬКО цвет — начертание то же:
   жирный янтарь звенел громче самой цены, к которой относится. */
.c-price .under{display:block;font-size:10.5px;color:var(--muted-2);font-weight:500;
                font-variant-numeric:tabular-nums;margin-top:2px}
.c-price .under.stale{color:var(--warn)}
/* Устаревшее наличие. Точка тоже уходит в янтарь: зелёный кружок рядом с
   янтарным числом утверждал бы свежесть, которой нет. */
.stk.stale .n{color:var(--warn)}
.stk.stale .transit{color:var(--warn);opacity:.85}
.stk.stale .dot.ok{background:var(--warn)}

/* Live / refresh button + states */
.live{width:28px;height:28px;border:1px solid var(--line);border-radius:var(--r-sm);background:#fff;color:var(--petrol);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:.12s;flex:none;padding:0}
.live:hover{background:rgba(30,189,226,.1);border-color:var(--cyan);color:var(--cyan)}
.live .ic{display:flex;line-height:1}
.live.htmx-request{border-color:var(--cyan);color:var(--cyan);cursor:default;pointer-events:none}
.live.htmx-request .ic{animation:spin .7s linear infinite}
.live.ok{border-color:var(--ok);background:rgba(18,161,80,.1);color:var(--ok);cursor:default;pointer-events:none}
.live.err{border-color:var(--danger);background:rgba(214,69,69,.1);color:var(--danger);cursor:default;pointer-events:none}

/* Task 17: «+» добавления в заказ — тот же размер/поведение, что и .live (28px) */
.addord{width:28px;height:28px;border:1px solid var(--line);border-radius:var(--r-sm);background:#fff;color:var(--petrol);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:.12s;flex:none;padding:0;line-height:1}
.addord:hover{background:rgba(30,189,226,.1);border-color:var(--cyan);color:var(--cyan)}
/* Task 24: колонка под «+» — узкая, ширина задана явным <col style="width:44px">
   в _results.html (седьмая колонка), стандартный td-паддинг .tbl tbody td (9px 12px =
   24px по горизонтали) оставил бы кнопке (28px) меньше её собственной ширины. Сужаем
   паддинг и центрируем, как .rtbl td.livec у панелей заказа делает для той же по
   смыслу узкой колонки-кнопки. */
.tbl td.addcell{text-align:center;padding:9px 4px}

/* Копирование PN / артикула: иконка — элемент в потоке справа от значения. Место
   резервируется всегда, по наведению строки меняется только видимость — поэтому текст
   не «прыгает», иконка никуда не налезает и не срезается краем таблицы. */
.cp-wrap{display:inline-flex;align-items:baseline;gap:4px;max-width:100%}
.copybtn{border:0;background:none;padding:0;margin:0;cursor:pointer;color:var(--muted-2);
  opacity:0;transition:opacity .12s,color .12s;line-height:0;flex:none;align-self:center}
.copybtn svg{display:block;width:1em;height:1em}
.copybtn:hover{color:var(--cyan)}
.copybtn:focus-visible{opacity:1}
.copybtn .ic-check{display:none}
.copybtn.copied{opacity:1;color:var(--ok)}
.copybtn.copied .ic-copy{display:none}
.copybtn.copied .ic-check{display:block}
.copybtn.copy-err{opacity:1;color:var(--danger)}
/* Показ по наведению строки СВОЕЙ таблицы. Панель замены/добавления — вложенная таблица
   (.rtbl) внутри <tr class="exprow">, а сама эта tr — ПРЯМОЙ потомок того же
   `table.oitems > tbody`, что и обычные строки заказа (вставляется hx-swap=afterend
   рядом с <tr id="item-…">). Без :not(.exprow) правило `.oitems > tbody > tr:hover`
   совпало бы и через exprow: курсор где угодно в развёрнутой панели держит эту tr
   «:hover», и правило подсветило бы разом ВСЕ .copybtn во вложенной таблице, а не
   только наведённую строку — ровно жалоба №4. Простое `.exprow .copybtn{opacity:0}`
   тут не спасает: специфичность (.exprow .copybtn) ниже, чем у самого правила
   `.oitems > tbody > tr:hover .copybtn` (2 класса против 3 классов+2 тега), и всегда
   проигрывает — гасить нужно СТРУКТУРНО, а не перебивать вес. Проброс на .rtbl ниже
   уже отдельно даёт подсветку только реально наведённой строке внутри панели. */
#results tbody tr:hover .copybtn,
.oitems > tbody > tr:not(.exprow):hover .copybtn,
.rtbl tbody tr:hover .copybtn{opacity:1}

/* Prices refresh + upload */
.rfr{min-width:150px;height:34px;padding:0 14px;border:1px solid var(--line);border-radius:var(--r-sm);background:#fff;font:inherit;font-weight:600;font-size:13px;color:var(--petrol);display:inline-flex;align-items:center;justify-content:center;gap:7px;cursor:pointer;transition:.12s;white-space:nowrap;text-decoration:none}
.rfr:hover{border-color:var(--cyan);background:rgba(30,189,226,.08);color:var(--cyan)}
.rfr .ic{display:flex}
.rfr.htmx-request,.rfr.pending{color:var(--cyan);border-color:var(--cyan);cursor:default;pointer-events:none}
.rfr.htmx-request .ic,.rfr.pending .ic{animation:spin .7s linear infinite}
.rfr.done{color:var(--ok);border-color:var(--ok);background:rgba(18,161,80,.08);cursor:default}
.rfr.fail{color:var(--danger);border-color:var(--danger);background:rgba(214,69,69,.08)}
/* display:inline-flex у .rfr перебивает UA-правило [hidden]{display:none} — без
   этого «скрытая» кнопка «Сохранить» в панели расписания оставалась бы видимой. */
.rfr[hidden]{display:none}
/* «Обновить» неактивного поставщика — resting-состояние с disabled, но НЕ .pending/
   .done (у тех свой disabled со своими цветами и своим pointer-events:none — просто
   .rfr:disabled без :not() перебил бы их правила ниже по каскаду). Без этого правила
   кнопка визуально выглядела и hover'илась как живая, хотя disabled. */
.rfr:disabled:not(.pending):not(.done){opacity:.4;pointer-events:none}
.st{width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;flex:none}
.st.ok{background:rgba(18,161,80,.12);color:var(--ok)}.st.err{background:rgba(214,69,69,.12);color:var(--danger)}
.ll{display:flex;align-items:center;gap:9px;white-space:nowrap}.ll .when{font-size:13px;color:var(--muted);font-variant-numeric:tabular-nums}
.filebtn{flex:none;height:34px;padding:0 12px;border:1px solid var(--line);border-radius:var(--r-sm);background:#fff;font-size:13px;font-weight:500;color:var(--ink);display:inline-flex;align-items:center;gap:7px;cursor:pointer;transition:.12s;white-space:nowrap}
.filebtn:hover{border-color:#cfd4da;background:var(--surface)}

/* Users badges + actions */
.count-num{font-size:14px;font-weight:600;font-variant-numeric:tabular-nums}
.ulogin{font-size:13px;color:var(--muted);font-variant-numeric:tabular-nums}
/* Роли — тот же компонент, что статусы заказа (.ost/.stwrap/.smenu). Цвета взяты
   из живой палитры: admin — семейство .ost.work, head — .ost.shipment,
   manager — .ost.draft. До этого роль рисовалась .badge с двумя состояниями
   (admin/neutral), из-за чего Руководитель и Менеджер были неотличимы. */
.ost.admin{background:rgba(30,189,226,.13);color:#0b5b73;border-color:rgba(30,189,226,.35)}
.ost.admin .sd{background:#1ebde2}
.ost.head{background:rgba(0,78,109,.1);color:#004e6d;border-color:rgba(0,78,109,.26)}
.ost.head .sd{background:#004e6d}
.ost.manager{background:#f2f3f5;color:#6b7480}
.ost.manager .sd{background:#9aa2ab}
/* Точки пунктов меню. Префикс .rd-, а не .sd- — см. web/roles.py. */
.rd-admin{background:#1ebde2}.rd-head{background:#004e6d}.rd-manager{background:#9aa2ab}
/* Неинтерактивный чип роли (своя страница пользователя, своя строка списка —
   себе роль не меняют) гасит курсор правилом, а не инлайновым style: тот же
   приём, что у .switch.ro ниже. */
.ost.ro{cursor:default}
/* Пароль. Шесть кругов ● (U+25CF) кеглем 9px: bullet • в базовом кегле слишком мелок.
   Кругов всегда ровно шесть — настоящая длина наружу не отдаётся. */
.pwstars{font-size:9px;letter-spacing:3px}
.pwform{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.pwform .finput{width:158px}
.finput.bad{border-color:var(--danger)}
.finput.good{border-color:var(--ok)}
.pwerr{color:var(--danger);font-size:11.5px;font-weight:500;white-space:nowrap}
.dtag{display:inline-flex;font-size:11.5px;color:var(--muted);border:1px solid var(--line);border-radius:6px;padding:2px 8px;margin-right:5px;background:#fff}
/* Инлайн-подтверждение действий — ЕДИНЫЙ компонент .act для всех таблиц:
   «Пользователи» и список заказов — горизонтальный ряд; строка позиции заказа —
   модификатор .vert (вертикальная колонка в узкой 56px-ячейке). Alpine-состояние
   confirming (на контейнере или на <tr> — см. orders/_item_row.html) прячет .icons
   и показывает .confirm. Кнопки-иконки — общий бейдж .ib (30px в ряду, 28px в
   .vert — базовый размер бейджа, см. блок .ib ниже); пара подтверждения — .c-yes
   текстом + .ib.no иконкой (широкие ячейки, .c-no снесён) либо целиком иконная
   (.ib.yes/.ib.no — узкие). До объединения тот же приём жил в ТРЁХ параллельных
   пространствах имён
   (.act/.acts/.rowact) с дословно скопированными правилами. */
.act{display:flex;align-items:center;justify-content:flex-end;gap:6px}
.act .icons{display:inline-flex;gap:6px}
.act .ib{width:30px;height:30px;flex:none}
.act .confirm{display:none;align-items:center;gap:8px}
.act.confirming .icons{display:none}
.act.confirming .confirm{display:flex}
.act.vert{flex-direction:column;justify-content:center}
.act.vert .icons{flex-direction:column}
.act.vert .ib{width:28px;height:28px}
.act.vert .confirm{flex-direction:column;gap:6px}
.act .c-yes{padding:0 12px;height:30px;border:none;background:var(--danger);color:#fff;font:inherit;font-weight:600;font-size:12.5px;border-radius:var(--r-sm);cursor:pointer;white-space:nowrap}
.act .ib.yes{border-color:var(--danger);background:rgba(214,69,69,.1);color:var(--danger)}
.act .ib.no{color:var(--muted)}
.act .ib.no:hover{color:var(--ink);border-color:#cfd4da;background:var(--surface)}
/* Кнопка подтверждения удаления лежит внутри <form> (обычный POST с редиректом,
   без htmx). Без display:contents форма стала бы собственным флекс-элементом .confirm
   и сбила бы выравнивание пары ✓/✕. Идиома уже живёт в блоке расходов (.oexp-row). */
.act .confirm form{display:contents}

/* Forms */
.pagehead{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}
.back{display:inline-flex;align-items:center;gap:6px;font-size:13px;color:var(--muted);text-decoration:none;margin-bottom:14px}.back:hover{color:var(--ink)}
.cards{display:flex;flex-wrap:wrap;gap:24px;align-items:flex-start}
.card{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:22px 24px;width:430px;max-width:100%}
.card h2{font-size:16px;font-weight:700;margin:0 0 4px}.card .csub{font-size:12.5px;color:var(--muted);margin:0 0 18px}
.field{margin-bottom:15px}
.flabel{display:block;font-size:12.5px;font-weight:600;color:var(--muted);margin-bottom:6px}
.field input,.field select{width:100%;height:40px;border:1px solid var(--line);border-radius:var(--r-sm);padding:0 12px;font:inherit;font-size:14px;background:#fff;color:var(--ink);outline:none;transition:.12s}
.field input:focus,.field select:focus{border-color:var(--cyan);box-shadow:0 0 0 3px rgba(30,189,226,.18)}
.field input:disabled{background:var(--surface);color:var(--muted);cursor:not-allowed}
.field .fh{font-size:11.5px;color:var(--muted-2);margin-top:5px}
.fh.err{color:var(--danger)}
.formmsg{font-size:13px;font-weight:500;margin-bottom:14px}
.formmsg.ok{color:var(--ok)}
.formmsg.err{color:var(--danger)}
.switch{display:inline-flex;align-items:center;gap:10px;cursor:pointer;font-size:14px;user-select:none}.switch input{display:none}
.track{width:38px;height:22px;border-radius:999px;background:#d4d8dd;position:relative;transition:.15s;flex:none}
.thumb{position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;background:#fff;transition:.15s;box-shadow:0 1px 2px rgba(0,0,0,.2)}
.switch input:checked + .track{background:var(--ok)}.switch input:checked + .track .thumb{transform:translateX(16px)}
/* Тумблер только для чтения (роль без права менять — см. _supplier_row.html).
   Клик по label с disabled-инпутом и так ничего не переключает: правило визуальное. */
.switch.ro{cursor:default}.switch.ro .track{opacity:.45}
.actions{display:flex;align-items:center;gap:14px;margin-top:22px}

/* Orders list (Task 9) */
.phead{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}
.phead .ptitle{font-size:20px;font-weight:700}

/* Тулбар списка заказов переиспользует общий .toolbar (flex-контейнер), но своя
   разметка поля поиска/кнопки/селектов — новая, скоуплена под .toolbar, чтобы не
   зацепить общий .field (лейбл+инпут форм) и будущие .sel других страниц. */
.toolbar .field{position:relative;flex:1;min-width:220px;margin-bottom:0}
.toolbar .field .lens{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted-2)}
.toolbar .field input{width:100%;height:40px;border:1px solid var(--line);border-radius:var(--r);background:#fff;padding:0 12px 0 36px;font:inherit;font-size:13.5px;outline:none}
.toolbar .field input:focus{border-color:var(--cyan)}
.toolbar .find{height:40px;border:none;border-radius:var(--r);background:var(--ink);color:#fff;font:inherit;font-weight:600;font-size:13.5px;padding:0 20px;cursor:pointer;transition:.15s}
.toolbar .find:hover{background:var(--petrol)}
.toolbar .sel{height:40px;border:1px solid var(--line);border-radius:var(--r);background:#fff;padding:0 32px 0 13px;font:inherit;font-size:13px;color:var(--ink);cursor:pointer;min-width:160px;appearance:none;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7480' stroke-width='2'><polyline points='6 9 12 15 18 9'/></svg>");background-repeat:no-repeat;background-position:right 11px center}

/* .otbl — модификатор поверх .tbl: overflow:visible (не hidden), т.к. абсолютно
   спозиционированные поповеры внутри таблицы иначе обрезаются её скруглённым краем:
   инлайн-меню смены статуса в списке заказов (.smenu, Task 10) и оверлей истории
   поиска в панелях замены/добавления на странице заказа (.searchhist, волна 4 —
   вдобавок к обрезке overflow:hidden делал таблицу программно скроллируемой, и
   scrollIntoView оверлея «увозил» её содержимое). */
.otbl{overflow:visible}

/* Ссылка-название строки таблицы — один стиль на «Заказы» и «Пользователи»
   (.oname) и «Поставщики» (.sup .nm a): цвет чернил, без подчёркивания, cyan на
   наведении. Второй селектор вместо навешивания класса .oname на поставщика —
   тот врал бы про сущность, а переименование .oname тянет за собой шаблоны
   заказов и их тесты. */
.oname,.sup .nm a{color:var(--ink);font-weight:600;text-decoration:none;font-variant-numeric:tabular-nums;transition:color .12s}
.oname:hover,.sup .nm a:hover{color:var(--cyan);text-decoration:none}

/* Клиент второй строкой в ячейке названия заказа — колонка «Клиент» удалена ради
   «Менеджера» (B.1). Та же приглушённая роль, что у бренда в «Бренд · PN» (.np .b),
   но не идентичные значения: у .np .b — 12px (наследуется от .np) и var(--muted);
   здесь — свой размер 11.5px, более светлый var(--muted-2) и обрез многоточием
   (.np .b его не имеет, т.к. .np уже целиком одна строка). */
.oclient{font-size:11.5px;color:var(--muted-2);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* Статус — бейдж + инлайн-поповер смены (сам поповер .smenu заполняется в Task 10,
   CSS уже здесь, т.к. использует те же .stwrap/.ost). */
.stwrap{position:relative;display:inline-block}
.ost{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:600;padding:3px 9px;border-radius:999px;border:1px solid transparent;white-space:nowrap;cursor:pointer;user-select:none}
.ost .sd{width:6px;height:6px;border-radius:50%}
.ost .car{opacity:.45;font-size:8px;margin-left:1px}
.ost.draft{background:#f2f3f5;color:#6b7480}.ost.draft .sd{background:#9aa2ab}
.ost.work{background:rgba(30,189,226,.13);color:#0b5b73;border-color:rgba(30,189,226,.35)}.ost.work .sd{background:#1ebde2}
.ost.purchase{background:rgba(201,138,0,.12);color:#8a5f00;border-color:rgba(201,138,0,.32)}.ost.purchase .sd{background:#c98a00}
.ost.shipment{background:rgba(0,78,109,.1);color:#004e6d;border-color:rgba(0,78,109,.26)}.ost.shipment .sd{background:#004e6d}
/* «Не оплачен» — те же значения, что были у снятого .ost.cancel: постановка прямо
   просит красное оформление «как у Отменён». */
.ost.unpaid{background:rgba(214,69,69,.1);color:#a33;border-color:rgba(214,69,69,.3)}.ost.unpaid .sd{background:#d64545}
.ost.done{background:rgba(18,161,80,.12);color:#0c6b38;border-color:rgba(18,161,80,.3)}.ost.done .sd{background:#12a150}
.ost.archive{background:#f2f3f5;color:#9aa2ab}.ost.archive .sd{background:#c7ccd2}
.smenu{position:absolute;top:calc(100% + 5px);left:0;z-index:30;background:#fff;border:1px solid var(--line);border-radius:var(--r);box-shadow:0 8px 24px rgba(15,15,15,.14);padding:5px;min-width:158px}
.smenu .si{display:flex;align-items:center;gap:8px;padding:6px 9px;border-radius:var(--r-sm);cursor:pointer;font-size:12.5px;font-weight:500;white-space:nowrap}
.smenu .si:hover{background:var(--surface)}.smenu .si .sd{width:7px;height:7px;border-radius:50%;flex:none}
.smenu .si.on{font-weight:700}
.smenu .si .ck{margin-left:auto;color:var(--petrol)}
/* Точки пунктов меню. Один ключ статуса — одно правило, цвет тот же, что у точки
   бейджа (.ost.<ключ> .sd) выше: в меню и на бейдже это одна и та же метка, и
   разойтись в цвете они не имеют права. Набор ключей держит тест
   test_status_menu_dot_rules_match_status_dictionary. */
.sd-draft{background:#9aa2ab}
.sd-work{background:#1ebde2}
.sd-purchase{background:#c98a00}
.sd-shipment{background:#004e6d}
.sd-unpaid{background:#d64545}
.sd-done{background:#12a150}
.sd-archive{background:#c7ccd2}

.date{font-size:12.5px;color:var(--muted);font-variant-numeric:tabular-nums}
.cmt{font-size:12.5px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Комментарий журнала выгрузок переносится, а не обрезается — и на десктопе
   тоже (спека 2026-09-01 §6.1). Замер на ошибке в 285 знаков при окне 1400:
   текст требует 1745 px, ячейка даёт 725; было видно треть, полный текст только
   в title. Метка .full обязательна: голый .cmt носит ещё комментарий заказа в
   списке заказов, и перенос уехал бы на чужую страницу. */
.cmt.full{white-space:normal;overflow:visible;text-overflow:clip}
/* Подпись смягчённой выдачи (_relaxed_note.html). Снаружи медиазапроса
   намеренно: она нужна и на широком экране — молчание одинаково вредит на
   обеих ширинах, и заказчик заказал её именно так. 12,5 px — размер, которым
   в приложении набран весь второстепенный текст (.cmt, .otot .k, .stbadge).
   Мобильного правила у строки нет и не нужно: собственная ширина строки
   265,3 px, на странице поиска она умещается в одну строку начиная с 320 px
   (там доступен 281 px). */
.rlx{margin:0 0 9px;font-size:12.5px;color:var(--muted)}
/* Действия строки списка заказов — единый компонент .act (см. блок «Инлайн-
   подтверждение действий» выше), отдельного пространства имён .acts больше нет. */

.btn-more{height:40px;border:1px solid var(--line);border-radius:var(--r);background:#fff;color:var(--ink);font:inherit;font-weight:600;font-size:13.5px;padding:0 22px;cursor:pointer}
.btn-more:hover{border-color:var(--cyan);color:var(--petrol)}
/* .count — общее объявление в секции "Футер таблицы" выше (используют и Поиск, и этот список). */

/* Login (dark) */
.login-page{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px;background:var(--ink);background-image:radial-gradient(1100px 500px at 50% -10%, rgba(30,189,226,.12), transparent 70%)}
.login{width:352px;max-width:100%;animation:rise .4s ease both}
.login .brand{text-align:center;margin-bottom:26px}.login .brand img{height:32px}
.login .card{padding:26px 26px 28px;box-shadow:0 20px 50px -20px rgba(0,0,0,.5);border:none;width:auto}
.login .card h1{font-size:17px;font-weight:700;margin:0 0 20px}
.login-btn{width:100%;height:44px;margin-top:8px;border:none;border-radius:var(--r-sm);background:var(--ink);color:#fff;font:inherit;font-weight:600;font-size:14px;cursor:pointer;transition:.15s}.login-btn:hover{background:var(--petrol)}
.login-err{background:rgba(214,69,69,.1);color:var(--danger);font-size:13px;font-weight:500;padding:9px 12px;border-radius:var(--r-sm);margin-bottom:14px}

/* Skeleton + animations */
.sk{background:linear-gradient(90deg,#edeff2 25%,#f7f8fa 37%,#edeff2 63%);background-size:400% 100%;animation:shimmer 1.25s ease-in-out infinite;border-radius:5px;height:11px}
@keyframes shimmer{0%{background-position:100% 0}100%{background-position:-100% 0}}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes rowin{from{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:translateY(0)}}
/* backwards, а НЕ both. both = backwards + forwards, и forwards оставляет конечный
   кадр применённым навсегда: на каждой <td> вечно висит transform: translateY(0), а
   transform != none создаёт контекст наложения. Из-за этого инлайн-меню смены статуса
   (.smenu, z-index:30) оказывалось заперто внутри контекста своей ячейки, и ячейки
   строк НИЖЕ рисовались поверх него. Конечный кадр совпадает с натуральным состоянием
   строки, поэтому отказ от forwards ничего не меняет визуально. */
tbody tr.entering td{animation:rowin .32s ease backwards}
@keyframes rise{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
/* Task 17: раскрытие панели поиска (замена/добавление) — высоту <tr> кроссбраузерно
   анимировать ненадёжно, поэтому анимируем внутренний контейнер .repl. */
@keyframes panelin{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}

/* Order detail page (Task 11): шапка (header-interactive-v3.html) + таблица позиций
   (compound-cells-v3.html) + итог (order-page-v3.html). .tbp-префикс убран; статус-бейдж
   переиспользует .stwrap/.ost/.smenu (Task 10) — .ostatus/.statuswrap из макета не заводим. */
.crumbs{font-size:12.5px;color:var(--muted);margin-bottom:14px}
.crumbs a{color:var(--petrol);text-decoration:none}
.ohead{background:var(--paper);border:1px solid var(--line);border-radius:var(--r);padding:16px 18px;margin-bottom:18px}
.hrow1{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
.titlewrap{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-height:32px}
/* Инлайн-правка шапки (Task 7): пунктир — единственный признак «это правится», ✎ и ✓ сняты.
   <button>, а не <span> с обработчиком: с уходом ✎ она остаётся единственным
   фокусируемым элементом поля, то есть единственным клавиатурным входом в правку.
   Пунктир рисуем text-decoration, а не border-bottom: он не входит в коробку
   элемента, поэтому просмотр остаётся той же ширины и высоты, и открытие правки не
   двигает соседей по строке.
   font:inherit — сброс шрифта КНОПКИ (браузер иначе подставляет системный UI-шрифт),
   но это шорткат: он разворачивается и в font-size:inherit/font-weight:inherit, а
   значит стирает любой font-size/font-weight, заданный классом конкретного поля
   (класс поля с одним селектором, той же специфичности 0-1-0). Кто
   победит при равной специфичности, решает порядок В ФАЙЛЕ, а не смысл — ловушка
   для любого поля, добавленного позже .fedit. Поэтому свой шрифт поле обязано
   задавать СОСТАВНЫМ селектором `.fedit.<класс-поля>` (специфичность 0-2-0, бьёт
   0-1-0 независимо от места в файле) — см. `.fedit.otitle`/`.fedit.cmtval`/
   `.fedit.pwstars` ниже; составной селектор держит тест
   test_fedit_field_classes_pin_font_via_composite_selector.
   У `.cmtval` голого правила НЕТ намеренно: класс носит единственный узел, и он же
   `.fedit`, поэтому второй блок с теми же свойствами был бы мёртвым дублем.
   Мобильный override шапки заказа (`.ohd .fedit.cmtval`, конец файла) этому не
   противоречит: он не голый — составной и под приставкой раздела — и меняет
   ДРУГОЕ свойство (white-space:normal вместо nowrap), а не дублирует эти.
   У `.otitle` голого правила больше НЕТ: его единственным носителем без .fedit был
   _field_supplier_name.html, и он тоже перешёл на .fedit (спека 2026-08-27, §4.1).
   Дата заказа (Замечание 1 смоука) переехала из кластера первой строки шапки в общий
   блок строк полей и рендерится тем же `.fval`/`.fedit.valtext`, что Менеджер/Клиент:
   своего класса и своего шрифта у неё больше нет — размер и начертание приходят от
   `.fval`, ровно как у соседей, поэтому составной пин `.fedit.odate` (и его обёртка
   `.dwrap`) снят как мёртвый. */
.fedit{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;text-align:left;cursor:pointer;text-decoration:underline dotted #c7ccd2;text-underline-offset:4px;text-decoration-thickness:1px}
.fedit:hover{text-decoration-color:var(--cyan)}
.fedit.otitle{font-size:20px;font-weight:700;letter-spacing:-.01em;font-variant-numeric:tabular-nums}
/* line-height:1 держит кнопку в высоте строки .fval (28px); overflow:hidden — ради
   многоточия у длинного комментария (Замечание 2 смоука было НЕ в этих строках, а
   в их сочетании: подчёркивание `.fedit` рисуется text-underline-offset:4px ниже
   базовой линии толщиной text-decoration-thickness:1px — итого 5px, — а тесная
   коробка line-height:1 без отступа кончается вплотную под текстом, ровно на
   clip-границе overflow:hidden, и линия обрезалась. У названия и даты overflow:hidden
   нет, там пунктир виден без правки — значит чинить нужно именно клип комментария.
   padding-bottom отодвигает clip-границу вниз, не отменяя ни overflow (эллипсис
   остаётся), ни компактную высоту (.fval центрирует кнопку внутри своих 28px).
   Ре-ревью (Minor 1): padding-bottom в одиночку увеличивает content-box только
   вниз, а .fval центрирует кнопку по ЕЁ ПОЛНОЙ высоте — сосед (.valtext, без
   паддинга) и текст комментария центрируются в 28px по-разному, и комментарий
   уезжал вверх на ~3px. padding-top тем же значением возвращает симметрию: box
   снова растёт поровну в обе стороны от текста, и .fval центрирует его так же,
   как соседей, без потери ни клипа (низ по-прежнему 6px), ни высоты строки. */
.fedit.cmtval{color:var(--ink);font-weight:500;line-height:1;padding-top:6px;padding-bottom:6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}
/* Пароль (_field_user_password.html): та же ловушка font:inherit, но с ТРЕТЬИМ
   носителем. Заглушка Task 8 была голым <span class="valtext pwstars"> — там
   .pwstars{font-size:9px} (ниже, «Users badges + actions») применялось само по
   себе. Task 10 превратил её в <button class="fedit valtext pwstars">, и
   font:inherit молча перебил кегль на 13.5px, унаследованные от родителя .fval —
   составной .fedit.pwstars (0-2-0) возвращает 9px независимо от порядка в файле,
   letter-spacing у голого .pwstars трогать не нужно: он не входит в шорткат font. */
.fedit.pwstars{font-size:9px}
.tcluster{display:inline-flex;align-items:center;gap:7px}
.titleinput{font-size:20px;font-weight:700;font-family:inherit;color:var(--ink);border:1.5px solid var(--cyan);border-radius:var(--r-sm);padding:1px 9px;outline:none;width:230px;font-variant-numeric:tabular-nums}
.dates{text-align:right;flex:none}
.dates .d{font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums;line-height:1.5}
.dates .d .k{color:var(--muted-2);margin-right:5px}
.hdiv{height:1px;background:var(--line);margin:14px 0}
/* Часть шапки под разделителем — две колонки: слева строки полей, справа блок
   «Дополнительные расходы» (спека §5.1). До ревизии правая половина пустовала.
   min-width:0 у колонки и у .fval обязателен: без него длинный комментарий распирал бы
   flex-элемент за пределы карточки вместо того, чтобы обрезаться многоточием. */
.hbody{display:flex;align-items:flex-start;gap:36px}
.hfields{flex:1;min-width:0}
.hfields .frow:first-child{margin-top:0}
.hfields .fval{min-width:0}
/* Ниже 900px две колонки не помещаются: 104px лейбла + значение + 360px блока. Шапка
   складывается в одну колонку. Отдельный брейкпоинт от таблично-мобильного (760px):
   там речь про горизонтальную прокрутку таблицы, здесь — про раскладку карточки.
   Ширина блока (.oexp{width:100%} на том же брейкпоинте) объявлена НЕ здесь, а ниже,
   рядом с базовым правилом .oexp{width:360px} (см. комментарий там): специфичность у
   обоих правил одинаковая (0,1,0), медиа-запрос её не поднимает, и внутри одинаковой
   специфичности побеждает более позднее объявление В ФАЙЛЕ — а .oexp{width:360px}
   лежит физически ниже блока «Расходы заказа». Держи оба переопределения здесь — и
   базовое правило снизу тихо перебивало бы override сверху на каждой отрисовке. */
@media (max-width:900px){
  .hbody{flex-direction:column;gap:16px}
}
.frow{display:flex;align-items:flex-start;gap:12px;margin-top:11px}
.frow .flbl{width:104px;flex:none;font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--muted-2);font-weight:600;line-height:28px}
/* Голое значение в .frow обязано быть такой же коробкой 28px, как подпись.
   .flbl задан line-height:28px — коробка 28px с текстом по центру; голый .valtext
   задан line-height:1 — коробка ~19px, прижатая к верху (.frow{align-items:flex-start}).
   Подпись оказывалась НИЖЕ своего значения. Внутри .fval дефекта нет: .fval сам
   высотой 28px и центрирует содержимое, поэтому он жил ровно там, где значение
   положено в .frow голым — страница пользователя и строки «Код»/«Направления» у
   поставщика (та чинится этим же, её шаблон не открывается).
   Дочерний комбинатор здесь несущий, а не косметика: он гарантирует, что .fval,
   форма расписания поставщика и всё прочее вложенное остаются нетронутыми. */
.frow > .valtext{line-height:28px}
.frow > .chips{min-height:28px;align-items:center}
/* Строка шапки, чей управляющий элемент ВЫШЕ 28-пиксельной коробки подписи: 34px
   у кнопки «Настроить» в строке «Категории» и у формы расписания. Без правила
   подпись и элемент расходятся по вертикали на 3px — .frow ставит детей по верху.

   Ровняем НЕ строку целиком (align-items:center), хотя это короче: форма
   расписания переносится на две полосы, едва ей становится тесно — на 601px она
   76px вместо 34px (замер scripts/mobile_stand.py measure supplier), — и
   центрирование увело бы подпись на 21px вниз от собственной строки ровно в том
   диапазоне 601-850px, который и без того числится за нерешёнными. Вместо этого
   коробка подписи получает высоту элемента: подпись остаётся у ВЕРХА строки и
   совпадает с ним по центру текста, а перенос растит блок вниз, ничего не двигая.

   Второе правило — короткому бейджу (23px), который иначе прижался бы к верху
   рядом с 34-пиксельной кнопкой: его центрируем поштучно, а не строкой.

   Конкурент у .flbl — не bare .frow, а .frow .flbl{…line-height:28px} строкой
   выше: тот компаунд (0,3,0) бьёт его (0,2,0) независимо от порядка правил,
   иначе строка так и осталась бы 28px. У .ost конкурента нет вовсе — без этого
   правила align-self просто не задан и молча берёт auto (=flex-start строки),
   поэтому здесь не борьба за очки, а первое значение. Имя tallrow проверено
   свободным в app.css и в шаблонах целиком. На телефоне оба правила инертны:
   .mhead .frow там переходит в display:block, а .mhead .frow .flbl задаёт свой
   line-height позже по файлу, в мобильном блоке. */
.frow.tallrow > .flbl{line-height:34px}
.frow.tallrow > .ost{align-self:center}
.fval{display:flex;align-items:center;gap:7px;font-size:13.5px;font-weight:500;height:28px}
.valtext{line-height:1}
.finput{font:inherit;font-size:13.5px;color:var(--ink);border:1.5px solid var(--cyan);border-radius:var(--r-sm);padding:0 9px;height:28px;outline:none}
.finput.cmtinput{width:560px}
/* Комбобокс клиента (Task 14) — input + выпадающий .combolist, фильтр на клиенте
   (clientCombo() в orders.js) над списком visible_clients, переданным из шаблона
   (Finding 9 финального ревью: переименован из my_clients — для admin/head это
   все клиенты видимых им заказов, а не только «свои»). */
.combo{position:relative}
.combo .finput{min-width:240px}
.combolist{position:absolute;top:calc(100% + 4px);left:0;z-index:20;background:#fff;border:1px solid var(--line);border-radius:var(--r);box-shadow:0 8px 24px rgba(15,15,15,.12);padding:4px;min-width:260px;max-height:230px;overflow:auto}
.ci{padding:7px 10px;border-radius:var(--r-sm);cursor:pointer;font-size:13px;white-space:nowrap}
.ci:hover{background:var(--surface)}
.ci b{color:var(--ink);font-weight:700}
.ci.new{color:var(--petrol);font-weight:600;border-top:1px solid var(--line);margin-top:3px;padding-top:9px}

/* Items table — .tbl уже задаёт границы/фон; .oitems переопределяет вертикальное
   выравнивание (верх, а не центр — строки многоуровневые: PN+наименование,
   поставщик+наличие+цена) и центрирует узкую колонку ↻. */
.oitems tbody td{vertical-align:top}
.oitems td.mid{vertical-align:middle;text-align:center;padding:11px 4px}
/* Шапка узких колонок этой таблицы — тот же горизонтальный отступ, что у .mid ниже.
   Общее правило .tbl thead th даёт padding:9px 12px, и в колонке ↻ (46px по colgroup,
   таблица table-layout:fixed) на содержимое остаётся 22px — меньше, чем 28px самой
   кнопки. Переполняющий инлайновый бокс Chrome не центрирует, а прижимает к началу
   контента, и кнопка вставала на 3px правее кнопок в строках (замерено в headless
   Chromium: центры 600.00 против 597.00). На глаз это читалось как «прыгает вправо при
   наведении» — смещение есть всегда, но заметно становится, когда ховер делает кнопку
   контрастной. С отступом 4px кнопка помещается (38px), центрируется по-настоящему, и
   центры совпадают ровно. Колонка «№» тем же правилом не задета: её содержимое узкое,
   а отступ симметричный, так что центр не двигается. */
.oitems thead th.c-mid{padding-left:4px;padding-right:4px}
.oitems td.numcell{vertical-align:top;text-align:center;padding-top:11px}
.pnum{font-size:12.5px;font-weight:600;color:var(--muted);font-variant-numeric:tabular-nums}
/* Task 4: стрелки перемещения строки — заказчик просил минималистично и мелко,
   в стиле остальных иконок (.ib/.live), а не как полноразмерные кнопки. */
.ord{display:flex;flex-direction:column;align-items:center;gap:2px}
.mv{width:18px;height:14px;border:none;background:none;padding:0;color:var(--muted-2);
    display:inline-flex;align-items:center;justify-content:center;cursor:pointer;
    border-radius:3px;transition:.12s;line-height:0}
.mv:hover:not(:disabled){color:var(--petrol);background:rgba(30,189,226,.1)}
.mv:disabled{opacity:.25;cursor:default}
.oitems th.c-qs{text-align:center}
.np{font-size:12px;line-height:1.25}
.np .b{color:var(--muted);font-weight:500}
.np .sep{color:var(--muted-2);margin:0 4px}
.np .p{color:var(--ink);font-weight:600;font-variant-numeric:tabular-nums}
.nm{font-size:13.5px;font-weight:500;margin-top:3px;line-height:1.35}
.sline{font-size:12.5px;font-weight:600}
.sline .a{color:var(--muted);font-weight:500;font-variant-numeric:tabular-nums}
.sstk{display:flex;align-items:center;gap:6px;font-size:12px;margin-top:5px}
.sstk .strans{color:var(--muted-2)}
.sstk.stale{color:var(--warn)}.sstk.stale .strans{color:var(--warn);opacity:.85}
.uprice{font-size:12.5px;font-weight:600;margin-top:6px;font-variant-numeric:tabular-nums}
.uprice .per{color:var(--muted-2);font-weight:500;font-size:11px}
/* Закупочная не известна НИ РАЗУ — состояние, блокирующее расчёт маржи. */
.uprice.nocost{color:var(--danger);font-size:12px;font-weight:600}
/* Закупочная разошлась с текущей ценой каталога. Тот же токен --warn, что у
   .sstk.stale, но СВОЙ модификатор, не переиспользование .stale (задача 8 сняла
   .uprice.stale как мёртвое правило — цены каталога с тех пор эта колонка не
   показывает): .stale значило бы «дата каталога устарела», а тут число вообще не
   каталожное — закупочная ДОКУМЕНТА, взятая в другой момент. Один цвет, два разных
   утверждения. */
.uprice.diff{color:var(--warn)}
.deadtag{display:inline-block;font-size:10px;font-weight:600;color:var(--danger);background:rgba(214,69,69,.1);border-radius:4px;padding:1px 6px;margin-top:3px;text-transform:uppercase;letter-spacing:.03em}
.empty-cell{color:var(--muted-2);font-size:13.5px;text-align:center;padding:28px 4px}

/* Цена продажи + qty stepper + line sum */
.qs{display:flex;flex-direction:column;align-items:center;gap:8px}
/* Продажная цена — поле в стеке .qs над степпером. Рамка/радиус/tabular наследуют вид
   .stepper input, отличаются ширина и правое выравнивание (числа читаются по разрядам,
   как в колонке «Сумма»). */
.saleprice{width:114px;height:28px;border:1px solid var(--line);border-radius:var(--r-sm);background:#fff;padding:0 8px;font:inherit;font-weight:600;font-size:13px;text-align:right;font-variant-numeric:tabular-nums;outline:none}
.saleprice:focus{border-color:var(--cyan);box-shadow:0 0 0 3px rgba(30,189,226,.18)}
.stepper{display:inline-flex;align-items:center;border:1px solid var(--line);border-radius:var(--r-sm);overflow:hidden}
.stepper button{width:26px;height:28px;border:none;background:#fff;color:var(--petrol);font-size:16px;line-height:1;cursor:pointer;padding:0}
.stepper button:hover{background:rgba(30,189,226,.1)}
/* width — раньше поле было readonly и держало 1-2 цифры (36px); теперь оно
   редактируемое и должно вмещать верхнюю границу 999999 (6 цифр) без обрезки: в 56 px
   шестизначное число не помещалось. */
.stepper input{width:66px;height:28px;border:none;border-left:1px solid var(--line);border-right:1px solid var(--line);text-align:center;font:inherit;font-weight:600;font-variant-numeric:tabular-nums}
.sum{font-size:14px;font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}
/* Продажа ниже закупки — та же семантика цвета, что у .uprice.nocost. Красится не <td>,
   а span.sumcell внутри неё: это цель ОСНОВНОГО свопа (решение плана №3, _sum_cell.html),
   класс below-cost стоит на самом спане, а не на ячейке-обёртке. */
.oitems td.c-price .sumcell.below-cost .price{color:var(--danger)}
/* Позиция без закупочной цены блокирует расчёт маржи по ВСЕМУ заказу — подсвечиваем
   строку, чтобы причина прочерка в итогах читалась сразу, без поиска глазами. */
.oitems tr.nocost-row td{background:rgba(214,69,69,.045)}
/* Отказ записи: 400 (цена вне границ) или 409 (значение изменил другой пользователь).
   Точный смысл — в title, который проставляет orders.js. Нового компонента здесь нет:
   тот же приём и тот же токен, что у .live.err. */
.saleprice.err,.stepper.err input{border-color:var(--danger);background:rgba(214,69,69,.06)}

/* Бейдж-иконка .ib — базовый размер 28px (строка позиции, панели);
   в горизонтальном ряду действий .act .ib укрупняется до 30px (см. блок «Инлайн-
   подтверждение действий»). text-decoration:none — бейджем бывает и <a>
   (карандаш «Изменить» на странице пользователей). Вертикальная колонка действий
   строки позиции — .act.vert (бывший .rowact, отдельного пространства имён нет). */
.ib{width:28px;height:28px;border:1px solid var(--line);border-radius:var(--r-sm);background:#fff;color:var(--petrol);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:.12s;text-decoration:none;padding:0}
.ib:hover{background:rgba(30,189,226,.1);border-color:var(--cyan);color:var(--cyan)}
.ib.del:hover{background:rgba(214,69,69,.1);border-color:var(--danger);color:var(--danger)}
.ib.rep.active{background:var(--cyan);border-color:var(--cyan);color:#08252c}

/* Кнопка обновления цены (строка позиции + шапка колонки). Базовый вид — существующий
   иконочный бейдж .ib; здесь только отличия. .rfr-live отличается акцентом молнии
   внутри SVG, отдельного цвета не берёт: разные цвета читались бы как разные
   ДЕЙСТВИЯ, а это одна операция разной глубины.

   Класс .rfr уже занят (см. «Prices refresh + upload» выше, строка ~186) текстовой
   кнопкой «Обновить» на «Поставщики» — правило там глобальное (bare .rfr, без родителя),
   и без компенсации наехало бы и на эту иконку 28×28, раздувая её далеко за 46px,
   отведённые колонке (решение плана №4). Компаунд-селектор .ib.rfr (специфичность
   0,2,0) бьёт bare .rfr (0,1,0) НЕЗАВИСИМО от порядка правил в файле — но только по
   тем свойствам, которые сам явно перечисляет. Свойства старого .rfr, которых
   .ib.rfr НЕ называет, по-прежнему решаются порядком между bare .rfr (строка ~186) и
   bare .ib (выше) — специфичность у них одинаковая (0,1,0). Поэтому здесь явно, по
   каждому свойству старого .rfr:

   - min-width:150px, height:34px, padding:0 14px — визуально ломают бейдж (раздувают
     28-пиксельный бокс), поэтому перечислены здесь явно (min-width:0, height:28px,
     padding:0), чтобы победа не зависела от того, кто ниже в файле.
   - border/border-radius/background — значения ДОСЛОВНО совпадают с .ib (тот же
     var(--line)/var(--r-sm)/#fff), поэтому от исхода спора порядка визуально ничего
     не меняется — трогать не нужно.
   - color — уже совпадает по значению (var(--petrol)) со старым .rfr и с .ib, но
     явно продублирован здесь: без этого состояние .htmx-request (ниже) не имело бы
     от чего оттолкнуться по специфичности.
   - font/font-weight/font-size/gap/white-space — только у старого .rfr, но на кнопке
     без текстового узла (единственный потомок — .ic со <svg>) не видны: нет текста
     для которого считать вес/размер/интервал/перенос. Безвредны, не перечислены.
   - cursor/transition/display/align-items/justify-content — значения совпадают,
     как border/background выше. */
.ib.rfr{min-width:0;height:28px;padding:0;color:var(--petrol)}
.ib.rfr:hover{background:rgba(30,189,226,.1)}
/* Индикатор ожидания: htmx вешает .htmx-request на саму кнопку на время запроса.
   `.rfr.htmx-request{color:var(--cyan);border-color:var(--cyan);...}` (строка ~189,
   специфичность 0,2,0 — два класса) конкурирует за color с .ib.rfr{color:...} тут
   же выше, у которого специфичность ТОЖЕ 0,2,0 (два класса). При равной специфичности
   спор решает порядок в файле, а .ib.rfr стоит НИЖЕ — значит без этого правила
   побеждал бы petrol, а не cyan: рамка на время запроса меняла цвет, а иконка внутри
   — нет (обнаружено ревью). .ib.rfr.htmx-request (три класса, 0,3,0) снимает эту
   зависимость от порядка безусловно. border-color/cursor/pointer-events из старого
   правила и так уже побеждают сегодня — по чистой специфичности (0,2,0 против 0,1,0
   у одиночного .ib), порядок файла тут ни при чём — дублировать их не нужно. */
.ib.rfr.htmx-request{color:var(--cyan)}
/* Попытка уточнить у поставщика не удалась — тот же токен, что у .live.err на Поиске
   (одна неудача обязана выглядеть одинаково в обоих местах). Отличие одно: там класс
   вешает и снимает клиент по таймеру, а сюда он приезжает от СЕРВЕРА в перерисованной
   разметке — свап уничтожает кнопку-инициатор, снимать класс было бы уже некому и не с
   чего. Поэтому гасит анимация: 3 с (столько же держит флеш Поиска), после чего
   правило само перестаёт действовать и кнопка возвращается к обычному виду без единой
   строки JS и без состояния, которое может залипнуть.
   pointer-events не трогаем, в отличие от .live.err: повторить попытку человек должен
   мочь сразу, а не через три секунды. */
@keyframes rfr-err{0%,66%{border-color:var(--danger);background:rgba(214,69,69,.1);color:var(--danger)}}
.ib.rfr.err{animation:rfr-err 3s}
/* Успех — дословно .live.ok Поиска, включая гашение кнопки. Держится, а не мигает, и
   разница с err осмысленная: неудачную попытку повторяют сразу, удачную повторять
   незачем — свежее уже получено, а лишний live тратит лимит поставщика (у OCS 180
   запросов в час). Своп уничтожает кнопку вместе с классом, но модуль липких меток
   (web/static/orders.js) держит id обновлённых строк в памяти страницы и раздаёт ok
   заново при каждой перерисовке #order-body — так метка живёт до перезагрузки, а не до
   ближайшего свопа. pointer-events:none здесь и есть цель правила: «эту позицию в этой
   вкладке уже обновляли», заказанное поведение (docs/instructions/orders_5.md:19), а не
   случайная блокировка клика.

   transition:none — потому что этот класс приезжает НЕ с разметкой, а от модуля липких
   меток, уже после свопа. К этому моменту браузер успевает посчитать свежевставленную
   кнопку белой (FLIP-слушатель перемещения строк форсирует пересчёт между свопом и
   раздачей меток), и переход .12s от .ib проявляет зелёный из белого — на глаз кнопка
   мигает белым при каждой перерисовке тела заказа (измерено покадрово на живой
   странице). Переходы самого НАЖАТИЯ это не трогает: они живут на .htmx-request и на
   :hover, а не в состоянии ok, и там .12s остаётся. */
.ib.rfr.ok{border-color:var(--ok);background:rgba(18,161,80,.1);color:var(--ok);
  cursor:default;pointer-events:none;transition:none}
/* Кнопка в заголовке колонки — визуально тише строчных, чтобы не читаться как
   заголовок-сортировка. */
.rfr-all{opacity:.65}
.rfr-all:hover{opacity:1}
/* Исход распределения расходов (спека 2026-08-25 §6.3). Обе метки — чистая
   CSS-анимация: они относятся к НАЖАТИЮ, а не к документу, поэтому хранить нечего и
   модуль липких меток (web/static/orders.js) здесь не участвует.

   Имена ОБЯЗАНЫ отличаться от .ok/.err: тот модуль на каждом свопе #order-body
   перебирает querySelectorAll("button.rfr") и раздаёт .ok из своего набора. Наши
   кнопки в набор не попадут (его idOf() ищет closest("tr[data-item-id]"), а таблица
   расходов лежит вне строк позиций), но опираться на это молча нельзя: одно
   переносное решение по вёрстке — и трёхсекундная метка стала бы липкой без единой
   правки JS.

   Успех держит 3 с и гасит клик — защита от второго нажатия «а нажалось ли?».
   Отказ держит те же 3 с, но клик НЕ блокирует: повторить попытку человек должен
   мочь сразу. Ровно та же пара решений, что у .ib.rfr.err / .ib.rfr.ok выше. */
@keyframes dst-ok{0%,66%{border-color:var(--ok);background:rgba(18,161,80,.1);color:var(--ok);pointer-events:none}}
@keyframes dst-err{0%,66%{border-color:var(--danger);background:rgba(214,69,69,.1);color:var(--danger)}}
.rfr.dst-ok{animation:dst-ok 3s}
.rfr.dst-err{animation:dst-err 3s}

tr.confirming-row td{background:rgba(214,69,69,.05)}
/* .tbl tbody tr:hover красит сам <tr> в #fafbfc — эта подложка меняется под полупрозрачным
   тинтом td.confirming-row и на глаз разбавляет предупреждающий фон ровно тогда, когда
   курсор на строке (а он там и есть — пользователь только что кликнул иконку удаления).
   Обнуляем фон tr именно в этой комбинации состояний, чтобы тинт ячейки компоновался
   с исходным фоном таблицы, как и без наведения. transparent, а не inherit — фон тут
   должен явно отсутствовать у строки, а не наследоваться от tbody (который может
   получить свой фон при будущих правках и незаметно исказить смысл правила).
   Специфичность .tbl tbody tr.confirming-row:hover — (0,0,3,2): классы .tbl/.confirming-row/:hover
   против (0,0,2,2) у .tbl tbody tr:hover (.tbl/:hover) — три класса против двух, честный
   перевес без !important. */
.tbl tbody tr.confirming-row:hover{background:transparent}

/* Task 15: инлайн-замена позиции — панель под строкой (detail-<tr>, colspan). Разметка —
   .superpowers/brainstorm/64639-1784272860/content/replace-inline-v2.html, без .tbp. */
tr.replacing td{background:rgba(30,189,226,.055)}
tr.replacing td:first-child{box-shadow:inset 3px 0 0 var(--cyan)}
.exprow>td{padding:0;border-bottom:1px solid var(--line);background:var(--surface)}
.repl{padding:12px 16px 14px;border-left:3px solid var(--cyan);background:rgba(30,189,226,.055)}
.exprow .repl{animation:panelin .22s ease both}
.rtop{display:flex;align-items:center;gap:10px;margin-bottom:11px}
.rsearch{position:relative;flex:1;max-width:400px}
.rsearch .lens{position:absolute;left:11px;top:50%;transform:translateY(-50%);color:var(--muted-2);width:15px;height:15px}
.rsearch .ic-spin{display:none}
.rsearch.htmx-request .ic-lens{display:none}.rsearch.htmx-request .ic-spin{display:block;color:var(--cyan);animation:spin .7s linear infinite}
.rsearch input{width:100%;height:36px;border:1px solid var(--line);border-radius:var(--r);background:#fff;padding:0 12px 0 34px;font:inherit;font-size:13px;outline:none;font-variant-numeric:tabular-nums}
.rsearch input:focus{border-color:var(--cyan)}
/* Task 15: кнопка «Найти» — тот же тёмный примари, что и .btn на главном Поиске, но
   в компактной высоте панели (36px, вровень с .rsearch input); индикатор загрузки не
   нужен отдельно — hx-indicator у неё указывает на #rs-.../#as (тот же узел, что и у
   поля), поэтому спиннер лупы уже подхватывается существующим .rsearch.htmx-request. */
.rgo{height:36px;padding:0 16px;border:none;border-radius:var(--r);background:var(--ink);color:#fff;font:inherit;font-weight:600;font-size:13px;cursor:pointer;transition:.15s;white-space:nowrap;display:inline-flex;align-items:center;justify-content:center;flex:none}
.rgo:hover{background:var(--petrol)}
.rx{margin-left:auto;background:none;border:none;color:var(--muted-2);cursor:pointer;padding:4px;display:inline-flex}
.rx:hover{color:var(--ink)}
/* Результаты замены — вид как в Поиске, но без колонки «Обновлено» (устаревание — амбер) */
.rtbl{width:100%;table-layout:fixed;border-collapse:collapse;background:#fff;border:1px solid var(--line);border-radius:var(--r);overflow:hidden}
.rtbl thead th{background:var(--surface);text-align:left;font-size:10.5px;letter-spacing:.04em;text-transform:uppercase;color:var(--muted);font-weight:600;padding:7px 10px;border-bottom:1px solid var(--line)}
.rtbl th.r{text-align:right}
.rtbl td{padding:8px 10px;border-bottom:1px solid var(--line);vertical-align:top}
.rtbl td.livec{vertical-align:middle;text-align:center;padding:8px 2px}
.rtbl td.pricec{text-align:right}
.rtbl tr:last-child td{border-bottom:none}
.rtbl tbody tr:hover td{background:var(--surface)}
/* Ширины: PN получает больше места за счёт наименования — длинные артикулы ломались
   на три строки; наличие расширено под две строки без переносов посреди фразы. */
.rc-bp{width:170px}.rc-price{width:104px}.rc-live{width:42px}.rc-stk{width:104px}.rc-sup{width:150px}.rc-act{width:44px}
.rbp .brand{font-size:11.5px;color:var(--muted);font-weight:500;line-height:1.15}
.rbp .pn{font-size:12.5px;color:var(--ink);font-weight:600;font-variant-numeric:tabular-nums;line-height:1.2}
.rname{font-size:13px;font-weight:500;line-height:1.3}
.rprice{font-size:13px;font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}
.rprice .cur{color:var(--muted);font-weight:600}
.rprice.stale{color:var(--warn)}
/* Наличие в две строки (заказчик просил разносить основное и удалённое наличие, а не
   клеить через «·» одной строкой): rstk-main — количество + индикатор, всегда сверху;
   rstk-sub — резерв/транзит, отдельной строкой ниже, приглушённым цветом. */
.rstk{display:block;font-size:12px}
.rstk.stale{color:var(--warn)}
.rstk-main{display:flex;align-items:center;gap:5px}
.rstk-sub{color:var(--muted-2);font-size:11px;margin-top:2px}
.rsup .snm2{font-size:12.5px;font-weight:600;line-height:1.15}
.rsup .sart2{font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums;margin-top:1px}
.live.sm{width:27px;height:27px;font-size:13px}
.repl .btn-more{height:34px;padding:0 16px;font-size:12.5px}
/* Индикатор ожидания «Показать ещё» панели замены позиции держался на унаследованном
   hx-indicator контейнера выдачи — том самом наследовании, что спека §2.4 убрала (оно
   же ломало кнопку ↻ внутри выдачи, отдавая ей чужой индикатор). Взамен — собственное
   состояние кнопки на время её ЖЕ запроса, тем же приёмом, что и .rfr.htmx-request
   выше. Заодно тем же правилом отклик получают панель добавления позиции, панель
   добавления элемента комплекта и панель замены элемента комплекта — их контейнеры
   выдачи hx-indicator никогда не несли (двух — потому что были голыми <div>, третьей —
   потому что несла только hx-vals), унаследованный индикатор их не касался. */
.repl .btn-more.htmx-request{color:var(--cyan);border-color:var(--cyan);cursor:default;pointer-events:none}

/* Task 19: бар «Добавить позицию» под таблицей позиций (_items.html, второй <tbody>) —
   открывает тот же _repl-компонент, что и замена, в режиме "add" (_add_panel.html). */
.addbar td{padding:0}
.addbtn{width:100%;display:flex;align-items:center;justify-content:center;gap:8px;height:44px;border:none;background:#fcfcfd;color:var(--petrol);font:inherit;font-weight:600;font-size:13px;cursor:pointer;border-top:1px dashed var(--line)}
.addbtn:hover{background:rgba(30,189,226,.06)}
/* Task 14: контроллер (orders.js window.orderPanels) помечает #addbar классом
   .panel-open, пока открыта панель добавления — прячем кнопку целиком вместо старой
   подсветки .addbtn.active (панель и так занимает её место в таблице). */
.addbar.panel-open .addbtn{display:none}

/* Task 17: «Добавить в заказ» — модалка в #modal-root (base.html), открывается «+»
   из товарного поиска (.addord, _result_row.html). Разметка/CSS — add-to-order-v3.html,
   .tbp-префикс заменён на .ao-modal (в макете .backdrop был просто секцией превью,
   здесь — фиксированный оверлей поверх всей страницы). */
.ao-backdrop{position:fixed;inset:0;z-index:100;background:rgba(15,15,15,.42);display:flex;align-items:center;justify-content:center;padding:24px}
.ao-modal{background:#fff;border-radius:12px;box-shadow:0 24px 64px rgba(0,0,0,.32);width:500px;max-width:100%;display:flex;flex-direction:column;max-height:min(560px,86vh);overflow:hidden}
.ao-modal .mhead{padding:14px 18px;border-bottom:1px solid var(--line);display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex:none}
.ao-modal .mtitle{font-size:15px;font-weight:700}
.ao-modal .prod{font-size:12px;margin-top:5px;font-variant-numeric:tabular-nums}
.ao-modal .prod .b{color:var(--muted);font-weight:500}
.ao-modal .prod .p{color:var(--ink);font-weight:600}
.ao-modal .prod .sep{color:var(--muted-2);margin:0 5px}
.ao-modal .prod .sup{color:var(--ink);font-weight:600}
.ao-modal .prod .pr{color:var(--petrol);font-weight:700}
.ao-modal .x{background:none;border:none;color:var(--muted-2);cursor:pointer;padding:2px;flex:none}
.ao-modal .x:hover{color:var(--ink)}
.ao-modal .mbody{padding:13px 18px 16px;overflow:auto;display:flex;flex-direction:column;flex:1;min-height:0}
.ao-modal .toprow{display:flex;gap:9px;flex:none;margin-bottom:10px}
.ao-modal .osearch{position:relative;flex:1}
.ao-modal .osearch .ic-spin{display:none}
.ao-modal .osearch.htmx-request .ic-lens{display:none}
.ao-modal .osearch.htmx-request .ic-spin{display:block;color:var(--cyan);animation:spin .7s linear infinite}
.ao-modal .osearch .lens{position:absolute;left:11px;top:50%;transform:translateY(-50%);color:var(--muted-2);width:15px;height:15px}
.ao-modal .osearch input{width:100%;height:38px;border:1px solid var(--line);border-radius:var(--r);background:#fff;padding:0 12px 0 34px;font:inherit;font-size:13px;outline:none}
.ao-modal .osearch input:focus{border-color:var(--cyan)}
.ao-modal .newbtn{height:38px;border:1px solid var(--line);border-radius:var(--r);background:#fff;color:var(--petrol);font:inherit;font-weight:600;font-size:13px;cursor:pointer;display:inline-flex;align-items:center;gap:6px;padding:0 13px;white-space:nowrap;transition:.12s;flex:none}
.ao-modal .newbtn:hover{border-color:var(--cyan);background:rgba(30,189,226,.08)}
.ao-modal .olist{display:flex;flex-direction:column;gap:7px}
.ao-modal .oblock{border:1px solid var(--line);border-radius:var(--r);padding:9px 12px;cursor:pointer;transition:.12s}
.ao-modal .oblock:hover{border-color:var(--cyan);background:rgba(30,189,226,.05)}
.ao-modal .obtop{display:flex;align-items:center;gap:9px}
.ao-modal .obname{font-size:13px;font-weight:700;font-variant-numeric:tabular-nums}
.ao-modal .obtop .ost{font-size:10px;padding:2px 7px;cursor:default}
.ao-modal .obdate{margin-left:auto;font-size:11px;color:var(--muted-2);font-variant-numeric:tabular-nums}
.ao-modal .obline2{font-size:12px;margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ao-modal .obline2 .cl{color:var(--ink);font-weight:500}
.ao-modal .obline2 .sep{color:var(--muted-2);margin:0 5px}
.ao-modal .obline2 .cm{color:var(--muted)}
.ao-modal .confirm{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:5px;flex:1;padding:20px}
.ao-modal .okc{width:50px;height:50px;border-radius:50%;background:rgba(18,161,80,.12);color:var(--ok);display:flex;align-items:center;justify-content:center;margin-bottom:8px}
.ao-modal .ctxt{font-size:15px;font-weight:600}
.ao-modal .ctxt .on{color:var(--petrol);font-variant-numeric:tabular-nums}
.ao-modal .csub{font-size:12.5px;color:var(--muted)}
.ao-modal .cbtns{display:flex;gap:10px;margin-top:16px}
.ao-modal .btn2{height:40px;border-radius:var(--r);font:inherit;font-weight:600;font-size:13.5px;padding:0 18px;cursor:pointer;border:1px solid var(--line);background:#fff;color:var(--ink);transition:.12s;text-decoration:none;display:inline-flex;align-items:center;justify-content:center}
.ao-modal .btn2:hover{border-color:var(--cyan);color:var(--petrol)}
.ao-modal .btn2.primary{background:var(--ink);color:#fff;border-color:var(--ink)}
.ao-modal .btn2.primary:hover{background:var(--petrol);border-color:var(--petrol)}

/* Footer: экспорт + итоги (orders/_foot.html). Зон снова ДВЕ: блок расходов уехал в
   шапку (спека §5.1), и вместе с ним ушли flex-wrap с row-gap — переносить нечего.
   align-items:flex-start остаётся: кнопка экспорта высотой 42px и многострочный .otot
   выравниваются по верхней кромке, а не по центру. */
.ofoot{display:flex;align-items:flex-start;justify-content:space-between;margin-top:16px;padding:6px 2px 0}
/* Task 14: блок итогов — мини-таблица «лейбл → значение» в правом нижнем углу.
   Заменяет однострочный .grand; порядок детей .ofoot тогда же изменился: экспорт уехал
   влево, итоги встали справа. (Сама .ofoot когда-то держала ещё и блок расходов и ради
   него получала flex-wrap; после его переезда в шапку (спека §5.1, Task 3) правило
   вернулось к двум зонам — см. её правило выше; align-items:flex-start, flex и
   space-between остались.)
   На .otot же сидит id="order-total" — OOB-цель правки поля позиции: обёртка .ofoot ею
   быть перестала, чтобы своп не уносил формы блока расходов, — но это было нужно, пока
   тот ещё жил в подвале. Довод сегодня двойной (тот же порядок, что и в
   orders/_total.html): во-первых, id и тогда стоял на .otot, а не на .ofoot, — своп
   адресует ровно те числа, которые правка поля меняет, соседей ей переотдавать незачем;
   во-вторых, сам блок расходов с тех пор переехал в шапку (см. абзац выше) и .ofoot
   вообще перестала быть ему соседкой — уносить оттуда стало нечего независимо от id.
   Числа берут общий макрос money() (span.price) — здесь только размер под контекст
   итога; .price .cur (приглушённый ₽) и прочерк для None остаются общими.
   Размер задан и на .v, и на .v .price: значение бывает прочерком (голый текстовый
   узел без .price — незакупленная позиция, неоценённый заказ), и без правила на самой
   ячейке он выпал бы из типографики столбца.

   margin-right — требование F.2 спеки: разряды итогов стоят в той же вертикали, что
   и колонка «Сумма» таблицы позиций (привычная анатомия счёта). Само собой это не
   выходит — .ofoot прижимает блок к правому краю страницы, а правее «Суммы» у таблицы
   есть ещё колонка действий, и без сдвига одно и то же число стояло в двух вертикалях
   (замер при 1440: разряды строки 1227,5 px, разряды итога 1294 px). Слагаемые взяты
   из существующей разметки, а не подобраны на глаз: 56px — колонка действий (colgroup
   в _items.html), 12px — правый padding ячейки (.tbl tbody td), 2px — собственный
   правый padding .ofoot. Остаточные 0,5 px — половина схлопнутой внешней рамки .tbl
   (border-collapse), в пиксель её не выкинуть. */
.otot{display:grid;grid-template-columns:auto 168px;gap:5px 18px;margin-right:calc(56px + 12px - 2px);font-variant-numeric:tabular-nums}
.otot .k{color:var(--muted);font-size:12.5px;text-align:right;align-self:center}
.otot .v{font-size:14px;font-weight:700;text-align:right}
.otot .rule{grid-column:1/-1;height:1px;background:var(--line);margin:4px 0 3px}
.otot .k.tot{color:var(--ink);font-weight:600;font-size:13px}
.otot .v.tot,.otot .v.tot .price{font-size:20px;font-weight:700}
.otot .pct{color:var(--ok);font-weight:700;font-size:12.5px}
/* Убыток красится состоянием ошибки — и сумма, и процент. Зелёный процент под красными
   суммами убыточных строк (.oitems td.c-price .sumcell.below-cost) читался бы как «маржа
   есть» ровно там, где заказ ушёл в минус. Селектор четырёхклассовый (.otot .v.loss .pct), поэтому бьёт .otot .pct. */
.otot .v.loss,.otot .v.loss .price,.otot .v.loss .pct{color:var(--danger)}
.otot .v.otot-err{color:var(--danger)}
.otot .v.warn,.otot .v.warn .price{color:var(--warn)}
.otot .note{grid-column:1/-1;text-align:right;font-size:11.5px;font-weight:500;margin-top:-1px}
.otot .note.err{color:var(--danger)}
.otot .note.warn{color:var(--warn)}

/* Расходы заказа — блок «Дополнительные расходы» (спека 2026-08-25 §5.2). Своих кнопок и
   начертаний блок не заводит: название правится .fedit, поля ввода — .finput, удаление —
   .ib.del плюс .act/.confirm, ровно те же, что у строки позиции. Новое здесь только
   раскладка.

   Сетка — ТРИ ЖЁСТКИЕ КОЛОНКИ, и это лечение конкретного дефекта первой редакции: там
   список верстался таблицей по контенту, а форма правки — полями фиксированной ширины,
   поэтому правка ОДНОГО расхода двигала все остальные строки. Теперь треки объявлены
   ровно здесь и больше нигде, а ряды (просмотр, правка, добавление) идут display:contents
   и берут эти же колонки. Разъехаться им нечем.

   Ширина фиксированная: блок, растянутый по содержимому, ездил бы по горизонтали при
   каждом действии — так и было в подвале со space-between.
   124px под сумму — это 999 999 999,99 ₽ (потолок PRICE_MAX) при 12,5px tabular-nums.
   54px под кнопки — две .ib по 24px с зазором 4px. */
.oexp{width:360px;display:grid;grid-template-columns:1fr 124px 54px;column-gap:10px;align-items:center}
/* Override брейкпоинта шапки (900px, см. .hbody выше) обязан стоять ЗДЕСЬ, ПОСЛЕ
   базового .oexp{width:360px}, а не рядом с .hbody/.hfields по месту действия: у
   media-запроса та же специфичность (0,1,0), что у голого класса, порядок в файле
   решает — и правило выше по файлу проигрывает правилу ниже независимо от медиа-обёртки.
   Разместить override раньше базы значило бы, что на мобильном блок расходов
   визуально остаётся 360px и вылезает за карточку горизонтальной прокруткой — база
   снизу тихо перебивала бы его на каждой отрисовке. */
@media (max-width:900px){
  .oexp{width:100%}
}
.oexp-h{grid-column:1/-1;color:var(--muted);font-size:12.5px;font-weight:600;padding-bottom:6px}
/* Обёртка ряда без собственного бокса: три её ячейки попадают прямо в сетку .oexp. Так
   форма правки остаётся настоящей <form> со своими полями ВНУТРИ (htmx собирает
   параметры обходом потомков, а не через FormData) и при этом делит колонки с рядом
   просмотра. Первая редакция ради первого жертвовала вторым — форма жила в одной ячейке
   <td colspan="3"> и имела свою ширину. */
.oexp-row{display:contents}
/* Одинаковая высота у просмотра и правки: .finput 26px внутри 32px-клетки. Переход
   «просмотр → правка» не двигает ни соседние ряды, ни полосу кнопок, ни левую колонку
   шапки. */
.oexp-c{min-height:32px;display:flex;align-items:center;padding:3px 0;border-top:1px solid var(--line)}
.oexp-nm{min-width:0}
/* line-height:1 + симметричный паддинг — НЕ отступ, а лечение клипа, и рецепт взят
   дословно у `.fedit.cmtval` выше: там эта ловушка уже разобрана. `overflow:hidden`
   стоит здесь ради многоточия у длинного названия, а пунктир `.fedit` рисуется на 4px
   ниже базовой линии толщиной 1px — итого 5px за нижней границей тесной коробки, то
   есть за clip-границей. Название расхода выглядело простым текстом, хотя «+ Добавить»
   в двух строках ниже (тот же `.fedit`, без overflow) пунктир показывал: одна роль
   читалась двумя разными способами.
   Те же 5px, что у комментария, а не «сколько-нибудь»: запас обязан быть не меньше
   offset+thickness, и общий порог держит один тест на оба правила. Ради него же взят
   line-height:1 — при унаследованных 1.4 коробка выросла бы до 28px и ряд просмотра
   стал бы выше ряда правки (26px .finput в 32px-клетке), а разъезд высот — ровно тот
   дефект, ради которого ревизия и затевалась. С line-height:1 выходит 23px, ряд не
   двигается.
   Паддинг с ДВУХ сторон, а не только снизу: `.oexp-c` центрирует кнопку по её ПОЛНОЙ
   высоте, и односторонний рост увёл бы название вверх относительно суммы справа — тот
   же вывод, что Minor 1 ре-ревью у комментария. */
.oexp-nm .fedit{font-size:13px;line-height:1;display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-top:5px;padding-bottom:5px}
.oexp-am{justify-content:flex-end;font-variant-numeric:tabular-nums}
.oexp-am .price{font-size:12.5px}
.oexp-bt{justify-content:flex-end}
.oexp .finput{width:100%;height:26px;font-size:12.5px;padding:0 7px}
.oexp .finput.ta{text-align:right}
/* Кластер действий приезжает из общего .act с 30px-кнопками и зазором 6px. В 54px-колонку
   он не влезает, поэтому сжимаем до 24px/4px. Селекторы двухклассовые против
   одноклассовых у .act — выигрывают независимо от места в файле. */
.oexp .act,.oexp .act .icons,.oexp .act .confirm{gap:4px}
.oexp .act .ib{width:24px;height:24px}
/* Аналог tr.confirming-row td у позиции заказа: подсветить ряд целиком перед удалением.
   Вешается на каждую ячейку — у обёртки с display:contents своего бокса нет. */
.oexp-c.oexp-confirm{background:rgba(214,69,69,.05)}
/* Полоса под списком: ДВА ЯКОРЯ на все состояния блока — «+ Добавить» слева,
   «Распределить» справа. В первой редакции у второй кнопки было три разных места. */
.oexp-bar{grid-column:1/-1;display:flex;align-items:center;justify-content:space-between;
  border-top:1px solid var(--line);padding-top:8px;margin-top:2px}
.oexp-bar .fedit{font-size:12.5px;color:var(--petrol)}
.oexp-dist{min-width:0;height:26px;padding:0 10px;font-size:12px;gap:6px}

/* Каталог не ответил (orders/_body.html). Приглушённая строка над таблицей позиций, а
   не состояние ошибки: заказ не сломан, его деньги хранимые и от каталога не зависят —
   красным здесь кричали бы о чужой неполадке там, где пользователю ничего делать не
   нужно. Тот же токен muted, что у прочих пояснительных подписей. */
/* .live-off — тот же вид и та же роль, но другой отказ: не ответил ПОСТАВЩИК, а
   каталог ответил. Одно оформление намеренно: для человека это один класс сообщений
   «данные не так свежи, как хотелось» — разный вид читался бы как разная серьёзность,
   а разница тут только в том, чей отказ. Различие несут слова, не цвет. */
.mkt-off,.live-off{font-size:12.5px;line-height:1.45;color:var(--muted);background:var(--surface);
  border:1px solid var(--line);border-radius:var(--r);padding:9px 13px;margin-bottom:12px}
.xls{display:inline-flex;align-items:center;gap:9px;height:42px;background:var(--ink);color:#fff;border:none;border-radius:var(--r);padding:0 22px;font:inherit;font-weight:600;font-size:14px;cursor:pointer;transition:.15s;text-decoration:none}
.xls:hover{background:var(--petrol)}

/* Responsive: narrow screens — horizontal scroll for wide tables, single-col forms */
@media (max-width:760px){
  .searchblock{width:100%}
  .cards{flex-direction:column}.card{width:100%}
  .tbl-scroll{overflow-x:auto}
  .tbl{min-width:720px}
}
@media (prefers-reduced-motion: reduce){
  .sk{animation:none}tbody tr.entering td{animation:none}
  .kfold,.kfold td{transition:none}
  .login{animation:none}.live.htmx-request .ic,.rfr.htmx-request .ic,.rfr.pending .ic,.searching .s,.btn.loading .spin,.ao-modal .osearch.htmx-request .ic-spin,.rsearch.htmx-request .ic-spin{animation:none}
  .exprow .repl{animation:none}
}

/* Строка «Расписание» на странице поставщика (_supplier_schedule.html). Страницу
   вообще видят только admin/head (у обоих есть право её сохранять — редактируемого
   и read-only вариантов строки нет, в отличие от снесённой общей панели). */
.sched{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:12px 16px;margin:0 0 18px;display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.sched .days{display:flex;gap:6px}
.sched .day{min-width:34px;height:30px;padding:0 6px;border:1px solid var(--line);border-radius:var(--r-sm);display:inline-flex;align-items:center;justify-content:center;font-size:12.5px;font-weight:600;color:var(--muted);cursor:pointer;user-select:none}
.sched .day.on{background:var(--cyan);border-color:var(--cyan);color:#08252c}
.sched .timein{height:34px;border:1px solid var(--line);border-radius:var(--r-sm);padding:0 10px;font:inherit;font-weight:600;font-variant-numeric:tabular-nums}
/* Строка встроена в .frow страницы поставщика — своя рамка/фон/отступы там лишние,
   их даёт уже сам .frow (см. .sched выше — карточный вид нужен только там, где
   строка стоит отдельно, чего теперь не бывает). */
.sched.inrow{background:none;border:0;border-radius:0;padding:0;margin:0;gap:12px}
.sched-note{margin-top:4px;font-size:11px;color:var(--muted-2);white-space:nowrap}
/* Кнопка «Файл» недоступность + «только API» */
.filebtn.disabled{opacity:.4;pointer-events:none}
.na{font-size:12.5px;color:var(--muted-2);font-style:italic}

.subttl{font-size:15px;font-weight:700;margin:22px 0 12px}
.stbadge{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600}
.stbadge.ok{color:var(--ok)} .stbadge.err{color:var(--danger)} .stbadge.skip{color:var(--muted-2)}

/* ---- Модификатор цен заказа ------------------------------------------------
   Панель лежит СНАРУЖИ #order-body и потому переживает свопы; раскрытие — класс
   .selmode на #order-work, он же показывает чекбоксы в колонке «№». */
.pmod{display:flex;align-items:center;gap:10px;margin:0 0 12px}
.pmod-body{display:none;align-items:center;gap:10px;flex:1;padding-left:12px;border-left:1px solid var(--line)}
#order-work.selmode .pmod-body{display:flex}
.pmod-trigger.active{border-color:var(--cyan);background:rgba(30,189,226,.08);color:var(--cyan)}
/* Существующее правило .sel в этом файле привязано к .toolbar и снаружи не действует —
   повторяем объявления под своим родителем. */
.pmod .sel{height:34px;border:1px solid var(--line);border-radius:var(--r-sm);background:#fff;padding:0 30px 0 11px;font:inherit;font-size:13px;color:var(--ink);cursor:pointer;appearance:none;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7480' stroke-width='2'><polyline points='6 9 12 15 18 9'/></svg>");background-repeat:no-repeat;background-position:right 10px center}
/* Ширина селекта фиксирована под самый длинный пункт: панель не должна прыгать при
   смене модификатора. */
.pmod #pmod-modifier{width:240px}
.pmod-val{display:inline-flex;align-items:center;gap:6px}
/* display:inline-flex перебивает UA-правило [hidden]{display:none} — тот же случай,
   что у .rfr[hidden] выше по файлу. */
.pmod-val[hidden]{display:none}
.pmod-val input{width:118px;height:34px;border:1px solid var(--line);border-radius:var(--r-sm);padding:0 10px;font:inherit;font-size:13px;color:var(--ink);text-align:right;font-variant-numeric:tabular-nums;outline:none}
.pmod-val input:focus{border-color:var(--cyan)}
/* Ширина значка фиксирована, а не по содержимому: иначе «%» и «₽» разной ширины двигали
   бы кнопку «Применить» при смене модификатора. */
.pmod-val .unit{width:12px;font-size:13px;color:var(--muted)}
/* 118 (поле) + 6 (gap) + 12 (значок) — ровно ширина .pmod-val: выпадающий шаг встаёт на
   её место без единого пикселя разницы, и «Применить» не ездит.
   Значение прижато вправо, как у числового поля, — но именно text-align-last, а не
   text-align: WebKit к тексту ЗАКРЫТОГО селекта text-align не применяет (проверено в
   Safari: правило доезжает, значение остаётся слева), а text-align-last применяет.
   Blink понимает оба, так что второе объявление ничего не добавило бы. Правый padding
   под «галочку» остаётся, поэтому цифра шага встаёт в 106 px от левого края слота
   против 108 px у числового поля — одна линия. */
.pmod .pmod-step{width:136px;text-align-last:right}
.pmod-err{font-size:12.5px;color:var(--danger)}
/* Тот же узел носит и отказ, и отчёт об успехе — цвет разводит их. Красное «изменено
   3 позиции» читалось бы как ошибка. */
.pmod-err.info{color:var(--muted)}
.pmod-count{margin-left:auto;font-size:12.5px;color:var(--muted);white-space:nowrap}
/* Чекбоксы выбора: в разметке всегда, показываются вместе с панелью. Ширина колонки
   (52 px) при подмене не меняется. */
.selbox{display:none;justify-content:center;padding-top:2px}
#order-work.selmode .selbox{display:flex}
#order-work.selmode .ord,#order-work.selmode .numhead{display:none}
/* Кнопка ↻ (строка — .ib.rfr в _item_row.html, шапка колонки — .ib.rfr.rfr-all в
   _items.html) — тот же довод, что у стрелок перемещения строкой выше: рисуя тело
   БЕЗ selected, оба маршрута молча возвращают выбор к «всем», и следующая наценка легла
   бы на весь заказ вместо оставленных менеджером трёх позиций из сорока. Отмены в
   проекте нет, поэтому кнопки, способные тихо стереть выбор перед необратимой денежной
   операцией, на время выбора прячем. Селектор `.ib.rfr` не задевает кнопки самой панели
   («Применить»/триггер несут `.rfr`, но не `.ib`).

   Область — ТАБЛИЦА ПОЗИЦИЙ, а не весь #order-work. Правило писалось, когда `.ib.rfr`
   в заказе носили только кнопки ↻; тот же класс несёт и построчная кнопка «Распределить»
   расхода (_expense_row.html), но она стоит в шапке заказа, СНАРУЖИ #order-work, — класс
   .selmode до неё физически не дотягивается вовсе, и сужать правило ради неё незачем.
   Сужение до .oitems остаётся по собственному доводу: прятать на время выбора нужно
   именно ↻, а обе они (строка — в #items-body, массовая — в её же <thead>) живут ВНУТРИ
   table.oitems, и .oitems накрывает их разом.

   Частичный выбор при этом от кнопок расхода НЕ защищён вовсе, и это не забытый случай,
   а рулинг: любая мутация расхода (добавление/правка/удаление/распределение,
   web/routes/orders_expenses.py::_render_with_expenses) отвечает телом заказа БЕЗ
   параметра selected, и `_item_row.html` рисует чекбоксы отмеченными все разом — открытая
   панель «Изменить цены» теряет отметки менеджера молча. Прятать эти кнопки было бы
   неверно (отменило бы решение, ради которого правило и сузили до .oitems), а прокидывать
   выбор в OOB-ответ расходов — уже другая задача, не эта волна правок. Прецедент тот же,
   что у смены статуса со страницы заказа, — она стирает выбор точно так же и давно.
   Ограничение описано и принято — спека §10 п.9.

   visibility, а НЕ display: `display:none` убирает вместе с кнопкой её коробку, а
   заголовок колонки держал высоту строки именно ей (28 px против высоты текста) — от
   этого при каждом открытии панели вся таблица подпрыгивала вверх (найдено на смоуке).
   `visibility:hidden` оставляет геометрию нетронутой, но так же убирает кнопку из
   кликов и из порядка обхода клавиатурой — то есть цель правила достигается полностью. */
#order-work.selmode .oitems .ib.rfr{visibility:hidden}
.selbox input{width:16px;height:16px;accent-color:var(--cyan);cursor:pointer;margin:0}

/* Метка вида строки. Нужна, потому что у ручной позиции и у комплекта нет наличия и
   кнопки ↻ — без метки пустые ячейки читались бы как «каталог молчит». */
.kindtag{display:inline-block;border-radius:5px;padding:1px 6px;font-size:10px;
  font-weight:700;letter-spacing:.03em;margin-right:6px;vertical-align:1px}
.kindtag.man{background:#fff4e5;color:#a55b00}
.kindtag.kit{background:#eef2ff;color:#4457c7}

/* Инлайновая форма ручного товара. Живёт в .exprow — той же полосе, что панели
   поиска и замены, поэтому фон и отступы берёт от .repl. */
.manform{padding:2px 0 6px}
.mfhead{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}
.mfgrid{display:grid;grid-template-columns:2.2fr 1fr 1.3fr;gap:9px 10px;margin-bottom:9px}
.mfgrid.two{grid-template-columns:1.2fr 1.2fr 1fr auto;align-items:end}
.mf{display:block;min-width:0}
.mf .cap{display:block;color:var(--muted);font-size:10px;text-transform:uppercase;
  letter-spacing:.04em;margin-bottom:3px}
.mf input{width:100%;box-sizing:border-box;border:1px solid var(--line);border-radius:7px;
  padding:6px 9px;font:inherit;font-size:12.5px;background:var(--paper)}
.mf input.r{text-align:right;font-weight:600}
.mfacts{display:flex;gap:8px;padding-bottom:1px}
.rnew{border:1px solid var(--line);border-radius:7px;padding:6px 12px;font-weight:600;
  background:var(--paper);white-space:nowrap;cursor:pointer}
.rnew:hover{border-color:var(--cyan);color:var(--cyan)}
/* Двери «＋ РУЧНОЙ ТОВАР» и «＋ КОМПЛЕКТ» носят цвета бейджей тех строк, которые они
   заводят (.kindtag.man / .kindtag.kit): кнопка и её результат читаются как одно. */
.rnew.man{border-color:#f0d9b5;background:#fff4e5;color:#a55b00}
.rnew.man:hover{border-color:#a55b00;color:#a55b00}
.rnew.kit{border-color:#ccd3f5;background:#eef2ff;color:#4457c7}
.rnew.kit:hover{border-color:#4457c7;color:#4457c7}
.rnew.man,.rnew.kit{text-transform:uppercase;letter-spacing:.03em;font-size:11px}
.warnbar{background:#fff4e5;color:#a55b00;border-radius:7px;padding:6px 10px;
  font-size:12px;margin-bottom:8px}

/* Спойлер состава — одна кнопка на весь блок «› N элементов». Поведение и поворот
   каретки — те же, что у спойлера продавцов s4b на «Поставщиках» (.grp выше): значения
   намеренно совпадают, это один и тот же жест в двух разделах. */
.kexp{display:inline-flex;align-items:center;gap:5px;padding:0;border:none;background:none;
  color:var(--muted);font:inherit;font-size:12px;cursor:pointer;
  border-bottom:1px dashed var(--line)}
.kexp:hover{color:var(--ink);border-bottom-color:var(--cyan)}
.kexp svg{transition:transform .15s}
.kexp.on svg{transform:rotate(90deg)}
/* Наименование комплекта занимает всю ширину своей строки: кнопка «Автоимя» уехала в
   верхнюю строку (_item_row.html), и делить ширину больше не с кем. */
.kitnm{display:block}
.kncluster{display:block;min-width:0}
.kname{border:0;background:none;padding:0;font:inherit;color:inherit;text-align:left;
  border-bottom:1px dashed var(--line);cursor:pointer}
.kname:hover{border-bottom-color:var(--cyan);color:var(--cyan)}
.knameinput{font:inherit;border:1px solid var(--line);border-radius:6px;padding:2px 6px;
  min-width:340px;max-width:100%;background:var(--paper)}
/* «⟳ Автоимя» — второй элемент управления той же верхней строки, что и спойлер, и
   выглядит так же: без обводки, с тем же пунктиром «это можно нажать», что у
   спойлера и у самого наименования (.kname). Обводка делала её третьей по весу
   вещью в строке, где и так есть бейдж. */
.kgen{border:none;border-bottom:1px dashed var(--line);background:none;padding:0;
  margin-left:12px;color:var(--muted);font:inherit;font-size:12px;cursor:pointer;
  white-space:nowrap}
.kgen:hover{color:var(--cyan);border-bottom-color:var(--cyan)}
.cap{color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.04em}

/* Раскрытие и сворачивание состава комплекта. Переход объявлен И на самой строке, И на
   её ячейках: transform на table-row браузеры применяют неохотно, поэтому едут ячейки
   (тот же приём, что у rowin для въезжающих строк выше), — а строке переход нужен,
   чтобы Alpine прочитал с неё длительность и дождался конца, прежде чем ставить
   display:none. Без правила на .kfold он снял бы строку мгновенно, и сворачивание
   осталось бы без анимации. */
.kfold{transition:opacity .18s ease}
.kfold td{transition:opacity .18s ease, transform .18s ease}
.kfold-off{opacity:0}
.kfold-off td{opacity:0;transform:translateY(-5px)}
.kfold-on{opacity:1}
.kfold-on td{opacity:1;transform:none}

/* Состав комплекта — строки-продолжения. Фон отличает их от позиций заказа заметно, но
   не кричаще: это тот же документ, а не вложенная таблица. */
.oitems tr.kidrow td{background:#f1f3f7}
.oitems tr.kfoot td{background:#f1f3f7;color:var(--muted)}
/* Итог группы прижат вправо и занимает колонки «Цена · Кол-во» + «Сумма» (разметка —
   _kit_parts.html): последнее число выражения встаёт ровно под суммами строк. Утилиты
   .r в этом проекте нет — она объявлена только для th таблиц результатов и для полей
   ввода, поэтому выравнивание живёт здесь, у самой ячейки. */
.oitems tr.kfoot td.kfsum{text-align:right}
.pnum.sub{color:var(--muted);font-weight:500}
.addk{border:0;background:none;padding:0;color:var(--cyan);font:inherit;
  font-weight:600;cursor:pointer}
.psum{font-weight:600;color:var(--muted);text-align:right}
/* Закупка всей строки — второй строкой под суммой продажи, тем же приглушённым тоном,
   что и подписи у элементов комплекта. Отдельной колонки не заводим: ячейка суммы
   вертикально почти пуста, а новая колонка стоила бы ширины у наименования. */
.oitems td.c-price .csum{display:block;color:var(--muted-2);font-size:11px;
  font-weight:500;margin-top:16px;line-height:1.45}
/* Подпись — своей строкой над числом. Ячейка стоит с white-space:nowrap, поэтому
   само число не разрывается никогда, каким бы длинным ни было. */
.oitems td.c-price .csum .ccap{display:block}
/* Версия и чейнджлог: бейдж в шапке (_nav.html) + оверлей (_changelog.html).
   Бейдж стоит на тёмной шапке, поэтому цвета берутся у .hdr .user, а не заводятся свои. */
.verbadge{background:none;border:none;padding:0;font:inherit;font-size:13px;font-weight:500;color:rgba(255,255,255,.55);cursor:pointer;position:relative;transition:color .15s}
.verbadge:hover{color:rgba(255,255,255,.85)}
.verbadge.has-new::after{content:'';position:absolute;top:-2px;right:-7px;width:6px;height:6px;border-radius:50%;background:var(--cyan)}
.cl-modal{background:var(--paper);border-radius:12px;box-shadow:0 24px 64px rgba(0,0,0,.32);width:560px;max-width:100%;display:flex;flex-direction:column;max-height:min(640px,86vh);overflow:hidden}
.cl-modal .mhead{padding:14px 18px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;gap:12px;flex:none}
.cl-modal .mtitle{font-size:15px;font-weight:700}
.cl-modal .x{background:none;border:none;color:var(--muted-2);cursor:pointer;padding:2px;flex:none}
.cl-modal .x:hover{color:var(--ink)}
.cl-modal .mbody{padding:0 18px 18px;overflow:auto;flex:1;min-height:0}
.cl-rel{padding:14px 0;border-bottom:1px solid var(--line)}
.cl-rel:last-child{border-bottom:none}
.cl-rel .cl-head{display:flex;align-items:baseline;gap:8px}
.cl-rel .v{font-size:14px;font-weight:700}
.cl-rel .d{font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}
.cl-sec{margin-top:9px}
.cl-sec .t{font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
.cl-sec ul{margin:5px 0 0;padding-left:18px}
.cl-sec li{margin:3px 0}
.cl-empty{padding:28px 0;color:var(--muted);text-align:center}

/* Раздел «Наши категории» (admin_categories.html, _categories_tree.html) и
   подстраница привязки категорий поставщика (admin_supplier_categories.html,
   _supplier_category_rows.html) рисуют ОДИН примитив дерева с раскрытием по
   клику — восемь правил ниже написаны ГРУППОВЫМ селектором, перечисляющим обе
   приставки (cd- у справочника, cb- у подстраницы), а не продублированы по
   одному на приставку (ревью Задачи 8 §6): это один и тот же рисунок под
   двумя именами, а не два дерева, случайно совпавших побайтово, и правка
   примитива (например, 20→24px под палец в Задаче 11) обязана попадать в оба
   сразу одной строкой, а не тихо мимо второй копии. */
.cd-title,.cb-title{font-size:19px;font-weight:600}
.cd-tree td.cd-name > div{display:flex;align-items:center;min-width:0}
.cd-pad,.cd-tw,.cb-pad,.cb-tw{flex:none}
/* background/border/padding — сброс кнопочного вида: класс писался под <a>, а
   носят его теперь три <button> (шевроны обоих деревьев и строки пикера,
   спека 2026-09-10 §5). Правило обязано остаться безразличным к тегу: его же
   носит <span class="cd-tw cd-leaf"> пустого места у листа. */
.cd-tw,.cb-tw{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;color:var(--muted);cursor:pointer;border-radius:4px;background:none;border:0;padding:0}
.cd-tw:hover,.cb-tw:hover{background:rgba(0,0,0,.06);color:var(--ink)}
/* Лист без потомков: невидимый распорок сохраняет отступ имени тем же, что у
   строк с шевроном. */
.cd-tw.cd-leaf,.cb-tw.cb-leaf{visibility:hidden;cursor:default}
.cd-tw svg,.cb-tw svg{transition:transform .12s ease;display:block}
.cd-tw.cd-open svg,.cb-tw.cb-open svg{transform:rotate(90deg)}
.cd-lbl,.cb-lbl{flex:1;min-width:0;line-height:20px;overflow-wrap:anywhere}
.cd-tree tr.cd-l1 .cd-lbl{font-weight:600}
/* Инлайновое поле имени — одно и то же в справочнике (строка дерева) и в пикере
   привязки (строка списка): общее написано ГРУППОВЫМ селектором тем же приёмом,
   что примитив дерева выше, под приставками остались только расхождения —
   у пикера теснее отбивка и своя высота, потому что он живёт в 390-пиксельной
   панели, а не в ячейке таблицы. Подсказка «Enter — сохранить» — .cd-hint/.cb-hint,
   один блок объявлений на два имени. */
.cd-edit,.cb-new{display:flex;align-items:center}
.cd-edit{gap:8px}
.cb-new{gap:6px;padding:5px 12px;flex-wrap:wrap}
.cd-edit input,.cb-new input[type="text"]{flex:1;min-width:0;font:inherit;font-size:13px;border:1px solid var(--cyan);border-radius:var(--r-sm);outline:none}
.cd-edit input{height:30px;padding:0 9px}
.cb-new input[type="text"]{height:28px;padding:0 8px}
.cd-hint,.cb-hint{color:var(--muted);font-size:11px;white-space:nowrap}
/* Занятое имя (задача 9b) — тот же цвет отказа везде, где он появляется: в
   подсказке поля (справочник/пикер, .cd-hint/.cb-hint) и в причине погашенной
   цели переноса (.cd-why, единственная у которой в моменте класс err — саму
   цель, только что отказавшую). Один груп-селектор, а не три копии правила. */
.cd-hint.err,.cb-hint.err,.cd-why.err{color:var(--danger)}
/* В ячейке таблицы подсказка стоит СПРАВА от поля, в 390-пиксельной панели она
   съедала у него половину ширины (замер: под неё оставалось 150 px, и
   плейсхолдер обрывался на «Подкатегория в «Ноу»), поэтому здесь — строкой ниже. */
.cb-new .cb-hint{width:100%}
/* Пикер нового родителя (справочник) и пикер привязки (подстраница поставщика) —
   один компонент .pick, тот же, что у фильтров; приставки его только
   позиционируют и расширяют (ширина побеждает .pick{width:246px} порядком в
   файле — специфичность у обоих одна). Общее — групповым селектором: Задача 11
   сузит панель под телефон, и правило, написанное под одной приставкой, молча
   миновало бы второй пикер (ревью Задачи 9 §3). position:absolute не
   повторяем — он уже в .pick. */
.cd-host,.cb-host{position:relative}
.cd-pop,.cb-pop{z-index:60;width:390px}
/* Справочник ставит панель в угол хоста статикой; у привязки координаты
   считает category_picker.js от строки. */
.cd-pop{right:0;top:0}
/* Высота списка — единственное, что у двух пикеров РАЗНОЕ, и разведено оно по
   приставкам намеренно: у привязки 290 (панель выходит 399,7 px — заказчик
   мерил её на макете и просил 390–400, спека §7.4), у переноса 270 —
   утверждённая высота его экрана. Слитые в одно правило, они подняли панель
   переноса с 303,1 до 323,1 px и показывали лишнюю полустроку: список там
   упирается в потолок при справочнике длиннее восьми строк, а в нём 184
   (ре-ревью Задачи 9 §1). Общее у панелей — всё остальное, строкой выше. */
.cd-pop .pl{max-height:270px}
.cb-pop .pl{max-height:290px}
.cd-pop .pr.dead{cursor:not-allowed;opacity:.55;pointer-events:none}
.cd-why{margin-left:auto;color:var(--muted);font-size:11px;padding-left:8px}
/* Категория без единого узла поставщика — серая: видно, что она не работает,
   а не ошибка (спека §8.2). */
.cd-tree tr.cd-empty{color:var(--muted-2)}
/* Цена удаления — единственная подпись .cd-hint, которой позволено переноситься:
   в ряду .act она стоит рядом с кнопками, и в одну строку не влезает. */
.act .cd-hint{white-space:normal;text-align:right;line-height:1.35}

/* Подстраница привязки категорий поставщика — то, что СВЕРХ общего примитива
   выше: своё выравнивание имени по верху (в отличие от справочника, где имя
   однострочное, здесь оно переносится до трёх строк — align-items:flex-start
   держит чевон и первую строку вровень, а не по центру блока), подсветка
   неразмеченного узла и ячейка привязки. Приставка cb-. */
.cb-tree td.cb-name > div{display:flex;align-items:flex-start;min-width:0}
.cb-tree tr.cb-todo > td{background:rgba(214,69,69,.055)}
.cb-tree tr.cb-todo:hover > td{background:rgba(214,69,69,.09)}
.cb-cell{display:flex;align-items:center;gap:8px}
.cb-act{flex:none}
.cb-chip{cursor:pointer;white-space:normal;text-align:left}
.cb-chip.cb-inh{opacity:.55;cursor:default;display:inline-flex;align-items:center;gap:5px}
.cb-chip.cb-inh svg{flex:none}
/* «Снять привязку» — вторичная (приглушённая) кнопка, той же парой правил,
   что .act .ib.no у справочника/заказов, но под своей приставкой: кнопка
   здесь стоит в .cb-cell, а не в .act, и голый класс .ib.no без области
   видимости не подхватывал ни одно из существующих правил (ревью §8). */
.cb-cell .ib.no{color:var(--muted)}
.cb-cell .ib.no:hover{color:var(--ink);border-color:#cfd4da;background:var(--surface)}

/* Пикер привязки (_supplier_category_picker.html): его рамка, ширина и высота
   списка — общие с пикером справочника и написаны выше групповым селектором,
   здесь только то, чего у того нет. Строка выравнивается по ВЕРХУ: у неё две
   строки текста (имя и путь под ним), и по центру путь уезжал бы от имени. */
.cb-pop .pr{align-items:flex-start;gap:2px}
/* Имя категории переносится, а не обрезается: .pick .pr .nm стоит nowrap ради
   узких пикеров фильтров, и один overflow-wrap его не отменяет — под nowrap
   переносить нечего. */
.cb-pop .pr .nm{white-space:normal;overflow-wrap:anywhere}
.cb-path{display:block;color:var(--muted);font-size:11px;margin-top:1px}
/* «+» проявляется на наведении: он нужен изредка, а в постоянном виде спорил бы
   с именем за внимание в каждой строке списка. */
.cb-add{opacity:0;border:none;background:none;cursor:pointer;color:var(--muted);font:600 17px/1 system-ui;padding:0 5px;flex:none;align-self:center}
.cb-pop .pr:hover .cb-add{opacity:1}
.cb-add:hover{color:var(--cyan)}
/* Бейдж своей привязки — <button> (единственный вход в смену категории обязан
   быть достижим с клавиатуры), а кнопке браузер даёт свой шрифт: .ost задаёт
   кегль и начертание, но не семейство. */
button.cb-chip{font-family:inherit;line-height:inherit}

/* ═══════════════════════════════════════════════════════════════════════════
   МОБИЛЬНЫЙ СЛОЙ.
   Спека: docs/superpowers/specs/2026-08-30-web-mobile-design.md

   Блок обязан оставаться ПОСЛЕДНИМ в файле: там, где специфичность мобильного
   правила совпадает с десктопным (.login-page, .searchrow input.q, .ao-modal,
   .cl-modal), решает порядок. Страж — test_mobile_layer.py.

   Ни одно правило выше не переписано и не удалено: шире 600 px браузер этого
   блока не читает, и десктоп остаётся ровно тем же.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Единственное, чему место СНАРУЖИ: выключение мобильных узлов шапки. Они едут в
   разметке всегда — сервер ширины экрана не знает и отдаёт одну и ту же страницу
   телефону и десктопу. display:none убирает их и из отрисовки, и из порядка
   табуляции, и из дерева доступности. */
.hdr .burger,.hdr .mark,.hdr .hsect,.navdrawer,.navscrim,.mlbl,.mico,.mdate{display:none}

@media (max-width:600px){

  /* ── Поля страницы (§3.3) ──
     24 px по краям на экране 320 px — 48 px, 15% ширины впустую. Отступ, а не
     раскладка: страницы вне объёма от него только выигрывают. */
  .wrap{padding:14px 12px 60px}

  /* ═══ ОБЩИЙ СЛОЙ: таблица с меткой mcards → стопка карточек ═══
     Ни одного имени, специфичного для выдачи поиска. Этот блок переносится на
     следующий раздел целиком, вместе с меткой; своей там будет только карта
     областей (§4.0, §13).

     Префикс `.tbl.mcards` (0,2,0) там, где надо перебить собственную обстановку
     .tbl (0,1,0): рамку, фон, паддинги ячеек, заливку шапки. Иначе специфичность
     совпадает и всё держится на порядке в файле — а он инвариант, но хрупкий. */
  /* Комбинатор ДОЧЕРНИЙ, а не потомка, у всех одиннадцати правил, говорящих
     «строки/ячейки этой таблицы». Потомок — заявление «любая строка под этим
     узлом», и оно ложно с того момента, как внутрь попадает вторая таблица:
     панель поиска заказа живёт в <tr class="exprow">, а в панели — своя .rtbl,
     чьи строки для внешней таблицы тоже «tbody tr». Вложенная таблица, если
     она сама носит mcards, получает те же правила по собственному праву.
     Специфичность при этом сохраняется у десяти правил из одиннадцати; у
     последнего она растёт с (0,2,2) до (0,2,3) — в селектор добавился `tr`,
     и на новое значение опирается гашение даты в карточке списка. */
  .mcards,.mcards > tbody{display:block}
  .tbl.mcards{min-width:0;border:none;background:transparent;border-radius:0;
    overflow:visible;table-layout:auto}
  /* colgroup описывает семь колонок таблицы; у стопки карточек колонок нет.
     Оставленный, он не мешает раскладке, но врёт читателю о структуре. */
  .mcards > colgroup{display:none}

  /* Обёртку .tbl-scroll не трогаем намеренно: min-width:0 снял переполнение, а
     значит overflow-x:auto (он приходит с точки 760) больше ничего не прокручивает
     и ничего не обрезает — из карточки наружу не рисуется ничто, пикеры лежат
     СИБЛИНГАМИ .tbl-scroll. Переопределять overflow пришлось бы через :has() на
     родителе, и то же правило задело бы три чужие таблицы в таких же обёртках. */

  /* — шапка → горизонтальная полоса чипов (§5) —
     <thead> не прячется: кнопки-фильтры физически лежат внутри <th>, и display:none
     унёс бы фильтры целиком. Разворачивая шапку, мы оставляем их там же, где они
     лежат в разметке — правок в шаблоне ноль, а отступ перед первой карточкой
     появляется сам. */
  .mcards > thead{display:block}
  .mcards > thead > tr{display:flex;gap:7px;overflow-x:auto;padding:8px 0 14px;
    scrollbar-width:none}
  .mcards > thead > tr::-webkit-scrollbar{display:none}
  .tbl.mcards > thead th{display:block;flex:none;background:transparent;border:none;
    padding:0}
  /* «Наименование» и две колонки-действия подписей не имеют — в полосе им нечего
     показывать. :has() не поддержан только в браузерах старше Safari 15.4 /
     Chrome 105; там :not(:has(...)) — невалидный селектор, и ПРАВИЛО ЦЕЛИКОМ
     отбрасывается парсером (в отличие от свойства с неизвестным значением —
     отбрасывается вся строка). Деградация здесь не мягкая: в полосе чипов
     появляется лишний текст «НАИМЕНОВАНИЕ» и два пустых заголовка-действия
     (их <th> есть в разметке, просто без .thf внутри). */
  .mcards > thead th:not(:has(.thf)){display:none}
  /* А это правило несущее, а не подстраховка: при filterable=false (смягчённая
     выдача, ядро не прислало фасетов) filter_head рендерит ГОЛЫЙ ТЕКСТ без .thf,
     и правило выше прячет все четыре заголовка разом. Без второго над карточками
     осталась бы пустая полоса в 22 px от собственного паддинга. Деградация та же,
     что и у правила выше (:not(:has(...)) целиком выпадает на тех же браузерах):
     на смягчённой выдаче полоса вернулась бы с четырьмя голыми подписями колонок
     вместо пустой строки. */
  .mcards > thead > tr:not(:has(.thf)){display:none}

  .mcards thead .thf{border:1px solid var(--line);background:#fff;border-radius:999px;
    padding:11px;font-size:11.5px;letter-spacing:0;text-transform:none;
    color:var(--muted);font-weight:600;white-space:nowrap;gap:4px}
  /* Шевронка в разметке остаётся (десктоп её показывает) — здесь она лишний шум:
     чип и так читается как нажимаемый. */
  .mcards thead .thf .car{display:none}
  .mcards thead .thf.on{border-color:var(--cyan);background:rgba(30,189,226,.12);
    color:var(--petrol);padding:11px 6px 11px 11px}
  /* Финальное ревью, находка 1 (Important): паддинг обёртки ВИЗУАЛЬНО достраивает
     пилюлю до 40 px (вровень с невыбранным чипом — было 10px и 38.1, на 2 px ниже
     и не в ряд с соседями по нижней кромке), но интерактивны внутри только .thl и
     .clr — обёртка (.thf.on) это <span>, не <button> (кнопку в кнопку не вложить),
     а делегирование клика (search_filters.js: closest('[data-filter]')/
     closest('[data-clear]')) ищет их, не обёртку. Тап в паддинг обёртки не делал
     ничего.
     Растим тач-цель .thl/.clr паддингом с компенсирующим отрицательным margin —
     тот же приём, что у .sh-del и .navdrawer .dclose: margin гасит паддинг РОВНО,
     margin-box (вклад элемента в высоту строки) не меняется, значит не сдвигается
     ни обёртка, ни текст/иконка внутри — растёт невидимая зона тапа, бесплатно
     перекрывая мёртвый паддинг (единственное исключение — .clr:hover красит
     свой padding-box, а не только контур иконки: на десктопе, суженном до
     ≤600px, подсветка при наведении растёт вместе с тач-целью, 13×13 → 19×29;
     на тач-устройстве и на десктопе шире 600px эффекта нет). Замерено
     Playwright'ом (headless Chromium, 375 px, реальный DOM .thf.on/.thl/.clr): .val
     и svg крестика стоят
     на тех же координатах до и после, обёртка выросла ровно на заявленные 2 px и
     встала вровень с соседями (40.09 px у обеих — было 38.09 против 40.09).

     Корректирующая правка (ре-ревью волны) добавила и ГОРИЗОНТАЛЬНЫЙ охват —
     первая редакция растила тач-цель только по вертикали, оставляя мёртвыми
     11 px паддинга слева от .thl и 6 px справа от .clr. Тот же приём (padding +
     отрицательный margin) работает и здесь, если бить компенсацию по СТОРОНАМ:
     .thl растёт влево (к левой границе обёртки), .clr — вправо (к правой), а
     сторона, обращённая друг к другу через 4-пиксельный gap, не трогается —
     иначе .clr перехлестнул бы .thl, и было бы неясно, чей клик. Playwright
     подтвердил: координаты .val, svg, обёртки И соседнего чипа побайтово те
     же, что и до этой правки, а клик в 3 px от левого края чипа теперь берёт
     .thl, в 3 px от правого — .clr (было — оба брала обёртка). */
  .mcards thead .thf.on .thl{padding:11px 0 11px 11px;margin:-11px 0 -11px -11px}
  /* .clr: margin −8 при паддинге 9 по вертикали — НЕ симметричная компенсация,
     а расчёт от исходного десктопного padding:1px (см. .thf.on .clr выше по
     файлу): margin-box обязан остаться 13 px (11 иконка + 2×1), иначе крестик
     сам сместился бы на пиксель внутри строки чипов при пересчёте flex
     cross-size (проверено тем же замером). 9−8=1 воспроизводит тот исходный
     1px точно, а видимая тач-цель растёт до 11+2×9=29 px по высоте — тот же
     пол, что у .sh-del. Справа та же арифметика с паддингом 7/margin −6
     (7−6=1, тот же исходный 1px) — растёт вправо, к границе обёртки; слева
     остаётся исходный padding:1px/margin:0 — граница с .thl через gap не
     трогается намеренно. */
  .mcards thead .thf.on .clr{padding:9px 7px 9px 1px;margin:-8px -6px -8px 0}

  /* — строка → карточка —
     Область карточки задаётся здесь, карта областей — частным слоем. Сетка без
     grid-template-areas не разъезжается: браузер разложит ячейки потоком. */
  .mcards > tbody > tr{display:grid;column-gap:9px;row-gap:9px;align-items:start;
    background:#fff;border:1px solid var(--line);border-radius:var(--r);
    margin-bottom:9px;padding:11px 12px}
  /* :hover на тач-экране не существует, но «залипает» после тапа — карточка
     оставалась бы подсвеченной до следующего касания. */
  .tbl.mcards > tbody > tr:hover{background:#fff}
  .tbl.mcards > tbody > tr > td{display:block;border:none;padding:0}

  /* ═══ ЧАСТНЫЙ СЛОЙ: карта областей выдачи поиска (§4.1–4.5) ═══
     Приставка #results несёт id и весит (1,0,0) — она перебивает ВСЁ, включая
     правила десктопа. Поэтому весь этот блок обязан лежать внутри медиазапроса:
     вынесенное наружу правило с такой приставкой не переопределить ничем. */

  /*  ┌──────────────────────────────────────────┐
      │ SAMSUNG                    15 190,00 ₽   │
      │ LS27D400GAIXCI ⧉                  09:15  │
      │ Монитор Samsung 27" Essential S4 …       │
      │ ● 200      Мерлион        [↻]     [+]    │
      │            ⧉ 2071821                     │
      └──────────────────────────────────────────┘

     Три решения несущие, и каждое куплено замером:

     1. Обе текстовые колонки ГИБКИЕ (fr), а не auto. С auto колонка поставщика
        сперва дорастает до max-content — а при white-space:nowrap это вся строка
        целиком — и на 320 px не оставляет идентификатору ни пикселя: замер
        показывал ширину .bp ровно 0. Две доли делят остаток предсказуемо.
     2. bp занимает ДВЕ первые колонки, цена — ТРИ последние. Иначе bp сидит в одной
        колонке, а незанятая часть растяжки цены принадлежит колонке поставщика, до
        которой идентификатор не дотягивается: 89→118 (320 px), 133→200 (402),
        148→228 (430).
     3. Кнопки получают ЖЁСТКИЕ 38 px. Гибкие колонки под кнопками при цене на
        девять знаков (999 999 999,99 ₽ — потолок PRICE_MAX) разъезжались бы; с
        жёсткими зазор между кнопками остаётся ровно column-gap = 9 px на любой
        ширине. */
  #results .mcards tbody tr{
    grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,auto) 38px 38px;
    grid-template-areas:
      "bp    bp   price price price"
      "name  name name  name  name"
      "stock sup  sup   live  add"}

  /* Живая кнопка есть не у всех поставщиков (LIVE_SUPPLIERS), и её ячейка в таких
     строках пуста. :has() не поддержан только в браузерах старше Safari 15.4 /
     Chrome 105; там правило игнорируется и остаётся пятиколоночный вид — деградация
     мягкая, ничего не ломается. */
  #results .mcards tbody tr:not(:has(.updcell .live)){
    grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,auto) 38px;
    grid-template-areas:
      "bp    bp   price price"
      "name  name name  name"
      "stock sup  sup   add"}
  /* Обязательно, а не косметично: на пустой ячейке остаётся grid-area:live, области
     `live` в четырёх колонках нет, браузер её не находит и доразмещает ячейку сам —
     дорисовывая в конец ДВЕ НУЛЕВЫЕ КОЛОНКИ. Их column-gap (9+9) отодвигал кнопку
     «в заказ» от правого края ровно на 18 px. С правкой отступ — 0. */
  #results .mcards tbody tr:not(:has(.updcell .live)) td.updcell{display:none}

  /* — бренд с PN и цена с датой: один ритм —
     До правки пары давали 31 и 40 px и не выравнивались по низу. Обеим задан общий
     ритм: верхняя строка 15 px, зазор 2 px, нижняя 17 px — итого 34 и 34. */
  #results .mcards tbody td.bp{grid-area:bp;min-width:0}
  #results .mcards tbody td.bp .brand{line-height:15px;overflow:hidden;
    text-overflow:ellipsis;white-space:nowrap}
  #results .mcards tbody td.bp .pn{display:flex;line-height:17px;margin-top:2px}
  #results .mcards tbody td.c-price{grid-area:price;text-align:right;
    justify-self:end;white-space:nowrap}
  #results .mcards tbody td.c-price .price{font-size:17px;line-height:15px;display:block}
  #results .mcards tbody td.c-price .under{line-height:17px;margin-top:2px}

  /* — наименование —
     Не обрезается НИКОГДА: занимает всю ширину карточки, ни с чем не делит колонку,
     переносится свободно. Обрезка идентификаторов рядом — выбор ради плотности, а не
     техническая необходимость: .bp,.name,.sup{overflow-wrap:anywhere} в проекте уже
     есть, и без обрезки сетка не ломается — карточка просто растёт (замер стресс-
     строки при переносе: 261/380/230 px против 169–187 при обрезке). */
  #results .mcards tbody td.name{grid-area:name;font-size:13px;line-height:1.4;
    color:var(--ink);font-weight:500}

  #results .mcards tbody td.stk{grid-area:stock;align-self:center}
  #results .mcards tbody td.sup{grid-area:sup;text-align:right;align-self:center;
    min-width:0}
  #results .mcards tbody td.sup .nm{overflow:hidden;text-overflow:ellipsis;
    white-space:nowrap}
  #results .mcards tbody td.updcell{grid-area:live;align-self:center;
    justify-self:end;padding:0}
  #results .mcards tbody td.addcell{grid-area:add;align-self:center;
    justify-self:end;padding:0}
  #results .mcards tbody td.updcell .live,
  #results .mcards tbody td.addcell .addord{width:38px;height:38px}

  /* — копирование в буфер (§4.5) —
     .copybtn имеет opacity:0 и показывается правилом #results tbody tr:hover. На
     тач-экране :hover НЕ СУЩЕСТВУЕТ — кнопки были бы в разметке, но невидимы и
     недоступны. Показываем всегда, приглушённо.
     Обрезается только значение (.v): многоточие на всей .cp-wrap срезало бы и саму
     иконку — ради этого значение и получило свой элемент (§10.1). */
  #results .mcards .cp-wrap{display:inline-flex;align-items:center;gap:7px;
    min-width:0;max-width:100%}
  #results .mcards .cp-wrap .v{overflow:hidden;text-overflow:ellipsis;
    white-space:nowrap;min-width:0}
  #results .mcards .copybtn{flex:none;opacity:.55;padding:5px;margin:-5px -3px -5px 0;
    border-radius:5px}
  #results .mcards .copybtn svg{width:14px;height:14px}
  /* У артикула иконка переезжает ВЛЕВО от значения: колонка выровнена по правому
     краю, и значение обязано упираться в край — иначе артикул не совпадает по
     правому краю с именем поставщика над ним. У PN текст выровнен влево, логика
     зеркальная, и иконка остаётся справа. */
  #results .mcards tbody td.sup .cp-wrap{gap:6px;flex-direction:row-reverse}
  #results .mcards tbody td.sup .copybtn{padding:5px;margin:-5px 0 -5px -3px}
  #results .mcards tbody td.sup .copybtn svg{width:13px;height:13px}

  /* ── Форма поиска (§4.6) ──
     Поле облегчается: лупа мельче и ближе к краю, высота 42 → 38, у кнопки «Найти»
     снят min-width:104px. Галочки «только в наличии» в форме нет — она давно живёт
     фильтром в заголовке (in_stock_only — скрытое поле в #filter-state). */
  .searchrow{gap:8px}
  /* font-size:16px стоит ЗДЕСЬ, а не отдельной строкой ниже: два правила на один
     селектор читаются как «одно из них лишнее», и следующий правщик снял бы не то. */
  .searchrow input.q{height:38px;font-size:16px;padding:0 12px 0 32px;
    border-radius:var(--r-sm)}
  .searchrow .field .lens{left:11px}
  .searchrow .field .lens svg{width:15px;height:15px}
  /* Приставка обязательна: .btn носят ещё четыре страницы вне объёма. */
  .searchblock .btn{height:38px;min-width:0;padding:0 15px;font-size:13.5px;
    border-radius:var(--r-sm)}

  /* ── 16 px во ВСЕХ полях объёма ──
     iOS Safari принудительно зумит страницу, когда фокус входит в поле со шрифтом
     меньше 16 px. Отключить нельзя: maximum-scale и user-scalable=no он игнорирует
     с iOS 10 намеренно, как барьер доступности. Лечится только шрифтом.

     Поля вне объёма (аккаунт, создание пользователя, тулбар списка заказов) не
     трогаем — они достанутся своей итерации вместе со своими страницами. */
  /* Строка поиска — выше, вместе со своим размером. */
  .pick .pf input{font-size:16px}          /* высота 32 px не меняется */
  .ao-modal .osearch input{font-size:16px} /* высота 38 px не меняется */
  /* .field select мёртв на этой странице (на входе нет <select>) и оставлен
     нарочно: базовое правило десктопа — тоже пара input,select, и снятие
     половины завело бы асимметрию, из-за которой первый же <select>, случайно
     попавший на страницу входа, молча уехал бы на 13 px и вернул автозум iOS. */
  .login-page .field input,.login-page .field select{font-size:16px}

  /* ══ ТАЧ-ЦЕЛИ (§9) ══
     Правило: увеличивай цель, когда это бесплатно; когда это отнимает видимое
     содержимое — взвешивай против цены промаха. Не «44 везде» и не «44 нигде».
     Норма Apple/Google (44 px) описывает надёжность попадания по ИЗОЛИРОВАННОЙ
     цели; на успех тапа влияют ещё расстояние до соседей и цена промаха.

     Приём везде один: padding с компенсирующим отрицательным margin — область
     нажатия растёт, картинка и высота строки остаются прежними.

     Приставки здесь НЕТ намеренно: пикер и история — плавающие оверлеи, общие с
     панелями заказа. Панели получают те же цели бесплатно, и один и тот же пикер
     ведёт себя одинаково, кем бы ни был открыт. */

  /* Строка пикера 32 → 44. Бесплатно: потолок списка поднят вместе со строкой,
     и видно даже больше значений, чем было (7,1 против 6,7) — но это верно
     только там, где высоты хватает. Место освободилось ровно потому, что
     автофокуса больше нет и клавиатуры здесь не бывает. */
  .pick .pr{padding:13px 12px}
  /* Финальное ревью, находка 2 (Important): 308px — потолок БЕЗ верхней границы
     свободного места, и на низких экранах (320/375 px, iPhone SE и аналоги)
     «Применить» уезжает за нижнюю кромку видимой области — измерено 27.19px
     на честных 320×568 (headless без вычета Safari chrome) и 107px при
     эмулированном --vvh=480 (реалистичная видимая высота Safari). Спека §7
     мерила 246×342 ДО того, как §7.2 в том же разделе подняла .pl 214→308 —
     тот замер недействителен (см. правку спеки).
     min(308px, …) — новая ветка обязана уместить и .ph, и .pf, и .pfoot: 290px —
     их суммарная высота ВМЕСТЕ со сдвигом пикера от начала документа (шапка +
     поля страницы + строка поиска + полоса чипов), а не только своя обвязка.
     Число снято Playwright'ом на реальной странице (сборка shell(/) + фрагмент
     /search с фасетами brand, headless Chromium): пикер «Бренд» на 320×568 —
     .pl.top 232.19px от верха документа, .pfoot 55px, граница .pick 596.19px
     (290 = 232.19 + 55 + запас на округление шрифта). При искусственно сжатом
     --vvh=480 (эмуляция видимой высоты Safari с открытой адресной строкой,
     спека §8) «Применить» ДО правки заканчивается на 587px — на 107px ниже
     границы; ПОСЛЕ правки — на 469px, внутри. На 402 px и шире var(--vvh) не
     меньше ~700px, 700−290=410>308 — min() отдаёт исходные 308px, ни один
     пиксель не сдвинут (тот же замер, до/после совпадают побайтово).

     Корректирующая правка (ре-ревью волны): без пола calc() уходит в 0 и
     ОТРИЦАТЕЛЬНОЕ на любом --vvh ≤ 290 — max-height клампится в 0, список
     значений исчезает целиком, остаются только шапка, поле фильтра и футер.
     --vvh обновляется по resize, то есть ровно на открытие клавиатуры: на
     320/375 с поднятой клавиатурой --vvh ≈ 200–300, и человек фильтрует
     список, который в этот момент обнуляется. То же без клавиатуры на
     568×320 (iPhone SE в альбомной ориентации — ширина 568 ≤ 600, мобильный
     слой применяется, --vvh ≈ 230–260). max(140px, …) держит пол — 140px
     это около трёх строк пикера (замер Playwright: строка `.pick .pr`
     43.55px после Задачи 9, 140/43.55 ≈ 3,2), список никогда не
     схлопывается полностью — проверено принудительным --vvh вплоть до 50px.

     Размен пола честный, а не бесплатный: пикер с полом становится 268.09px
     (ph+pf+140+pfoot+рамки — фиксированная величина при ЛЮБОМ --vvh ≤ 290,
     список больше не сжимается), и на --vvh ниже ~430px (pick.top 160.09 +
     268.09) «Применить» снова уходит за нижнюю кромку — тот самый исходный
     дефект находки 2, только порог сместился с 290 на 430. Это неизбежно —
     иначе выбирать в списке было бы нечего — и не опасно: .pick абсолютный
     в потоке #results, страница скроллится, футер доскроллится тем же
     жестом, что и до всей этой волны правок.

     Вырожденная ветка (записано, не лечится здесь): в браузере, который
     понимает var()/calc()/min() внутри CSS, но НЕ понимает единицу dvh, а
     JS (viewport.js) ещё не выставил --vvh (первый рендер) — сработает
     фолбэк var(--vvh, 100dvh), но «100dvh» сам по себе invalid для такого
     движка. Это invalid at computed-value time: свойство целиком получает
     СВОЁ НАЧАЛЬНОЕ значение (max-height: none), а не откатывается к
     базовому десктопному правилу (214px) — список на миг становится во всю
     длину содержимого. Движков без dvh, но с остальным синтаксисом, на
     практике почти не осталось — оставлено как задокументированный, не
     как устранённый случай. */
  .pick .pl{max-height:min(308px, max(140px, calc(var(--vvh,100dvh) - 290px)))}
  /* «Применить» 24 → 38 — тот же рост, что у кнопок в карточке (↻ и «в заказ»):
     мобильные кнопки одного роста. «Сброс» в шапке пикера получает высоту
     паддингом, надпись остаётся на месте: 11/−11, а не 9/−9 — тот же пол 38 px,
     что у .pk-ap и .hdr .rate .live (находка «мелкая 2» финального ревью; было
     34 px, ниже заявленного в §9 пола, — ряд с .pk-ap внутри общего .pfoot
     этот запас уже даёт даром, margin-box .pk-rs остаётся тем же (0), что и с
     9/−9, только достаёт до пола). */
  .pick .pk-ap{height:38px;padding:0 14px;font-size:12.5px}
  .pick .pk-rs{padding:11px 0;margin:-11px 0}

  /* Строка истории 37 → 39, и только. Компенсировать нечем: оверлей ВСЕГДА живёт с
     поднятой клавиатурой (появляется по focus/click/input в поле поиска), до неё
     остаётся 5 px запаса, и растить его некуда. Размен строгий 1:1 — каждый
     лишний пиксель строки съедает видимый запрос, а промах стоит одного тапа.
     Высоту самого оверлея (.sh-list) не трогаем: шрифт покрупнее или клавиатура с
     автоподсказками загонят нижнюю строку под неё. Список прокручивается, поэтому
     ничего не ломается, но трогать это не подумав нельзя. */
  .searchhist .sh-row{padding:10px 13px}
  /* Крестик удаления 21 → 29, а НЕ до нормы — намеренно. Это единственное
     разрушающее действие в оверлее, и стоит оно вплотную к строке, тап по которой
     всего лишь подставляет запрос. Растить его до 44 значило бы повысить
     вероятность промаха именно в удаление. */
  .searchhist .sh-del{padding:8px;margin:-8px -6px -8px 0}

  /* ── Шапка (§6) ──
     Место сходится: бургер 38 + знак 23 + название ~82 + курс ~145 (с кнопкой 38 px)
     плюс зазоры ≈ 320 px из 382 доступных на 402 px. На 320 px под название остаётся
     меньше, и длинные («Пользователи») обрезаются многоточием — раздел при этом
     опознаётся, а точное имя видно в открытой панели. */
  .hdr{gap:9px;padding:0 10px}
  .hdr .logo{display:none}
  .hdr .mark{display:block;height:23px}
  .hdr .burger{display:inline-flex;align-items:center;justify-content:center;
    width:38px;height:38px;margin-left:-4px;padding:0;border:none;
    background:transparent;color:#fff;cursor:pointer}
  /* min-width:0 обязателен: без него флекс не даст элементу сжаться ниже ширины
     содержимого, и многоточие не сработает — вместо него полосу растянет вбок. */
  .hdr .hsect{display:block;font-size:14px;font-weight:600;color:#fff;min-width:0;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .hdr .nav,.hdr .user,.hdr .verbadge,.hdr form{display:none}
  .hdr .rate-slot{flex:none}
  .hdr .rate .val{font-size:12.5px}
  /* Кнопка обновления курса — та же .live, что в карточке; в полосе стоит
     изолированно, растить бесплатно. Приставка .hdr .rate обязательна: .live носят
     и строки заказа, которые вне объёма. */
  .hdr .rate .live{width:38px;height:38px}

  /* ── Выдвижная панель (§6.5) ──
     Ящик, а не вкладки и не нижняя панель. Решающий довод — рост числа разделов:
     «Клиенты» почти наверняка, «Каталог» возможно, то есть 5–6 вместо нынешних 3–4.
     Вкладки на пяти разделах становятся прокруткой, в которой раздел надо искать, а
     не видеть. У нижней панели жёсткий потолок 3–5 (Apple HIG, Material): на шести
     четыре видны, остальные уезжают в «Ещё» — та же двухтапность, что у ящика,
     только оплаченная 58 px внизу КАЖДОГО экрана. Ящику рост безразличен: шесть
     пунктов выглядят как три.

     Правила показа лежат ЗДЕСЬ, внутри медиазапроса, хотя бургер на десктопе и так
     скрыт: так «десктоп не меняется» верно механически, а не по рассуждению. */
  /* Находка «мелкая 7» финального ревью: body.menu-open{overflow:hidden} держит
     СТРАНИЦУ на месте, но не мешает протащить её фон пальцем НАСКВОЗЬ через
     затемнение — iOS Safari умеет скроллить body под fixed-элементом жестом,
     который начался на overlay. touch-action:none на затемнении убирает у него
     жестовую прокрутку вовсе; overscroll-behavior:contain стоит на .dnav —
     это он скроллер (overflow-y:auto, список разделов может быть выше
     панели), а не .navdrawer (computed overflow-y:visible — свойство на нём
     инертно, корректирующая правка ре-ревью). Не даёт скроллу списка
     разделов перетечь дальше, на страницу под панелью. */
  body.menu-open{overflow:hidden}
  .navscrim{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:49;
    touch-action:none}
  .navdrawer{position:fixed;top:0;bottom:0;left:0;width:268px;max-width:82%;
    background:var(--ink);z-index:50;flex-direction:column;
    padding:14px 0 calc(14px + env(safe-area-inset-bottom));
    box-shadow:8px 0 34px -10px rgba(0,0,0,.5)}
  body.menu-open .navscrim{display:block}
  body.menu-open .navdrawer{display:flex}

  .navdrawer .dhead{display:flex;align-items:center;justify-content:space-between;
    padding:0 16px 14px;border-bottom:1px solid rgba(255,255,255,.1);margin-bottom:6px}
  .navdrawer .dhead img{height:20px}
  .navdrawer .dclose{background:none;border:none;color:rgba(255,255,255,.55);
    cursor:pointer;padding:10px;margin:-10px}
  .navdrawer .dnav{display:flex;flex-direction:column;overflow-y:auto;
    overscroll-behavior:contain}
  .navdrawer .dnav a{display:flex;align-items:center;gap:12px;padding:13px 18px;
    color:rgba(255,255,255,.72);text-decoration:none;font-size:15px;font-weight:500}
  .navdrawer .dnav a.active{color:#fff;background:rgba(255,255,255,.07);
    box-shadow:inset 3px 0 0 var(--cyan)}
  .navdrawer .dfoot{margin-top:auto;border-top:1px solid rgba(255,255,255,.1);
    padding-top:8px}
  .navdrawer .drow{display:flex;align-items:center;justify-content:space-between;
    gap:10px;padding:11px 18px;color:rgba(255,255,255,.6);font-size:13.5px;
    text-decoration:none}
  .navdrawer .drow.acc{color:#fff;font-weight:500}
  .navdrawer button.drow{width:100%;background:none;border:none;font:inherit;
    cursor:pointer}
  /* Бейдж в панели — на тёмном фоне, как и всё здесь: он берёт цвет от .verbadge
     (rgba(255,255,255,.55)), и точка «что нового» на нём та же. */

  /* ── Модалки и вход (§8) ──
     Высота привязана к --vvh, которую ставит viewport.js по window.visualViewport.
     Базовые правила (86vh) НЕ переписываются: десктоп читает их, мобильный слой
     лишь перекрывает здесь. Фолбэк 100dvh работает, если API недоступен, — тогда
     лечится хотя бы адресная строка: на iOS vh привязан к БОЛЬШОМУ вьюпорту, тому,
     каким он станет со свёрнутой строкой, и пока она развёрнута, 86vh больше
     реально видимой высоты. Результат: модалка сжимается, .mbody{overflow:auto}
     (он там уже есть) начинает прокручивать список, содержимое остаётся доступным
     целиком. */
  .ao-modal{max-height:min(560px,calc(var(--vvh,100dvh) * .88))}
  .cl-modal{max-height:min(640px,calc(var(--vvh,100dvh) * .88))}

  /* .ao-backdrop центрирует модалку, а поиск по заказам вызывает клавиатуру сразу —
     центрированная модалка теряет снизу список, ради которого её открыли.
     Паддинг 24 → 10: 48 px из 402 — заметная потеря. Оба вида модалок живут в этом
     же оверлее (_changelog.html:4), так что правка накрывает и чейнджлог. */
  .ao-backdrop{align-items:flex-start;padding:10px}
  .ao-modal .mhead,.cl-modal .mhead{padding:12px 14px}
  .ao-modal .mbody{padding:11px 14px 14px}
  .cl-modal .mbody{padding:0 14px 14px}
  .ao-modal .x,.cl-modal .x{padding:10px;margin:-10px -8px -10px 0}

  /* Шапка товара — двухрядная сетка: бренд и PN сверху, поставщик и цена снизу.
     Правок разметки не требуется: автоматическая расстановка грида кладёт четыре
     значения в две строки сама, а разделители прячутся. Переносов внутри значений
     больше нет — проверено на «Ресурс-Медиа Дистрибьюция». */
  .ao-modal .prod{display:grid;grid-template-columns:auto auto;justify-content:start;
    column-gap:7px;row-gap:2px;font-size:12.5px}
  .ao-modal .prod .sep{display:none}

  /* «Новый заказ» уходит под поле во всю ширину: в строку с полем она занимает
     ~130 px и оставляет полю около 150 на 402 px. */
  .ao-modal .toprow{flex-wrap:wrap}
  .ao-modal .osearch{flex:1 1 100%}
  .ao-modal .newbtn{flex:1 1 100%;justify-content:center;height:40px}

  /* Подтверждение: кнопки столбиком, главная сверху. column-reverse, а не правка
     разметки — порядок в шаблоне остаётся десктопным. */
  .ao-modal .confirm{padding:22px 14px}
  .ao-modal .cbtns{flex-direction:column-reverse;gap:8px;width:100%}
  .ao-modal .btn2{width:100%;height:44px}

  /* Вход. Карточка центрируется внутри ВИДИМОЙ области: поле логина несёт autofocus,
     клавиатура поднимается сразу при открытии, и кнопка «Войти» оказывалась под ней.
     Механизм тот же, что у модалок, — отдельного лекарства не заводится. */
  .login-page{min-height:var(--vvh,100dvh);padding:16px}
  .login .brand{margin-bottom:18px}
  .login .brand img{height:26px}
  .login .card{padding:20px 18px 22px}

  /* ═══════════════════════════════════════════════════════════════════════════
     РАЗДЕЛ «ПОЛЬЗОВАТЕЛИ» (спека 2026-08-31-web-mobile-users-design.md)

     ПРАВИЛО ГРАНИЦЫ (§2). Раздел почти целиком собран из классов, которые носят
     и чужие страницы: .ohead/.frow/.flbl — страница поставщика и шапка заказа,
     .fval/.finput — поля заказа, .chips — поставщик И ПОИСК, .ost — статус
     заказа, .smenu — меню статуса, .switch — четыре страницы поставщиков,
     .back — страница поставщика. Поэтому у каждого правила есть два способа
     записи, и выбор между ними не вкусовой:

       правка ЧИНИТ поломку или растит НЕВИДИМУЮ зону нажатия
         → пишется коротко и достаётся всем;
       правка МЕНЯЕТ раскладку
         → пишется под меткой раздела (.ucards / .uhead / .cards).

     Безприставочных селекторов ровно пять, и список закрыт — стережёт
     tests/web/test_mobile_users.py::test_the_unprefixed_list_is_closed.
     ═══════════════════════════════════════════════════════════════════════════ */

  /* — общее 1/4 — Перенос чипов.
     Первая из ДВУХ независимых причин бокового хода страницы пользователя
     (вторая — тумблер в .hrow1, см. секцию «страница пользователя» ниже;
     починка одной дала бы «стало лучше, но всё равно едет»). Два чипа — 283,9 px
     в колонке 210 px: ряд начинается на x=147 и кончается на 430,9, то есть на
     28,9 px правее экрана 402 px. Страница поставщика ломалась так же и чинится
     заодно. Поиск носит тот же класс — там оба чипа и так помещаются, перенос не
     срабатывает: геометрия чипов, строки поиска и кнопки «Найти» проверена
     побайтово до/после на 320/360/402/430 (спека §10.1). */
  .chips{flex-wrap:wrap}

  /* — общее 2/4 — Строка меню выбора роли 29,5 → 44,9 px.
     Меню плавающее: оно ничего под собой не двигает, поэтому рост бесплатен.
     Тот же компонент — меню смены статуса заказа (_role_menu.html дословно
     повторяет _status_menu.html), и странно, если один и тот же список на
     телефоне ведёт себя по-разному в зависимости от того, откуда открыт.
     Оверлей 158×100,5 → 184×148,7; правый край на 211 px, то есть помещается
     даже на 320 с запасом 109 px. */
  .smenu{padding:6px;min-width:184px}
  .smenu .si{padding:13px 11px;font-size:13.5px}

  /* — общее 3/4 — Тач-цель тумблера 22 → 40 px, невидимо.
     Нажимается ВЕСЬ <label> (подпись + дорожка), а он прозрачный — значит
     работает приём прошлой ветки: padding плюс компенсирующий отрицательный
     margin. Дорожка остаётся 38×22 и стоит на прежнем месте (замер: label
     98,9×40, дорожка на 9 px от верха), высота карточки не меняется.

     Вертикальные 9 px — НЕ круглое число «на глаз»: они в точности равны
     вертикальному паддингу ячейки таблицы (.tbl tbody td{padding:9px 12px}),
     поэтому зона нажатия дорастает ровно до границы своей строки и не
     перехлёстывает соседнюю. Менять это число можно только вместе с паддингом
     ячейки — страж это стережёт. Достаётся четырём страницам поставщиков.

     В списке пользователей опора ДРУГАЯ, хотя число то же: там карточка гасит
     паддинг ячейки (.tbl.mcards tbody td{padding:0}), и зона нажатия достаёт
     ровно до соседней ячейки потому, что row-gap карточки — тоже 9 px
     (.mcards tbody tr). То есть 9 связаны с ДВУМЯ числами сразу: сменит кто-то
     зазор карточки — на телефоне тумблер начнёт перехлёстывать соседнюю строку
     карточки, и страж паддинга ячейки об этом промолчит. */
  .switch{padding:9px 0;margin:-9px 0}

  /* — общее 4/4 — Тач-цель ссылки возврата 19 → 42,2 px, тем же приёмом.
     Достаётся странице поставщика: ссылка возврата там такая же.

     Нижнее значение 2px, а НЕ -12px, и это не украшение. У .back снаружи
     медиазапроса стоит margin-bottom:14px (app.css:398), и краткая запись
     `margin:-12px -8px` переписывала бы её на -12px: приём переставал быть
     невидимым и утаскивал всё, что ниже ссылки, на 11 px вверх. Замер Задачи 5
     на странице поставщика (вне объёма ветки, то есть прямое нарушение правила
     границы §2): блок .ohead стоял на y = 100,19 без ветки и на 89,19 с ней,
     страница становилась на 27 px короче. 14 − 12 = 2 возвращает прежний
     margin-box.

     Почему 11, а не 14, которые обещает одна арифметика полей: .back —
     атомарный строчный бокс (display:inline-flex), единственный в анонимной
     строке внутри .wrap, и высоту этой строки задаёт выравнивание по базовой
     линии против страта родителя (line-height 19,6 при шрифте 14), а не один
     только margin-box. Замер: margin-box 18,19 давал строку 21,19 (три лишних
     пикселя — то, что страт держит ниже базовой линии и чего сжавшийся
     margin-box не занимает), margin-box 32,19 даёт строку ровно 32,19. Отсюда
     100,19 − 89,19 = 11. Вывод от этого не меняется, а усиливается: одинаковый
     margin-box даёт одинаковую строку —
     после правки первый расходящийся пиксель страницы поставщика приходится на
     второй чип направления, то есть на единственную разрешённую разницу. */
  .back{padding:12px 8px;margin:-12px -8px 2px}

  /* ══ СПИСОК: таблица → стопка карточек (§4) ══

     Общий слой примитива (.mcards, прошлая ветка) НЕ ТРОГАЕТСЯ — он уже умеет
     всё нужное: строка становится гридом, ячейки теряют рамки и паддинги,
     таблица теряет min-width:720px, colgroup гасится, а шапка без .thf-фильтров
     скрывается целиком. Здесь только частный слой.

     Приставка — КЛАСС, а не id, в отличие от #results у поиска: там id уже
     существовал как цель htmx, здесь заводить его было бы не для чего.
     Арифметика: .mcards.ucards tbody td.<класс> весит (0,3,2) и бьёт общее
     .tbl.mcards tbody td (0,2,2), а заодно .tbl tbody td.c-mid (0,2,2), которое
     иначе центрировало бы ячейку тумблера. Приставка-класс лучше приставки-id
     ещё и тем, что не несёт (1,0,0) и не перебивает собой всё подряд.

     Отступ перед первой карточкой даёт .pagehead своим margin-bottom:16px —
     шапка таблицы здесь скрыта и его не даёт (это вторая половина ограничения
     §13 прошлой спеки, общего решения ей эта ветка не даёт).

      ┌────────────────────────────────────────┐
      │ Екатерина Константинопольская-         │
      │ Задунайская                            │
      │ e.konstantinopolskaya    [● Менеджер]  │
      │ ⟨Компьютерное⟩ ⟨Инструментальное⟩      │
      │ Активен ( ● )                    [🗑]  │
      └────────────────────────────────────────┘

     Три решения несущие:
     1. Имя занимает ОБЕ колонки. Внутренняя ширина карточки на 402 px — 337 px;
        в ряд с самой широкой пилюлей («Администратор», 116,7 px) имени осталось
        бы 211,3 px, и длинное имя легло бы в три строки вместо двух.
     2. Вторая колонка auto, а не 1fr: её содержимое (пилюля сверху, кнопки
        снизу) имеет естественную ширину, растяжка лишь отодвинула бы логин.
     3. Логин обрезается многоточием, имя — НЕТ. Логин служебный идентификатор,
        ровная высота карточек делает список сканируемым; имя — то, ради чего на
        карточку смотрят. */
  .mcards.ucards tbody tr{
    grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:
      "name  name"
      "login role"
      "dirs  dirs"
      "act   del";}
  /* overflow-wrap:anywhere — вырожденный случай: фамилия ОДНИМ словом на сорок
     знаков. Без него имя (356,3 px в карточке шириной 281) не переносится, его
     обрезает скроллер .tbl-scroll, и карточка ездит вбок под пальцем — замер
     Задачи 5 на 320/360/402. Ровно тот же случай на странице пользователя ловит
     .uhead .fedit.otitle{overflow-wrap:anywhere}: два правила обязаны оставаться
     СИММЕТРИЧНЫМИ — один раздел не может на одинаковых данных вести себя
     по-разному на списке и на странице. Оба правила разом стережёт
     test_long_unbreakable_name_wraps_in_the_card. */
  .mcards.ucards tbody td.u-name{grid-area:name;font-size:15px;line-height:1.3;overflow-wrap:anywhere}
  .mcards.ucards tbody td.ulogin{grid-area:login;align-self:center;min-width:0;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .mcards.ucards tbody td.u-role{grid-area:role;justify-self:end;align-self:center}
  .mcards.ucards tbody td.u-dirs{grid-area:dirs}
  .mcards.ucards tbody td.u-act{grid-area:act;align-self:center;text-align:left}
  .mcards.ucards tbody td.u-del{grid-area:del;justify-self:end;align-self:center}
  /* Кнопки карточки 30 → 38 px — как кнопки карточки поиска: мобильные кнопки
     одного роста. Высота карточки при развороте подтверждения НЕ меняется
     (замер: 154,3 и 154,3), то есть список не дёргается под пальцем. */
  .ucards .act .ib{width:38px;height:38px}
  .ucards .act,.ucards .act .icons{gap:8px}

  /* ══ СТРАНИЦА ПОЛЬЗОВАТЕЛЯ (§5) ══
     Метка .uhead обязательна: .ohead/.frow/.flbl/.fval/.finput/.ost носят также
     страница поставщика, расписание поставщика и шапка заказа — все вне объёма. */

  /* ══ ОБЩИЙ СЛОЙ ШАПКИ СУЩНОСТИ (спека 2026-09-01 §3) ══
     Десять правил ниже описывают не раздел, а компонент — блок .ohead на узком
     экране, — и их носят ДВЕ страницы: пользователя и поставщика. Метку каждая
     надевает осознанно; шапка заказа (orders/_header.html) её не носит и правил
     не получает.
     Прошлая ветка написала эти правила под .uhead и предсказала (её §11), что
     поставщик повторит двенадцать из них дословно. Общими стали десять: .ost и
     .chips .chip остались у пользователя — там это кнопка смены роли и всегда
     правимые чипы, а у поставщика read-only бейдж и чипы, правимые только у
     admin.
     Переезд невидим: текст правил, специфичность (0,2,0) и порядок в файле те
     же. Проверено стендом на 1400/601/402/360/320 — расхождений нет.
     Имя .mhead занято раньше этой ветки: `_changelog.html` и
     `orders/_add_modal.html` называют так шапку МОДАЛКИ — компонент, никак не
     связанный с этим слоем, с компаунд-селекторами `.ao-modal .mhead`/
     `.cl-modal .mhead` специфичностью (0,2,0), которая перебивает голое .mhead
     (0,1,0) ниже. Совпадение неопасно: ни один из потомков десяти правил
     (.hrow1/.titlewrap/.fedit.otitle/.titleinput/.tcluster/.frow/.flbl/
     .valtext/.chips) не встречается ни в одном из двух шаблонов модалки —
     проверено обходом обоих файлов, не только рассуждением о специфичности. */
  .mhead{padding:14px}

  /* — шапка блока: тумблер уходит на свою строку —
     ВТОРАЯ из двух независимых причин бокового хода (первая — чипы, секция
     «общее 1/4»). Имя — кнопка 20 px полужирным, её min-content
     («Константинопольская-») шире, чем остаётся после тумблера, а флекс не
     сжимает элемент ниже min-content: тумблер выезжал за экран, замер 374 px
     при окне 320. Три правила, и ни одно не лишнее: перенос отпускает тумблер
     на свою строку; min-width:0 разрешает имени сжиматься; overflow-wrap ловит
     вырожденный случай — фамилию одним словом на сорок знаков. */
  .mhead .hrow1{flex-wrap:wrap;gap:10px 14px}
  /* flex-basis:100% — тумблер уходит под роль ВСЕГДА, а не только когда имя не
     влезло. Перенос по нехватке места делал раскладку зависимой от длины имени:
     у короткого тумблер вставал справа от имени, у длинного — под ролью, а в
     карточке списка он всегда внизу слева. Один переключатель в двух местах —
     плохо для управления доступом; 30 px высоты дешевле непредсказуемости.
     Растянута КОРОБКА ИМЕНИ, а не сам тумблер: растяни тумблер — нажималась бы
     вся строка вместе с пустотой справа, и случайный тап отключал бы человеку
     доступ. Побочно у коробки появляется определённая ширина, что структурно
     закрывает класс дефекта из Задачи 5 (процент внутри shrink-to-fit
     флекс-элемента разрешался циклически). */
  .mhead .titlewrap{min-width:0;flex-basis:100%}
  .mhead .fedit.otitle{overflow-wrap:anywhere}
  /* Поле правки имени: жёсткие 230 px → вся ширина блока, высота 31 → 40 px.
     Шрифт у него 20 px, то есть автозума iOS здесь не было и не будет. */
  .mhead .titleinput{width:100%;height:40px}
  /* Одного width:100% для «всей ширины блока» НЕ ХВАТАЕТ: процент считается от
     .tcluster — обёртки поля, а она флекс-ЭЛЕМЕНТ .titlewrap и потому ужимается
     по содержимому. Процент разрешался циклически, и поле вставало в 252 px
     независимо от того, 333 в блоке или 361 (замер Задачи 5; без ветки было
     230, то есть правило давало +22 px вместо обещанной ширины блока).
     flex-basis:100% даёт обёртке определённую ширину — проценту внутри наконец
     есть от чего считаться: 251/291/333/361 на 320/360/402/430.
     :has() отделяет правку от просмотра: в просмотре та же .tcluster держит имя
     кнопкой, и безусловная растяжка увела бы пилюлю роли на свою строку даже
     там, где имя короткое (замерено: с :has() просмотр не сдвигается ни на
     пиксель). На браузерах без :has() (Safari < 15.4, Chrome < 105) правило
     отбрасывается целиком, и поле остаётся 252 px — деградация мягкая. */
  .mhead .titlewrap .tcluster:has(.titleinput){flex-basis:100%}

  /* — пилюля роли и чипы направлений —
     Ветка 2026-08-31 растила .ost здесь же, отдельно от чипа статуса заказа
     (23,4 → 35,5 px), и платой было расхождение размера с той же пилюлей в
     списке пользователей. Ветка 2026-09-02 (раздел «Заказы», Задача 2) это
     решение пересмотрела: заказчик потребовал одного размера у одного
     компонента, и рост .ost переехал в групповую запись
     `.uhead .ost,.ohd .ost,.ocards .ost` (конец блока) — 30,1 px чипа сразу в
     трёх местах: пользователь, шапка заказа, список заказов. Здесь остаётся
     то, что в группу не вошло: стрелка ▾ кнопки смены роли (нужна только там,
     где .ost кликабелен) и видимый рост чипов направлений — фон и рамка
     .chip делают приём с компенсирующим отрицательным margin неприменимым и
     здесь, поэтому паддинг растёт как есть, без компенсации. */
  .uhead .ost .car{font-size:9px}
  .uhead .chips .chip{padding:9px 15px}

  /* — подпись НАД значением —
     Довод не в красоте, а в независимости от данных. Двухколоночный вариант
     тоже работает и вдвое ниже; он был построен, показан на макетах и отклонён:
     на 320 px под значение остаётся 146 px, «e.konstantinopolskaya» упирается в
     край впритык, и следующий логин длиннее сломал бы строку. В стопке ширина
     значения не зависит от его длины вовсе. Второй довод: форма смены пароля
     обязана занимать всю ширину в любом случае, то есть в двухколоночном
     варианте одна строка из пяти всё равно жила бы по правилам стопки. */
  .mhead .frow{display:block;margin-top:14px}
  .mhead .frow .flbl{width:auto;display:block;line-height:1.5;margin-bottom:2px}
  .mhead .frow > .valtext{line-height:1.5}
  .mhead .frow > .chips{min-height:0}
  .uhead .fval{height:auto;min-height:30px}

  /* — форма смены пароля —
     Два поля по 158 px в ряд не помещаются ни на одной ширине объёма: уходят во
     всю ширину столбиком, кнопки под ними. Текст ошибки получает свою строку —
     «Пароли не совпадают» в остаток строки не влезает.
     16 px — тот же капкан iOS, что и в формах: Safari принудительно зумит
     страницу при фокусе в поле мельче, и отключить это нельзя. Приставка
     обязательна: .finput носят поля заказа.
     Перенос строки повторять здесь нечего: flex-wrap:wrap стоит у базового
     .pwform и действует и на телефоне. */
  .uhead .finput{font-size:16px}
  .uhead .pwform .finput{width:100%;height:38px}
  .uhead .pwform .ib{width:38px;height:38px}
  .uhead .pwerr{flex-basis:100%;white-space:normal}

  /* ══ ФОРМЫ: создание пользователя и аккаунт (§6) ══
     Приставка .cards обязательна: .field носят ещё вход, строка поиска и тулбар
     списка заказов. Сам класс .cards уже стоял в разметке обеих страниц и
     БОЛЬШЕ НИГДЕ (проверено обходом шаблонов) — заводить новую метку не
     потребовалось.
     Раскладка в колонку тут уже есть от точки 760 px, чинить её не нужно. */
  .cards{gap:14px}
  .cards .card{padding:18px 16px}
  .cards .card h2{font-size:17px}
  .cards .csub{margin-bottom:16px}
  .cards .field{margin-bottom:14px}
  /* 16 px — капкан iOS (см. секцию «16 px во ВСЕХ полях объёма» выше). Этими
     полями закрыты последние места приложения, где Safari зумил страницу. */
  .cards .field input{font-size:16px;height:44px}
  /* Главная кнопка во всю ширину, «Отмена» под ней по центру — тот же приём,
     которым прошлая ветка разложила кнопки подтверждения в модалке «Добавить в
     заказ», то есть на телефоне вид уже знакомый. column, а не column-reverse:
     в разметке главная кнопка и так стоит первой. .actions носят только эти две
     страницы, приставка не нужна. */
  .actions{flex-direction:column;align-items:stretch;gap:6px;margin-top:20px}
  .actions .btn{width:100%;height:46px}
  .actions .btn-link{text-align:center;padding:13px 0;margin:0}

  /* ═══ ДОПОЛНЕНИЯ ОБЩЕГО СЛОЯ ПРИМИТИВА (спека 2026-09-01 §4.6, §8) ═══ */

  /* Механизм подписей. У него две половины: гашение снаружи медиазапроса
     (`.mlbl` — один из перечня в единственной строке .hdr .burger,…{display:
     none} выше блока, заведена веткой 2026-08-31) и показ здесь.
     Половины обязаны иметь ОДИН охват, и это не эстетика: первая редакция спеки
     написала показ как `.mcards .mlbl`, то есть заперла его в таблицах, — и
     подпись «Авто» у тумблера расписания, который живёт в шапке блока, а не в
     таблице, осталась невидимой, хотя в разметке была. Класс придуман ровно ради
     одного — слово, видимое только на телефоне; охват ему ограничивает страж по
     шаблонам (test_mobile_suppliers.py::test_label_stands_only_where_intended),
     а не приставка в селекторе. Тот же приём, что у .wrap и .actions.
     Копия .ucards .mlbl удалена: она была первой половиной этой же ошибки. */
  .mlbl{display:inline;font-size:13px;color:var(--muted)}

  /* Подвал таблицы. Примитив его не знал — у выдачи поиска и у списка
     пользователей <tfoot> нет. Здесь он есть у двух таблиц из трёх (список
     поставщиков и категории s4b), и обе строят его одинаково: слева итог
     словами, справа число.
     td:empty несущее, а не подстраховка: в подвале списка третья ячейка —
     <td colspan="5"></td>, пустая распорка под таблицу, и блоком она дала бы
     пустую строку. */
  .tbl.mcards tfoot{display:block;background:transparent;border:none}
  .tbl.mcards tfoot tr{display:flex;justify-content:space-between;gap:12px;
    padding:2px 2px 0}
  .tbl.mcards tfoot td{display:block;padding:0;border:none;background:transparent}
  .tbl.mcards tfoot td:empty{display:none}

  /* ═══ ЧАСТНЫЙ СЛОЙ: карточка поставщика (спека 2026-09-01 §4.3) ═══
     Слева то, что читают; справа то, что нажимают. Правая колонка читается
     сверху вниз одной мыслью: сколько товаров и когда они обновлялись.
     Ячейка .sup неделима — имя, кнопка «N продавцов» и чипы направлений лежат в
     одном <td>, и разнести их по строкам сетки нельзя, не заводя восьмую колонку
     на десктопе, — поэтому она занимает две верхние строки левой колонки.
     Правая колонка auto, а не 1fr: её содержимое имеет естественную ширину, и
     растяжка отобрала бы место у имени. */
  .mcards.pcards tbody tr{
    grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:
      "sup   count"
      "sup   last"
      "on    auto"
      "file  rfr";
  }
  .mcards.pcards tbody td.sup{grid-area:sup;min-width:0;align-self:start}
  /* Число крупным, подпись под ним: тот же ритм пары, что у цены и даты в
     карточке поиска (спека 2026-08-30 §4.4) — верхняя строка 15, зазор 2,
     нижняя 17. */
  .mcards.pcards tbody td.p-count{grid-area:count;justify-self:end;text-align:right;
    align-self:start;white-space:nowrap;font-size:17px;line-height:15px}
  .pcards td.p-count .mlbl{display:block;font-size:10.5px;line-height:17px;margin-top:2px}
  /* Статус поджат к подписи «товаров»: строка сетки отдаёт 9 px зазора, а пара
     «число + подпись» живёт на зазорах в 2 px — отрицательный отступ возвращает
     статус в тот же ритм (9 − 5 = 4 px), не трогая высоту строки. Число связано
     с row-gap карточки: сменят его — пересчитать. */
  .mcards.pcards tbody td.p-last{grid-area:last;justify-self:end;align-self:start;
    margin-top:-5px}
  .pcards td.p-last .ll{justify-content:flex-end;gap:7px}
  .pcards td.p-last .st{width:15px;height:15px}
  .pcards td.p-last .st svg{width:10px;height:10px}
  .pcards td.p-last .when{font-size:12px}
  /* Тумблеры разведены по разным краям карточки — это не вкус, а требование:
     в одной колонке их зоны нажатия (по 40 px) СОПРИКАСАЮТСЯ, просвет 0,0 px, и
     промах ниже «Активен» попадает в «Авто». Цена промаха несимметрична:
     auto_update — мягкий флаг, enabled УДАЛЯЕТ товары поставщика из БД. С
     разведением просвет 160,7 px.
     align-self:start у обоих обязателен: иначе «Активен» центрируется в строке,
     которую растягивает подпись расписания под «Авто», и опускается на 18 px. */
  .mcards.pcards tbody td.p-on{grid-area:on;justify-self:start;align-self:start}
  .mcards.pcards tbody td.p-auto{grid-area:auto;justify-self:end;text-align:right;
    align-self:start}
  .mcards.pcards tbody td.p-file{grid-area:file;justify-self:start;align-self:center}
  .mcards.pcards tbody td.p-rfr{grid-area:rfr;justify-self:end;text-align:right;
    align-self:center}
  /* Кнопки 34 → 38 px, как кнопки карточки поиска. min-width:150px снят: он
     держал подпись рядом с иконкой в узкой колонке таблицы, а в карточке ширину
     даёт сетка. */
  .pcards .rfr{height:38px;min-width:0;padding:0 14px}
  .pcards .filebtn{height:38px}
  .pcards .sched-note{margin-top:3px}
  .pcards td.sup .nm{font-size:15px}
  /* Продавцы агрегатора: та же карточка, но подчинённая — сдвинута вправо, на
     подложке и без собственных действий (их нет и в таблице). */
  .mcards.pcards tbody tr.vendor-row{
    margin-left:18px;background:var(--surface);
    grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:
      "vname vcount"
      "vnote vnote";
  }
  .mcards.pcards tbody td.vendor-name{grid-area:vname;padding-left:0;font-weight:600}
  .mcards.pcards tbody tr.vendor-row td.p-count{grid-area:vcount;text-align:right;
    font-size:14px;line-height:1.4}
  .mcards.pcards tbody td.vendor-note{grid-area:vnote}

  /* ═══ СТРАНИЦА ПОСТАВЩИКА: своё (спека 2026-09-01 §5) ═══
     Шапку блока целиком даёт общий слой .mhead — своего в ней нет ни одного
     правила, и это проверка того, что общий слой заведён правильно. */

  /* Расписание. Семь чипов дней в строку не помещаются: 7×34 + 6×6 = 274 px при
     268 доступных внутри блока на экране 320 — «Сб» и «Вс» уезжали за экран.
     Гибкая строка их «спасает» переносом двух последних на вторую строку, и
     неделя перестаёт читаться неделей. Сетка из семи равных колонок делит ширину
     поровну: 43,3 px на 402 и 31,6 на 320 — все семь на своих местах.
     31,6 ниже тач-нормы, и это осознанный размен: семь целей в ряд шире сделать
     не из чего, а цена промаха минимальна — чип подсвечивается сразу, правка
     стоит одного тапа, и до «Сохранить» ничего не происходит. Тот же довод, что
     у строки истории поиска в 39 px. Высота 30 → 40 px: рост бесплатный. */
  .shead .sched{gap:10px}
  .shead .sched .days{flex-basis:100%;display:grid;gap:5px;
    grid-template-columns:repeat(7,minmax(0,1fr))}
  .shead .sched .day{min-width:0;height:40px;padding:0}
  /* Поле времени — ДЕВЯТОЕ поле приложения, где iOS зумит страницу при фокусе: у
     него font:inherit, то есть 14 px. Прошлая ветка закрыла восемь и назвала
     список закрытым — он был закрыт по её объёму, а не по приложению. */
  .shead .sched .timein{font-size:16px;height:44px}
  .shead .sched .rfr{height:38px}
  .shead .sched .switch{gap:8px}
  /* Чипы направлений растут ТОЛЬКО там, где они переключатели: у admin это
     чекбоксы внутри <form class="chips">, у head — те же слова в <div> и только
     для чтения. Разметка различает эти случаи сама. Рост видимый, а не паддингом
     с компенсирующим margin: у чипа есть фон и рамка, его паддинг красится. */
  .shead form.chips .chip{padding:9px 15px}

  /* ═══ ЧАСТНЫЙ СЛОЙ: карточка журнала выгрузок (спека 2026-09-01 §6) ═══
     Метки хватило, чтобы страница перестала ехать вбок: min-width:720px снимает
     уже существующее правило примитива .tbl.mcards{min-width:0}. */
  .mcards.hcards tbody tr{
    grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:
      "when  status"
      "count count"
      "cmt   cmt";
  }
  .mcards.hcards tbody td.when{grid-area:when;font-size:13.5px;font-weight:600;
    align-self:center}
  .mcards.hcards tbody td.h-st{grid-area:status;justify-self:end;align-self:center}
  .mcards.hcards tbody td.h-count{grid-area:count;text-align:left;margin-top:-4px}
  /* Перенос комментария общий с десктопом (см. .cmt.full снаружи блока). Здесь
     остаётся только место в сетке и межстрочный интервал. */
  .mcards.hcards tbody td.cmt{grid-area:cmt;line-height:1.45}
  /* Успешный прогон комментария не имеет, и «—» отдельной строкой — шум.
     Метка своя, а не общий `empty`: у того есть десктопное правило, и его
     text-align:center доставал ячейку — прочерк стоял по центру колонки
     вместо левого края. */
  .mcards.hcards tbody td.cmt.h-empty{display:none}

  /* ═══ ЧАСТНЫЙ СЛОЙ: карточка категории s4b (спека 2026-09-01 §7) ═══ */
  .mcards.ccards tbody tr{
    /* Правая колонка не уже подтверждения, которое в ней появляется. Без пола
       она сжимается до ширины корзины у категории, которую ещё ни разу не
       выгружали (число строк — прочерк, подписи нет), и разворот подтверждения
       отбирает 44 px у колонки имени: длинное имя переносится на лишнюю строку,
       карточка растёт ПОД ПАЛЬЦЕМ. Пол делает ширину колонки одинаковой в обоих
       состояниях, и высота перестаёт зависеть от состояния вовсе.
       84 px — ширина иконочной пары 38 + 8 + 38. */
    grid-template-columns:minmax(0,1fr) minmax(84px,auto);
    grid-template-areas:
      "name  id"
      "when  rows"
      "en    del";
  }
  .mcards.ccards tbody td.c-name{grid-area:name;font-size:14px;font-weight:600;
    line-height:1.3;overflow-wrap:anywhere}
  .mcards.ccards tbody td.c-id{grid-area:id;justify-self:end;align-self:start;
    color:var(--muted-2)}
  .mcards.ccards tbody td.c-when{grid-area:when;align-self:center}
  .mcards.ccards tbody td.c-rows{grid-area:rows;justify-self:end;align-self:center;
    text-align:right}
  .mcards.ccards tbody td.c-en{grid-area:en;align-self:center;text-align:left}
  .mcards.ccards tbody td.c-del{grid-area:del;justify-self:end;align-self:center}
  .ccards .act .ib{width:38px;height:38px}
  /* Подтверждение удаления — ИКОНОЧНОЕ, как в списке пользователей и в списке
     заказов, и по той же причине: текстовая пара шире корзины на 82 px и на
     320 px отбирает ширину у имени категории. Слово остаётся десктопу. */
  .ccards .act .c-yes{width:38px;height:38px;padding:0}
  .ccards .act .c-yes .mico{display:inline-flex}
  .ccards .act .c-yes .dlbl{display:none}

  /* Форма добавления категорий: поле во всю ширину, кнопка под ним.
     16 px — ДЕСЯТОЕ и последнее поле приложения с автозумом iOS; после этой
     ветки список закрыт по приложению, а не по объёму ветки. */
  .catadd{flex-direction:column;align-items:stretch}
  .catadd textarea{font-size:16px}
  .catadd .btn{width:100%;height:46px}

  /* ═══ Список заказов: строка → карточка ═══
     Приставка ocards — от ключа раздела, которым его зовёт _nav.html. */
  .mcards.ocards > tbody > tr{
    grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:
      "nm    sum"
      "nm    st"
      "cmt   cmt"
      "mgr   act";}
  /* Название занимает ДВЕ строки сетки: пока высоту задаёт правая стопка
     (сумма 21 + зазор 9 + чип 30 = 60 px), а левый блок короче (38 px), статус
     стоит под суммой на постоянном зазоре и от длины имени клиента не зависит.
     С одной строкой зазор «сумма → статус» гулял от 27,4 до 44,2 px. */
  .ocards td.o-nm{grid-area:nm;min-width:0}
  /* Тяжёлая форма — ту же коллизию уже разбирала ветка 2026-08-31 (app.css,
     комментарий у .mcards.ucards): ячейка несёт class="c-mid o-st", и
     десктопное `.tbl tbody td.c-mid{text-align:center}` весит (0,2,2) против
     (0,2,1) у голого `.ocards td.o-st` — центрирование по умолчанию побеждало
     бы, и правой не было видно только потому, что justify-self:end сжимает
     элемент сетки по содержимому. `.mcards.ocards tbody td.o-st` весит
     (0,3,2) и перебивает c-mid без опоры на порядок в файле. */
  .mcards.ocards tbody td.o-st{grid-area:st;justify-self:end;text-align:right;align-self:start}
  .ocards td.o-cmt{grid-area:cmt;min-width:0}
  .ocards td.o-sum{grid-area:sum}
  .ocards td.o-mgr{grid-area:mgr;min-width:0;align-self:center;font-size:13px}
  .ocards td.o-act{grid-area:act;align-self:center}
  /* Гашение исходной ячейки даты весит (0,3,3): правило примитива
     `.tbl.mcards > tbody > tr > td{display:block}` весит (0,2,3), и
     двухклассовая запись ему проигрывала — дата рисовалась дважды. */
  .mcards.ocards > tbody > tr > td.o-date{display:none}
  .ocards .mdate{display:block;font-size:12px;color:var(--muted);margin-top:4px}
  /* Пустой комментарий не занимает строку. В ячейке лежит прочерк, а не
     пустота, поэтому метка из разметки, а не :empty. Метка СВОЯ: у общего
     класса .empty есть десктопное правило, и его text-align:center дотянулся
     бы до ячейки — `.tbl tbody td` выравнивания не задаёт. */
  .ocards td.o-cmt.o-empty{display:none}
  .ocards .oname{font-size:15px;line-height:18px}
  .ocards .oclient{font-size:12px;white-space:normal;margin-top:3px}
  .ocards .cmt{white-space:normal;overflow:visible;text-overflow:clip;
    font-size:12.5px;line-height:1.4}
  .ocards .act .ib{width:38px;height:38px}
  .ocards td.o-sum .price{font-size:15px}
  /* Подпись менеджера — двухстрочная, начертанием как подписи полей в шапках
     «Пользователей» и «Поставщиков» (.flbl): это не новый вид. */
  .ocards td.o-mgr .mlbl{display:block;font-size:10.5px;letter-spacing:.05em;
    text-transform:uppercase;color:var(--muted-2);font-weight:600;
    line-height:1.5;margin:0}
  /* Меню статуса открывается от ЛЕВОГО края чипа, а чип прижат к ПРАВОМУ: на
     320 px меню уезжало за экран на 37,6 px и возвращало странице боковой ход
     ровно в момент нажатия на статус. Метка обязательна — на странице заказа
     чип стоит слева, и там left:0 безобиден. */
  .ocards .smenu{left:auto;right:0}
  /* Один компонент — один размер: чип статуса 30,1 px в списке заказов, в
     шапке заказа и на странице пользователя. Группа из приставок, а не общее
     правило: .ost носят ещё бейдж «live-API» на странице поставщика (она тоже
     под .mhead) и пилюля роли в СПИСКЕ пользователей.
     Ветка 2026-09-09 дописала четвёртую приставку — бейдж своей привязки на
     подстранице категорий поставщика: он тот же .ost и он ЕДИНСТВЕННЫЙ вход в
     смену категории (кнопка рядом только снимает привязку), то есть кнопка.
     Растёт именно button.cb-chip: приглушённый бейдж НАСЛЕДОВАНИЯ — <span> с
     cursor:default, подпись, а не кнопка, и остаётся мелким по тому же доводу,
     что бейдж «live-API». Охват — по .cb-cell, а не по метке таблицы: такая же
     ячейка стоит вне таблицы, в блоке «категория на весь каталог». */
  .uhead .ost,.ohd .ost,.ocards .ost,.cb-cell button.cb-chip{font-size:11.5px;padding:6px 11px;gap:6px}
  .uhead .ost .sd,.ohd .ost .sd,.ocards .ost .sd{width:7px;height:7px}
  /* «Ничего не найдено» — строка ВНУТРИ таблицы (в выдаче поиска и в панелях
     пустое состояние живёт абзацем снаружи, поэтому механизма до сих пор не
     было). Её единственная ячейка ни одной области не называет. */
  .mcards.ocards > tbody > tr:has(> td.empty){display:block;background:transparent;
    border:none;border-radius:0;margin:0;padding:0}
  /* Тот же дефект, что чинила Задача 5 у #items-empty-row: `.empty{padding:
     48px 4px}` (0,1,0) перебивается примитивом `.tbl.mcards > tbody > tr >
     td{padding:0}` (0,2,3, app.css:1482) независимо от padding:0 у tr выше —
     без своего правила «Ничего не найдено» вставало впритык под тулбаром
     фильтров. `.mcards.ocards > tbody > tr > td.empty` весит (0,3,3) —
     трёхклассовым перебивает примитив без опоры на порядок в файле. */
  .mcards.ocards > tbody > tr > td.empty{display:block;padding:48px 4px}

  /* ── Тулбар списка заказов ──
     Три фильтра сегодня стоят ЗА КРАЕМ экрана: x = 344 / 520 / 696 при
     экране 320. Приставка .otb обязательна: класс .toolbar носит ещё
     страница поиска (обёртка чипов направлений). */
  .phead{gap:10px}
  .phead .btn{min-width:0;padding:0 15px;flex:none}
  .otb{flex-wrap:wrap;gap:8px}
  /* Базис 0, а не auto: флекс ломает строку по ГИПОТЕТИЧЕСКОМУ размеру, то
     есть по flex-basis, ещё до сжатия. При auto поле заявляет max-content,
     строка ломается, и min-width:0 этого уже не отменяет. Замерено на 320:
     с auto «Найти» уезжает вниз и тулбар растёт с 200 до 252 px. */
  .otb .field{flex:1 1 0;min-width:0}
  .otb .field input{height:44px;font-size:16px}
  .otb .find{flex:0 0 auto;padding:0 18px;height:44px;font-size:15px}
  /* Селекты — во всю ширину, а не по два в ряд: селект тратит 45 px на
     паддинги, и при двух в ряд «Все менеджеры» (121 px) обрезалось на двух
     ширинах из трёх. Высота тулбара та же — 200 px, построчность бесплатна. */
  .otb .sel{flex:1 1 100%;min-width:0;height:44px;font-size:16px}

  /* ═══ Шапка заказа ═══
     Общую метку .mhead надевает третья страница сущности. Своя приставка —
     .ohd, а НЕ .ohead: последнее носят все три страницы, то есть это имя
     компонента, а не раздела. */
  /* Даты «Создан»/«Изменён» — под названием и статусом: на десктопе они справа
     от названия, на 320 px не помещаются и вылезают за рамку блока. */
  .ohd .dates{text-align:left;flex-basis:100%;display:flex;flex-wrap:wrap;
    gap:0 16px}
  /* Две колонки шапки (поля слева, расходы справа) — в стопку. */
  .ohd .hbody{display:block}
  .ohd .hfields{margin-bottom:18px}
  /* Значение поля перестаёт быть строкой фиксированной высоты (.fval несёт
     height:28px): длинное имя клиента и комментарий в неё не помещались. */
  .ohd .frow > .fval{height:auto;min-height:24px;align-items:flex-start;gap:0}
  .ohd .fedit.valtext,.ohd .fedit.cmtval{white-space:normal;text-align:left}
  /* Плотнее общего .mhead: у заказа четыре поля подряд плюс блок расходов, и
     общий ритм в 14 px читался разреженно. Зазор «подпись → значение» НЕ
     трогается — он приходит из .mhead и равен 2,0 px, как у соседей. */
  .ohd .frow{margin-top:10px}
  .ohd .hdiv{margin:12px 0 2px}
  /* Правка по месту: поля во всю ширину блока (251 px на 320) и 16 px.
     Поля НАЗВАНИЯ здесь нет намеренно: у .titleinput 20 px, автозума iOS нет,
     а 16 px уменьшили бы поле против отображаемого названия. */
  .ohd .finput{font-size:16px;height:38px;width:100%}
  .ohd .combo{flex:1 1 auto;min-width:0;width:100%}
  .ohd .combo .finput{min-width:0}
  .ohd .combolist{min-width:0;width:100%}

  /* ═══ Расходы заказа ═══
     Сегодня блок — сетка 1fr 124px 54px; на 320 px названию остаётся 45 px
     («Дост…»), а кнопки сжаты до 24 px, чтобы влезть в свою колонку. */
  .ohd .oexp{width:100%;grid-template-columns:minmax(0,1fr) auto;column-gap:10px}
  .ohd .oexp-h{font-size:13px;padding-bottom:8px}
  /* Название — во всю ширину: на нём же рисуется разделитель между расходами,
     и только так он получается сплошным. */
  .ohd .oexp-nm{grid-column:1/-1;border-top:1px solid var(--line);
    align-items:flex-end;padding-bottom:0;min-height:34px}
  .ohd .oexp-nm .fedit{font-size:14px;white-space:normal;overflow:visible;
    text-overflow:clip;padding-top:7px;padding-bottom:4px}
  /* Сумма подтянута к названию: подниматься ей позволяет align-items:flex-start
     — высоту строки задаёт кнопочная ячейка (44 px), но сумма в ней стоит
     вверху, а не по центру. Зазор «название → сумма» 6,8 px. */
  .ohd .oexp-am{grid-column:1;justify-content:flex-start;border-top:none;
    min-height:0;padding:0 0 8px;align-items:flex-start}
  .ohd .oexp-am .price{font-size:16px}
  /* Кнопки — во второй колонке ТОЙ ЖЕ строки, что сумма. `grid-row:span 2`
     здесь пробовался и оказался ловушкой: авторазмещение ставит растянутую
     ячейку начиная с текущего курсора, то есть на строку ниже своего названия,
     и кнопки уезжали к соседнему расходу. */
  .ohd .oexp-bt{grid-column:2;border-top:none;min-height:44px;
    align-items:center;justify-content:flex-end;gap:8px}
  /* Кнопки форм (✓/✕) лежат прямо в .oexp-bt, а не внутри .act, — правило на
     .act их не достаёт, и в форме они оставались 24-пиксельными. */
  .ohd .oexp .ib{width:38px;height:38px}
  .ohd .oexp .act,.ohd .oexp .act .icons,.ohd .oexp .act .confirm{gap:8px}
  .ohd .oexp .finput{height:38px;font-size:16px}
  /* Полоса под списком: «+ Добавить» и «Распределить» — тоже цели. */
  .ohd .oexp-bar{gap:10px;padding-top:10px}
  .ohd .oexp-bar .fedit{font-size:14px;padding:10px 2px}
  .ohd .oexp-dist{height:38px;font-size:13px;padding:0 14px}

  /* ═══ Таблица позиций: строка → карточка ═══
     Самая плотная строка приложения: семь колонок, из них шесть
     содержательных. Порядок областей — из сценария заказчика «смотрю и правлю
     цифры»: наименование, суммы, наличие, цена, количество, обновление — в
     первом плане; замена, ручной товар, комплект, перестановка и удаление
     остаются доступны целиком, но вторым планом. */

  /* Строки, которые карточками НЕ становятся: бар «добавить», панель поиска
     (в ней живёт вторая таблица) и строка пустого состояния. Подвал комплекта
     сюда НЕ входит — он тонированная плашка и объявлен своим правилом целиком
     (см. «Подвал комплекта» ниже). */
  .mcards.icards > tbody > tr.addbar,
  .mcards.icards > tbody > tr.exprow,
  .mcards.icards > tbody > tr#items-empty-row{display:block;background:transparent;
    border:none;border-radius:0;margin:0 0 9px;padding:0}
  .mcards.icards > tbody > tr.addbar > td,
  .mcards.icards > tbody > tr.exprow > td{display:block;padding:0}
  /* Пустое состояние держит СВОЁ вертикальное поле, а не padding:0 группы
     выше: `.empty-cell{padding:28px 4px}` (0,1,0) перебивается примитивом
     `.tbl.mcards > tbody > tr > td{padding:0}` (0,2,3, app.css:1482)
     независимо от общей группы addbar/exprow. Id в этом селекторе
     (1,2,3) поднимает вес выше примитива — без него «Пока пусто — добавьте
     товар из поиска» вставало впритык под полосой действий. */
  .mcards.icards > tbody > tr#items-empty-row > td{display:block;padding:28px 4px}

  /* :not(#items-empty-row) — на сегодня строка и так исключена из карты
     областей весом id (.mcards.icards > tbody > tr#items-empty-row весит
     (1,2,2) против (0,5,2) у карты и без этого :not()), но переименуй id в
     класс — и список :not() ниже остался бы единственной защитой; ни один
     существующий страж такую замену не ловит. */
  .mcards.icards > tbody > tr:not(.addbar):not(.exprow):not(.kfoot):not(#items-empty-row){
    grid-template-columns:auto minmax(0,1fr) auto;
    grid-template-areas:
      "num  nm   nm"
      "sup  sup  mid"
      "qs   qs   qs"
      "sum  sum  act";}

  /* Ячейки карточки */
  .icards td.i-num{grid-area:num;align-self:start}
  .icards td.i-nm{grid-area:nm;min-width:0}
  /* ↻ (у ручной позиции — «править») справа напротив блока поставщика, по
     центру его высоты. Разнести поставщика, артикул, наличие и закупочную по
     разным областям нельзя: они лежат в одной ячейке разметки. */
  .icards td.i-mid{grid-area:mid;justify-self:end;align-self:center}
  .icards td.i-sup{grid-area:sup;min-width:0;justify-self:start}
  .icards td.i-qs{grid-area:qs}
  .icards td.i-sum{grid-area:sum}
  .icards td.i-act{grid-area:act;align-self:center}

  /* Наименование получает всю ширину — сегодня оно 9 px шириной и 1020 px
     высотой, то есть буквально по символу в строку. Стало 212 px на 320. */
  .icards .nm{font-size:14px;line-height:1.35;white-space:normal}
  .icards .np{font-size:12px;flex-wrap:wrap}
  .icards .sline,.icards .sstk{font-size:12.5px;white-space:normal}
  .icards .uprice{font-size:13px;margin-top:4px}
  .icards .pnum{font-size:13px}

  /* Копирование PN и артикула поставщика (§18.3 п. 5 спеки — «.copybtn
     показаны всегда»). У .copybtn стоит opacity:0, и показывает её десктопное
     `.oitems > tbody > tr:not(.exprow):hover .copybtn`. На тач-экране :hover
     НЕ СУЩЕСТВУЕТ — кнопки были бы в разметке, но невидимы и недоступны.
     Показываем приглушённо, а не в полную непрозрачность: то же решение и те
     же числа, что уже стоят у .rtbl.mcards (панель) и #results (поиск), —
     копирование тут вспомогательное действие рядом со значением, а не
     самостоятельная кнопка вроде ↻/удалить, и полная непрозрачность спорила
     бы с ними за внимание на каждой строке. Колонка поставщика (.i-sup)
     выровнена ВЛЕВО (justify-self:start, в отличие от поиска и панели), и
     значению незачем упираться в правый край — реверс иконки им не нужен.

     Финальное ревью, находка сверх волны: `.cp-wrap`/`.copybtn` — общие
     классы макроса `copy_val()` (_result_row.html), и панель замены/поиска
     (`.rtbl`, живёт в `tr.exprow`) рендерит ими те же PN/артикул тем же
     макросом — без своей приставки `r-*`. Голое `.mcards.icards .copybtn`
     доставало бы её строки наравне со своими: сегодня это не видно, только
     потому что `.rtbl.mcards .copybtn` (app.css:2721) весит ровно столько
     же (0,3,0) и стоит в файле ПОЗЖЕ — та же хрупкость «держится порядком»,
     которую по всей ветке лечит дочерний комбинатор. Приставки `i-*`/`r-*`
     здесь не спасают: они на СВОИХ классах ячеек (`td.i-sup`, `td.r-sup`), а
     `cp-wrap`/`copybtn` — общий слой макроса ВНУТРИ этих ячеек, приставки не
     несёт вовсе. Лечится тем же приёмом, что и десктопное правило-образец
     `.oitems > tbody > tr:not(.exprow):hover .copybtn` выше: полная цепочка
     `> tbody > tr:not(.exprow)` — строки состава комплекта (`tr.row-part`,
     `tr.kidrow`) не несут `.exprow` и остаются под правилом. */
  .mcards.icards > tbody > tr:not(.exprow) .cp-wrap{display:inline-flex;
    align-items:center;gap:7px;min-width:0;max-width:100%}
  .mcards.icards > tbody > tr:not(.exprow) .cp-wrap .v{overflow:hidden;
    text-overflow:ellipsis;white-space:nowrap;min-width:0}
  .mcards.icards > tbody > tr:not(.exprow) .copybtn{flex:none;opacity:.55;
    padding:5px;margin:-5px -3px -5px 0;border-radius:5px}
  .mcards.icards > tbody > tr:not(.exprow) .copybtn svg{width:14px;height:14px}

  /* Цена и счётчик делят ширину поровну — без переменного зазора между ними. */
  .icards .qs{flex-direction:row;align-items:center;gap:9px}
  .icards .saleprice{flex:1 1 0;width:auto;max-width:none;min-width:0;
    font-size:16px;height:44px}
  .icards .stepper{flex:1 1 0;min-width:0;justify-content:space-between;height:44px}
  .icards .stepper input{flex:1 1 0;width:auto;min-width:0;font-size:16px;height:44px}
  /* Кнопки ± уже: на 320 px пара по 42 px съедала 84 из 115, числу оставалось
     31 px. Ширина 34 при высоте 44 — цель ниже 38 по одной стороне, но цена
     промаха здесь наименьшая: соседняя кнопка возвращает значение обратно. */
  .icards .stepper button{width:34px;height:44px;font-size:18px}

  /* Итог позиции — крупнее поля продажной цены (16 px); закупка под ним на
     2 px: на десктопе между ними 16 px, потому что там ячейка вертикально
     почти пуста и отступ работает на воздух, а в карточке это была дыра.
     Левое выравнивание требует полного префикса: десктопное
     `.tbl tbody td.c-price{text-align:right}` весит (0,2,2). */
  .mcards.icards > tbody > tr > td.i-sum{text-align:left}
  .icards td.i-sum .price.sum{font-size:19px}
  .icards td.i-sum .csum{font-size:11.5px;white-space:nowrap;margin-top:2px}
  /* Подпись «Закупка:» на десктопе стоит НАД числом — колонка 132 px. В
     карточке места хватает, ставим в строку. */
  .icards td.i-sum .csum .ccap{display:inline;margin-right:4px}
  .icards .sumcell .price.sum{font-size:16px}

  /* Кнопки — тач-цели, как в остальных карточках. Вертикальная стопка
     разворачивается в строку: в карточке она занимала бы три строки высоты. */
  .icards .act.vert{flex-direction:row;justify-content:flex-end}
  .icards .act.vert .icons{flex-direction:row}
  .icards .act.vert .confirm{flex-direction:row}
  .icards .act.vert .ib{width:38px;height:38px}
  .icards .ib.rfr,.icards .ib.edt{width:38px;height:38px}
  /* Стрелки перемещения — 18×14 на десктопе, то есть меньше любой цели. Обе
     до 38 не растут: это две цели в одной узкой колонке, и подъём поднял бы
     колонку номера до 84 px, отобрав их у наименования. */
  .icards .ord{gap:0}
  .icards .mv{width:34px;height:30px}

  /* Подложка строки без закупочной на десктопе красится ПО ЯЧЕЙКАМ, а у
     карточки ячейки — элементы сетки с зазорами: заливка выходила заплатками. */
  .mcards.icards > tbody > tr.nocost-row{background:rgba(214,69,69,.045)}
  .mcards.icards > tbody > tr.nocost-row td{background:transparent}

  /* ═══ Комплект и его состав ═══
     Строки состава (.kidrow) — сиблинги в той же таблице, свёрнутые x-show.
     Инлайновый display:none от Alpine перебивает display:grid из примитива,
     поэтому свёртка работает как была; при раскрытии строка возвращается к
     сетке. Подложка — на строку, а не на ячейки (та же правка, что у
     .nocost-row): у карточки между ячейками зазоры. */
  .mcards.icards > tbody > tr.kidrow{background:#f1f3f7}
  .mcards.icards > tbody > tr.kidrow td{background:transparent}

  /* Счётчик элемента НЕ растягивается: у элемента в строке ввода нет продажной
     цены — только подпись «в комплекте» и счётчик, делить ширину пополам ему
     не с кем, и растянутый на половину карточки он выглядел раздутым. */
  .icards > tbody > tr.row-part .qs{justify-content:space-between}
  .icards > tbody > tr.row-part .stepper{flex:0 0 auto}
  .icards > tbody > tr.row-part .stepper input{flex:0 0 auto;width:66px}
  /* .psum несёт СОБСТВЕННЫЙ text-align:right — левое выравнивание ячейки его
     не перебивает, и сумма элемента оставалась у правого края. */
  .icards td.i-sum .psum{text-align:left}

  /* Подвал комплекта: итог состава НАД кнопкой «Добавить элемент» (просьба
     заказчика). Порядок меняется order, а не перестановкой в шаблоне, — но
     order работает только во флекс-контейнере: объяви строка блоком, и
     правило молчит. Поэтому подвал объявлен ОДНИМ собственным правилом и в
     группу строк-не-карточек не входит: он тонированная плашка состава, и
     попади он в группу, значения пришлось бы сперва обнулить, а потом вернуть
     вторым правилом с тем же селектором. */
  .mcards.icards > tbody > tr.kfoot{display:flex;flex-wrap:wrap;align-items:center;
    gap:6px;background:#f1f3f7;border:none;border-radius:var(--r);
    margin:0 0 9px;padding:9px 12px}
  .mcards.icards > tbody > tr.kfoot > td{display:block;padding:0;background:transparent}
  /* Спорит с соседним правилом выше за display пустой td комплекта: оба
     весят (0,3,3) (.icards+.kfoot+:empty против .mcards+.icards+.kfoot,
     tbody+tr+td у обоих) и держатся ПОРЯДКОМ в файле — поэтому строка ниже,
     а не выше. Переедь она раньше соседа, td:empty снова стал бы display:block
     пустой плашкой в подвале комплекта. */
  .icards > tbody > tr.kfoot td:empty{display:none}
  .icards > tbody > tr.kfoot td.kfsum{order:-1;flex:1 1 100%;margin:0;text-align:right}
  .icards > tbody > tr.kfoot td:has(.addk){flex:1 1 100%}
  .icards .addk{font-size:13.5px;padding:7px 0}
  /* Наименование комплекта: min-width:340px, то есть шире экрана. */
  .icards .knameinput{min-width:0;width:100%;font-size:16px;height:38px}

  /* ═══ Панели добавления и замены ═══
     Панель — не карточка: её строка выведена из-под примитива (tr.exprow), а
     внутри живёт собственная таблица .rtbl. Отступы сжимаются: на десктопе
     .repl несёт 12/16 px плюс левую полосу 3 px. */
  .icards .repl{padding:10px 10px 12px}

  /* Верхняя строка: поле, «Найти», крестик — первая строка; две двери —
     вторая. Пять целей в ряд не помещаются: «＋ КОМПЛЕКТ» просит 85 px, а при
     равном делении ему достаётся 63, и надпись выезжала на крестик. */
  .icards .rtop{flex-wrap:wrap;gap:8px;align-items:center}
  /* Базис 50 %, а не 0 и не auto. Ноль не ломает строку, и первая дверь
     запрыгивала к «Найти», сплющивая поле до 7 px; auto ломает её слишком
     рано, и крестик уезжал вниз. 50 % — единственный диапазон, работающий и
     на 320, и на 402. Замер: поле 140 px на 320, 222 на 402. */
  .icards .rsearch{flex:1 1 50%;min-width:0}
  .icards .rsearch input{height:38px;font-size:16px}
  .icards .rgo{flex:0 0 auto;height:38px;padding:0 15px;font-size:13.5px;
    min-width:0;border-radius:var(--r-sm)}
  /* Крестик мельче кнопок — по просьбе заказчика. Одно правило на оба
     крестика: тот же класс носит шапка формы ручного товара. */
  .icards .rx{flex:0 0 auto;width:32px;height:38px}
  /* order: в разметке крестик стоит ПОСЛЕ дверей, на экране должен стоять
     рядом с «Найти». Порядок табуляции остаётся разметочным — размен
     осознанный, крестик и так последний по смыслу. */
  .icards .rnew{flex:0 0 calc(50% - 4px);height:34px;font-size:10.5px;
    padding:0 6px;min-width:0;order:1}
  /* В панели состава дверь одна — комплект в комплект не вкладывается. */
  .icards .rtop:not(:has(.rnew.kit)) .rnew{flex-basis:100%}

  /* ═══ Выдача панели — вторая карточная таблица раздела ═══
     Метка mcards даёт общий слой, но .rtbl не носит .tbl, поэтому четыре
     правила примитива, писанные как `.tbl.mcards`, до неё не дотягиваются:
     повторяем их на `.rtbl.mcards`. Приставка ячеек — r-, а не p-: последняя
     занята карточкой списка поставщиков. */
  .rtbl.mcards{min-width:0;border:none;background:transparent;border-radius:0;
    overflow:visible;table-layout:auto}
  /* display:block здесь НЕ пишется намеренно: правило примитива
     `.mcards > thead th:not(:has(.thf))` весит ровно столько же, и при равной
     специфичности решает порядок в файле — объявленный ниже display:block
     вернул бы на экран заголовок «Наименование» и два пустых заголовка-
     действия. В флекс-строке ячейка и так блокифицируется. */
  .rtbl.mcards > thead th{flex:none;background:transparent;border:none;padding:0}
  .rtbl.mcards > tbody > tr:hover td{background:transparent}
  .rtbl.mcards > tbody > tr > td{display:block;border:none;padding:0}

  /* Карта областей — ТА ЖЕ, что у карточки поиска, потому что это та же
     выдача. Повторяется, а не наследуется: приватный слой поиска писан под
     приставкой-id #results (вес 1,0,0) и до панелей не дотягивается по
     построению, а классы ячеек у двух шаблонов разные. */
  .rtbl.mcards > tbody > tr{
    grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,auto) 38px 38px;
    grid-template-areas:
      "bp    bp   price price price"
      "name  name name  name  name"
      "stock sup  sup   live  add";}
  .rtbl.mcards > tbody > tr:not(:has(.r-live .live)){
    grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,auto) 38px;
    grid-template-areas:
      "bp    bp   price price"
      "name  name name  name"
      "stock sup  sup   add";}
  /* Без гашения пустая ячейка доразмещается сама и дорисовывает две нулевые
     колонки — тот же дефект, что разобран в карточке поиска. */
  .rtbl.mcards > tbody > tr:not(:has(.r-live .live)) td.r-live{display:none}
  .rtbl.mcards > tbody > tr > td.r-bp{grid-area:bp;min-width:0}
  .rtbl.mcards > tbody > tr > td.r-name{grid-area:name;min-width:0}
  .rtbl.mcards > tbody > tr > td.r-price{grid-area:price;text-align:right}
  .rtbl.mcards > tbody > tr > td.r-live{grid-area:live}
  .rtbl.mcards > tbody > tr > td.r-stk{grid-area:stock;align-self:center}
  /* Поставщик и артикул прижаты вправо, к кнопкам, — как в карточке поиска. */
  .rtbl.mcards > tbody > tr > td.r-sup{grid-area:sup;min-width:0;
    text-align:right;align-self:center}
  .rtbl.mcards > tbody > tr > td.r-add{grid-area:add}
  .rtbl.mcards > tbody > tr > td.r-live,
  .rtbl.mcards > tbody > tr > td.r-add{align-self:center;justify-self:end;padding:0}
  .rtbl.mcards .rname{white-space:normal;font-size:13.5px}
  .rtbl.mcards .rprice{font-size:15px}
  .rtbl.mcards .rbp .brand{white-space:nowrap;overflow:hidden;
    text-overflow:ellipsis}
  .rtbl.mcards .rsup{min-width:0}
  .rtbl.mcards .snm2,.rtbl.mcards .sart2{white-space:nowrap;overflow:hidden;
    text-overflow:ellipsis}
  .rtbl.mcards td .live,.rtbl.mcards td .ib{width:38px;height:38px}

  /* Копирование PN и артикула. У .copybtn стоит opacity:0, и показывает её
     правило `.rtbl tbody tr:hover`. На тач-экране :hover НЕ СУЩЕСТВУЕТ —
     кнопки были бы в разметке, но невидимы и недоступны. Показываем всегда,
     приглушённо: то же решение и те же числа, что в карточке поиска. */
  .rtbl.mcards .cp-wrap{display:inline-flex;align-items:center;gap:7px;
    min-width:0;max-width:100%}
  .rtbl.mcards .cp-wrap .v{overflow:hidden;text-overflow:ellipsis;
    white-space:nowrap;min-width:0}
  .rtbl.mcards .copybtn{flex:none;opacity:.55;padding:5px;margin:-5px -3px -5px 0;
    border-radius:5px}
  .rtbl.mcards .copybtn svg{width:14px;height:14px}
  /* У артикула иконка переезжает ВЛЕВО от значения: колонка выровнена по
     правому краю, и значение обязано упираться в край — иначе артикул не
     совпадёт по правому краю с именем поставщика над ним. */
  .rtbl.mcards td.r-sup .cp-wrap{gap:6px;flex-direction:row-reverse}
  .rtbl.mcards td.r-sup .copybtn{padding:5px;margin:-5px 0 -5px -3px}
  .rtbl.mcards td.r-sup .copybtn svg{width:13px;height:13px}

  /* ═══ Модификатор цен ═══
     На десктопе это одна строка: триггер, разделитель, селект 240 px, поле,
     кнопка, счётчик. На 320 px из неё видно треть. Вертикальный разделитель
     между триггером и телом становится горизонтальным. */
  .pmod{flex-wrap:wrap;gap:9px;margin-bottom:10px}
  .pmod-trigger{flex:1 1 100%;height:42px;min-width:0}
  #order-work.selmode .pmod-body{flex:1 1 100%;flex-wrap:wrap;padding-left:0;
    padding-top:10px;border-left:none;border-top:1px solid var(--line);gap:9px}
  /* 16 px у СЕЛЕКТОВ — не перестраховка: зум на iOS случается от фокуса, а не
     от клавиатуры, и <select> входит в список наравне с <input>. Стендом это
     не воспроизводится (headless Chromium такого поведения не имеет), а цена
     нулевая — селект и так во всю ширину. */
  .pmod #pmod-modifier{width:auto;flex:1 1 100%;height:44px;font-size:16px}
  .pmod-val{flex:1 1 0;min-width:0}
  .pmod-val input{width:auto;flex:1 1 0;min-width:0;height:44px;font-size:16px}
  .pmod .pmod-step{width:auto;flex:1 1 0;min-width:0;height:44px;font-size:16px}
  .pmod-apply{flex:0 0 auto;height:44px;min-width:0;padding:0 18px}
  .pmod-err,.pmod-count{flex:1 1 100%;margin-left:0}

  /* ═══ Шапка таблицы позиций — единственное место, где примитив переспорен ═══
     Он прячет шапку нефильтруемой таблицы целиком, и для списков поставщиков и
     пользователей это верно. Здесь в шапке живут две НАСТОЯЩИЕ кнопки:
     массовое обновление закупочных цен всего заказа и «выбрать все». Спрятать
     их значило бы отнять у телефона обновление цен всего заказа. Шапка
     остаётся полосой действий, и в ней всегда ровно один элемент. */
  /* Спорит с примитивом `.mcards > thead > tr:not(:has(.thf)){display:none}`
     (app.css:1420) за display этой же tr: веса равны — (0,2,2) у обоих
     (mcards+icards против mcards+:not(:has(.thf))-как-класс, tags thead+tr
     у обоих), — и держится ТОЛЬКО порядком в файле: правило ниже примитива
     побеждает. Переедь оно выше — тут нет ни одного .thf, примитив вернёт
     display:none, и с телефона исчезнет вся полоса действий вместе с
     массовым обновлением закупочных цен. */
  .mcards.icards > thead > tr{display:flex;align-items:center;gap:10px;
    padding:0 0 10px;overflow:visible}
  /* Безусловный, а не через примитивное `:not(:has(.thf))`: сегодня разницы
     нет — ни у одного th здесь нет .thf, и примитив спрятал бы их точно так
     же. Обход намеренный на будущее: появись здесь когда-нибудь фильтруемая
     колонка, примитив показал бы её через :has(.thf), а это правило снова
     спрячет — единственная таблица карточек, где так, и это осознанный выбор,
     а не забытая копия примитива.
     Та же гонка порядка, что и у tr выше, но с другим примитивом: `.tbl.mcards
     > thead th{display:block;…}` (app.css:1403) весит (0,2,2) — ровно
     столько же, сколько .mcards.icards > thead th. Переедь это правило
     выше примитива — все th полосы (кроме .rfr-all/.selbox, у них своя
     тяжёлая запись) вернутся видимыми display:block пустыми блоками рядом
     с кнопкой, и полоса перестанет быть «ровно один элемент». */
  .mcards.icards > thead th{display:none}
  .mcards.icards > thead th:has(.rfr-all){display:block;flex:1 1 100%}
  /* Кнопка во всю ширину с подписью, как «Изменить цены»: значком массовое
     обновление быть перестаёт — в полосе над карточками одинокая иконка не
     называет себя ничем. Селектор ТРЁХклассовый: общее правило карточки
     `.icards .ib.rfr` весит (0,3,0) и двухклассовым не перебивалось. */
  .icards > thead .ib.rfr-all{width:100%;height:42px;padding:0 14px;
    border:1px solid var(--line);border-radius:var(--r);background:#fff;
    gap:8px;font-size:13.5px;font-weight:600;color:var(--petrol)}
  .icards > thead .ib.rfr-all .mlbl{font-size:13.5px;color:inherit}
  /* В режиме выбора кнопку приходится убирать ИЗ ПОТОКА, а не гасить: десктоп
     прячет её через visibility:hidden, коробка остаётся, и кнопка во всю
     ширину держала бы пустую полосу в 42 px. */
  #order-work.selmode .mcards.icards > thead th:has(.rfr-all){display:none}
  #order-work.selmode .mcards.icards > thead th:has(.selbox){display:block;
    flex:1 1 100%}
  /* «№» в полосе действий бессмысленно: номеров-колонок на телефоне нет. */
  .icards > thead .numhead{display:none}
  .icards > thead .selbox{padding:0;justify-content:center;gap:9px;
    align-items:center}
  /* Капс и разрядку подпись наследует от <th> (.tbl thead th) — в полосе
     действий это подпись у чекбокса, а не заголовок колонки. */
  .icards > thead .selbox .mlbl{font-size:13.5px;font-weight:600;
    text-transform:none;letter-spacing:0;color:var(--ink)}
  .icards .selbox input{width:22px;height:22px}

  /* ═══ Бар «Добавить позицию» ═══
     44 → 48 px, пунктирная рамка со скруглением вместо верхней пунктирной
     черты: в стопке карточек полоса без рамки читалась как продолжение
     последней карточки. */
  .icards .addbtn{height:48px;font-size:14px;border:1px dashed var(--line);
    border-radius:var(--r);background:#fff}

  /* ═══ Подвал заказа ═══
     «Экспорт в XLSX» встаёт ПОСЛЕ итогов через column-reverse, а не
     перестановкой в шаблоне: разметка не меняется, десктоп не трогается
     вовсе. .otot теряет фиксированную колонку 168 px и правый отступ 66 px —
     без этого он единственный тащил бы страницу вбок даже после карточек. */
  .ofoot{flex-direction:column-reverse;align-items:stretch;gap:14px}
  .otot{margin-right:0;grid-template-columns:auto minmax(0,1fr);gap:6px 12px}
  .xls{width:100%;justify-content:center;height:46px}

  /* ═══ Форма ручного товара ═══
     На десктопе две сетки по три и четыре колонки. На 320 px в них не
     помещается ни одна подпись: «Артикул поставщика» и «Закупочная цена»
     наползали друг на друга, поля сжимались до 30 px, кнопки «Отмена» и
     «Сохранить» вставали поверх полей. В стопку: подпись, под ней поле во всю
     ширину, кнопки своей строкой пополам.
     Своего правила для крестика здесь НЕТ намеренно: класс .rx носят оба
     крестика — панели и формы, — и селектор у них один (см. панели выше). */
  .icards .mfgrid,.icards .mfgrid.two{display:block;margin-bottom:0}
  .icards .mf{display:block;margin-bottom:11px}
  .icards .mf input{font-size:16px;padding:9px 10px}
  .icards .mfacts{display:flex;gap:9px;margin-top:14px;width:100%}
  .icards .manform .btn{height:44px;flex:1 1 0;min-width:0}
  .icards .mfhead{margin-bottom:12px}

  /* ═══════════════════════════════════════════════════════════════════════════
     КАТЕГОРИИ: справочник и привязка узлов поставщика
     (спека 2026-09-09-category-dictionary-and-mapping-design.md §10)

     Два экрана, две таблицы, две приставки: cd- у справочника, cb- у привязки.
     Обе проверены свободными в app.css целиком — первая редакция макета назвала
     свой пикер .pick и молча сломалась о существующий пикер фильтров.
     Правило, нужное обоим экранам, пишется ОДНИМ групповым селектором (панель
     пикера, поле имени), а не третьей копией: это один рисунок под двумя
     именами, и правка обязана попадать в оба сразу — тем же приёмом, каким
     объявлен общий примитив дерева на десктопе (ревью Задачи 8 §6).
     Строки адресуются ПРЯМЫМИ потомками (спека 2026-09-02 §3.2). Вложенной
     таблицы у этих двух сегодня нет — панель пикера живёт сиблингом
     .tbl-scroll, — но цена одного `>` сейчас ниже цены переписывания
     одиннадцати правил примитива, которое ветка «Заказы» уже проделала.
     Своего правила для <thead> здесь НЕТ намеренно: обе шапки нефильтруемые,
     и примитив (.mcards > thead > tr:not(:has(.thf))) прячет их целиком —
     замер подтверждает, что шапка не занимает ни пикселя.
     ═══════════════════════════════════════════════════════════════════════════ */

  /* ═══ ЧАСТНЫЙ СЛОЙ: карточка справочника ═══
      ┌────────────────────────────────────────┐
      │ Ноутбуки, планшеты и аксессуары        │
      │ 96 узлов                  41 207 товаров│
      │                       [+] [✎] [⇄] [🗑] │
      └────────────────────────────────────────┘
     Ряд действий не делит строку с числами: четыре кнопки по 38 px с зазорами
     занимают 176 px из 270 доступных на 320 px, и числу товаров осталось бы 94
     при собственной ширине 104 — оно переносилось бы под подпись. */
  .mcards.ccats > tbody > tr{
    grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:
      "name  name"
      "nodes prods"
      "acts  acts";}
  .mcards.ccats > tbody > tr > td.cd-name{grid-area:name}
  /* Тяжёлая форма (0,3,3) обязательна: ячейки числа несут class="c-mid
     count-num", а десктопное .tbl tbody td.c-mid{text-align:center} весит
     (0,2,2) — двухклассовая запись оставила бы оба числа по центру карточки. */
  .mcards.ccats > tbody > tr > td.cd-nodes{grid-area:nodes;text-align:left}
  .mcards.ccats > tbody > tr > td.cd-prods{grid-area:prods;justify-self:end;
    text-align:right}
  .mcards.ccats > tbody > tr > td.cd-act{grid-area:acts}
  /* У строки с полем новой категории действий нет вовсе, и `:empty` тут НЕ
     работает — это разобрано ревью и стоит записать, чтобы приём не вернулся:
     третью дорожку сетки и её зазор задаёт `grid-template-areas`, а не элемент,
     поэтому схлопывание самой ячейки в ноль оставляет под числами 9 px пустого
     зазора (замер: карточка .cd-new на 320 px — 122,98 px при низе строки чисел
     на 647,11 и низе карточки на 668,11). Убирается только своей картой на две
     строки; ячейка при этом уходит из потока целиком, а не сжимается.
     Оба селектора тяжелее того, что перебивают: (0,3,2) против (0,2,2) у карты
     карточки и (0,4,3) против (0,2,3) у примитива `.tbl.mcards > tbody > tr >
     td{display:block}` — иначе `display:none` ему проигрывал бы. */
  .mcards.ccats > tbody > tr.cd-new{
    grid-template-areas:
      "name  name"
      "nodes prods";}
  .mcards.ccats > tbody > tr.cd-new > td.cd-act{display:none}
  /* Кнопки 30 → 38 px, как в карточках «Пользователей», «Поставщиков» и
     «Заказов»: мобильные кнопки одного роста. Подтверждение удаления остаётся
     СЛОВОМ, а не значком (в отличие от категорий s4b): цена удаления стоит тут
     же строкой («вместе с ней: 23 подкатегории, 244 привязки»), она уже
     переносится (.act .cd-hint{white-space:normal}), и ширины хватает — замер
     на 320 px: подсказка 141,8 + «Удалить» 74,2 + отмена 38 в 270 доступных. */
  .ccats .act .ib{width:38px;height:38px}
  .ccats .act,.ccats .act .icons,.ccats .act .confirm{gap:8px}
  .ccats .act .c-yes{height:38px}

  /* Поле имени и его подсказка. Подсказка несёт white-space:nowrap и в строке с
     полем занимает 181,7 px: замер до правки — строка правки 408,7 px внутри
     карточки шириной 270, то есть карточка ездила вбок ПОД ПАЛЬЦЕМ (документ
     при этом не едет — её прокручивает .tbl-scroll, тот же вырожденный случай,
     что ловило overflow-wrap:anywhere в карточке пользователя). Лечение то же,
     каким десктоп уже развёл поле и подсказку в пикере (.cb-new .cb-hint). */
  .cd-edit{flex-wrap:wrap}
  .cd-edit .cd-hint{width:100%}
  /* 16 px — капкан iOS: Safari принудительно зумит страницу при фокусе в поле
     мельче, и отключить это нельзя. Групповой селектор тот же, что на десктопе:
     это одно поле в двух местах — строка дерева и строка пикера. Высота 30/28 →
     38, тот же пол, что у полей правки по месту в шапках заказа и пользователя.
     Реестр полей ведётся вручную (test_every_reachable_input_is_16px), и оба
     дописаны туда: без записи следующий их снова не заметит. */
  .cd-edit input,.cb-new input[type="text"]{font-size:16px;height:38px}

  /* ═══ ЧАСТНЫЙ СЛОЙ: карточка узла поставщика ═══
      ┌────────────────────────────────────────┐
      │ Источники бесперебойного питания и     │
      │ стабилизаторы напряжения для стойки    │
      │ 4 471 товаров                          │
      │ [⛓] ⟨Компьютерная техника⟩             │
      └────────────────────────────────────────┘
     Стопка, а не сетка: имя узла доходит до 120 знаков (спека §A.4), и любая
     вторая колонка отнимает у него ширину — замер на 320 px: во всю ширину имя
     в 90 знаков ложится в три строки, в паре с числом справа — в пять.
     row-gap 9 → 6: три строки подряд на общем зазоре читались разреженно, а
     карточек на экране разметки сотня. */
  .mcards.scats > tbody > tr{
    grid-template-columns:minmax(0,1fr);
    grid-template-areas:
      "name"
      "qty"
      "bind";
    row-gap:6px;}
  .mcards.scats > tbody > tr > td.cb-name{grid-area:name}
  /* Та же тяжёлая форма и по той же причине, что у чисел справочника. */
  .mcards.scats > tbody > tr > td.cb-qty{grid-area:qty;text-align:left}
  .mcards.scats > tbody > tr > td.cb-cat{grid-area:bind}

  /* Кнопки привязки и снятия — на .cb-cell, а НЕ на метке таблицы, и это не
     небрежность: ровно такая же ячейка стоит ВНЕ таблицы — в блоке «категория
     на весь каталог» (_supplier_default_category.html), у источников без единого
     узла (сегодня FIT и Брауни). Половины одного механизма обязаны иметь один
     охват (спека 2026-09-01 §8): под меткой таблицы правило до того блока не
     дотянулось бы, и там, где дерева нет вовсе, кнопка осталась бы
     30-пиксельной. Своих правил тому блоку не требуется ничего сверх этого:
     заголовок, пояснение и та же .cb-cell — обычная стопка блоков, которая на
     узком экране складывается сама (проверено стендом). */
  .cb-cell .ib{width:38px;height:38px}

  /* Бейдж своей привязки размер получает не здесь, а дописанной приставкой в
     группе «один компонент — один размер» (.uhead .ost,… выше по блоку, спека
     2026-09-02 §12): он тот же .ost, что чип статуса, и второе правило с
     другими числами завело бы четвёртый размер одного компонента. */

  /* ═══ Тач-цели обоих деревьев ═══
     Шеврон раскрытия — 20 × 20 px, самая мелкая цель раздела и при этом
     главная: дерево на телефоне только им и листают. Растёт НЕВИДИМО, тем же
     приёмом, что тумблер и ссылка возврата: margin-box остаётся 20 px, строка и
     отступ уровня не сдвигаются. Девять пикселей сверху и снизу равны
     row-gap карточки — цель дорастает ровно до границы своей строки.
     Только по вертикали, и это решение, а не полумера: тот же класс носит
     шеврон СТРОКИ ПИКЕРА, где нажатие на строку привязывает узел к категории,
     и отобранные у строки пиксели меняли бы привязку вместо раскрытия ветви.
     Слева расти тоже нельзя: box-sizing:border-box у всего файла, то есть
     ширину пришлось бы растить самим width, а отрицательный margin утащил бы
     шеврон за границу панели с её overflow:hidden. Длинная форма margin —
     правило CLAUDE.md: краткая переписала бы и стороны, которых не касается. */
  .cd-tw,.cb-tw{height:38px;margin-top:-9px;margin-bottom:-9px}

  /* «+» заведения подкатегории прямо из пикера показан правилом
     `.cb-pop .pr:hover .cb-add`. На тач-экране :hover НЕ СУЩЕСТВУЕТ — кнопка
     есть в разметке, занимает место и нажимается, но человек её не видит, то
     есть завести категорию из пикера с телефона нельзя вовсе. То же решение и
     по той же причине, что у .copybtn в карточке выдачи: показываем всегда,
     приглушённо. Заодно цель 20,75 × 17 растёт до 26,75 × 37 — паддингом с
     компенсирующим margin, то есть не сдвигая имени категории рядом.
     Компенсация НЕ симметричная, и это расчёт, а не описка: исходный паддинг
     кнопки — `0 5px` (см. .cb-add выше по файлу), и margin-box обязан остаться
     тем же по КАЖДОЙ стороне. Формула — padding − |margin| == исходный паддинг
     этой стороны: 10 − 10 = 0 по вертикали, 8 − 3 = 5 по горизонтали. Возьми
     margin −8 «за симметрию» — кнопка отдала бы имени 10 px и сдвинулась внутри
     строки (замер поймал ровно это). Тот же расчёт, что у крестика .clr. */
  .cb-pop .cb-add{opacity:.55;padding:10px 8px;margin:-10px -3px -10px -3px}

  /* ═══ Панель пикера: одна ширина на оба экрана ═══
     390 px на экране 320 и 360 — дефект с двумя лицами. Пикер переноса стоит
     статикой в углу хоста (.cd-pop{right:0}) и вылезает ВЛЕВО: замер до правки
     x = −82 на 320 px и −42 на 360 px, а влево документ не прокручивается —
     эти пиксели просто недостижимы. Пикер привязки координаты получает от
     category_picker.js, который упирает левый край в 0, — и та же лишняя ширина
     вылезает ВПРАВО: документ 402 px при экране 320, то есть страница начинает
     ездить вбок ровно в момент открытия панели.
     Проценты считаются от хоста (.cd-host/.cb-host — они position:relative и
     шириной в поля страницы), то есть 296 px на 320 и 336 на 360. Правило одно
     на оба и написано ТЕМ ЖЕ групповым селектором, что десктопная ширина:
     специфичность у них совпадает, решает порядок в файле — на нём же держится
     и весь остальной мобильный блок (страж test_mobile_block_is_last_in_app_css).
     Высоту списка обеим панелям и так уже даёт безприставочное
     .pick .pl{max-height:min(308px,…)} этого блока: у .cd-pop .pl/.cb-pop .pl
     та же специфичность (0,2,0), и правило блока стоит ниже. */
  .cd-pop,.cb-pop{width:min(390px,100%)}
  /* Суженная панель переноса режет имена целей: `.pick .pr .nm` носит nowrap
     ради узких пикеров фильтров, и рядом с причиной отказа («стало бы глубже
     трёх уровней», 166 px) от имени оставалось «Компьютерна…». Имя тут — то
     единственное, ради чего в список смотрят, и гасить его в пользу причины
     нельзя. Правило одно, а не групповое: у пикера ПРИВЯЗКИ ровно то же самое
     уже написано снаружи медиазапроса (`.cb-pop .pr .nm`, там имена в 390 px
     переносятся и на десктопе), и вторая запись была бы его копией. */
  .cd-pop .pr .nm{white-space:normal;overflow-wrap:anywhere}
}
