/* Поведение платформы — режим нативных оболочек (`html.is-app` / `.native-shell` / `.is-pushed`):
   зоны касания, выделение текста, скроллбары, 16px в полях (iOS не зумит), скелетон свопа,
   скрытие дублей веб-хрома. Грузится на ВСЕХ страницах после app.css и cabinet.css, до темы —
   на том же месте каскада, где эти правила стояли в конце старого app.css. Вынесено
   tools/css_split.py. docs/css/ARCHITECTURE.md */

/* Реальный контент внутри хрома выше остаётся выделяемым — user-select не наследуется
   вниз через явное переопределение потомка. Добавляй .selectable на новые текстовые поля,
   которые случайно попали под шаблон выше ([class*="-item"] и т.п.). */
.is-app input, .is-app textarea, .is-app select, .is-app [contenteditable],
.is-app .selectable, .is-app .msg-text, .is-app .mail-msg-body, .is-app .mail-msg-text,
.is-app pre, .is-app code, .is-app td, .is-app th {
  -webkit-touch-callout: default; -webkit-user-select: text; user-select: text;
}

/* Мгновенный отклик на тап — без задержки анимации, которая на вебе смягчает hover→active.
   :disabled/htmx-request (уже «занят», см. App-feel выше) отклик не перебивают. */
.is-app .btn:not(:disabled):active, .is-app button:not(:disabled):active,
.is-app .cab-link:active, .is-app [class*="-item"]:active, .is-app [class*="-row"]:active,
.is-app [role="tab"]:active {
  transition-duration: 0s !important; opacity: .97; transform: scale(.97);
}

/* Убираем системный тянущийся скролл-бар — в приложении скролл ведёт сам WebView/ОС. */
.is-app ::-webkit-scrollbar { display: none; }

.is-app * { scrollbar-width: none; }

/* Родительский документ не «резинится» и не запускает системный pull-to-refresh браузера —
   обновление экрана делает нативная оболочка (UIRefreshControl/SwipeRefreshLayout). */
.is-app html, .is-app body { overscroll-behavior-y: none; }

/* Системный шрифт для основного текста — приложение должно читаться как iOS/Android UI,
   не как веб-страница со шрифтом бренда. Заголовки/бренд-элементы (--font-display, kpi,
   .page-title, .btn) НАМЕРЕННО оставлены на Onest — это фирменный акцент, не тело текста. */
.is-app body { --font-body: -apple-system, BlinkMacSystemFont, "SF Pro Text", Roboto, system-ui, sans-serif; }

/* iOS зумит страницу при фокусе на input с font-size < 16px — в приложении это выглядит
   как баг рендера, не масштаб пользователя. max() не уменьшает то, что и так крупнее. */
.is-app input, .is-app textarea, .is-app select { font-size: max(16px, 1em); }

/* Хит-таргет иконок-кнопок — 44×44 (Apple HIG), не 40 (WCAG-минимум у обычного веба). */
.is-app [class*="icon-btn"], .is-app [class*="ic-btn"] { min-width: 44px; min-height: 44px; }

/* Место под открывшуюся системную клавиатуру при скролле к полю ввода (композер чата/письма). */
.is-app { scroll-padding-bottom: 220px; }

.is-app textarea, .is-app input { scroll-margin-bottom: 220px; }

/* Детальные экраны из push_patterns (docs/fermo-app/NATIVE_FEEL.md, E) открываются приложением
   НОВЫМ нативным экраном со своим системным back/swipe-back — их собственные веб-ссылки
   «← К заказам»/«← К воронке»/хлебные крошки/«К списку» задваивали бы навигацию, скрываем.
   Класс добавлен ТОЧЕЧНО на детальных страницах (cabinet_order_detail.html, _crm_card_body*.html,
   cabinet_product_detail.html, _mail_thread.html, _storefront_inbox_thread.html) — не на всём
   подряд: у страниц без своего пуш-экрана (напр. /cabinet/products/new) назад по-прежнему нужен. */
.is-app .web-back { display: none !important; }

/* -- Skeleton-заглушка для медленных HTMX-свопов #appMain (docs/fermo-app/NATIVE_FEEL.md, C) --
   Класс app-loading ставит/снимает app.js по htmx:beforeRequest/afterSwap, только когда
   ответ не успел за 150мс — быстрые свопы её не видят вовсе (нет мигания на каждый тап). */
.is-app #appMain.app-skel { position: relative; min-height: 200px; overflow: hidden; }

.is-app #appMain.app-skel > * { opacity: 0; }

.is-app #appMain.app-skel::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(var(--surface) 0 22px, transparent 0) 0 0,
    linear-gradient(var(--surface) 0 14px, transparent 0) 0 40px,
    linear-gradient(var(--surface) 0 14px, transparent 0) 0 68px,
    linear-gradient(var(--surface) 0 120px, transparent 0) 0 104px;
  background-size: 60% 62px, 92% 62px, 40% 62px, 100% 226px;
  background-repeat: no-repeat; padding: var(--s-5);
  animation: appSkelPulse 1.1s ease-in-out infinite;
}

/* -- Duplicate section header --------------------------------------------------------
   `.page-title`/`.page-sub` is the shared convention for section headers (orders, CRM,
   products, calls, marketplaces, company, tenders…) — safe to hide everywhere under
   `.is-app` since the native nav bar already carries the title. The dashboard greeting
   uses a DIFFERENT class (`.fd-title`, cabinet_fermo_dashboard.html) so it is untouched by
   this rule. `.mailbox-title`/`.mailbox-hint` is mail's own heading (cabinet_mail.html).
   `.app-hide` is the escape hatch for headings that reuse a class also used elsewhere for
   something that must stay (payments' `.fd-title`/`.fd-lead` — dashboard uses the same
   classes for the greeting, so payments marks its own heading explicitly instead). */
.is-app .page-title, .is-app .page-sub,
.is-app .mailbox-title, .is-app .mailbox-hint,
.is-app .app-hide {
  display: none !important;
}

.is-app .app-filters summary.app-filters-btn {
  display: inline-flex; align-items: center; gap: 6px; list-style: none; cursor: pointer;
  width: fit-content; padding: 0 var(--s-3); height: 34px; border-radius: var(--r-pill);
  border: var(--bw) solid var(--line); font-size: var(--fs-sm); color: var(--ink-2); background: var(--paper);
}

.is-app .app-filters summary.app-filters-btn::-webkit-details-marker { display: none; }

.is-app .app-filters[open] > summary.app-filters-btn { border-color: var(--ink); color: var(--ink); }

.is-app .app-filters .app-filters-body { margin-top: var(--s-2); }

/* Toolbar row (search + "Фильтры" disclosure) moved ahead of the status chips — search is
   the first thing you see under the top, sticky through the list below it. */
.is-app #ordFilters, .is-app #prodFilters { display: flex; flex-direction: column; gap: var(--s-2); }

.is-app #ordFilters > .adm-toolbar--row, .is-app #prodFilters > .adm-toolbar--row { order: -1; }

.is-app #ordFilters .adm-search, .is-app #prodFilters .adm-search,
.is-app #untriagedFilters .adm-search {
  position: sticky; top: 0; z-index: 2; background: var(--paper);
}

/* -- KPI/stat tiles collapsed into one compact horizontal-scroll row ---------------
   Products (6 tiles) and payments (4 tiles) used to push the list a full screen down.
   Dashboard reuses `.fd-kpis`/`.fd-kpi` for its own row — same compaction is a harmless,
   welcome side-effect there too (the greeting itself is untouched, see above). */
.is-app .prodstat, .is-app .fd-kpis {
  display: flex; flex-wrap: nowrap; overflow-x: auto; gap: var(--s-2);
  padding: 2px 0 var(--s-1); margin-bottom: var(--s-3);
}

.is-app .fd-kpi {
  flex: none; min-width: 138px; padding: 10px 14px; box-shadow: none !important;
  border: var(--bw) solid var(--fd-line); border-radius: 12px;
}

.is-app .fd-kpi + .fd-kpi { box-shadow: none !important; }

.is-app .fd-kpi-top { font-size: 11px; }

.is-app .fd-kpi-v { font-size: 17px; }

.is-app .fd-seg { flex-wrap: nowrap; overflow-x: auto; }

.is-app .fd-seg-b { flex: none; white-space: nowrap; padding: 0 10px; font-size: 12px; }

.is-app .pay-filters { flex-wrap: nowrap; overflow-x: auto; }

/* -- Messenger thread header: name never wraps, action chips scroll in their own row
   below it (regardless of viewport — .is-app is always narrow, this just makes the
   existing 720px rule unconditional and gives the scroll row breathing room so the last
   chip isn't visually clipped at the edge). */
.is-app .msg-head { flex-wrap: wrap; row-gap: var(--s-2); }

.is-app .msg-head-main { flex-basis: 100%; }

.is-app .msg-head-peer { max-width: 100%; }

.is-app .msg-assign { margin-left: 0; width: 100%; overflow-x: auto; padding-right: var(--s-3); -webkit-overflow-scrolling: touch; }

.is-app .msg-assign > * { flex: none; }

/* -- Pushed native screens (iOS/Android shell sets `is-pushed` on the newly pushed
   WebView's <html> when it opens a push_patterns URL as its own screen — see
   docs/fermo-app/NATIVE_FEEL.md, E) get the system back gesture/button for free.
   `.web-back` already hides under plain `.is-app` (order/CRM/product/mail detail — those
   are ALWAYS opened as a pushed screen). The storefront inbox thread is different: the
   SAME `/cabinet/inbox/{id}` page is also reached inline, master-detail, by tapping a
   conversation row in the list WITHOUT a native push (plain HTMX swap of #thread) — there
   `.msg-back` is the only way back and must stay. Only hide it when this particular
   screen really is a native push. */
.is-pushed .msg-back { display: none !important; }

/* -- Messenger list (`/cabinet/inbox`): source chips + Мои/Без ответа merged into ONE
   scrollable row (were two stacked flex rows, each wrapping to 2 lines at 402pt); search
   input shrunk from the site-wide `.search` hero component (fs-xl, tall padding+shadow) to
   a normal compact field. `.msg-list-tabs` wraps both `<nav class="msg-list-chips">` and
   `<div class="msg-filters">` (_storefront_inbox_list.html) — plain block on desktop/plain
   mobile browser (zero visual change, each keeps its own row); under `.is-app` it becomes
   the actual flex/scroll row, and `display: contents` on the two children flattens THEM so
   their `<a>`s become direct flex items of `.msg-list-tabs` — one continuous scroll strip,
   not two nested independently-scrolling containers. */
.is-app .msg-list-search.search {
  padding: 6px var(--s-2) 6px var(--s-3); border-radius: var(--r-el); box-shadow: none; margin-bottom: 0;
}

.is-app .msg-list-search.search input { font-size: var(--fs-sm); font-weight: var(--fw-med); padding: var(--s-1) 0; }

.is-app .msg-list-tabs {
  display: flex; flex-wrap: nowrap; align-items: center; gap: 6px; overflow-x: auto;
  padding: var(--s-2) var(--s-3) 0; scrollbar-width: none;
}

.is-app .msg-list-tabs::-webkit-scrollbar { display: none; }

.is-app .msg-list-tabs .msg-list-chips, .is-app .msg-list-tabs .msg-filters { display: contents; }

.is-app .msg-list-tabs .msg-list-chip, .is-app .msg-list-tabs .badge { flex: none; white-space: nowrap; }

/* -- Messenger thread: pushed screen already carries the contact name as its native title —
   hide the page's own name line so it isn't shown twice, keep the channel line + assignment/
   action chips below it. Only under `.is-pushed` (this same URL is also reached inline,
   master-detail, from the list — there the page IS the only place the name shows). */
.is-pushed .msg-head-peer { display: none; }

/* -- Tenders: law chips — one scrollable row, never wrap. Topic chips ("ПОКАЗАТЬ: IT,
   Компьютеры…") — collapsed behind the same `.app-filters` disclosure (_tenders_results.html).
   `.tender-law-chips` is an extra class alongside the shared `.chips` (used site-wide as a
   plain wrapping tag list elsewhere) — scoped here on purpose, `.chips` itself is untouched. */
.is-app .tender-law-chips { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }

.is-app .tender-law-chips::-webkit-scrollbar { display: none; }

.is-app .tender-law-chips .chip { flex: none; white-space: nowrap; }

/* Secondary per-row meta (positions/guarantee/place/docs icons) — drops a whole line off
   every tender card; title/sub/price/deadline stay. */
.is-app .tl-meta { display: none; }

/* == Ощущение приложения в браузере и PWA (static/appfeel.js) ===========================
   В отличие от .is-app (только нативные оболочки), эти правила работают и в обычном
   мобильном браузере, и в установленном PWA (.is-pwa — ставит инлайн-скрипт в <head>). */

/* Установленное PWA на телефоне ведёт себя как оболочка в главном: поля не зумят, иконки-кнопки
   по HIG, хром не выделяется долгим тапом, страница не «резинится» (обновление — свой жест).
   .web-back и заголовки разделов НЕ прячем: у PWA нет нативной навигации назад. */
@media (pointer: coarse) {
  .is-pwa input, .is-pwa textarea, .is-pwa select { font-size: max(16px, 1em); }
  .is-pwa [class*="icon-btn"], .is-pwa [class*="ic-btn"] { min-width: 44px; min-height: 44px; }
  .is-pwa .btn, .is-pwa button, .is-pwa nav, .is-pwa [role="tab"], .is-pwa .cab-link,
  .is-pwa .fs-nav, .is-pwa .fs-top, .is-pwa .chip, .is-pwa svg {
    -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
  }
  html.is-pwa, .is-pwa body { overscroll-behavior-y: none; }
}

/* Pull-to-refresh: кружок съезжает сверху за пальцем, на пороге «защёлкивается», потом крутится. */
.af-ptr {
  position: fixed; left: 50%; top: calc(env(safe-area-inset-top, 0px) - 44px); z-index: 9998;
  width: 36px; height: 36px; margin-left: -18px; border-radius: 50%;
  background: var(--surface); box-shadow: var(--e-2); display: grid; place-items: center;
  transform: translateY(var(--pull, 0px)); opacity: 0; pointer-events: none;
  transition: opacity .15s;
}
.af-ptr.is-on { opacity: 1; }
.af-ptr:not(.is-on) { transition: opacity .15s, transform .2s ease; }
.af-ptr.is-busy { transform: translateY(64px); transition: transform .2s ease; }
.af-ptr-spin {
  width: 18px; height: 18px; border-radius: 50%; border: 2px solid var(--line);
  border-top-color: var(--ink); transform: rotate(var(--turn, 0deg));
}
.af-ptr.is-ready .af-ptr-spin { border-color: var(--ink); border-top-color: transparent; }
.af-ptr.is-busy .af-ptr-spin { animation: afSpin .7s linear infinite; }
@keyframes afSpin { to { transform: rotate(360deg); } }

/* Свайп строки: дети строки едут за пальцем, под ними — подложка с подписью действия. */
.af-swiping, .af-swipe-back { position: relative; overflow: hidden; }
.af-swiping > :not(.af-swipe-bg), .af-swipe-back > :not(.af-swipe-bg) { transform: translateX(var(--sx, 0px)); }
.af-swipe-back > :not(.af-swipe-bg) { transition: transform .2s cubic-bezier(.2, .8, .2, 1); }
/* Подложка занимает ровно открывшуюся полосу (--sxa = |сдвиг|), а не всю строку: у детей строки
   прозрачный фон, и цвет под ними просвечивал бы сквозь текст. */
.af-swipe-bg {
  position: absolute; top: 0; bottom: 0; width: var(--sxa, 0px); display: flex; align-items: center;
  overflow: hidden; white-space: nowrap; padding: 0 var(--s-4); box-sizing: border-box;
  font-size: var(--fs-sm); font-weight: var(--fw-med, 600);
  color: #fff; background: var(--ink-2); opacity: .55; transition: opacity .12s;
}
.af-swipe-back .af-swipe-bg { transition: width .2s cubic-bezier(.2, .8, .2, 1); }
.af-swipe-bg[data-side="left"] { right: 0; justify-content: flex-end; }
.af-swipe-bg[data-side="right"] { left: 0; justify-content: flex-start; background: var(--ok); }
.af-swipe-ready .af-swipe-bg { opacity: 1; }

/* Оптимистичное удаление строки (data-opt-hide): уезжает и схлопывается сразу по тапу. */
.af-opt-hide {
  max-height: var(--af-h, 80px); overflow: hidden; pointer-events: none;
  animation: afOptHide .26s ease forwards;
}
@keyframes afOptHide {
  40% { opacity: 0; transform: translateX(-24px); max-height: var(--af-h, 80px); }
  100% { opacity: 0; transform: translateX(-24px); max-height: 0; padding-top: 0; padding-bottom: 0; margin: 0; }
}

/* Новый фрагмент после действия пользователя — мягко проявляется, без «мигания» вставки. */
.af-in { animation: afIn .2s ease both; }
@keyframes afIn { from { opacity: .35; } to { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  .af-opt-hide { animation-duration: .01s; }
  .af-swipe-back > :not(.af-swipe-bg) { transition: none; }
}
.af-pending { opacity: .55; transition: opacity .2s; }
