/* ═══════════ ОБЩИЕ КОМПОНЕНТЫ ЭКРАНОВ: ОКНО, ВОПРОС, УВЕДОМЛЕНИЕ, СТЕППЕР, ПОЛЕ ДАТЫ ═══════════
 *
 * Аудит дублей ПК, пп. 14–16 (26.09.2026). До этого файла каждый компонент
 * жил копией в каждом модуле: стили окна – в «Отборе», «Портфолио» и «Моем
 * аккаунте» (О7), степпер – в четырех файлах с разными мерками, свое окно
 * подтверждения и свое уведомление – в «Клиентах» (Кл26). Копии уже
 * разошлись словами и числами. Теперь вид каждого компонента – здесь, один раз.
 *
 * ПОЧЕМУ ОТДЕЛЬНЫМ ФАЙЛОМ, А НЕ В theme.css. «Отбор» и клиентские страницы
 * не подключают theme.css (у них свои копии токенов, см. их шапки), а окна и
 * степперы нужны и им. Файл берет только токены (--surface-*, --text-*,
 * --radius-*), значит работает с любой из копий. Подключается ПОСЛЕ sq.css и
 * theme.css: <link rel="stylesheet" href="/shared/components.css">.
 * Компьютерные поправки окна (по центру, без ручки) – в desktop.css.
 *
 * Поведение: окно и вопрос – shared/dialog.js, уведомление – тост() в
 * shared/ui.js, степпер – степпер() в shared/ui.js, поле даты –
 * shared/datewheel.js; слова пустоты – shared/empty.js (вид – .empty в theme.css).
 * Сторож – tests/components-one-copy.test.mjs. */

/* ── ОКНО ──
   Телефон: «парящая» шторка снизу – не касается краев, скруглена со всех
   сторон, с тенью (правило дизайна 2026-08-04). Разметка:
     <div class="sheetwrap" id="…"><div class="sheet"> … </div></div>
   Шапка с «Отмена» / заголовком / «Готово» – .shead (там, где отмена
   отменяет ввод). Селектор окна – через .sheetwrap: класс .sheet без обертки
   занят страницей документа /d/ (там это колонка листа, а не окно). */
.sheetwrap{position:fixed; inset:0; background:rgba(0,0,0,.45); display:none; align-items:flex-end;
  z-index:30; padding:0 12px calc(12px + env(safe-area-inset-bottom))}
.sheetwrap.show{display:flex}
.sheetwrap .sheet{width:100%; max-width:560px; margin:0 auto; background:var(--surface-card);
  border:0.5px solid var(--border); border-radius:var(--radius-sheet, 24px); padding:12px 16px 16px;
  box-shadow:var(--shadow-sheet, 0 -8px 32px rgba(16,17,20,.12)); box-sizing:border-box}
/* Ручка шторки. Радиус равен половине высоты тела (36x4): это форма самого
   предмета, а не ступень шкалы. */
.sheetwrap .sheet::before{content:''; display:block; width:36px; height:4px; border-radius:2px;
  background:var(--border-strong); margin:0 auto 10px}
.shead{display:flex; justify-content:space-between; align-items:center; margin-bottom:8px}
.shead b{font-size:var(--fs-sub, 15px); color:var(--text-primary)}
.shead button{border:none; background:none; font-size:var(--fs-sub, 15px); cursor:pointer; padding:8px 4px}
.shead .cancel{color:var(--text-secondary)}
.shead .done{color:var(--accent-ink, var(--accent)); font-weight:600}

/* Закрытие на широком окне – ОДНО (КОД_ДИЗАЙНА §13.8, аудит дублей О7):
   крестик в углу, Esc и нажатие мимо. Крестик ставит shared/dialog.js
   (подключитьОкна) каждому окну с атрибутом data-x. Кнопка, которая только
   закрывает («Закрыть», «Отмена» без ввода), помечена data-only-close и на
   широком окне прячется: второй способ закрыть то же самое не нужен.
   «Отмена», которая отменяет ввод, остается и крестика рядом не получает.
   На телефоне все как было: крестик там не рисуется. */
.sheet-x{display:none}
html.shell-wide .sheetwrap[data-x] .sheet{position:relative}
html.shell-wide .sheet-x{position:absolute; top:10px; right:10px; z-index:2; width:36px; height:36px;
  display:inline-flex; align-items:center; justify-content:center; padding:0; border:0;
  border-radius:var(--radius-sm, 12px); background:transparent; color:var(--text-secondary); cursor:pointer}
html.shell-wide .sheet-x:hover{background:var(--surface-sunken); color:var(--text-primary)}
html.shell-wide .sheetwrap [data-only-close]{display:none !important}
/* Заголовок окна с крестиком не залезает под крестик. */
html.shell-wide .sheetwrap[data-x] .sheet > :is(div, p, b):first-of-type{padding-right:40px}

/* ── ВОПРОС ПЕРЕД НЕОБРАТИМЫМ ── (shared/dialog.js, спросить)
   Одно окно подтверждения на приложение (аудит дублей Кл26): удалить,
   очистить, уйти с набранной формой, отключить автопродление. Системный
   confirm() в приложении не показывается (правило Б8.1). Тот же вид у
   вопроса с полем (спроситьЗначение): системного prompt() тоже нет, его
   главное действие – ввод, поэтому «да» залито акцентом всегда.
   Телефон – та же парящая шторка, кнопки друг под другом: сперва действие
   (красное слово на утопленной плитке, КОД_ДИЗАЙНА 9.3), под ним отказ.
   Широкое окно – окно 420 по центру, кнопки в строку справа, безопасный
   ответ залит акцентом и стоит в фокусе. */
.ask-wrap{z-index:96}
.ask-wrap .sheet{max-width:420px}
.ask-h{margin:0 0 8px; font-size:var(--fs-body); line-height:var(--lh-body, 1.35); font-weight:600; color:var(--text-primary)}
/* Текст под заголовком: на телефоне 17, как в прежних шторках «Отбора» и
   «Портфолио» до объединения (владелец, 26.09.2026: 13 серым читалось сноской).
   На широком окне 15 – ступень пояснения (КОД_ДИЗАЙНА §3): заголовок 17
   жирным, текст 15, кнопки 15 – три ступени на окне 420. */
.ask-t{margin:0 0 14px; font-size:var(--fs-body, 17px); line-height:var(--lh-body, 23px); color:var(--text-secondary)}
.ask-t b{color:var(--text-primary)}
.ask-t:empty{display:none}
/* Поле окна: слово для набора (.ask-word) и ответ вопроса с полем (.ask-input,
   спроситьЗначение вместо системного prompt(), 26.09.2026) – одно правило. */
.ask-word, .ask-input{display:block; width:100%; box-sizing:border-box; margin:0 0 12px; padding:12px 14px; font:inherit;
  font-size:var(--fs-body); color:var(--text-primary); outline:none;
  border:none; border-radius:0; background:transparent; border-image:var(--sq-pole) 42 fill / 42px / 14px}
.ask-input::placeholder{color:var(--text-muted)}
.ask-err{margin:-4px 0 12px; font-size:var(--fs-cap); line-height:1.45; color:var(--danger-text)}
.ask-err:empty{display:none}
.ask-btns{display:flex; flex-direction:column; gap:8px}
.ask-btns button{width:100%; min-height:48px; padding:12px 16px; font:inherit; font-size:var(--fs-sub);
  font-weight:600; color:var(--text-primary); cursor:pointer}
html.shell-wide .ask-wrap .sheet{max-width:420px; padding:20px}
html.shell-wide .ask-t{font-size:var(--fs-sub, 15px); line-height:var(--lh-sub, 20px)}
html.shell-wide .ask-btns{flex-direction:row; justify-content:flex-end; gap:12px}
html.shell-wide .ask-btns button{width:auto; min-height:44px; padding:0 20px}
html.shell-wide .ask-btns .ask-no.sq-accent{color:#fff}
/* Вопрос обратимого шага (спросить({ главное: 'да' }), выход с компьютера,
   28.09.2026, холст 138): «да» залито и стоит справа, отказ тихий слева,
   кнопки поровну – как в прототипе. На телефоне – столбцом, «да» первым. */
.ask-btns .ask-yes.sq-accent{color:#fff}
html.shell-wide .ask-lead-yes .ask-btns button{flex:1 1 0}
html.shell-wide .ask-lead-yes .ask-yes{order:2}
/* Строка-переключатель под текстом вопроса (спросить({ выключатель })):
   подпись и пояснение слева, общий переключатель .sw справа. Вся строка
   нажимается. Подложка утопленная, рамки нет (Б12: справочное – плоское). */
.ask-switch{display:flex; align-items:center; justify-content:space-between; gap:16px; width:100%;
  box-sizing:border-box; margin:0 0 16px; padding:12px 16px; border:0; border-radius:var(--radius-md, 16px);
  background:var(--surface-sunken); font:inherit; text-align:left; color:var(--text-primary); cursor:pointer;
  -webkit-tap-highlight-color:transparent}
.ask-switch:focus-visible{outline:none; box-shadow:inset 0 0 0 1.5px var(--accent)}
.ask-switch[disabled]{cursor:default}
.ask-switch-t{min-width:0}
.ask-switch-t b{display:block; font-size:var(--fs-sub, 15px); line-height:var(--lh-sub, 20px); font-weight:600}
.ask-switch-t small{display:block; font-size:var(--fs-cap, 13px); line-height:var(--lh-cap, 18px); color:var(--text-secondary)}
.ask-switch .sw{pointer-events:none}

/* ── УВЕДОМЛЕНИЕ ── (shared/ui.js, тост)
   Одна строка внизу экрана на 4 секунды: там, где действие могло не сработать
   молча, и там, где раньше выскакивал системный alert(). Ответ кнопки на
   собственное нажатие – сама кнопка (правило Б14), не уведомление. */
.toast{position:fixed; left:50%; bottom:calc(88px + env(safe-area-inset-bottom)); z-index:9999;
  transform:translateX(-50%) translateY(14px); box-sizing:border-box; max-width:min(92vw, 420px);
  padding:12px 16px; border-radius:var(--radius-md, 16px); background:rgba(20,20,24,.94); color:#fff;
  font-size:var(--fs-sub, 15px); line-height:1.35; text-align:center; box-shadow:0 8px 28px rgba(0,0,0,.28);
  opacity:0; pointer-events:none; transition:opacity var(--dur-1, .15s), transform var(--dur-1, .15s)}
.toast.show{opacity:1; transform:translateX(-50%)}
/* Широкое окно: над рабочим полем, а не над меню. */
html.shell-wide .toast{left:calc(var(--shell-menu, 0px) + (100vw - var(--shell-menu, 0px)) / 2); bottom:32px; max-width:560px}
/* Уведомление с кнопками (О1 прототипа, 2026-09-28): слова слева, справа
   второе действие и «Отменить» тихими словами акцентом поверх темной подложки.
   Нажимается только кнопка; сама строка мимо нажатий не ловит. */
.toast.acts{display:flex; align-items:center; gap:4px; padding:4px 4px 4px 16px; text-align:left;
  max-width:min(calc(100vw - 32px), 560px); width:max-content}
.toast.acts.show{pointer-events:auto}
html.shell-wide .toast.acts{max-width:680px}
.toast.acts{flex-wrap:wrap; justify-content:flex-end}
/* Текст не уже 200: на телефоне второе действие и «Отменить» уходят строкой
   ниже, справа, а слова не ломаются по слову в столбик. */
.toast-t{flex:1 1 200px; min-width:0; padding:8px 8px 8px 0}
.toast-b{flex:0 0 auto; min-height:44px; padding:0 12px; border:0; border-radius:var(--radius-pill, 999px);
  background:transparent; font:inherit; font-size:var(--fs-sub, 15px); font-weight:600; color:#8FA8FF; cursor:pointer;
  white-space:nowrap; -webkit-tap-highlight-color:transparent}
.toast-b:hover, .toast-b:focus-visible{background:rgba(255,255,255,.12)}
.toast-b:active{scale:var(--press-scale, .96)}

/* ── СТЕППЕР ── (shared/ui.js, степпер)
   Число меняется элементами приложения: кнопками ±, удержанием, протягиванием
   (правило Б8), на широком окне – еще и цифрами. Разметка в странице – одно
   поле: <span class="stepper"><input type="number" …></span>. Кнопки ±
   со знаками набора ставит степпер(). До этого место под них держит отступ
   поля (первый и последний ребенок) – экран не прыгает (правило Б11).
   Кнопка 44: во что попадают пальцем – не меньше 44. */
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none; margin:0}
input[type=number]{-moz-appearance:textfield; appearance:textfield}
.stepper{display:flex; align-items:stretch; border:0.5px solid var(--border-strong); box-sizing:border-box;
  border-radius:var(--radius-sm); overflow:hidden; background:var(--surface-card); touch-action:none; user-select:none}
.stepper button{flex:0 0 44px; width:44px; border:none; background:var(--surface-sunken); color:var(--text-primary);
  line-height:1; cursor:pointer; padding:0}
.stepper button:active{background:var(--surface-hover)}
.stepper button svg{display:block; margin:auto}
.stepper input{flex:1; width:40px; min-width:0; border:none !important; text-align:center; font:inherit;
  font-size:var(--fs-sub); padding:11px 4px; background:transparent; color:var(--text-primary); outline:none; cursor:ew-resize}
.stepper > input:first-child{margin-left:44px}
.stepper > input:last-child{margin-right:44px}
/* Поле, в которое печатают (широкое окно, класс ставит степпер()): курсор
   ввода вместо «тянуть», выделение набранного, рамка акцентом, пока поле в
   работе (заполняемое – светлая подложка и рамка, КОД_ДИЗАЙНА §2). */
.stepper.stepper-typing{user-select:auto; -webkit-user-select:auto; touch-action:auto}
.stepper.stepper-typing input{cursor:text; user-select:text; -webkit-user-select:text}
.stepper.stepper-typing:focus-within{border-color:var(--accent); box-shadow:0 0 0 1px var(--accent)}

/* ── ПОЛЕ ДАТЫ ── (shared/datewheel.js, подключитьПолеДаты)
   Одно поле-кнопка на «Клиентов», «Мой аккаунт» и бриф (аудит дублей Кл25:
   было три обертки). Разметка – поле в обертке:
     <span class="dfield"><input type="text" readonly …></span>
   Значок календаря слева и шеврон справа ставит подключитьПолеДаты; место под
   них держат отступы поля с первого кадра – экран не прыгает (Б11). Ответ –
   полужирным, пустое – подсказкой обычным весом (Б13: поле крупнее вариантов).
   Селектор с [readonly] – сильнее полей страниц (.remnew input[type=text]). */
.dfield{position:relative; display:block}
.dfield > input[type=text][readonly]{padding-left:38px; padding-right:34px; font-size:var(--fs-body, 17px);
  font-weight:600; cursor:pointer}
.dfield > input[type=text][readonly]:placeholder-shown{font-weight:400}
.dfield .dic, .dfield .dch{position:absolute; top:50%; transform:translateY(-50%);
  color:var(--text-muted); pointer-events:none; display:flex}
.dfield .dic{left:13px}
.dfield .dch{right:13px}

/* ── ПУСТОЕ СОСТОЯНИЕ ── один компонент уже есть: .empty в shared/theme.css.
   Слова пустоты по разделам – словарь ПУСТО в shared/empty.js. */

/* ── ЗАСЛОНКА СОГЛАСИЯ ── (аудит путей Н3, 25.09.2026; сюда из theme.css –
   аудит дублей ПК, К16, 26.09.2026). Разметку и логику дает shared/consent.js,
   стили – здесь, одной копией: заслонка встает поверх любого модуля, включая
   «Отбор» на своих токенах. До 26.09 у «Отбора» жила своя отставшая копия
   (без знака приложения, «что изменилось» и «Подробнее»). Экран целиком,
   две галочки, кнопка внизу. Выше всех слоев модуля (у них до 91),
   ниже всплывающих сообщений ui.js. */
.gate{position:fixed; inset:0; background:var(--surface-page); z-index:95; overflow-y:auto;
  display:none; padding:calc(30px + env(safe-area-inset-top)) 22px
                        calc(28px + env(safe-area-inset-bottom))}
.gate.show{display:flex; flex-direction:column; justify-content:center}
.gate h1{font-size:var(--fs-display); letter-spacing:-.3px; margin:0 0 10px}
.gate .appznak{width:56px; height:56px; display:block; margin:0 0 18px; flex:none}
.gate .gsub{font-size:var(--fs-sub); color:var(--text-secondary); line-height:1.55; margin:0 0 22px}
.gate .hintbox{background:var(--surface-sunken); border:none; border-radius:var(--radius-md); padding:13px 15px;
  font-size:var(--fs-cap, 13px); color:var(--text-secondary); line-height:1.5; margin:0 0 18px; text-align:left}
.gate .gcheck{display:flex; gap:12px; align-items:flex-start; padding:16px; cursor:pointer; background:transparent; border:none; border-radius:0; border-image:var(--sq-flat) 42 fill / 42px / 14px}
.gate .gcheck input{width:21px; height:21px; margin:1px 0 0; accent-color:var(--accent); flex:0 0 auto}
.gate .gcheck span{font-size:var(--fs-sub); line-height:1.5}
.gate .gcheck a{color:var(--accent-text)}
.gate .gcheck.gsoft{margin-top:10px; background:none; border-color:var(--border)}
.gate .gcheck.gsoft span{font-size:var(--fs-cap); color:var(--text-secondary)}
.gate .gcheck.gsoft i{display:block; font-style:normal; font-size:var(--fs-cap);
  color:var(--text-muted); margin-top:3px}
.gate .gwhy{font-size:var(--fs-cap); color:var(--text-muted); line-height:1.5; margin:10px 0 0}
/* Подробности спрятаны (09.09.2026): на первом экране остаются суть и две галочки. */
.gate .gmore{margin-top:16px}
.gate .gmore summary{list-style:none; cursor:pointer; font-size:var(--fs-cap); color:var(--accent-text)}
.gate .gmore summary::-webkit-details-marker{display:none}
.gate .gbtn{width:100%; margin-top:20px; padding:14px; color:#fff; font:inherit; font-size:var(--fs-sub); font-weight:650; cursor:pointer; background:transparent; border:none; border-radius:0; border-image:var(--sq-accent) 42 fill / 42px / 14px}
.gate .gbtn:disabled{opacity:.45}

/* ── ОТПРАВИТЬ ССЫЛКУ ── (shared/share-link.js; аудит дублей ПК, О15)
   Одни круги каналов на приложение: окно «Отбора» (с переключателем входа
   клиента) и окно «Портфолио» (страница и подборка). Шапка – по листу
   «Поделиться» iOS 27 (24.09.2026): обложка и название того, что уходит. */
.lnkhead{display:flex; align-items:center; gap:12px; margin-bottom:16px}
.lnkthumb{width:52px; height:52px; flex:0 0 52px; border-radius:var(--radius-sm); object-fit:cover;
  background:var(--surface-sunken)}
.lnkthumb[hidden]{display:none}
.lnkchan{display:flex; gap:4px; margin-top:16px; overflow-x:auto; scrollbar-width:none}
.lnkchan button{flex:0 0 76px; display:flex; flex-direction:column; align-items:center; gap:8px;
  padding:0; border:none; background:none; cursor:pointer; font:inherit; font-size:var(--fs-cap);
  color:var(--text-primary); -webkit-tap-highlight-color:transparent}
.lnkchan button[hidden]{display:none}
.lnkchan button > span:last-child{max-width:76px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.lnkic2{width:52px; height:52px; display:grid; place-items:center; color:var(--accent-ink, var(--accent))}
.lnkhead + .lnkchan{margin-top:0}

/* ── ОБЛОЖКА ИЗ РАБОТ ── (shared/cover-pick.js; аудит дублей ПК, О21)
   Одно окно сеткой для обложки проекта и обложки услуги. */
.cover-pick-h{font-size:var(--fs-sub); font-weight:600; margin-bottom:10px; color:var(--text-primary)}
.cover-pick{display:grid; grid-template-columns:repeat(4, 1fr); gap:6px; max-height:52vh; overflow-y:auto}
.cover-pick img{width:100%; aspect-ratio:1; object-fit:cover; border-radius:var(--radius-sm); cursor:pointer;
  background:var(--surface-sunken); display:block}
.cover-pick img.cur{outline:2px solid var(--accent); outline-offset:-2px}

/* ── НАЗВАНИЕ И ЧИСЛО ── (правило Б19.1, 27.09.2026; сюда из theme.css)
   «Выбранные 12», «Все проекты 9», «Прошлые круги 2». Точки-разделителя нет:
   число – отдельным элементом, вторичным цветом, цифрами одной ширины, чтобы
   счет не дергал подпись. На залитой кнопке – полупрозрачностью. Жил в theme.css,
   и в «Отборе» (theme.css не подключен) счет прилипал к слову: «Прошлые круги2».
   Страница автора /p/ этот лист не грузит (.empty и .show у нее свои) и держит
   ту же мерку строкой у себя. */
.cnt{margin-left:.4em; color:var(--text-secondary); font-variant-numeric:tabular-nums}
.sq-accent .cnt{color:inherit; opacity:.72}

/* ── СТАТИСТИКА СТРОКАМИ ── (26.09.2026, аудит дублей ПК, О9)
   Карточка строк «название – число»: статистика «Отбора» и статистика страницы
   «Портфолио». Жила в «Отборе»; второй экран получил ее отсюда, копии нет. */
.stcard{background:var(--surface-card); border:0.5px solid var(--border); border-radius:var(--radius-md);
  padding:2px 15px; margin-bottom:14px}
.stkv{display:flex; justify-content:space-between; gap:12px; padding:11px 0; font-size:var(--fs-sub);
  border-top:0.5px solid var(--border)}
.stkv:first-child{border-top:none}
.stkv b{font-variant-numeric:tabular-nums; white-space:nowrap}
