/* Мастерская, раздел «Восстановить фото». fotocvet.ru
   Все селекторы начинаются с .pb-ws. Глобальных правил нет, @import нет, @font-face нет.
   Шрифты берутся из токенов сайта (tokens.css): --sans (Inter) для текста, --font-head (Onest)
   для заголовков. Lora из макета не подключается - решение спеки § 2. */

.pb-ws *{box-sizing:border-box}

.pb-ws{
  /* токены витрины, объявлены локально и наружу не выходят */
  --pb-ink:#2e1c12;
  --pb-muted:#7d6f64;
  --pb-bg:#fafaf7;
  --pb-band:var(--bg);
  --pb-accent:#b8652a;
  --pb-accent-hover:#a4571f;
  --pb-accent-soft:#fdf6ee;
  --pb-accent-band:#f9efe1;
  --pb-line:rgba(46,28,18,.09);
  --pb-ok:#2e8540;

  /* шрифты - токены сайта из tokens.css */
  --pb-font:var(--sans);
  --pb-font-display:var(--font-head);

  /* геометрия экрана: три числа, от которых считается всё остальное */
  --pb-menu:264px;   /* меню слева */
  --pb-panel:372px;  /* колонка настроек справа */
  --pb-canvas:520px; /* высота кадра и панели */

  font-family:var(--pb-font);
  color:var(--pb-ink);
}

/* Экран состояния. В бою на странице остаётся один - остальные снимаются разметкой,
   не скрываются: скрытый экран всё равно грузил бы свои картинки. */
.pb-ws__screen[hidden]{display:none}

/* Общий [hidden] внутри .pb-ws: атрибут-селектор + класс .pb-ws даёт специфичность выше, чем у
   одиночного класса компонента (.pb-ws__offer-card{display:grid} и т.п.) - без этого правила
   UA-стиль [hidden]{display:none} проигрывает авторскому классу по origin, а не по специфичности
   (найдено на плитке welcome в data-panel="zero": hidden=true, но текст всё равно рендерился). */
.pb-ws [hidden]{display:none}

/* Рамка экрана. В макете была карточка 1440 с тенью, здесь - обычный фон страницы. */
.pb-ws__frame{background:var(--pb-bg)}

/* Переключатель «Было / Стало» поверх кадра. Механизм подсветки — канонический
   .ba-tabs: пилюля ::before ездит по АТРИБУТУ data-active, цвет активной подписи даёт
   .is-active (оба пишет setActive() в script.js). Свой класс is-on здесь был мёртвым:
   JS его не трогал, белая заливка намертво висела на «Стало» поверх кремовой
   канонической пилюли — на экране горели две подсветки сразу. Модификатор задаёт
   только вид поверх фото: тёмная подложка, белая пилюля, тач-цель 44px. */
.pb-ws__ba{position:absolute;left:50%;top:16px;transform:translateX(-50%);width:auto;max-width:none;height:auto;margin:0;padding:3px;gap:0;border:0;border-radius:99px;background:rgba(28,20,16,.58)}
/* .ws-view задаёт кремовую пилюлю всем .ba-tabs страницы — поверх фото нужна белая,
   поэтому селектор той же силы (два класса). */
.ws-view .ba-tabs.pb-ws__ba::before{top:3px;bottom:3px;left:3px;width:calc(50% - 3px);background:#fff;border:0;box-shadow:var(--shadow-1)}
/* Пилюля ::before ровно в половину полосы, значит и кнопки строго 50/50: без min-width:0
   более длинное «Стало» распирало свою половину (91px против 84px) и пилюля не совпадала
   с подписью. Поле 16px держит «Стало» уже половины, текст в пилюлю попадает целиком. */
.pb-ws__ba-btn{flex:1 1 50%;min-width:0;min-height:38px;padding:0 14px;border-radius:99px;color:rgba(255,255,255,.86);font:600 15.5px/1 var(--pb-font),system-ui,sans-serif;transition:color .18s ease}
.pb-ws__ba-btn.is-active{color:var(--pb-ink)}

/* ===== Каркас экрана ===== */
.pb-ws__header{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;padding:0 28px;height:66px;background:#fff;border-bottom:1px solid var(--pb-line)}
.pb-ws__body{display:grid;grid-template-columns:var(--pb-menu) 1fr}
.pb-ws__content{display:grid;gap:0}
.pb-ws__layout{display:grid;grid-template-columns:minmax(0,1fr) var(--pb-panel);gap:28px;align-items:start}
/* Узкий экран: панель уходит под кадр и растягивается на всю ширину, высоты становятся
   свободными — фиксированные 520 на узком дают обрезанное содержимое, а не компактность.
   Порог 1180 = меню 264 + кадр не уже 480 + панель 372 + зазоры. */
@media (max-width:1180px){
  .pb-ws__layout{grid-template-columns:minmax(0,1fr);gap:20px}
  .pb-ws__panel{height:auto;min-height:0;overflow:visible}
  .pb-ws__dropzone,.pb-ws__canvas{height:min(var(--pb-canvas),56vh)}
  /* Код ужимается вместе с областью: при 56vh на невысоком экране прежние 220px вместе с
     подписями и кнопками не влезали и панель прокручивалась внутри себя. Высоту держит сама
     дропзона — панель лишь начинка. */
  .pb-ws__qr-img{width:min(220px,34vh);height:min(220px,34vh)}
  .pb-ws__qr-wait{width:min(240px,36vh);height:min(240px,36vh)}
}
/* Совсем узко: поля рабочей области по мобильному канону, панель без внешних полей */
@media (max-width:820px){
  .pb-ws__main{padding:12px var(--gutter,16px) 24px}
  .pb-ws__panel{padding:18px}
}
.pb-ws__main{padding:20px 44px 44px}

/* ===== Шапка ===== */
.pb-ws__logo{font:600 21px/1 var(--pb-font-display);letter-spacing:-.2px}
.pb-ws__nav{display:flex;align-items:center;gap:22px}
.pb-ws__nav-item{font:500 15px/1 var(--pb-font);color:#5c4e3f}
.pb-ws__nav-item--active{font:600 15px/1 var(--pb-font);color:var(--pb-ink);border-bottom:2px solid var(--pb-accent);padding-bottom:3px}
.pb-ws__header-right{display:flex;align-items:center;justify-content:flex-end;gap:14px}
.pb-ws__header-right--guest{display:flex;justify-content:flex-end}
.pb-ws__balance-pill{display:inline-flex;align-items:center;gap:9px;padding:8px 11px;border:1px solid rgba(184,101,42,.32);border-radius:99px;background:var(--pb-accent-soft)}
.pb-ws__balance-pill--plain{border-color:rgba(46,28,18,.16);background:#fff}
.pb-ws__balance-count{font:600 15px/1 var(--pb-font)}
.pb-ws__balance-count--plain{color:#5c4e3f}
.pb-ws__balance-add{display:inline-grid;place-items:center;width:23px;height:23px;border-radius:99px;background:var(--pb-accent);color:#fff;font:700 16px/1 var(--pb-font)}
.pb-ws__avatar{display:inline-grid;place-items:center;width:38px;height:38px;border-radius:99px;background:var(--pb-band);font:600 15px/1 var(--pb-font);color:#5c4e3f}
.pb-ws__status-pill{display:inline-flex;align-items:center;gap:9px;padding:8px 12px;border:1px solid rgba(184,101,42,.32);border-radius:99px;background:var(--pb-accent-soft)}
.pb-ws__timer-pill{display:inline-flex;align-items:center;gap:7px;padding:6px 10px;border-radius:99px;background:#fff;border:1px solid rgba(184,101,42,.32)}
/* Табличные цифры (правка оператора 10.09). Цифры в основном шрифте разной ширины, и текст
   таймера дышал посекундно: 23:59:26 -> 23:59:25 меняло ширину плашки 106.42 -> 106.00px, а
   шапка выровнена по space-between, поэтому таймер полз сам по себе. Оператор заметил это на
   сворачивании - ход просто делает дрожание видимым, но причина не в нём. */
/* Табличные цифры (правка оператора 10.09). Цифры в основном шрифте разной ширины, и текст
   таймера дышал посекундно: 23:59:26 -> 23:59:25 меняло ширину плашки 106.42 -> 106.00px, а
   шапка выровнена по space-between, поэтому таймер полз сам по себе. Оператор заметил это на
   сворачивании - ход просто делает дрожание видимым, но причина не в нём. */
.pb-ws__timer-text{font:700 15px/1 var(--pb-font);color:var(--pb-ink);font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1}
.pb-ws__dot{width:9px;height:9px;border-radius:99px;background:var(--pb-accent);display:block}
.pb-ws__btn-login{min-height:42px;padding:0 18px;border:1px solid rgba(46,28,18,.18);border-radius:99px;background:#fff;font:600 15px/1 var(--pb-font);color:var(--pb-ink);cursor:pointer}

/* ===== Боковое меню ===== */
.pb-ws__menu{padding:20px 14px;border-right:1px solid rgba(46,28,18,.08);display:grid;gap:4px;align-content:start}
.pb-ws__menu-item{display:flex;align-items:center;gap:12px;padding:11px 12px;border-radius:11px}
.pb-ws__menu-item--active{background:var(--pb-accent-band)}
.pb-ws__menu-icon{font-family:'uicons-fc';font-size:20px;line-height:1;color:var(--pb-muted);flex:0 0 22px;text-align:center}
.pb-ws__menu-icon--active{color:var(--pb-accent)}
.pb-ws__icon-slot{flex:0 0 22px}
.pb-ws__menu-lock-slot{flex:0 0 14px}
.pb-ws__menu-chevron{flex:0 0 15px}
.pb-ws__label-stack{display:grid;gap:2px}
.pb-ws__label-stack--grow{flex:1 1 auto}
.pb-ws__label-stack--tight{gap:3px}
.pb-ws__menu-title{font:600 15.5px/1.2 var(--pb-font)}
.pb-ws__menu-title--active{color:var(--pb-ink)}
.pb-ws__menu-sub{font:400 14px/1.3 var(--pb-font);color:var(--pb-muted)}
.pb-ws__menu-sub--nowrap{white-space:nowrap}
.pb-ws__menu-badge{display:inline-grid;place-items:center;min-width:22px;height:22px;padding:0 6px;border-radius:99px;background:var(--pb-accent);color:#fff;font:700 13px/1 var(--pb-font);flex:0 0 auto}
.pb-ws__menu-divider{margin:14px 2px 10px;height:1px;background:rgba(46,28,18,.1)}

/* ===== Кнопки ===== */
.pb-ws__btn-topup{margin-top:6px;display:flex;align-items:center;justify-content:center;gap:8px;min-height:46px;border:1.5px solid rgba(184,101,42,.45);border-radius:11px;background:var(--pb-accent-soft);color:var(--pb-ink);font:600 15.5px/1 var(--pb-font);cursor:pointer}
.pb-ws__next-row{display:grid;grid-template-columns:1fr 1fr;gap:10px;width:100%}
.pb-ws__suggest-card--compact{min-height:56px;padding:0 14px;gap:10px;text-decoration:none;color:inherit;min-width:0}
.pb-ws__suggest-card--compact .pb-ws__menu-title{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:15.5px}
.pb-ws__suggest-card--compact .pb-ws__cost-inline{flex:0 0 auto}
.pb-ws__suggest-card--accent{border-color:var(--pb-accent)}
.pb-ws__suggest-card--accent .pb-ws__menu-title{color:var(--pb-accent)}
/* Замок гостя на кнопках ряда результата («Поделиться», «Следующее»): раскладка та же, что
   после регистрации, недоступность читается цветом. Палитра - та же пара, что у погашенной
   главной кнопки (.pb-ws__btn-primary--disabled): #ece8e0 фон, #a2988c текст.
   Курсор оставляем default, но pointer-events НЕ снимаем: замок обязан ловить наведение
   (подсказка объясняет причину) и клик (ведёт в регистрацию), см. _setGuestLockedBtn. */
.pb-ws__suggest-card--locked{border-color:rgba(46,28,18,.10);background:var(--paper-3);color:#a2988c;cursor:default}
.pb-ws__suggest-card--locked .pb-ws__menu-title{color:#a2988c}
.pb-ws__suggest-card--locked .pb-ws__suggest-icon{color:#b8ada0}
.pb-ws__suggest-icon svg{width:21px;height:21px;display:block}
.pb-ws__btn-primary{min-height:58px;border:0;border-radius:12px;background:var(--pb-accent);color:#fff;font:600 18.5px/1 var(--pb-font);cursor:pointer}
/* Цена операции на кнопке: искры и число. В боевой раскладке она живёт строкой под кнопкой
   (#restoreMetaLine), поэтому узел скрыт; показывает его только нижняя полоса.
   Гостю списывать нечего - искры прячем, остаётся слово «бесплатно». */
.pb-ws__btn-cost{display:none}
/* Искры и число выравниваются по ЦЕНТРУ, а не по базовой линии: svg - строчный элемент, и по
   умолчанию он садится на baseline цифры, отчего знак висел выше неё. Плюс сдвиг на пол-пикселя
   вниз: сами звёзды нарисованы в верхней части квадрата 24x24 (нижний край на 19.5), их
   оптический центр выше геометрического (замер 04.09, замечание оператора). */
.pb-ws__btn-cost{align-items:center;gap:5px;line-height:1}
/* Сдвиг на 1.5px, а не на пол-пикселя: глаз ведёт КРУПНАЯ звезда, её центр в квадрате 24x24
   стоит на 9 при геометрическом центре 12, то есть выше на четверть высоты знака. По числам
   всё сходилось и раньше, по виду цифра оставалась приподнятой. */
.pb-ws__btn-cost .restore-spark{width:17px;height:17px;transform:translateY(1.5px)}
.pb-ws__btn-cost #restoreBtnCostNum{font-weight:600;font-size:15px}
.pb-ws__btn-cost[data-free="1"] .restore-spark{display:none}
/* Тот же знак в подписи карточки действия («Оживить этот снимок») */
.pb-ws__cost-inline{display:inline-flex;align-items:center;gap:4px;vertical-align:-3px}
.pb-ws__cost-inline .restore-spark{width:15px;height:15px;color:var(--pb-accent)}
.pb-ws__cost-inline b{font-weight:700;color:var(--pb-ink)}
/* Мерцание на карточке действия - то же правило, что у кнопок (workshop.html:2581) */
.pb-ws__suggest-card:hover .rs-star{animation:restoreTwinkle 1.1s ease-in-out infinite}
.pb-ws__suggest-card:hover .rs-star-2{animation-delay:.18s}
.pb-ws__suggest-card:hover .rs-star-3{animation-delay:.36s}
.pb-ws__btn-primary--icon{display:flex;align-items:center;justify-content:center;gap:10px}
.pb-ws__btn-primary--disabled{border:0;background:var(--paper-3);color:#a2988c;cursor:default}
.pb-ws__btn-primary:disabled{background:var(--paper-3);color:#a2988c;cursor:default}
.pb-ws__btn-ghost{min-height:46px;padding:0 16px;border:1px solid rgba(46,28,18,.16);border-radius:11px;background:#fff;color:var(--pb-ink);font:600 15px/1 var(--pb-font);cursor:pointer}
.pb-ws__btn-ghost-lg{min-height:58px;border:1px solid rgba(46,28,18,.16);border-radius:12px;background:#fff;color:var(--pb-ink);font:600 17px/1 var(--pb-font);cursor:pointer}
.pb-ws__btn-accent-sm{min-height:52px;border:0;border-radius:11px;background:var(--pb-accent);color:#fff;font:600 17px/1 var(--pb-font);cursor:pointer}
.pb-ws__btn-accent-sm--ghost{border:1.5px solid rgba(184,101,42,.45);background:var(--pb-accent-soft);color:var(--pb-ink)}
.pb-ws__btn-upload{min-height:56px;padding:0 26px;border:0;border-radius:12px;background:var(--pb-accent);color:#fff;font:600 17.5px/1 var(--pb-font);cursor:pointer;display:inline-flex;align-items:center;gap:10px}
.pb-ws__btn-upload--ghost{border:1.5px solid rgba(184,101,42,.45);background:var(--pb-accent-soft);color:var(--pb-ink)}
.pb-ws__btn-overlay{display:inline-flex;align-items:center;gap:8px;min-height:42px;padding:0 15px;border:0;border-radius:99px;background:rgba(28,20,16,.72);color:#fff;font:600 15px/1 var(--pb-font);white-space:nowrap;cursor:pointer}
.pb-ws__btn-suggest-color{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:48px;padding:0 14px;border:1px dashed rgba(46,28,18,.2);border-radius:12px;background:var(--pb-bg);color:#5c4e3f;font:500 15.5px/1.3 var(--pb-font);cursor:pointer;text-align:left}
.pb-ws__btn-row{display:grid;grid-template-columns:1fr 1fr;gap:9px}

/* ===== Панель настроек справа ===== */
.pb-ws__panel{height:var(--pb-canvas);box-sizing:border-box;display:grid;gap:18px;padding:26px;background:#fff;border:1px solid var(--pb-line);border-radius:16px}
.pb-ws__panel--main{grid-template-rows:1fr auto}
.pb-ws__panel--summary{grid-template-rows:auto 1fr}
.pb-ws__panel-top{display:grid;gap:14px;align-content:start}
.pb-ws__panel-top--tight{gap:13px}
.pb-ws__panel-bottom{display:grid;gap:14px;align-content:start;padding-top:18px;border-top:1px solid var(--pb-line)}
.pb-ws__panel-bottom--gap13{gap:13px}
.pb-ws__panel-bottom--gap12{gap:12px}
.pb-ws__panel-bottom--gap11{gap:11px}
.pb-ws__panel-heading{margin:0;font:600 17px/1.3 var(--pb-font)}
.pb-ws__panel-heading--muted{color:var(--pb-muted)}
.pb-ws__panel-heading-row{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap}
.pb-ws__cta-group{display:grid;gap:11px}
.pb-ws__meta-row{display:flex;align-items:center;justify-content:space-between;gap:12px}
.pb-ws__meta-line{display:flex;align-items:center;justify-content:center;gap:9px;flex-wrap:wrap;font:500 15px/1.4 var(--pb-font);color:#5c4e3f}
.pb-ws__meta-line-alt{font:500 15px/1.4 var(--pb-font);color:#5c4e3f}
.pb-ws__meta-sep{color:var(--paper-4)}
/* Иконка времени под кнопкой (06.09, § 4 приёмки): снята дословно с прода (fotocvet.ru/
   workshop.html, .restore-meta) - тот же svg, декоративная (aria-hidden в разметке), цвет
   наследуется от строки. Обёртка держит иконку и текст одним flex-item с меньшим gap, чем
   у остальных сегментов строки (9px, разделяет смысловые части) - иконка относится именно
   к времени, а не ко всей строке. */
.pb-ws__meta-time{display:inline-flex;align-items:center;gap:4px}
.pb-ws__meta-icon{width:15px;height:15px;flex:0 0 15px}
.pb-ws__hint-sm{margin:0;text-align:center;font:400 14px/1.45 var(--pb-font);color:var(--pb-muted)}
.pb-ws__note-sm{font:400 14px/1.4 var(--pb-font);color:#5c4e3f}
.pb-ws__note-center{margin:0;text-align:center;font:400 15px/1.45 var(--pb-font);color:var(--pb-muted);text-wrap:pretty}
.pb-ws__body-text{margin:0;font:400 15px/1.5 var(--pb-font);color:#5c4e3f;text-wrap:pretty}
.pb-ws__body-text-lh155{margin:0;font:400 15px/1.55 var(--pb-font);color:#5c4e3f;text-wrap:pretty}
.pb-ws__login-hint{margin:0;text-align:center;font:500 15px/1.4 var(--pb-font);color:#5c4e3f}
.pb-ws__success-row{display:flex;align-items:center;gap:9px}
.pb-ws__success-text{margin:0;font:600 16px/1.35 var(--pb-font);color:var(--pb-ok)}
.pb-ws__check-circle{display:inline-grid;place-items:center;width:22px;height:22px;border-radius:99px;background:var(--pb-ok);color:#fff;flex:0 0 22px}

/* ===== Кадр (канвас) ===== */
.pb-ws__canvas{height:var(--pb-canvas);box-sizing:border-box;position:relative;border:1px solid rgba(46,28,18,.1);border-radius:16px;background:var(--bg);overflow:hidden}
.pb-ws__canvas-img{display:block;width:100%;height:100%;object-fit:contain}
.pb-ws__canvas-img--cover{object-fit:cover}
.pb-ws__canvas-img--dim{filter:brightness(.72)}
/* Плашки прижаты к правому верхнему углу ОКНА кадра (правка 14.09, решение оператора).
   До зума они висели на углу самой фотографии — это было верно, пока фотография стояла
   неподвижно. С появлением масштабирования снимок растекается за края окна и ездит под
   курсором, и значки на его углу уплывали вместе с ним: на 200% оказывались посреди кадра,
   при сдвиге ушли бы за край совсем (замер scripts/tmp/_diag-icons-on-zoom.mjs).
   Привязка к окну — то же решение, что у лупы ниже, и то же, что у Figma, Miro, Canva.
   z-index нужен, потому что увеличенная фотография рисуется в своём слое из-за transform.
   Перенос строки в ряду сохранён: текст внутри пилюли не переносится (white-space:nowrap
   у .pb-ws__btn-overlay - иначе «Обрезать и повернуть» ломалось на две строки внутри пилюли
   высотой 42 и вылезало за неё), а ряд, когда не влезает по ширине, переносит ЦЕЛУЮ плашку
   на вторую строку и прижимает её вправо. */
.pb-ws__canvas-actions{position:absolute;top:14px;right:14px;display:flex;flex-wrap:wrap;justify-content:flex-end;gap:9px;z-index:2}
.pb-ws__btn-overlay-label{white-space:nowrap}
/* Дубли основных действий (обрезать/заменить/скачать) значками поверх правого верхнего угла
   САМОЙ фотографии — .pb-ws__canvas-actions уже меряет этот угол, здесь только сама кнопка.
   Мобильный канон: тач-цель 44px, радиус ≤ четверти высоты (11px, никакого pill). Подложка
   тёмная полупрозрачная — читается на любом фото; наведение/нажатие слоем на --on-dark-*
   (tokens.css: --hover-layer рассчитан на светлый фон и на фото не виден, см. коммент там же). */
/* Подложка .78, а не .55: с 14.09 значки стоят в углу ОКНА кадра и при фото меньше окна
   ложатся на светлое бежевое поле (#efe9e0), где полупрозрачная подложка выцветает.
   Замер scripts/tmp/_shot-zoom-final.mjs: при .55 белый значок давал 4.60:1 — норма 4.5
   впритык; при .78 → 9.71:1. На самой фотографии читаемость и так была в порядке. */
.pb-ws__frame-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;border:0;border-radius:11px;background:rgba(28,20,16,.78);color:#fff;cursor:pointer;position:relative;isolation:isolate}
.pb-ws__frame-icon-btn::before{content:"";position:absolute;inset:0;border-radius:inherit;background:var(--on-dark-hover);opacity:0;transition:opacity .16s ease}
@media (hover: hover){.pb-ws__frame-icon-btn:hover::before{opacity:1}}
.pb-ws__frame-icon-btn:active::before{background:var(--on-dark-press);opacity:1}
.pb-ws__frame-icon-btn svg{position:relative;width:20px;height:20px}
@media (prefers-reduced-motion: reduce){.pb-ws__frame-icon-btn::before{transition:none}}
/* Заливка ОДНА с .pb-ws__canvas (правка оператора 06.09). Кадр - это два элемента, которые
   подменяют друг друга по состоянию: пустой - дропзона, выбранное фото - канвас. Коробка у
   них совпадает до пикселя (замер 06.09: 322x90, 770x507 в обоих), а цвет был разный (белый
   против #efe9e0) - человек видел, что при загрузке фото «поменялся фон». Оба числа приехали
   02.09 из макета и между собой не сверялись. Токена под цвет не заводим: он живёт в двух
   местах этого файла и меняется парой (см. .pb-ws__canvas). */
.pb-ws__dropzone{height:var(--pb-canvas);box-sizing:border-box;border:1.5px dashed rgba(184,101,42,.42);border-radius:16px;background:var(--bg);display:grid;gap:20px;place-content:center;justify-items:center;padding:32px}
/* Заголовок над парой кнопок. Крупнее подсказки внизу и тем же весом, что заголовки панели:
   он называет действие, а подсказка под кнопками — условия. */
.pb-ws__dropzone-title{margin:0;text-align:center;font:600 20px/1.3 var(--pb-font);color:var(--pb-ink)}
.pb-ws__dropzone-actions{display:flex;gap:12px}
.pb-ws__dropzone-hint{margin:0;text-align:center;font:400 15px/1.5 var(--pb-font);color:var(--pb-muted)}
@media (max-width:560px){
  /* Две кнопки рядом на телефоне не помещаются по ширине тач-цели: в колонку, каждая во всю
     ширину. Мобильный канон — CONVENTIONS.md § Мобильный канон. */
  .pb-ws__dropzone-actions{flex-direction:column;width:100%;max-width:320px}
  .pb-ws__dropzone-actions .pb-ws__btn-upload{justify-content:center;width:100%}
}

/* Начинка дропзоны и панель кода — две сменные НАЧИНКИ одной коробки (правка оператора 20.09).
   Раскладку по центру задаёт сама .pb-ws__dropzone (grid + place-content:center), поэтому обе
   начинки только выстраивают свои элементы столбиком. Ни рамки, ни фона, ни высоты у них нет:
   граница области фотографии принадлежит дропзоне и при показе кода не меняется. */
.pb-ws__dropzone-body,.pb-ws__qr{display:grid;justify-items:center;gap:20px}
/* Панель кода «залить кадр с телефона» (qr-mobile-upload-workshop-spec.md §5.0). */
.pb-ws__qr{gap:14px}
/* Пояснение над кодом. Первая строка — заголовком, вторая приглушена: главное здесь «сразу
   сюда», а «быстрее» лишь довод. Тот же вес, что у .pb-ws__dropzone-title, которую панель
   закрывает: на её месте и стоит. */
.pb-ws__qr-lead{margin:0;text-align:center;font:600 20px/1.3 var(--pb-font);color:var(--pb-ink);max-width:38ch}
.pb-ws__qr-lead span{display:inline-block;margin-top:6px;font:400 15px/1.5 var(--pb-font);color:var(--pb-muted)}
/* Канвас рисует shared/qr-code.js в своих пикселях; размер на экране задаём здесь, иначе
   картинка кода прыгала бы вместе с плотностью экрана. Белая подложка обязательна: код читается
   сканером по контрасту, а фон дропзоны бежевый. */
.pb-ws__qr-img{width:220px;height:220px;background:#fff;border-radius:12px;padding:10px;box-sizing:content-box}
.pb-ws__qr-hint{margin:0;text-align:center;font:600 17px/1.4 var(--pb-font);color:var(--pb-ink)}
.pb-ws__qr-life{margin:0;text-align:center;font:400 15px/1.4 var(--pb-font);color:var(--pb-muted);min-height:21px}
.pb-ws__qr-acts{display:flex;gap:10px;margin-top:4px}
/* Обратный путь к загрузке с компьютера. Тон и размер — как у .pb-ws__dropzone-hint, которую
   панель на время закрывает: это та же по весу подсказка, а не второе действие. */
.pb-ws__qr-alt{margin:0;text-align:center;font:400 15px/1.5 var(--pb-font);color:var(--pb-muted)}
.pb-ws__qr-alt-link{border:0;background:none;padding:0;font:inherit;color:var(--pb-accent);text-decoration:underline;cursor:pointer}
.pb-ws__qr-alt-link:hover{color:var(--pb-accent-hover)}
/* 44px — тач-цель канона; кнопки вторичные, поэтому рамкой, а не заливкой: заливка здесь
   спорила бы с акцентом «Выбрать фото» на соседнем экране. */
.pb-ws__qr-act{min-height:44px;padding:0 18px;border:1.5px solid rgba(184,101,42,.45);border-radius:12px;background:transparent;color:var(--pb-ink);font:600 16px/1 var(--pb-font);cursor:pointer}
.pb-ws__qr-act:hover{background:var(--pb-accent-soft)}
/* Ожидание кадра: код уже сняли. Занимает место картинки, поэтому размер тот же — иначе
   панель схлопывалась бы по высоте в момент, когда человек смотрит на телефон. */
.pb-ws__qr-wait{width:240px;height:240px;display:grid;gap:8px;place-content:center;justify-items:center;text-align:center;font:400 16px/1.45 var(--pb-font);color:var(--pb-muted)}
.pb-ws__qr-wait b{font:600 18px/1.3 var(--pb-font);color:var(--pb-ink)}
.pb-ws__qr-pulse{width:44px;height:44px;border-radius:50%;background:var(--pb-accent);opacity:.25;animation:pb-ws-qr-pulse 1.6s ease-in-out infinite}
@keyframes pb-ws-qr-pulse{0%,100%{transform:scale(.8);opacity:.2}50%{transform:scale(1.15);opacity:.45}}
/* Вестибулярные расстройства: пульсация — украшение, смысл несут слова рядом. */
@media (prefers-reduced-motion:reduce){.pb-ws__qr-pulse{animation:none}}

/* Прогресс обработки поверх кадра — карточка снята (правка 06.09→разметка 1:1 с продом,
   #restorePopup.restore-popup без обёртки); ниже остатки старой формы, HTML их больше не
   ставит. */
.pb-ws__progress-stage{margin:0;font:600 14px/1.35 var(--pb-font),system-ui,sans-serif;color:#6a5a48;text-align:center}
.pb-ws__progress-track{width:100%;height:6px;border-radius:3px;background:var(--bg-alt);overflow:hidden}
/* .pb-ws__progress-fill: ширину задаёт ws.js инлайн-стилем (width в %), здесь только статичная часть */
.pb-ws__progress-fill{display:block;height:100%;background:#ffc95c}
.pb-ws__progress-title{margin:0;font:600 19px/1.3 var(--pb-font);color:#2a201a;text-align:center}
.pb-ws__progress-sub{margin:0;text-align:center;font:400 14px/1.4 var(--pb-font);color:#8a7864;font-variant-numeric:tabular-nums}

/* Список шагов "что происходит" в панели работы */
.pb-ws__progress-step{display:flex;align-items:center;gap:11px}
.pb-ws__progress-step--idle{opacity:.55}
.pb-ws__progress-step-label{font:500 15.5px/1.35 var(--pb-font);color:#5c4e3f}
.pb-ws__progress-step-label--active{color:var(--pb-ink)}
.pb-ws__progress-step-label--current{font:600 15.5px/1.35 var(--pb-font);color:var(--pb-ink)}
.pb-ws__progress-dot-slot{display:inline-grid;place-items:center;width:26px;height:26px;border-radius:99px;background:var(--pb-band);flex:0 0 26px}
.pb-ws__progress-dot-slot--done{background:var(--pb-ok);color:#fff}
.pb-ws__progress-dot-slot--active{background:var(--pb-accent-band)}

/* ===== Выбор цвета (состояние "фото выбрано") ===== */
.pb-ws__choice-list{display:grid;gap:10px}
.pb-ws__choice-btn{display:flex;align-items:center;gap:13px;padding:10px;border:1px solid rgba(46,28,18,.14);border-radius:12px;background:#fff;text-align:left;cursor:pointer}
.pb-ws__choice-btn--selected{border:2px solid var(--pb-accent);background:var(--pb-accent-soft)}
/* Иконка режима в карточке. Сами значки (палитра и круг с закрашенной половиной) взяты
   один-в-один с боевого тумблера (.mode-opt-ic, workshop.html на main) - второго рисунка для
   тех же двух режимов не заводим. Миниатюры кадра, стоявшие тут до 06.09, сняты: обе брали
   ОДИН кадр, «Ч/Б» отличался фильтром, и на чёрно-белом исходнике карточки были одинаковы. */
.pb-ws__choice-ic{width:22px;height:22px;flex:0 0 22px;color:var(--pb-muted);transition:color .15s}
.pb-ws__choice-title{font:600 16px/1.2 var(--pb-font)}
.pb-ws__choice-sub{font:400 14px/1.4 var(--pb-font);color:var(--pb-muted)}

/* ===== Переключатель режима «Раскрасить / Ч/Б» (полоса действий, оформление пакета
   Claude Design, 09.09) ===== Только #modeToggleBtn - карточки «Оживления» (#animStyleList)
   остались на .pb-ws__choice-list/.pb-ws__choice-btn выше и этот блок не задевают.
   Цвета - токены канона (tokens.css), не литералы пакета: дорожка var(--track), кноб
   var(--card), акцент var(--pick-ink) - у Мастерской своего --pick-* пакет не переопределяет
   (data-brand не объявлен), см. комментарий в tokens.css у --pick-ink. */
.pb-ws__toggle{position:relative;display:flex;gap:3px;padding:3px;
  background:var(--track);border:1px solid rgba(46,28,18,.08);border-radius:12px}
.pb-ws__knob{position:absolute;top:3px;bottom:3px;left:3px;width:calc(50% - 4.5px);
  border-radius:9px;background:var(--card);box-shadow:var(--shadow-1);
  transition:left 220ms cubic-bezier(.2,.8,.2,1);pointer-events:none}
.pb-ws__toggle[data-mode="bw"] .pb-ws__knob{left:calc(50% + 1.5px)}
.pb-ws__seg{position:relative;flex:1 1 0;min-width:0;display:flex;align-items:center;
  justify-content:center;gap:8px;border:0;border-radius:9px;background:transparent;
  cursor:pointer;color:var(--muted);font:600 16px/1 var(--pb-font);white-space:nowrap;
  transition:color 180ms ease}
.pb-ws__seg-ic{flex:0 0 18px;width:18px;height:18px}
.pb-ws__seg-label{color:var(--pb-ink);transition:color 180ms ease}
.pb-ws__toggle[data-mode="color"] .pb-ws__seg[data-opt="color"] .pb-ws__seg-label,
.pb-ws__toggle[data-mode="bw"] .pb-ws__seg[data-opt="bw"] .pb-ws__seg-label{color:var(--pick-ink)}
.pb-ws__toggle[data-mode="color"] .pb-ws__seg[data-opt="color"] .pb-ws__seg-ic,
.pb-ws__toggle[data-mode="bw"] .pb-ws__seg[data-opt="bw"] .pb-ws__seg-ic{color:var(--pick-ink)}
/* .pb-ws__note-sm внутри сегмента скрыт визуально тем же приёмом, что у карточек
   .pb-ws__choice-btn (правило дальше по файлу): узел остаётся в DOM для скринридера и как
   источник текста подсказки (colorNoteEl/bwNoteEl, workshop.html), в дорожке ему негде
   встать - решение оператора 09.09. */
.pb-ws__seg .pb-ws__note-sm{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ===== Правки кадра «Обрезать | Заменить» - единый блок с рамкой (оформление пакета,
   09.09). Раньше - две гуляющие кнопки в свободном ряду; теперь общая рамка var(--pb-line)
   и разделитель между ними, как в дорожке режима слева. #wsDockRow3 остаётся id-якорем
   (ступень narrow/wide, сценарии диагностики) - здесь только новый вид контейнера. */
.pb-ws__edit{display:flex;align-items:center;border:1px solid var(--pb-line);
  border-radius:11px;background:var(--card)}
.pb-ws__sep{flex:0 0 1px;width:1px;height:24px;background:var(--pb-line)}

/* ===== "Что можно дальше" / офферы ===== */
.pb-ws__suggest-card{display:flex;align-items:center;gap:12px;padding:12px;border:1px solid rgba(46,28,18,.14);border-radius:12px;background:#fff;text-align:left;cursor:pointer}
/* Бокс значка 24px рассчитан на шрифтовую иконку, её центрирует text-align. Но в ряду
   «готово» внутри лежит svg 21px с display:block, а на блочный элемент text-align не
   действует - svg прижимался к левому краю, справа оставалось 3px (замер 07.09: слева 0,
   справа 3 во всех трёх кнопках ряда). Подпись при этом стоит по центру кнопки, поэтому
   связка «значок + текст» читалась съехавшей. Центрируем флексом: шрифтовую иконку он
   ставит так же, а svg наконец ровно. */
.pb-ws__suggest-icon{font-family:'uicons-fc';font-size:21px;line-height:1;color:var(--pb-accent);flex:0 0 24px;text-align:center;display:flex;align-items:center;justify-content:center}
.pb-ws__offer-card{display:grid;gap:12px;padding:20px;border:1.5px solid rgba(184,101,42,.4);border-radius:14px;background:var(--pb-accent-soft)}
.pb-ws__offer-head{display:flex;align-items:center;gap:10px}
.pb-ws__offer-head-row{display:flex;align-items:center;justify-content:space-between;gap:10px}
.pb-ws__emoji{font-size:20px;line-height:1}
.pb-ws__price-row{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.pb-ws__price{font:700 30px/1 var(--pb-font);color:var(--pb-accent)}
.pb-ws__price-unit{font:600 19px/1.2 var(--pb-font);color:var(--pb-ink)}
.pb-ws__price-old{font:400 15px/1.2 var(--pb-font);color:var(--pb-muted);text-decoration:line-through}

/* ===== Ховеры кнопок и плиток ===== */
.pb-ws__btn-topup:hover{background:var(--pb-accent-band)}
.pb-ws__btn-upload:hover{background:var(--pb-accent-hover)}
.pb-ws__btn-upload--ghost:hover{background:var(--pb-accent-band)}
.pb-ws__choice-btn:not(.pb-ws__choice-btn--selected):hover{border-color:rgba(184,101,42,.45)}
/* Наведение на главную кнопку НЕ трогает краску (решение оператора 07.09): заливка
   остаётся акцентной, отклик даёт тень с подъёмом. Причина - контраст белой подписи.
   Акцент #b8652a даёт белому 4,26:1 при пороге WCAG 4,5 для такого кегля (15px/600),
   то есть запаса нет уже в покое: любое ОСВЕТЛЕНИЕ уводило подпись до 3,74, а прежнее
   ЗАТЕМНЕНИЕ (--pb-accent-hover) хоть и давало 5,30, но читалось как «кнопка гаснет».
   Тень цветовую пару не трогает - контраст на наведении равен контрасту в покое.
   Форма тени взята у #restoreBtn (строка ниже): тёплая, не чёрная - чёрная на кремовом
   фоне читается грязью. */
.pb-ws__btn-primary:not(.pb-ws__btn-primary--disabled):not(:disabled){transition:box-shadow .18s ease,transform .18s ease}
/* @media (hover: hover) обязателен: на тач-экране hover эмулируется первым тапом и залипает
   до следующего касания - кнопка осталась бы приподнятой с тенью. У краски это читалось как
   «просто другой оттенок», у подъёма читается как поломка. Тот же приём, что в style.css:401. */
@media (hover: hover) and (pointer: fine){
  .pb-ws__btn-primary:not(.pb-ws__btn-primary--disabled):not(:disabled):hover{box-shadow:0 6px 14px rgba(148,77,26,.38);transform:translateY(-1px)}
}
.pb-ws__btn-primary:not(.pb-ws__btn-primary--disabled):not(:disabled):active{transform:translateY(0);box-shadow:0 2px 6px rgba(148,77,26,.28)}
.pb-ws__btn-ghost:hover,.pb-ws__btn-ghost-lg:hover{border-color:rgba(184,101,42,.5);background:var(--pb-accent-soft)}
.pb-ws__suggest-card:not(.pb-ws__suggest-card--locked):hover{border-color:rgba(184,101,42,.45);background:var(--pb-accent-soft)}
.pb-ws__btn-accent-sm:hover{background:var(--pb-accent-hover)}
.pb-ws__btn-accent-sm--ghost:hover{background:var(--pb-accent-band)}

/* ===== Встраивание в workshop.html =====
   Ниже - правила, которых нет в макетном пакете: они связывают раскладку с живыми узлами
   раздела (дропзона, канвас, попап прогресса, блок ошибки) и переключают блоки панели
   по data-state. Всё под .pb-ws, наружу не выходит. */

/* Обёртка «зона работы» (кадр + полоса действий), нужна только трёхзонной раскладке
   restore на широком экране (media ниже). Вне неё - прозрачный контейнер: узкий экран и
   остальные разделы (animate, fotolab) продолжают использовать .pb-ws__layout как раньше,
   .pb-ws__stage-col и .pb-ws__panel остаются его прямыми ячейками грида. */
.pb-ws__workzone{display:contents}
/* .container притащен в разметку Оживления/Фотолаборатории вместе с .upload-section из
   index.html (тот же приём, что уже гасили на мобиле - workshop.html:2372): его 48px паддинга
   с обеих сторон сужали кадр и панель до 714px при workzone 810 (замер 06.09, диагностика
   _diag-dock-canon-compare) - у «Восстановить» узла .container внутри полосы вовсе нет,
   поэтому расхождение видно только тут. Гасим паддинг для любой ширины экрана - у мобильного
   правила (2372) тот же смысл, второй источник числа не заводим. */
.pb-ws[data-ws-section] .container{padding:0}
/* .upload-section{padding:12px 0 24px} - тот же завоз из index.html, что и .container выше:
   отступ был рассчитан на заголовок секции лендинга, которого в Мастерской нет. Внутри
   workzone он добавлял 24px пустоты МЕЖДУ низом кадра и полосой действий поверх row-gap
   канона (14px) - зазор кадр→полоса выходил 38px вместо 14 (замер scripts/tmp/_probe-gap2.mjs
   06.09/круг 8, новая проверка _diag-dock-canon-compare). */
.pb-ws[data-ws-section] .pb-ws__workzone .upload-section{padding:0}
/* Инфопанель (третья зона) существует только на широком экране. Ниже 1181 у generic
   .pb-ws__layout нет для неё места в сетке (одна колонка на кадр+полосу-под-кадром) - решение
   §9: скрыть, а не тащить отдельную мобильную раскладку под задачу, которую не проверяли. */
.pb-ws__aside{display:none}

/* Колонка кадра: сам кадр и подсказка про съёмку под ним — одна ячейка сетки */
.pb-ws__stage-col{min-width:0}
/* Кадр: одна ячейка сетки, внутри по очереди дропзона, канвас и результат */
.pb-ws__stage{position:relative}
.pb-ws__stage.drag-over .pb-ws__dropzone{border-color:var(--pb-accent);background:var(--pb-accent-soft)}
/* «Оживить»/«Фотолаборатория» держат класс канона РЯДОМ со старым .upload-area
   (style.css: своя рамка 1.5px, радиус 20, белый фон) - без сброса на элементе рисовались
   ДВЕ рамки разом: своя снаружи и .pb-ws__dropzone/__canvas изнутри (замер _diag-dock-canon-compare
   06.09, круг 7 - радиус 20 вместо 16, фон белый вместо бежевого канона). У «Восстановить»
   .pb-ws__stage изначально прозрачный - рамку рисует только ребёнок, этим правилом выравниваем
   остальные два раздела на то же поведение. */
.pb-ws__stage.upload-area{border:0;border-radius:0;background:transparent;padding:0}
/* Тот же сброс, но атрибут+класс - выше специфичностью на равных 2 классах. Инлайновый
   `.fl-work .upload-area{padding:16px}` (workshop.html, копия лендинга «Фотолаборатории»)
   стоит НИЖЕ этого файла в документе и при равной специфичности (0-2-0 против 0-2-0 выше)
   выигрывает порядком - паддинг возвращался что в пустом, что в заполненном состоянии.
   Итог без этой строки: 16px паддинга кадра ели у зазора кадр→полоса (14 канона превращались
   в 30, замер _diag-dock-canon-compare 06.09/круг 8, новая проверка «зазорДоПанели»). */
.pb-ws[data-ws-section] .pb-ws__stage.upload-area{padding:0}

/* Канвас превью: вписываем целиком, не обрезая (спека § 2, допущение A1) */
.pb-ws__canvas-canvas{display:block;width:100%;height:100%;object-fit:contain;cursor:zoom-in}
/* Коробка кадра центрирует фотографию, .pb-ws__canvas-fit повторяет её размер один в один:
   до правки object-fit вписывал картинку внутрь коробки, а плашки «Обрезать» и «Заменить»
   стояли у угла КОРОБКИ - на вертикальном кадре они оказывались на бежевом поле рядом с
   фотографией (замечание оператора 03.09). Пропорцию обёртке кладёт drawPreview
   (script.js:1155) переменной --canvas-ar. display:flex у коробки ставит setUploadState
   инлайн-стилем (script.js:184), здесь только выравнивание. */
.pb-ws__canvas{align-items:center;justify-content:center}
.pb-ws__canvas-fit{position:relative;max-width:100%;max-height:100%;aspect-ratio:var(--canvas-ar,1);line-height:0}
.pb-ws .result-img-wrap{position:relative;width:100%;height:100%}
/* Обёртка результата по размеру САМОЙ фотографии (тот же приём, что у .pb-ws__canvas-fit) —
   иначе иконка-дубль «Скачать» в углу садится на бежевое поле коробки, а не на фото. Класс
   ставит script.js:showResult() онлайн картинки, когда пропорция уже известна — до этого
   момента держим 100%x100%, иначе на пустой aspect-ratio коробка схлопнется в 0. */
.pb-ws .result-img-wrap.has-ar{width:auto;height:auto;max-width:100%;max-height:100%;aspect-ratio:var(--result-ar,auto)}

/* Прогресс поверх кадра: разметка и показ/скрытие теперь 1:1 с продом — класс restore-popup
   на #restorePopup, показ через .visible (style.css), .pb-ws-специфичные переопределения
   маскота/стадий сняты (жалоба оператора: маскот отличался от прода). */

/* Подсказка «фото набок» под результатом */
.pb-ws__sideways-hint{position:absolute;left:16px;right:16px;bottom:16px;padding:9px 12px;border-radius:10px;background:rgba(255,248,225,.96);font:400 14px/1.4 var(--pb-font);color:#7a6000;text-align:center}

/* Выбор режима: две карточки, выбранная - одна тёплая заливка (цветовой код проекта) */
/* Выбранная карточка красит иконку акцентом - тем же, что рамка и заливка (§ Цветовой код) */
.pb-ws__choice-list[data-mode="color"] .pb-ws__choice-btn[data-opt="color"] .pb-ws__choice-ic,
.pb-ws__choice-list[data-mode="bw"] .pb-ws__choice-btn[data-opt="bw"] .pb-ws__choice-ic{color:var(--pb-accent)}
.pb-ws__choice-list[data-mode="color"] .pb-ws__choice-btn[data-opt="color"],
.pb-ws__choice-list[data-mode="bw"] .pb-ws__choice-btn[data-opt="bw"]{border:2px solid var(--pb-accent);background:var(--pb-accent-soft)}
/* Общий признак активной карточки (Оживление/Фотолаборатория выбирают стиль/операцию не
   парой data-mode/data-opt, а произвольным числом карточек с классом is-active) - та же
   заливка и рамка, что у выбора цвета выше. Правила по data-mode НЕ трогаем - на них живёт
   реставрация. */
.pb-ws__choice-btn.is-active{border:2px solid var(--pb-accent);background:var(--pb-accent-soft)}
.pb-ws__choice-btn.is-active .pb-ws__choice-ic{color:var(--pb-accent)}

/* Поле подсказки цвета: раскрытие живёт на data-open (workshop.html:8570) */
.pb-ws .ci-field[data-open="false"] .ci-body{display:none}
.pb-ws .ci-body{display:grid;gap:8px;padding-top:10px}
.pb-ws .ci-body textarea{width:100%;box-sizing:border-box;min-height:88px;resize:vertical;border:1px solid var(--pb-line);border-radius:11px;padding:11px 12px;font:400 15px/1.5 var(--pb-font);color:var(--pb-ink);background:#fff}
.pb-ws .ci-body textarea:focus{outline:none;border-color:rgba(184,101,42,.5)}
.pb-ws .ci-foot{display:flex;justify-content:flex-end;font:400 14px/1 var(--pb-font);color:var(--pb-muted)}

/* Предупреждение о кадре (боком, лишние поля) - строка над кнопкой */
.pb-ws .hdr-status{display:flex;align-items:flex-start;gap:9px;padding:11px 12px;border-radius:11px;background:var(--pb-accent-band);font:500 14.5px/1.45 var(--pb-font);color:var(--pb-ink)}
.pb-ws .hdr-status[hidden]{display:none}
.pb-ws .hdr-status svg{flex:0 0 18px;margin-top:1px}

/* Блок ошибки внутри панели (спека § 2, допущение A8) */
.pb-ws .error-area{display:grid}
.pb-ws .error-box{display:grid;gap:12px}
.pb-ws .error-icon{font-size:28px;line-height:1}
.pb-ws .error-actions{display:grid;gap:9px}

/* Бонусный баннер и подсказка про съёмку живут в панели пустого состояния */
.pb-ws .ws-bonus-cta{display:flex;align-items:center;gap:11px;padding:12px;border:1px solid rgba(184,101,42,.35);border-radius:12px;background:var(--pb-accent-soft);color:var(--pb-ink);text-decoration:none}
.pb-ws .ws-bonus-cta[hidden]{display:none}
/* Баннер бонусов заполняет bonus-widget.js; пока заголовок пуст (гость, ошибка запроса) —
   на экране висел значок подарка и пустая плашка. Пустой не показываем. */
.pb-ws .ws-bonus-cta:has(.ws-bonus-cta__title:empty){display:none}
/* Подсказка про съёмку под кадром: только строка-кнопка, развёрнутый вариант с картинкой
   рисуется для узких экранов, десктопной раскладке он не нужен. */
.pb-ws .ws-howto-under{margin-top:14px}
.pb-ws .ws-howto-inline{display:none}
.pb-ws .ws-bonus-cta__txt{display:grid;gap:2px}
.pb-ws .ws-bonus-cta__title{font:600 15px/1.25 var(--pb-font)}
.pb-ws .ws-bonus-cta__sub{font:400 14px/1.35 var(--pb-font);color:var(--pb-muted)}
.pb-ws .ws-bonus-cta__go{margin-left:auto;font:600 14px/1 var(--pb-font);color:var(--pb-accent)}
.pb-ws .howto-trigger{display:inline-flex;align-items:center;gap:8px;min-height:44px;padding:0;border:0;background:transparent;color:var(--pb-muted);font:500 14.5px/1.3 var(--pb-font);cursor:pointer;text-align:left}
.pb-ws .howto-trigger:hover{color:var(--pb-accent)}

/* Строка «Первое фото бесплатно» - только гостю; флаг ставит script.js на корне раздела */
.pb-ws__guest-only{display:none}
.pb-ws[data-guest="1"] .pb-ws__guest-only{display:block}

/* Панель: своя прокрутка, страница не растёт (спека § 2) */
.pb-ws__panel{overflow:auto}

/* Блоки панели показываются по состоянию раздела. data-panel держит список состояний
   через пробел, поэтому селектор ~= - один блок живёт сразу в нескольких состояниях. */
.pb-ws [data-panel]{display:none}
.pb-ws[data-state="empty"] [data-panel~="empty"],
.pb-ws[data-state="picked"] [data-panel~="picked"],
.pb-ws[data-state="working"] [data-panel~="working"],
.pb-ws[data-state="done"] [data-panel~="done"],
.pb-ws[data-state="guest-done"] [data-panel~="guest-done"],
.pb-ws[data-state="error"] [data-panel~="error"],
.pb-ws[data-state="zero"] [data-panel~="zero"],
.pb-ws[data-state="window-24h"] [data-panel~="window-24h"],
.pb-ws[data-state="guest-used"] [data-panel~="guest-used"]{display:grid}

/* В рабочих состояниях главная кнопка прижата к низу панели, в остальных - поток сверху */
.pb-ws__panel{align-content:start}
.pb-ws[data-state="empty"] .pb-ws__panel,
.pb-ws[data-state="picked"] .pb-ws__panel{grid-template-rows:1fr auto;align-content:stretch}

/* Гостю после результата плашку «Поделиться / Другое фото» прячет script.js:1846 */
.pb-ws[data-state="guest-done"] [data-done-next]{display:none}

/* Хинт под главной кнопкой: у гостя нет баланса, ему нечего показывать в «на балансе».
   Развилку держит CSS, а не JS: строка рисуется сразу с разметкой, до первого вызова
   setUploadState, и гость успевал увидеть «на балансе —» (замер 03.09). */
.pb-ws__cost-guest{display:none}
.pb-ws[data-guest="1"] .pb-ws__cost-guest{display:inline}
.pb-ws[data-guest="1"] #restoreMetaCost,
.pb-ws[data-guest="1"] #restoreMetaBalanceSep,
.pb-ws[data-guest="1"] #restoreMetaBalancePart{display:none}

/* ===== Раздел как экран, а не лента (правка оператора 03.09) =====
   Три поверхности - меню, кадр, панель - вписаны в окно с одинаковым полем по краям.
   Страница не прокручивается: прокрутка живёт ВНУТРИ поверхности, у каждой своя.
   До правки высоты кадра и панели были прибиты к --pb-canvas (520): на окне 1440x900
   снизу оставалось 93px пустоты, на низком окне обе колонки уезжали за край и
   прокручивалась страница целиком - обе колонки сразу.
   Высоту рабочей области задаёт .ws-app (workshop.html), сюда она доезжает цепочкой
   height:100% - разорви цепочку в одном звене, и высота схлопнется в auto.
   Порог по ширине 1181 - там же, где раскладка становится двухколонной (см. выше).
   Порог по высоте 480 (было 600, понижен 11.09 по записи Вебвизора). Разрешение ЭКРАНА
   не равно вьюпорту: у посетителя с экраном 1280x720 панели браузера съедают 100-180px,
   и окно выходит 560-620 - то есть ПОД прежним порогом. Ниже него раскладка отваливалась
   целиком: .pb-ws__workzone распускался в display:contents (строка 377), кадр и полоса
   становились ячейками внешней сетки, и полоса вставала СПРАВА от кадра, на месте
   инфопанели; сами подсказки при этом пропадали. Замер 11.09 на проде, окно 1280x560:
   полоса 602px справа от кадра, прокрутка 276px. Новый порог 480 оставляет «раздел =
   экран» на обычном ноутбучном окне (кадр ужимается до 294-414px и остаётся читаемым),
   а ниже 480 честная лента с прокруткой по-прежнему разумнее. Барьер - сценарий
   ws-short-window. */
@media (min-width:1181px) and (min-height:480px){
  /* Левый оффер-язычок (welcome-promo.js / promo-render.js) не нужен там, где есть правая
     инфопанель с картой офферов (решение оператора 09.09): условие то же, что у .pb-ws__aside
     ниже. На узких окнах панели нет, и язычок остаётся единственным напоминанием. Файл
     подключён только в workshop.html, лендинг не задет; body в селекторе - перебить
     display:flex по id из style.css. */
  body #welcomeSideTab,body #promoSideTab,body #userOfferSideTab{display:none}
  .pb-ws{height:100%}
  /* Поле по краям держит одна точка - .ws-app; свои 44px рабочая область снимает,
     иначе поле у кадра 44, а у меню 24, и «край экрана» получается разный. */
  .pb-ws__main{height:100%;min-height:0;padding:0}
  .pb-ws__layout{height:100%;min-height:0;align-items:stretch}
  /* Кадр забирает остаток высоты, подсказка про съёмку остаётся своей строкой под ним */
  .pb-ws__stage-col{display:grid;grid-template-rows:minmax(0,1fr) auto;min-height:0}
  .pb-ws__stage,.pb-ws__dropzone,.pb-ws__canvas{height:100%;min-height:0}
  /* «Оживить»/«Фотолаборатория» кладут кадр не в .pb-ws__stage-col (её там нет), а в
     .ws-exact-card - обёртку из копии лендинга (workshop.html) с чужими слоями внутри
     (section.upload-section, .container, у «Фотолаборатории» ещё .fl-main). Ни один из
     них не имеет height:100% - .pb-ws__stage выше получает 100% от ближайшего предка,
     а не от ячейки грида, и цепочка схлопывается в auto на первом же блочном слое
     (замер scripts/tmp/_probe-grid.mjs 06.09/круг 8 - кадр 319px вместо 554 доступных).
     Пробрасываем высоту тем же приёмом, что .pb-ws__stage-col делает грид-строкой
     (там не нужно - у «Восстановить» одна обёртка), а здесь обёрток четыре подряд.
     Селектор ограничен .pb-ws__workzone - вне Мастерской у .container и .upload-section
     своя обычная блочная раскладка, трогать её нельзя. */
  .pb-ws[data-ws-section] .pb-ws__workzone > .ws-exact-card.ws-create-card{height:100%;min-height:0;display:flex;flex-direction:column}
  .pb-ws[data-ws-section] .pb-ws__workzone .ws-exact-card.ws-create-card .upload-section{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
  /* width:100% в довесок к flex:1 1 auto - у .container margin:0 auto (style.css, центрирование
     блока в старой вёрстке лендинга): авто-отступы по горизонтали отменяют auto-stretch по
     кросс-оси флекс-колонки (align-items:normal ведёт себя как stretch, но проигрывает
     auto-margin), контейнер схлопывался до 240px по контенту вместо 810 доступных
     (замер scripts/tmp/_probe-grid3.mjs 06.09/круг 8). */
  .pb-ws[data-ws-section] .pb-ws__workzone .ws-exact-card.ws-create-card .upload-section > .container{flex:1 1 auto;min-height:0;width:100%;max-width:none;display:flex;flex-direction:column}
  /* max-width:none (09.09): .container из style.css держит потолок 1220, и на 1920 кадр «Оживить»
     отступал от зоны на 28px с каждой стороны, у эталона кадр = зона (замер 09.09). */
  /* .fl-main (только «Фотолаборатория») - уже flex-column из style.css (кадр + плитка
     операций + поле уточнения + кнопка идут друг под другом), но САМ не растягивался под
     доступную высоту. flex вместо height:100% - процент высоты не работает как явное
     значение внутри flex-контейнера без него, а .container выше теперь тоже flex. */
  .pb-ws[data-ws-section] .pb-ws__workzone .ws-exact-card.ws-create-card .fl-main{flex:1 1 auto;min-height:0}
  /* Сам кадр (.pb-ws__stage/__dropzone/__canvas): height:100% выше ПРОИГРЫВАЛ по
     специфичности двум чужим правилам style.css - «.upload-area.has-preview{height:auto}»
     (Оживить/Фотолаборатория, фото выбрано) и «.fl-work .upload-area{height:auto}»
     (Фотолаборатория, любое состояние) - у обеих специфичность 0-2-0 против 0-1-0 канона,
     и они побеждали независимо от порядка правил в каскаде (замер: кадр 352px при 554
     доступных, .container уже растянут верно). Атрибут+класс здесь поднимает вес канона
     выше обеих. flex:1 1 auto - тот же приём, что и на .fl-main: внутри flex-предка
     (.container, .fl-main) высота в процентах не считается «явным размером» без
     flex-basis, только flex-grow гарантированно забирает остаток. */
  /* width:100% - та же причина, что у .container выше: .upload-area{max-width:760px;
     margin:0 auto} (style.css) центрирует себя авто-отступами, которые отменяют
     авто-stretch кросс-оси флекс-колонки (.container). Без width кадр «Оживить» схлопывался
     до 240px по контенту вместо 810 доступных - высота уже была верна, ширина нет (замер
     scripts/tmp/_probe-grid2.mjs 06.09/круг 8). */
  .pb-ws[data-ws-section] .pb-ws__stage,.pb-ws[data-ws-section] .pb-ws__dropzone,.pb-ws[data-ws-section] .pb-ws__canvas{height:100%;min-height:0;width:100%;flex:1 1 auto}
  /* Кадр «Оживить»/«Фотолаборатории» сидит на старом .upload-area (style.css: max-width 760,
     margin auto) - кадр был уже зоны на 13px с каждой стороны и не доставал до потолка
     колонки (замер 09.09: 760 при зоне 786, у эталона кадр = зона). Канон отдаёт кадру
     всю зону; вес (0,4,0) против (0,1,0) style.css. */
  .pb-ws[data-ws-section] .pb-ws__stage.upload-area{max-width:none;margin:0}
  /* Соседи кадра внутри .fl-main (витрина примеров, скрытая плашка «выбрать другое фото») не
     должны расти - без anti-grow первого ребёнка (кадра) не хватило бы. #flDock (панель
     операций) сюда больше не входит: она переехала из .fl-main в прямые дети .pb-ws__workzone
     (тот же уровень, что и у «Оживить» - #wsAnimDock рядом с .ws-exact-card, не внутри неё),
     чтобы получить СВОЙ ряд грида (auto), а не делить один flex-контейнер с кадром - иначе
     кадр и панель делили высоту .fl-main пополам вместо канонных 1fr/auto (замер
     scripts/tmp/_probe-fl-ancestors.mjs 06.09/круг 8: кадр 376px вместо 471 доступных). */
  .pb-ws[data-ws-section] .fl-main > .fl-examples,.pb-ws[data-ws-section] .fl-main > .restore-plate{flex:none}
  /* Отступ строки подсказки снят: обе её формы сейчас скрыты (кнопка - правилом в
     workshop.html, развёрнутая карточка - правилом выше в этом файле), и пустой блок
     воровал у кадра 14px - низ кадра не доходил до низа панели. Подсказку вернут -
     вернуть и отступ вместе с ней. Запись о самой пропаже: Напоминания, 03.09. */
  .pb-ws .ws-howto-under{margin-top:0}
  /* Группа дропзоны стоит в ОПТИЧЕСКОМ центре кадра, а не в геометрическом: на высоком
     кадре группа, центрированная по числам, читается сидящей низко. Сдвиг вверх 40px -
     нижнее поле 72 против верхних 32. Проверяется глазом, замером такое не ловится. */
  .pb-ws__dropzone{padding-bottom:72px}

  /* ===== Нижняя полоса действий (боевая с 04.09) =====
     Просьба оператора: лаконичное нижнее меню в духе панелей генераторов, но крупными
     кнопками (раздел смотрят люди, которым мелкая иконка не видна) и В НАШИХ ЦВЕТАХ -
     белая карточка, тёплая линия, акцент #b8652a. Выбрана 04.09 из двух стендов (p5 -
     подсказка нейросети сверху, p6 - карточки цвета сверху): взят порядок p6, стенд снят,
     правила ниже действуют всегда. Узкий экран (<1181) остаётся на базовой раскладке
     «панель под кадром», низкое окно (<600) при широком экране - на «панели справа».
     Полоса - ВТОРАЯ СТРОКА сетки, а не слой поверх кадра: сколько заняла, столько кадр и
     отдал. Первый заход держал её на position:absolute с запасом снизу у кадра, запас
     разошёлся с живой высотой полосы (156 против 223) и полоса накрыла низ фотографии.

     РАСКЛАДКА СОСТОЯНИЙ. Место главного действия ОДНО во всех состояниях - правый край
     полосы: в пустом «Восстановить фото» неактивна, в выбранном она же оживает, в готовом
     на её месте «Скачать фото», в денежных - «Оплатить»/«Все тарифы». Кнопка под курсором
     не прыгает (тот же довод, что у боевой панели выше по файлу).
     Слева содержимое состояния течёт строкой, вложенные столбики (цены, «что можно
     дальше», карточка оффера) разворачиваются в строки - в полосе высота дороже ширины.
       пусто        три шага в строку, гостевая строка и бонус | неактивная кнопка + хинт
       выбрано      ряд подсказки и ряд выбора цвета           | кнопка 64 + хинт списания
       обработка    стадии строкой во всю ширину (действия нет, колонка не резервируется)
       готово       «Готово», «Поделиться», «Другое фото», «Оживить» | «Скачать фото»
       ошибка       текст ошибки строкой                       | «Попробовать снова»
       0 обработок  тарифы строкой                             | «Все тарифы»
       окно 24ч     карточка оффера строкой, тарифы вторым рядом | «Оплатить»
       гость исчерпал  карточка оффера строкой                 | «Войти и забрать»
     Полоса и значок «Связаться» (feedback.css: fixed, right 20, bottom 20) стоят на одном
     уровне: низ обоих на 24 + --pb-dock-bottom от края окна, а пересечься им не даёт ширина
     полосы - справа остаются свободными 60px. С плашкой cookie низ считается от --cookie-h.
     Префикс .pb-ws[data-ws-section] держит вес селекторов не ниже стендового
     body[data-ws-dock]: инлайн-правила workshop.html (например .ci-field:hover) стоят в
     документе позже и при равном весе перебивали бы полосу.
     Атрибут БЕЗ значения (06.09, правка оператора «панель должна быть в едином стиле и
     логике, одни отступы, одна анимация при изменении размера»): панель управления
     действиями одна на «Восстановить», «Оживить» и «Фотолабораторию». Раньше префикс был
     [data-ws-section="restore"], и два других раздела строились на своём каркасе
     ws-anim-dock со своими числами - расхождение замерено 06.09: радиус кнопки 50 против
     12, шрифт 16/600 против 20/700, панели нет вовсе, подписи карточек обрезаны.
     Частное каждого раздела держится не префиксом, а привязкой к своим id (#modeToggleBtn,
     #wsDockRow3 у реставрации) - в чужом разделе такое правило просто не находит элемент.
     Барьер против повторного расхождения - сценарий _diag-dock-canon-compare: снимает три
     раздела и краснеет на разнице отступов, радиуса, шрифта и ширины панели. */

  /* --pb-dock - высота полосы во ВСЕХ рабочих состояниях (пусто, выбрано, обработка, готово,
     ошибка). Одна на всех намеренно: между ними человек ходит подряд, и полоса, меняющая рост
     на каждом шаге, дёргает под курсором и кадр, и кнопку. Денежные состояния - отдельная
     группа, они появляются вместо рабочего сценария, там своя высота. */
  /* --pb-dock 208 (было 156) - высота полосы при поле пожеланий в ОДНУ строку (мокап
     оператора 06.09, 910x208). Растёт вместе с textarea до 292 (четыре строки, CI_MAX в
     workshop.html) обычным потоком контента (grid-template-rows:auto auto auto у empty/
     picked ниже по файлу) - второй переменной под «выросшую» высоту не заводим, она и так
     auto. Денежные состояния (zero/window-24h/guest-used) на --pb-dock не завязаны
     (height:auto у них отдельным правилом ниже) - их эта правка не трогает. */
  /* --pb-dock-bottom 8 (было 16) - правка оператора 06.09 приёмки, § 3: полоса стояла
     слишком далеко от нижнего края экрана. Значок «Связаться» и cookie-формула ниже
     считаются от той же переменной, второй источник числа не заводим - оба поднимаются/
     опускаются вместе с полосой автоматически. */
  /* 07.09 (замечание оператора «CTA занимает слишком много места, нужно 2 к 1»): колонка
     действия была жёстко 280px, поэтому её доля зависела от ширины полосы - в развёрнутом
     меню полоса 762 и раскладка выходила 422/280, то есть 1.5:1, а подпись «Обрезать и
     повернуть» налезала на свой значок (замер: зазор -13.5px). В рельсе полоса 900 давала
     560/280 = ровно 2:1, и там оператор претензий не имел. Поэтому задаём саму ПРОПОРЦИЮ:
     треть полосы, но не уже 200 (кнопка с ценой) и не шире прежних 280 - иначе на 1920
     колонка раздувалась бы до 385. */
  /* 08.09, пакет Claude Design: высота 134 вместо 208 (решение оператора — «134 оптимально,
     выглядит аккуратно»). Раскладка пакета: два ряда по --pb-dock-row 48 с зазором
     --pb-dock-gap 8 внутри левой колонки, поля 14 со всех сторон, рамка 1 → 14+48+8+48+14+2
     = 134. Прежние 208 набирались тремя рядами (карточки 56 + поле 50 + док 40) с зазорами
     14 и полями 18/14; замер состава — scripts/scenarios/_diag-dock-rows.mjs.
     Числа взяты из пакета БЕЗ подгонки: он держит 134 без вылетов и обрезок на всех ширинах
     602-1000 (замер _diag-pkg-fit). Менять их поодиночке нельзя — высота собирается ими всеми. */
  /* 17.09 (задание оператора «все поля вокруг кадра одинаковые»): --pb-dock-bottom 8 → 0.
     Замер _diag-frame-gaps-uniform ДО правки: сверху/слева/справа по 24, кадр→полоса 14,
     под полосой 32 — то есть снизу поле было на 8 шире эталонных 24 ровно на эту величину,
     а между кадром и полосой на 10 уже (row-gap правится отдельно, 14 → 24).
     Прежнее значение 8 (было 16) ставили 06.09 по обратной жалобе — полоса стояла слишком
     далеко от края; теперь расстояние до края держит поле раскладки .ws-app (24px), и
     добавка поверх него как раз и ломала равенство. Значок «Связаться» и формула cookie
     считаются от этой же переменной и поднимутся вместе с полосой — так и задумано. */
  body{--pb-dock:134px;--pb-dock-bottom:0px;--pb-act:clamp(200px, 33.33%, 280px);
       --pb-dock-pad:14px;--pb-dock-row:48px;--pb-dock-gap:8px}
  /* Подъём полосы над плашкой cookie СНЯТ (решение оператора 14.09): плашка живёт 10 секунд
     и уходит сама, а полоса инструментов при её появлении и уходе прыгала на высоту плашки -
     это оператор читает как дефект раскладки. Плашка теперь ложится поверх полосы (z-index
     9000 против полосы), перекрытие временное. */

  /* Кнопка «Связаться» - только значок и НА СВОЁМ месте в правом нижнем углу (правка
     оператора 04.09). Разъехаться с полосой ей помогает не подъём, а сужение самой полосы:
     полоса не доходит до правого края на 60px, кнопка занимает свои 52 и не пересекается. */
  body .feedback-trigger{width:52px;height:52px;padding:0;border-radius:99px;justify-content:center;
    bottom:calc(24px + var(--pb-dock-bottom))}
  body .feedback-trigger-text{display:none}

  /* ===== Три зоны (правка 04.09, макет mockups/workshop-3zone.html) =====
     Слева зона работы (кадр + полоса, одна колонка грида, обёртка .pb-ws__workzone в
     разметке), справа инфопанель шириной --pb-info (300, замер макета). Раньше .pb-ws__layout
     был одноколоночным, а полоса мерила ширину от % ЭТОЙ колонки, то есть от всей рабочей
     области, а не от кадра - на широком экране «Восстановить фото» уезжала вправо от
     фотографии (правка оператора). Теперь полоса - просто width:100% ВНУТРИ workzone, и
     повторяет ширину кадра сама, без width:min(...) и без второго источника числа. */
  /* 07.09: 300→318. Замер воздуха показал перекос - в меню от самого длинного пункта до края
     оставалось 98px пустоты, в инфопанели справа всего 39. Оператор решил сузить меню до 264
     и разделить освободившиеся 36px поровну: +18 кадру и +18 инфопанели. */
  body{--pb-info:318px}
  .pb-ws[data-ws-section] .pb-ws__layout{grid-template-columns:minmax(0,1fr) calc(var(--pb-info) * var(--ui-z, 1));column-gap:24px;align-items:stretch}
  .pb-ws[data-ws-section] .pb-ws__workzone{display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto;row-gap:24px;min-height:0;min-width:0}
  .pb-ws[data-ws-section] .pb-ws__stage-col{grid-column:1;grid-row:1}

  /* Сама полоса: живёт в ТОЙ ЖЕ колонке грида, что и кадр (workzone), поэтому центрирование
     по кадру не нужно вычислять отдельно - обе ячейки одной ширины автоматически. Верхний
     потолок 900 снят вместе с width:min(...): кадр в трёхзонной раскладке и так не шире
     рабочей колонки минус инфопанель, второй потолок был бы мёртвым числом. */
  /* Скругление 18 (было 20) и внутренние отступы 18/20/14 (было 12/14 со всех сторон) -
     мокап оператора 06.09 (910x208). Вертикальный gap 14 (было 10, горизонтальный 18 не
     трогаем - задание чисел про него не давало): при трёх рядах empty/picked (карточки 56 +
     поле 50 + кнопки 40), паддинге 18+14=32 и рамке 1+1 ровно 14px между рядами даёт высоту
     полосы 208 (32+2+2*14+56+50+40) - подобрано под целевое число, а не наоборот; при росте
     поля до потолка 134 (CI_MAX, workshop.html) высота становится 292 (208 + (134-50)),
     совпадает со вторым числом мокапа без отдельной переменной. */
  /* Потолок ширины полосы (правка оператора 06.09): на широком экране полоса растягивалась
     во всю ширину кадра (1250px на 1920) - редкий ряд из трёх коротких блоков и кнопка,
     уехавшая на полметра вправо от поля. Число 900 не новое: этот же потолок стоял до
     трёхзонной раскладки 04.09 (width:min(900px,...)), тогда он ушёл вместе с ручным
     центрированием - теперь возвращается, но центрирование уже держит сама сетка
     (justify-self), второго источника числа нет.
     Канон: полоса не шире 900 и всегда по центру кадра. Ниже 900 она равна кадру, то есть
     на 1181-1439 ничего не меняется. Барьер - сценарий workshop-restore-layout: он ждёт
     ширину min(кадр, 900) и совпадение центров. */
  body{--pb-dock-max:900px}
  .pb-ws[data-ws-section] .pb-ws__panel{position:relative;grid-column:1;grid-row:2;margin-bottom:var(--pb-dock-bottom);
    width:min(100%,var(--pb-dock-max));justify-self:center;
    /* height:auto, коробку держит min-height: смену состояний ведёт анимация min-height
       (событие transitionstart приходит именно на нём), а числовой height задавал бы новую
       высоту мгновенно и обгонял переход - полоса прыгала за один кадр (замер 07.09). */
    height:auto;min-height:var(--pb-dock);max-height:300px;overflow:auto;
    display:grid;grid-template-columns:minmax(0,1fr) var(--pb-act);grid-template-rows:auto auto;
    /* Поля и зазор — из пакета (14 со всех сторон, 12 между колонками). Прежние 18/20/14
       и gap 14/18 давали высоту 208; на 134 бюджет держится только этими числами. */
    align-content:center;align-items:center;gap:var(--pb-dock-gap) 12px;padding:var(--pb-dock-pad);
    background:#fff;border:1px solid var(--pb-line);border-radius:18px;
    box-shadow:var(--shadow-3);
    transition:width .2s ease, height .24s ease, min-height .24s ease}
  /* Прокрутка внутри полосы - недопустима (правка оператора 09.09). max-height:300px и
     overflow:auto выше - наследие старой раскладки, где полоса росла содержимым до потолка
     и после него уходила в свою прокрутку; сейчас высоту держит пакет константой 134 во
     всех девяти состояниях, а поле подсказки растёт вместе с полосой (input у ab.js
     поднимает textarea до трёх строк, и вместе с ним растёт вся коробка через height:auto
     на .pb-ws__panel). Снимаем потолок и прокрутку ТОЛЬКО у панели с пакетом внутри:
     :has(.pb-ab) - у «Оживить»/«Фотолаборатории» (#wsAnimDock/#flDock, та же .pb-ws__panel)
     разметка прежняя, там рост содержимым до 300px с прокруткой после ещё нужен, общее
     правило строкой выше их не трогаем. */
  /* Рамка, фон и тень сняты 09.09: полосу целиком рисует пакет (.pb-ab со своей рамкой),
     а панель под ним оставалась второй коробкой - её рамка добавляла 2px, из-за чего
     scrollHeight (134) превышал clientHeight (132) и в done/guest-done появлялась полоса
     прокрутки. Панель теперь только держит место в сетке и ширину. */
  .pb-ws[data-ws-section] .pb-ws__panel:has(.pb-ab){max-height:none;overflow:visible;
    border:0;background:transparent;box-shadow:none;min-height:0}
  /* Готово: полоса выглядела полупустой на всю ширину кадра (жалоба оператора) - в done/
     guest-done её содержимое (кнопка+подписи, без третьего ряда обрезки/замены) заметно уже
     потолка 900. Сужаем под контент, а не растягиваем - переход анимирован transition:width
     выше; «Заменить фото» возвращает picked, где действует базовое правило (min(100%,900)),
     и полоса плавно расширяется обратно. reduced-motion гасит transition ниже по файлу. */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__panel,
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__panel{width:fit-content;max-width:min(100%,var(--pb-dock-max))}
  /* 06.09, круг 5 (правка оператора «сделать в одну линию, не меняя ширину, но меняя
     высоту»): в «готово» слева ОДИН ряд («Поделиться» + «Следующее фото», 56px), а полоса
     держала общую --pb-dock 208 - под рядом оставалось 133px пустоты, и главная кнопка
     висела в этой пустоте справа (замер 06.09). Высоту в этих двух состояниях задаёт
     содержимое: самая высокая колонка - действие, 22 (итог) + 8 + 64 (кнопка) + 8 + 21
     (расход) = 123, плюс поля 18/14 = 155.
     Почему только done/guest-done: у picked/working/empty слева три ряда (режим 56,
     пожелания 50, док 40 - низ на 193), им 208 нужны целиком, и общая высота между
     обработкой и готовым здесь не нужна - «готово» человек видит один раз в конце, а не
     перещёлкивает туда-сюда (довод 04.09 про одну высоту касался пары «обработка → готово»
     на ОДНОМ кадре; в «готово» кадр уже сменился на результат, полоса всё равно меняет
     ширину анимацией).
     Подписи над и под кнопкой выведены из потока (см. «Место действия» ниже), поэтому
     min-height:123 на колонке действия ставим явно: без него в высоту вошла бы одна
     кнопка (64), а подписи вылезли бы за края полосы. */
  /* min-height ЧИСЛОМ (не 0): между «готово» и рабочими состояниями человек ходит туда-сюда
     («Следующее фото» возвращает в empty), и плавность даёт именно переход min-height -
     116 против 208. С min-height:0 и height:auto браузеру нечего анимировать, и полоса
     прыгала за один кадр (замер 07.09: 116→208 за 18мс, жалоба «мигает экран»).
     116 - фактическая высота ряда: поля 18/14 + строка исхода 18 + зазор 14 + кнопка 52.
     height остаётся auto: если строка исхода когда-нибудь станет длиннее, полоса вырастет
     сама, а не срежет текст. */
  /* height:auto + числовой min-height. Анимацию ведёт min-height: событие transitionstart на
     нём приходит (замер 07.09), тогда как height пересчитывается мгновенно и в переходе не
     участвует - пока height задан числом, он и определяет коробку, обгоняя анимацию.
     116 - высота ряда: поля 18/14 + строка исхода 18 + зазор 14 + кнопка 52. */
  /* 07.09 (правка оператора «высота полосы как у выбора режимов, кнопка на том же уровне»):
     min-height возвращён к канону полосы --pb-dock (208), было 116. При 116 полоса «готово»
     садилась на фактические 196 против 208 в «выбрано», ряд кнопок оказывался на 22px выше
     центра, и главная кнопка уходила из-под мыши на переходе выбор -> результат (замер 07.09,
     _diag-panel-geometry). Денежные состояния (zero/window-24h/guest-used) на --pb-dock
     по-прежнему не завязаны - у них своя высота 116, правило ниже. */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__panel,
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__panel{height:var(--pb-dock);min-height:var(--pb-dock)}
  /* По центру полосы стоит РЯД КНОПОК, а не блок целиком (правка оператора 07.09: «кнопка
     на том же уровне, чтобы оставалась под мышкой»). Центрируй мы весь грид (align-content),
     ряд уехал бы вверх ровно на половину высоты звёзд - те самые 23px, что оператор заметил.
     Механизм: три ряда 1fr / auto / 1fr. Средний ряд - кнопки, он и оказывается серединой
     полосы; строка исхода прижата к низу верхнего ряда, звёзды - к верху нижнего. Ряды
     равны по высоте, поэтому кнопки стоят там же, где «Восстановить» в «выбрано»
     (замер 07.09: 2px от центра). Пустой 1fr-ряд не растит полосу: её высота задана числом
     (--pb-dock), а не содержимым. */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__cta-group[data-panel~="done"],
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__cta-group[data-panel~="done"]{
    grid-template-rows:1fr auto 1fr;row-gap:0}
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__act .pb-ws__success-row,
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__act .pb-ws__success-row{
    grid-row:1;align-self:center;margin:0}
  /* Вариант E макета mockups/workshop-done-rhythm.html, выбор оператора 07.09: строка
     исхода стоит СЕРЕДИНОЙ между верхним краем полосы и рядом кнопок, а не отступает
     на фиксированное число. Прежние align-self:end + margin 10 давали замер 60/0 -
     весь воздух над строкой, сама строка вплотную к кнопкам. Теперь 30/30, и ритм
     держится сам при любой длине текста исхода - подгонять числами нечего. */
  .pb-ws[data-ws-section][data-state="done"] #downloadBtn,
  .pb-ws[data-ws-section][data-state="guest-done"] #downloadBtn,
  .pb-ws[data-ws-section][data-state="done"] #shareBtn,
  .pb-ws[data-ws-section][data-state="guest-done"] #shareBtn,
  .pb-ws[data-ws-section][data-state="done"] #newPhotoBtn,
  .pb-ws[data-ws-section][data-state="guest-done"] #newPhotoBtn{grid-row:2}
  .pb-ws[data-ws-section][data-state="done"] #thumbRow,
  .pb-ws[data-ws-section][data-state="guest-done"] #thumbRow{
    grid-row:3;align-self:center;margin:0}
  /* Тот же вариант E: блок «подпись + звёзды» - серединой нижней половины (замер 16/16),
     было align-self:start + margin 6, то есть 0 сверху и 38 снизу. */
  /* Место под подписи резервируем ПОЛЕМ, а не высотой группы: подписи выведены из потока и
     привязаны к краям КНОПКИ, поэтому растянутая до 123 группа centered кнопку по своей
     высоте, а подписи уезжали за края полосы (замер 06.09: -11 сверху, -14 снизу). Поле
     оставляет группе её 64 (высота кнопки), а 30/29 сверху и снизу отдаёт подписям:
     22 (итог) + 8 зазора и 21 (расход) + 8. */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__act,
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__act{min-height:64px;margin:30px 0 29px}
  /* empty и picked раздвигаются по содержимому, остальные рабочие состояния держат общую
     --pb-dock (довод 04.09 про одну высоту касается обработки, готового и ошибки - между
     ними человек ходит подряд, и прыгающая полоса дёргает кадр под курсором).
     Почему empty здесь же: с 04.09 пустой экран показывает ТУ ЖЕ форму полосы, что и
     «выбрано» (правка оператора - «начальный вид панели должен быть такой же, просто кнопка
     неактивна»), а три ряда в 156px не помещаются: контент 182 вываливался за белую карточку
     наружу (overflow:visible ниже), и любая схема центрирования кнопки спорила с видимой
     коробкой - кнопка вставала на 25px выше центра (замер 04.09).
     grid-template-rows третьей строкой - только у этих двух: у остальных состояний третий
     ряд грида не существует, и пустая строка с двойным gap сдвигала кнопку действия на 5px
     вверх (замер 04.09 - кнопка вставала на 27 от верха вместо 32).
     08.09, раскладка пакета: третьего ряда для empty/picked больше нет вообще - правки
     кадра переехали в первый ряд к карточкам (см. правило .pb-ws__panel общее ниже по файлу).
     Это правило специфичнее общего ([data-state] в селекторе), поэтому ряды здесь обязаны
     повторять его схему - оставленное auto auto auto добавляло третий трек и лишний gap
     8px поверх канона (полоса выходила 142 вместо 134, замер 08.09). */
  /* height:auto оставлен НАРОЧНО: полоса в «выбрано» растёт под поле пожеланий (сценарий
     workshop-restore-layout ждёт 292 при поле в 4 строки). Плавность перехода из «готово»
     сюда даёт не height, а min-height - он числовой в обоих состояниях (116 против 208) и
     анимируется, а height:auto просто следует за ним, пока содержимое ниже потолка. */
  .pb-ws[data-ws-section][data-state="empty"] .pb-ws__panel,
  .pb-ws[data-ws-section][data-state="picked"] .pb-ws__panel{height:auto;min-height:var(--pb-dock);
    grid-template-rows:minmax(var(--pb-dock-row),auto) minmax(var(--pb-dock-row),auto)}
  /* Пустой экран и выбранное фото укладываются целиком - прокрутка не нужна, а без неё
     работает подсказка о кадре, вынесенная НАД полосой (см. ниже). */
  .pb-ws[data-ws-section][data-state="empty"] .pb-ws__panel,
  .pb-ws[data-ws-section][data-state="picked"] .pb-ws__panel{overflow:visible}
  /* Содержимое полосы центрируется по её высоте в ЛЮБОМ состоянии. Правила выше по файлу
     (.pb-ws[data-state="empty"|"picked"] .pb-ws__panel{align-content:start}) писались для
     панели во весь экран, где поток сверху был осознанным решением; в полосе фиксированной
     высоты они прижимали содержимое к верхней кромке, и группа действия вставала на 13
     вместо 31 (замер ленты 04.09). */
  .pb-ws[data-ws-section][data-state] .pb-ws__panel{align-content:center}

  /* Блок состояния - строка. Список состояний повторяет базовый (см. выше по файлу):
     своего display у блоков нет, их гасит .pb-ws [data-panel] и включает этот список. */
  .pb-ws[data-ws-section][data-state="empty"] [data-panel~="empty"],
  .pb-ws[data-ws-section][data-state="picked"] [data-panel~="picked"],
  .pb-ws[data-ws-section][data-state="working"] [data-panel~="working"],
  .pb-ws[data-ws-section][data-state="done"] [data-panel~="done"],
  .pb-ws[data-ws-section][data-state="guest-done"] [data-panel~="guest-done"],
  .pb-ws[data-ws-section][data-state="error"] [data-panel~="error"],
  .pb-ws[data-ws-section][data-state="zero"] [data-panel~="zero"],
  .pb-ws[data-ws-section][data-state="window-24h"] [data-panel~="window-24h"],
  .pb-ws[data-ws-section][data-state="guest-used"] [data-panel~="guest-used"]{display:flex;flex-wrap:wrap;align-items:center;align-content:center;gap:10px 18px;grid-column:1;grid-row:1/3}

  /* Состояния без своей колонки действия занимают полосу целиком */
  .pb-ws[data-ws-section][data-state="working"] [data-panel~="working"],
  .pb-ws[data-ws-section][data-state="done"] [data-panel~="done"],
  .pb-ws[data-ws-section][data-state="guest-done"] [data-panel~="guest-done"],
  .pb-ws[data-ws-section][data-state="error"] [data-panel~="error"],
  .pb-ws[data-ws-section][data-state="zero"] [data-panel~="zero"],
  .pb-ws[data-ws-section][data-state="window-24h"] [data-panel~="window-24h"],
  .pb-ws[data-ws-section][data-state="guest-used"] [data-panel~="guest-used"]{grid-column:1/-1}

  /* «Пусто» и «выбрано»: блок распадается на ряды полосы (display:contents), чтобы подсказка
     и карточки цвета встали разными рядами. Одна и та же разметка (data-panel="empty picked",
     правка 04.09 - оператор отверг отдельный блок трёх шагов в empty) обслуживает оба
     состояния этим же правилом. Узлы остаются на месте - обработчики script.js и блока
     доп.инструкций ищут их по id и через closest. */
  .pb-ws[data-ws-section][data-state="empty"] .pb-ws__panel-top[data-panel~="empty"],
  .pb-ws[data-ws-section][data-state="working"] .pb-ws__panel-top[data-panel~="picked"],
  .pb-ws[data-ws-section][data-state="picked"] .pb-ws__panel-top[data-panel~="picked"]{display:contents}
  /* Состояния перечислены поимённо: [data-state] без значения показывал этот блок ВО ВСЕХ
     состояниях (правило перебивало .pb-ws [data-panel]{display:none}), и в денежных сквозь
     карточку оффера просвечивала строка «≈ 1 мин · баланс не спишется» - замер 03.09. */

  /* Порядок рядов в «выбрано»: карточки цвета, под ними подсказка нейросети (выбор оператора 04.09).
     06.09: место в гриде задаём по КЛАССУ .pb-ws__choice-list, а не по #modeToggleBtn - тот же
     ряд занимают карточки стилей «Оживления» (#animStyleList). У реставрации класс на том же
     элементе, поэтому её раскладка не меняется.
     09.09: #modeToggleBtn сменил класс на .pb-ws__toggle (переключатель вместо карточек) -
     место в гриде адресуем ЯВНО по id, вторым селектором, класс .pb-ws__choice-list по-прежнему
     держит место «Оживления». */
  /* 08.09, раскладка пакета Claude Design: слева ДВА ряда вместо трёх. Первый делят
     карточки цвета и правки кадра («Обрезать | Заменить»), второй занимает поле подсказки.
     Прежние три ряда (карточки / поле / правки) давали высоту 208; на 134 третьему ряду
     места нет, а пакет ставит правки кадра именно рядом с выбором режима.
     Ряд объявлен подсеткой: у карточек и правок общая высота --pb-dock-row, и обе части
     садятся на одну линию без второго источника числа. */
  /* Первый ряд левой колонки делится НАДВОЕ именованными линиями: карточки от края до
     линии «правки», правки — от неё до конца. Без именованных линий оба блока попадали бы
     в одну ячейку и наложились друг на друга. Ширину правок задаёт их содержимое (auto),
     карточкам достаётся остаток (1fr). */
  /* Колонки: minmax, а не жёсткое число - поле подсказки растёт до четырёх строк (CI_MAX),
     и второй ряд обязан расти вместе с ним - вместе с ним растёт и полоса, как было до правки.
     Ряда ровно ДВА. Без явного grid-template-rows оставался третий, нулевой высоты, и его
     зазор 8px добавлялся к высоте: полоса выходила 142 вместо 134 (замер 08.09). */
  .pb-ws[data-ws-section] .pb-ws__panel{
    grid-template-columns:[режим] minmax(0,1fr) [правки] auto [действие] var(--pb-act);
    grid-template-rows:minmax(var(--pb-dock-row),auto) minmax(var(--pb-dock-row),auto)}
  .pb-ws[data-ws-section] .pb-ws__choice-list,
  .pb-ws[data-ws-section] #modeToggleBtn{grid-column:режим / правки;grid-row:1;min-height:var(--pb-dock-row)}
  .pb-ws[data-ws-section] #customInstructionField{grid-column:режим / действие;grid-row:2;min-height:var(--pb-dock-row)}
  /* Третий ряд «выбрано»/«пусто» (вариант A 04.09, форма empty=picked 04.09): обрезка/замена,
     переехали с оверлея кадра. Обычный flex-ряд, кнопки - рамка с текстом (не тёмный оверлей,
     который был на кадре). Счётчик «Фото N из M» снят 04.09 - см. комментарий у разметки.
     Радиус 12 - один ряд с остальными мелкими кнопками полосы (правка 04.09, «скругления
     привести к одному ряду»): раньше был 99 (пилюля), разнобой с 16 у крупных блоков и
     карточек цвета отмечал оператор. */
  /* flex-wrap:nowrap (было wrap) - оператор 06.09, §6: перенос кнопки на вторую строку менял
     высоту полосы при переключении рельс/полное меню НА ОДНОЙ И ТОЙ ЖЕ ширине окна - ровно
     тот «прыжок», который правка запрещает. Вместо переноса кнопки СЖИМАЮТСЯ (flex:1 1 0,
     min-width:0) и обрезают подпись многоточием - высота третьего ряда одна и та же на
     любой ширине от 1181. Честное ограничение (см. отчёт задачи): на 1181 с развёрнутым
     меню (кадру ~511px) подписи жмутся до одного-двух символов, но НЕ переносятся и высоту
     не ломают. */
  /* 08.09: ряд правок кадра переехал из третьего ряда в ПЕРВЫЙ, к карточкам цвета
     (раскладка пакета). Третьего ряда в бюджете 134 нет.
     Колонка 1 первого ряда делится на две части сама (см. правило .pb-ws__choice-list
     ниже): карточки тянутся, правки встают справа по содержимому - как в пакете, где блок
     «Обрезать | Заменить» прижат к правому краю ряда управления. Ставить их в колонку 2
     нельзя: там живёт главная кнопка. */
  /* padding и gap сняты 08.09 (правка оператора, п.3): заливка половины обязана доходить до
     общей рамки, а внутренний отступ блока оставлял между ними светлое поле 5px. Разделитель
     между кнопками рисует .pb-ws__sep, зазор ему не нужен. overflow:hidden - чтобы прямые
     углы заливки не выступали за скруглённые углы блока. */
  .pb-ws[data-ws-section] #wsDockRow3{grid-column:правки / действие;grid-row:1;
    display:flex;flex-wrap:nowrap;align-items:center;gap:0;min-width:0;
    height:var(--pb-dock-row);padding:0;overflow:hidden}
  /* Кнопки правок больше не растягиваются на всю ширину (было flex:1 1 0 под отдельный
     третий ряд): теперь ряд стоит рядом с карточками и обязан занимать ровно своё
     содержимое, иначе он отжимает карточки. Подписи при этом перестают резаться. */
  .pb-ws[data-ws-section] #wsDockRow3 .pb-ws__btn-dock{flex:0 0 auto;height:100%;min-height:0}
  /* 08.09 (правка оператора, п.2): на УЗКОЙ полосе правки кадра сворачиваются до значков,
     а не жмут карточки цвета. Подпись прячется целиком - обрубка с многоточием не бывает.
     Ступень ставит скрипт атрибутом data-dock-tier по ФАКТИЧЕСКОЙ ширине полосы: медиа-запрос
     по окну здесь врёт, ширина полосы зависит ещё и от того, свёрнуто ли боковое меню
     (на 1280 полоса 602 при развёрнутом и 802 при рельсе). Порог 776 - из пакета.
     Подсказку значку без подписи ставит tipUnlessVisible (workshop.html) - она читает
     фактическую видимость подписи, отдельного порога не заводим. */
  .pb-ws[data-ws-section][data-dock-tier="narrow"] #wsDockRow3 .pb-ws__btn-dock{
    width:var(--pb-dock-row);padding:0;justify-content:center}
  .pb-ws[data-ws-section][data-dock-tier="narrow"] #wsDockRow3 .pb-ws__btn-dock span{display:none}
  /* Канон дополнительных кнопок 07.09: значок ведущий, зазор до подписи 9 (эталон
     .pb-ws__choice-btn). Выравнивание - по ЦЕНТРУ, но центрируется ПАРА «значок + подпись»
     целиком, а не текст отдельно: подпись не растягивается (flex:0 1 auto ниже), поэтому
     зазор остаётся 9 при любой ширине кнопки и разъезда, которым грешила прежняя схема
     (значок у края + растянутый текст по центру), не возникает. */
  /* 09.09: рамка и белый фон переехали на обёртку .pb-ws__edit (единый блок правок, оформление
     пакета) - сама кнопка внутри него прозрачная, границу между «Обрезать» и «Заменить»
     держит .pb-ws__sep, а не рамка каждой кнопки по отдельности. */
  /* 08.09 (правка оператора, п.3): заливка кнопки идёт ПО ОБВОДКЕ блока, без зазора.
     Кнопка занимает половину блока целиком (height:100%, свой радиус не держит - он
     наследуется от углов блока), поэтому наведение красит ровно левую или правую половину
     до самой рамки. Прежние border-radius:10px и padding блока оставляли по 5px светлого
     поля между заливкой и обводкой (замер 08.09). Внешние углы половин скругляются под
     радиус блока правилом ниже, внутренние - прямые, они стыкуются с разделителем. */
  .pb-ws[data-ws-section] .pb-ws__btn-dock{display:inline-flex;align-items:center;gap:9px;
    justify-content:center;
    height:100%;min-height:0;padding:0 14px;border-radius:0;border:0;
    background:transparent;color:var(--pb-ink);font:400 16px/1 var(--pb-font);cursor:pointer;
    flex:1 1 0;min-width:0;overflow:hidden}
  /* Внешние углы половин повторяют радиус блока минус его рамка (11 - 1 = 10). */
  .pb-ws[data-ws-section] #wsDockRow3 .pb-ws__btn-dock:first-child{border-radius:10px 0 0 10px}
  .pb-ws[data-ws-section] #wsDockRow3 .pb-ws__btn-dock:last-child{border-radius:0 10px 10px 0}
  .pb-ws[data-ws-section] .pb-ws__btn-dock span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .pb-ws[data-ws-section] .pb-ws__btn-dock svg{flex:0 0 auto;color:var(--pb-muted)}
  .pb-ws[data-ws-section] .pb-ws__btn-dock:hover{background:var(--hover-layer)}

  /* Заголовки рядов В ПОЛОСЕ лишние: «Что сделать с цветом» повторяет сами карточки,
     «Что будет дальше» - нумерацию шагов, «Что происходит» - список стадий. В денежных
     состояниях заголовок несёт смысл («0 обработок на балансе») и остаётся. Селектор сужен
     до .pb-ws__panel (сама полоса) - без этого он гасил бы .pb-ws__panel-heading и ВНУТРИ
     карточек инфопанели (.pb-ws__aside), где заголовок как раз нужен (правка 04.09). */
  .pb-ws[data-ws-section][data-state="empty"] .pb-ws__panel .pb-ws__panel-heading,
  .pb-ws[data-ws-section][data-state="picked"] .pb-ws__panel .pb-ws__panel-heading,
  .pb-ws[data-ws-section][data-state="working"] .pb-ws__panel .pb-ws__panel-heading{display:none}

  /* Вложенные столбики разворачиваются в строки */
  .pb-ws[data-ws-section] .pb-ws__panel-bottom{display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px;padding-top:0;border-top:0;width:100%}
  /* Гостю после результата плашка «Поделиться / Другое фото» скрыта (правило выше по файлу,
     script.js:1846). Строка полосы весит столько же и стоит позже - прятание повторено здесь,
     иначе плашка всплывала в guest-done (замер снятия стенда 04.09). */
  .pb-ws[data-ws-section][data-state="guest-done"] [data-done-next]{display:none}
  .pb-ws[data-ws-section] .pb-ws__offer-card{display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px;width:100%;padding:12px 14px}
  .pb-ws[data-ws-section] .pb-ws__btn-row{display:flex;gap:10px}
  .pb-ws[data-ws-section] .pb-ws__meta-row{gap:8px}
  /* Ошибка - та же сетка с колонкой действия: «Попробовать снова» встаёт туда же, где были
     «Восстановить фото» и «Скачать фото». Строкой не выходит: .error-area показывается из
     script.js ИНЛАЙН-стилем (display:block), а инлайн перебивает любой наш display - раскладку
     приходится строить ВНУТРИ неё, на .error-box. Прижатие через margin-left:auto давало
     кнопку в 311px от края (замер 03.09). */
  .pb-ws[data-ws-section] .error-area{width:100%}
  /* max-width и margin снимаем явно: в style.css:2066 карточка ошибки центрирована
     колонкой 500px, и в полосе она вставала посередине с полем 278px слева (замер 03.09). */
  .pb-ws[data-ws-section] .error-box{display:grid;grid-template-columns:minmax(0,1fr) var(--pb-act);
    grid-auto-flow:row;align-items:center;align-content:center;gap:8px 20px;width:100%;max-width:none;margin:0;padding:0;text-align:left}
  .pb-ws[data-ws-section] .error-icon,
  .pb-ws[data-ws-section] .error-box .error-title,
  .pb-ws[data-ws-section] #errorText{grid-column:1;text-align:left}
  /* justify-* в stretch: из style.css у ряда кнопок центрирование, и в гриде оно сжимало
     трек до ширины текста - кнопка выходила 230 вместо колонки 320. */
  .pb-ws[data-ws-section] .error-actions{justify-content:stretch;justify-items:stretch}
  .pb-ws[data-ws-section] #errorRetryBtn{width:100%;min-height:64px;padding:0}
  .pb-ws[data-ws-section] .pb-ws__stage-list{display:flex;flex-wrap:wrap;gap:8px 18px}

  /* «Готово» - такая же сетка с колонкой действия, как у пустого и выбранного: «Скачать
     фото» встаёт ровно туда, где только что была «Восстановить фото». Одной строкой блок
     не укладывался - «Скачать» переносило на второй ряд с дырой слева (замер 03.09).
     Колонок три: галка с итогом, дальнейшие шаги, действие. */
  /* 06.09: колонок ДВЕ, а не три. Средняя колонка под «Что можно дальше» была 108px, карточка
     «Оживить» вставала в неё столбиком 132x197 и распирала ряд: сумма рядов переставала
     помещаться в полосу фиксированной высоты и выдавливала строку «Готово. Сохранено в галерее»
     на 11px ВЫШЕ верхнего края полосы (замер 06.09). Теперь раскладка повторяет «выбрано»:
     слева ряды содержимого, справа колонка главного действия. */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__panel,
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__panel{grid-template-columns:minmax(0,1fr) var(--pb-act);grid-template-rows:auto 1fr}
  /* 08.09: третьего ряда у working тоже больше нет (см. правку у empty/picked выше) - тот же
     лишний трек добавлял 8px и здесь, хотя контент working растянут на grid-row:1/3 и висит
     не рядами, а сеткой целиком через display:flex у [data-panel~="working"]. */
  .pb-ws[data-ws-section][data-state="working"] .pb-ws__panel{grid-template-rows:minmax(var(--pb-dock-row),auto) minmax(var(--pb-dock-row),auto)}
  .pb-ws[data-ws-section][data-state="working"] #wsDockRow3{visibility:hidden}
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__cta-group[data-panel~="done"],
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__cta-group[data-panel~="guest-done"]{display:contents}
  .pb-ws[data-ws-section] .pb-ws__panel > .pb-ws__cta-group > .pb-ws__success-row{grid-column:1;grid-row:1}
  /* ===== Место действия =====
     Группа действия крепится к САМОЙ полосе: правый край, центр по высоте. Через сетку это не
     держалось - высота строк зависит от содержимого состояния, и кнопка гуляла (32 у вошедшего,
     42 у гостя, у которого нижний ряд пуст, 115 при попытке растянуть ряды). Колонку под неё
     сетка всё равно резервирует (--pb-act в grid-template-columns), так что текст слева под
     кнопку не заезжает.
     04.09: держится СЕТКОЙ, а не position:absolute. Прежний способ (absolute + top:50% +
     translateY(-50%)) в трёхзонной раскладке давал кнопку на 25px ВЫШЕ центра в empty и
     picked: группа - грид-ребёнок полосы, общее правило блоков состояния даёт ей место в
     сетке, а у абсолютного грид-ребёнка с заданным местом containing block - грид-область,
     не полоса, и 50% считались не от той высоты. Растяжка grid-area:1/1/-1/-1 вертикаль не
     чинила и уводила группу от правого края на 29px вместо 14 (замер 04.09: containing
     block становился content-box, а он уже на величину padding). Сетка держит оба правила
     сразу: grid-row:1/-1 отдаёт группе всю высоту полосы, align-self:center сажает её по
     центру этой высоты в ЛЮБОМ состоянии, justify-self:end прижимает к правому краю.
     grid-column:-2 - предпоследняя линия, то есть колонка --pb-act, сколько бы колонок
     сетка ни объявила. Правило оператора «главная кнопка всегда посередине по высоте»
     (04.09) держит сценарий workshop-restore-layout допуском 3px. */
  .pb-ws[data-ws-section][data-state="empty"] .pb-ws__cta-group[data-panel="empty picked"],
  .pb-ws[data-ws-section][data-state="picked"] .pb-ws__cta-group[data-panel="empty picked"],
  .pb-ws[data-ws-section][data-state="working"] .pb-ws__cta-group[data-panel="empty picked"],
  /* Три денежных состояния (правка 07.09): офферная карточка ушла в инфопанель, в полосе
     остаётся ТОЛЬКО кнопка - та же колонка действия, что у "empty picked" выше и у .pb-ws__act
     ниже, второго способа центрирования не заводим. Раньше здесь стоял общий поток строкой
     (см. отменённый блок «денежные текут строкой» ниже) - высота полосы зависела от контента
     оффера (замер ДО правки: 300/254/288), теперь кнопка просто занимает колонку действия. */
  .pb-ws[data-ws-section][data-state="zero"] .pb-ws__cta-group[data-panel="zero"],
  .pb-ws[data-ws-section][data-state="window-24h"] .pb-ws__cta-group[data-panel="window-24h"],
  .pb-ws[data-ws-section][data-state="guest-used"] .pb-ws__cta-group[data-panel="guest-used"],
  .pb-ws[data-ws-section] .pb-ws__act,
  /* 07.09: ширину блока действия задаёт КОЛОНКА сетки (--pb-act в grid-template-columns),
     сам блок её просто заполняет (justify-self:stretch). Прежнее width:var(--pb-act) после
     перевода --pb-act в clamp(...33.33%...) ломало кнопку: процент внутри clamp считался от
     самого блока, а не от полосы, и упирался в нижнюю границу - кнопка застревала на 200px и
     не менялась при сворачивании меню, хотя колонка ехала 240↔280 (замечание оператора). */
  .pb-ws[data-ws-section] .error-actions{grid-column:-2;grid-row:1/-1;
    align-self:center;justify-self:stretch;position:relative;
    display:grid;gap:8px;align-content:center;min-height:64px;margin:0}
  /* 06.09: по центру полосы стоит САМА КНОПКА, а не группа «кнопка + подписи» (правка
     оператора: «кнопка должна быть по центру панели по высоте, все подписи ниже»). Раньше
     центрировалась группа целиком (align-content:start + min-height:93), и подпись под
     кнопкой тянула кнопку вверх на половину своей высоты.
     Механизм: в ПОТОКЕ группы остаётся одна кнопка, поэтому центр группы = центр кнопки.
     Подписи выведены из потока и привязаны к краям кнопки: статус кадра над ней, время и
     баланс под ней. Разбор отвергнутого варианта (три ряда 1fr/auto/1fr внутри растянутой
     группы): fr-ряд заставляет контейнер вместить содержимое ПРОПОРЦИОНАЛЬНО, то есть
     пустой ряд над кнопкой становился ростом с подпись под ней, и на ГОСТЕ, где строка
     «≈ 1 мин · бесплатно, баланс не спишется» переносится на две строки (51px), полоса
     распухала с 208 до 216 (замер 06.09). Высота полосы задана числом (--pb-dock) и от
     способа центрирования зависеть не должна - отсюда вывод подписей из потока.
     Барьер - сценарий workshop-restore-layout, он меряет ЦЕНТР КНОПКИ против центра полосы
     с допуском 3px. */
  .pb-ws[data-ws-section] #hdrStatus{position:absolute;left:0;right:0;bottom:100%;margin:0 0 8px}
  /* 06.09: строка состояния встаёт НАД кнопкой (правка оператора «статус-надпись над главной
     кнопкой»). Из потока группы подписи выведены по-прежнему, поэтому центр кнопки не едет и
     допуск 3px сценария workshop-restore-layout держится. #errorSecondaryCta остаётся снизу:
     это второе действие, а не статус. */
  /* Подписи шире кнопки: место под них резервирует поле колонки (30/29), и в одну строку они
     влезают только если им разрешено выйти за 200px ширины кнопки. Пока стояло left:0;right:0,
     обе переносились на две строки, контент полосы вырастал со 155 до 162, и полоса включала
     свою прокрутку - при том что справа от кнопки пусто (замер 07.09: полоса 602, занято 200).
     Центр кнопки не едет: подписи по-прежнему вне потока и центрируются трансформом. */
  .pb-ws[data-ws-section] #restoreMetaLine,
  .pb-ws[data-ws-section] #wsDoneHint,
  .pb-ws[data-ws-section] #errorSecondaryCta{position:absolute;left:50%;right:auto;transform:translateX(-50%);
    width:max-content;max-width:var(--pb-act-cap, 300px);top:100%;margin:8px 0 0}
  /* 08.09 (правка оператора, п.1): в бюджете 134 подпись, выведенная из потока под кнопку
     (top:100%), уезжала за нижний край полосы - на замере её верх приходился на 107 при
     высоте полосы 134. Раскладка пакета иная: кнопка и мета-строка стоят в ОДНОМ потоке
     колонки действия, и группа прижата к низу - низ подписи совпадает с низом остальной
     полосы, а кнопка стоит над ней.
     Прежнее центрирование кнопки по высоте (align-content:center) больше не действует:
     оператор 08.09 выровнял кнопку по нижней части, как в пакете. Сценарий
     workshop-restore-layout, меривший центр кнопки против центра полосы, обновляется
     вместе с этой правкой - иначе он держал бы отменённое правило. */
  /* Узел группы в рабочих состояниях - .pb-ws__cta-group[data-panel="empty picked"],
     а НЕ .pb-ws__act (тот пуст в picked/empty/working, замер 08.09). Правило, повешенное
     на .pb-ws__act, туда не доезжало, и кнопка оставалась центрированной по высоте. */
  /* Группа растянута на всю высоту полосы, внутри - два ряда: кнопка (её высота) и подпись
     (остаток). Так верх кнопки совпадает с верхом соседних блоков, а подпись садится на
     нижнее поле. align-content:end прижимал бы ОБА ряда вниз и уводил кнопку с линии. */
  .pb-ws[data-ws-section][data-state="picked"] .pb-ws__cta-group[data-panel="empty picked"],
  .pb-ws[data-ws-section][data-state="empty"] .pb-ws__cta-group[data-panel="empty picked"],
  .pb-ws[data-ws-section][data-state="working"] .pb-ws__cta-group[data-panel="empty picked"],
  .pb-ws[data-ws-section][data-state="picked"] .pb-ws__act,
  .pb-ws[data-ws-section][data-state="empty"] .pb-ws__act,
  .pb-ws[data-ws-section][data-state="working"] .pb-ws__act{
    align-content:start;align-self:stretch;grid-template-rows:auto 1fr;gap:6px}
  /* Подпись прижата к низу своего ряда: её нижняя линия совпадает с нижним полем полосы. */
  /* 08.09 (правка оператора, п.1): верх главной кнопки стоит на ОДНОЙ линии с переключателем
     режима и блоком правок кадра - все три начинают первый ряд полосы. Кнопка выше ряда
     (64 против 48), поэтому её низ уходит ниже; подпись под ней встаёт последней строкой
     и заканчивается у нижнего поля полосы. */
  .pb-ws[data-ws-section][data-state="picked"] #restoreBtn,
  .pb-ws[data-ws-section][data-state="empty"] #restoreBtn,
  .pb-ws[data-ws-section][data-state="working"] #restoreBtn{align-self:start}
  /* margin-bottom 5 (правка оператора 08.09): вплотную к нижнему полю подпись читалась
     прижатой. Отступ живёт ИМЕННО В ЭТОМ правиле, а не отдельным блоком выше: здесь стоит
     margin:0, и любое более раннее правило той же специфичности он перебивал молча. */
  .pb-ws[data-ws-section][data-state="picked"] #restoreMetaLine,
  .pb-ws[data-ws-section][data-state="empty"] #restoreMetaLine,
  .pb-ws[data-ws-section][data-state="working"] #restoreMetaLine{
    position:static;transform:none;left:auto;top:auto;margin:0 0 5px;width:auto;max-width:none;
    align-self:end;justify-content:center}
  /* 08.09 (правка оператора, п.1): значки и цифры в мета-строке стоят на ОДНОЙ линии.
     До правки значок кошелька был на 93.5, значок часов на 95.5, текст на 92.5 - три разные
     линии, потому что flex центрировал каждую часть по своей высоте, а svg наследовал
     vertical-align:baseline. Выравниваем по центру ряда и гасим базовую линию у значков. */
  .pb-ws[data-ws-section] #restoreMetaLine{align-items:center;line-height:1;gap:7px}
  .pb-ws[data-ws-section] #restoreMetaLine svg,
  .pb-ws[data-ws-section] #restoreMetaLine .pb-ws__meta-icon{display:block;vertical-align:middle;
    flex:0 0 auto;width:16px;height:16px}
  .pb-ws[data-ws-section] #restoreMetaLine .pb-ws__meta-time,
  .pb-ws[data-ws-section] #restoreMetaLine #restoreMetaBalancePart{display:inline-flex;
    align-items:center;gap:5px;line-height:1}
  /* Знак «равно» между кошельком и числом (правка оператора 08.09): «кошелёк = 99».
     Приглушён как остальная служебная строка, своей краски не заводит. */
  .pb-ws[data-ws-section] #restoreMetaLine .pb-ws__meta-eq{color:var(--pb-muted);line-height:1}
  /* Итог обработки - единственная подпись НАД кнопкой (правка оператора 06.09). Из потока
     выведена так же, как подписи снизу, поэтому центр кнопки остаётся на месте. */
  .pb-ws[data-ws-section] .pb-ws__act .pb-ws__success-row{position:absolute;left:50%;right:auto;transform:translateX(-50%);
    width:max-content;max-width:var(--pb-act-cap, 300px);bottom:100%;margin:0 0 8px;justify-content:center}

  /* Тот же 8px, что у подписи над кнопкой: правка оператора 06.09 «подпись всегда на одном
     расстоянии сверху и снизу». margin:0 здесь гасил зазор до 0 (замер 06.09). */
  .pb-ws[data-ws-section] #wsDoneHint{margin:8px 0 0;text-align:center}
  /* Кегль подписей в полосе (правка оператора 07.09 «сделать чуть мельче, чтобы всегда были
     отцентрованы»): 16/15 давали строку шире потолка 300 и перенос возвращался. 14 - нижняя
     граница мобильного канона для вспомогательного текста, ниже не опускаем. Правило живёт
     внутри media 1181+, на телефоне кегль остаётся прежним. */
  .pb-ws[data-ws-section] .pb-ws__act .pb-ws__success-text{font-size:14px;line-height:1.3}
  .pb-ws[data-ws-section] #wsDoneHint{font-size:14px;line-height:1.35}
  /* Кружок галочки под уменьшенный кегль: 22px рядом с 14px текстом выглядел мишенью. */
  .pb-ws[data-ws-section] .pb-ws__act .pb-ws__check-circle{width:18px;height:18px;flex:0 0 18px}
  .pb-ws[data-ws-section] #resultSharePlate{grid-column:1;grid-row:2}

  /* ===== «Готово»: ряд из трёх равных кнопок и строка исхода над ним (правка оператора
     07.09, макет mockups/workshop-dock-states.html) =====
     Было: «Скачать фото» 200x64 справа, «Поделиться» и «Следующее» 174x56 слева, итог над
     кнопкой, расход под ней - три кнопки на трёх уровнях (замер: верх 77 против 47) и два
     текста по разные стороны. Стало: одна строка исхода, под ней три одинаковые кнопки.
     Три кнопки лежат в РАЗНЫХ родителях (.pb-ws__act держит статус и «Скачать»,
     #resultSharePlate - «Поделиться» и «Следующее»), поэтому оба контейнера гасим в
     display:contents - все узлы становятся детьми одного грида на .pb-ws__cta-group. */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__cta-group[data-panel~="done"],
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__cta-group[data-panel~="done"]{
    display:grid;grid-template-columns:repeat(3,1fr);column-gap:12px;width:100%;
    /* Три ряда: воздух / кнопки / воздух (вариант E макета workshop-done-rhythm.html, выбор
       оператора 07.09). Крайние ряды равны, поэтому строка исхода и блок звёзд стоят
       серединой своих половин, а ряд кнопок - серединой полосы.
       height:100% и grid-row:1/-1 обязательны: полоса объявляет свои ряды (auto 1fr, строка
       ~903), и без растяжки группа занимала высоту содержимого - 174 при полосе 208 (замер
       07.09), крайние 1fr делили остаток и центрировать было нечего.
       Правило живёт ЗДЕСЬ, а не выше по файлу: display:contents (строка ~907) и колонки
       перебивали ряды, объявленные раньше. */
    grid-template-rows:1fr auto 1fr;row-gap:0;height:100%;grid-row:1/-1;align-self:stretch}
  /* 07.09, замер _diag-panel-geometry: было gap:12px (шорхэнд) - переустанавливал row-gap
     обратно в 12px ПОВЕРХ row-gap:0 из правила выше (строка 729, тот же селектор, но раньше
     по файлу - позже побеждает при равной специфичности). Два лишних гэпа между тремя
     grid-строками (1fr auto 1fr) растили группу до 211px при полосе 208 - переполнение.
     column-gap держит зазор между тремя кнопками по горизонтали, row-gap трогать нельзя. */
  /* Гостю «Поделиться» и «Следующее» не показывают вовсе (script.js прячет #shareBtn и
     #resultSharePlate инлайновым display:none - решение старше этой правки). Три колонки
     ему не нужны: одна кнопка растягивалась бы на треть ширины, а остальные две трети
     оставались пустыми. Плюс инлайновый display:none на плашке перебивает display:contents
     ниже, из-за чего грид ломался и полоса раздувалась до 300px с прокруткой (замер 07.09). */
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__cta-group[data-panel~="done"]{
    grid-template-columns:minmax(0,320px);justify-content:center}
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__act,
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__act,
  .pb-ws[data-ws-section][data-state="done"] #resultSharePlate,
  .pb-ws[data-ws-section][data-state="guest-done"] #resultSharePlate{display:contents}
  /* Ряд звёзд (правка оператора 07.09, § центровка): #thumbRow - такой же ребёнок грида
     полосы, как три кнопки, и без явного места вставал в ПЕРВУЮ колонку из трёх. Звёзды
     центрировались внутри своей колонки шириной 196px, то есть на 182px левее середины
     полосы (замер 07.09) - оператор видел их прижатыми влево.
     grid-column:1/-1 отдаёт ряду всю ширину, дальше .thumb-row (style.css) центрирует
     содержимое сам. Высота компактная: полоса живёт фиксированным числом (--pb-dock 208),
     и лишние пиксели ряда выдавливали бы кнопки вверх. */
  .pb-ws[data-ws-section][data-state="done"] #thumbRow,
  .pb-ws[data-ws-section][data-state="guest-done"] #thumbRow{
    grid-column:1/-1;min-height:0;margin:0;justify-self:center;width:100%;align-self:center;
    /* .thumb-row (style.css) несёт min-height:48 и margin-top:2 для лендинга - в полосе
       высоту задаёт грид, иначе блок занимает всю нижнюю половину и центрировать нечего
       (замер 07.09: 0 сверху, 2 снизу). */
    gap:0}
  .pb-ws[data-ws-section][data-state="done"] .rating5-wrap,
  .pb-ws[data-ws-section][data-state="guest-done"] .rating5-wrap{gap:2px}
  /* Звезда 28px в макете лендинга; в полосе ряд идёт третьей строкой под кнопками, и полный
     размер отнимал бы высоту у кнопок. 22px рисунка при padding 11px даёт кликабельную
     цель 44px - нижняя граница мобильного канона (CONVENTIONS.md § Мобильный канон), при
     этом строка ряда остаётся компактной. */
  .pb-ws[data-ws-section][data-state="done"] .rating5-star svg,
  .pb-ws[data-ws-section][data-state="guest-done"] .rating5-star svg{width:29px;height:29px}
  .pb-ws[data-ws-section][data-state="done"] .rating5-star,
  .pb-ws[data-ws-section][data-state="guest-done"] .rating5-star{padding:2px}
  /* padding 4, не 8: при рисунке 29px кликабельная цель выходит 37px. Мобильный канон (44)
     тут не нарушается - полоса с этим рядом живёт только на широком экране (медиазапрос
     1181+), на телефоне ряд идёт прежним .thumb-row из style.css со своими размерами.
     Причина ужатия: ряд грида 68.4px, а блок «подпись + звёзды» занимал ровно 68 - на
     центрирование в своей половине не оставалось места (замер 07.09: 0 сверху, 8 снизу). */
  .pb-ws[data-ws-section][data-state="done"] .rating5-label,
  .pb-ws[data-ws-section][data-state="guest-done"] .rating5-label{font-size:13px;line-height:1.1}
  /* Строка исхода: своя строка во всю ширину, в поток (не absolute, как было при привязке
     к кнопке). Потолок ширины снят - при нём строка ломалась пополам НЕЗАВИСИМО от кегля
     (замер 07.09: текст 453.8px не помещался в 300px ни при 16px, ни при 12px). nowrap
     держит её в одну строку: перенос растит высоту полосы. */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__act .pb-ws__success-row,
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__act .pb-ws__success-row{
    position:static;left:auto;transform:none;max-width:none;width:auto;
    grid-column:1/-1;justify-content:center;margin:0}
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__success-text,
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__success-text{
    white-space:nowrap;font-size:13px;line-height:1.4}
  /* Кнопки ряда: одинаковые габариты и ОДНА типографика. Иерархию несёт заливка, шрифту её
     дублировать незачем - до правки было 15.5/600 против 18/700 и три разных цвета подписи. */
  .pb-ws[data-ws-section][data-state="done"] #downloadBtn,
  .pb-ws[data-ws-section][data-state="guest-done"] #downloadBtn,
  .pb-ws[data-ws-section][data-state="done"] #shareBtn,
  .pb-ws[data-ws-section][data-state="guest-done"] #shareBtn,
  .pb-ws[data-ws-section][data-state="done"] #newPhotoBtn,
  .pb-ws[data-ws-section][data-state="guest-done"] #newPhotoBtn{
    height:52px;min-height:52px;width:100%;margin:0;box-sizing:border-box;
    display:flex;align-items:center;justify-content:center;gap:12px;
    font-size:15px;font-weight:600;letter-spacing:normal}
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__menu-title,
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__menu-title{
    font-size:15px;font-weight:600}
  /* ВЫРАВНИВАНИЕ ПОДПИСЕЙ РЯДА (правка оператора 07.09, 4-й заход - прежние три чинили не то).
     Симптом: у «Скачать фото» значок и подпись стоят компактной парой по центру кнопки, а у
     «Поделиться» и «Следующего» подпись отъезжает от значка вправо, слева пусто.
     Причина не в центрах и не в зазоре (замер: у всех троих центр 0, зазор 13.5) - в ШИРИНЕ
     блока подписи: базовое .pb-ws__suggest-card--compact .pb-ws__menu-title даёт ей
     flex:1 1 auto, и текст растягивается на всю свободную ширину кнопки (замер: 170px против
     102.3px у «Скачать», которому этот класс не достаётся). Внутри растянутого блока слово
     прижато влево - отсюда и вид «съехало».
     Ряд «готово» - кнопки равной ширины с центрированным содержимым, растяжение подписи здесь
     не нужно: flex:0 1 auto возвращает блоку ширину самого текста. Многоточие при нехватке
     места сохраняем (min-width:0 + ellipsis остаются от базового правила). */
  .pb-ws[data-ws-section][data-state="done"] #shareBtn .pb-ws__menu-title,
  .pb-ws[data-ws-section][data-state="guest-done"] #shareBtn .pb-ws__menu-title,
  .pb-ws[data-ws-section][data-state="done"] #newPhotoBtn .pb-ws__menu-title,
  .pb-ws[data-ws-section][data-state="guest-done"] #newPhotoBtn .pb-ws__menu-title{flex:0 1 auto}
  /* Акцент ряда - на «Следующее фото» (правка оператора 07.09): после скачивания человек
     возвращается к работе, следующий кадр и есть продолжение сценария, а «Скачать» он к
     этому моменту уже нажал. Заливку получает #newPhotoBtn, «Скачать» и «Поделиться»
     остаются белыми с тёмной подписью. Селектор по .pb-ws__menu-title обязателен: цвет
     подписи ставит класс .pb-ws__suggest-card--accent на самой кнопке (строка 128), и
     правило по одному #id его не перебивает. */
  .pb-ws[data-ws-section][data-state="done"] #shareBtn,
  .pb-ws[data-ws-section][data-state="guest-done"] #shareBtn,
  .pb-ws[data-ws-section][data-state="done"] #downloadBtn,
  .pb-ws[data-ws-section][data-state="guest-done"] #downloadBtn,
  .pb-ws[data-ws-section][data-state="done"] #shareBtn svg,
  .pb-ws[data-ws-section][data-state="guest-done"] #shareBtn svg,
  .pb-ws[data-ws-section][data-state="done"] #downloadBtn svg,
  .pb-ws[data-ws-section][data-state="guest-done"] #downloadBtn svg,
  .pb-ws[data-ws-section][data-state="done"] #shareBtn .pb-ws__menu-title,
  .pb-ws[data-ws-section][data-state="guest-done"] #shareBtn .pb-ws__menu-title,
  .pb-ws[data-ws-section][data-state="done"] #downloadBtn .pb-ws__menu-title,
  .pb-ws[data-ws-section][data-state="guest-done"] #downloadBtn .pb-ws__menu-title{color:var(--pb-ink)}
  /* Порядок карточек инфопанели ДЛЯ ГОСТЯ (правка оператора 07.09): оффер первым, «Что даёт
     вход» следом, «Оживить» и «Фотолаборатория» ниже. В разметке они идут в другом порядке -
     он общий с вошедшим, у которого оффера нет и первыми уместны продолжения работы. Гостю
     же первым экраном нужен повод остаться: без входа его результат никуда не сохранится.
     Меняем order, а не разметку - иначе пришлось бы дублировать карточки на два состояния. */
  /* Порядок отрицательный (09.09): карточка акции promo-render.js renderPromoAsideCard живёт
     без data-panel (видна во всех состояниях) и без order сама встала бы выше офферов welcome
     (у остальных карточек order 0). Приоритет стопки: welcome -2, акция -1, всё прочее 0+. */
  .pb-ws[data-ws-section][data-state="guest-done"] #wsAsideGuestOffer{order:-2}
  /* Оффер на экране результата - первым, как и у гостя (правка оператора 07.09). В разметке
     карточка лежит среди офферов денежных состояний, то есть НИЖЕ «Оживить» и
     «Фотолаборатории», и без order человек видел предложение с 24-часовым таймером последним
     (замер 07.09: порядок был «Сохранено | Оживить | Фотолаборатория | 🎁»).
     Карточке «Сохранено в галерее» (data-panel="done", без id) порядок задаём ЯВНО: по
     умолчанию у неё order:0, и она обходила оффер, какой бы номер он ни получил.
     Оба правила по одному селектору с id: если гасить соседей через :not(#id), тот выигрывает
     по специфичности у правила с id и обнуляет порядок самого оффера (замер 07.09). */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__aside-card[data-panel="done"]{order:2}
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__aside-card[data-panel="done guest-done"]{order:3}
  .pb-ws[data-ws-section][data-state="done"] #wsAsideDoneOffer{order:-2}
  /* #wsAsideDoneOffer скрывает refreshDoneWelcomeOffer() атрибутом hidden (баланс>0 или окно
     истекло), но карточка попадает под .pb-ws__aside-card[data-panel~="done"]{display:block}
     ниже по файлу (специфичность 0,5,0) - она выше общего .pb-ws [hidden]{display:none}
     (0,1,1, комментарий строки 38-41), и hidden молча проигрывает (та же гоча, что у плитки
     welcome в data-panel="zero"). id-селектор перебивает то и другое. */
  #wsAsideDoneOffer[hidden]{display:none}
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__aside-card[data-panel="guest-done"]{order:2}
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__aside-card[data-panel="done guest-done"]{order:3}
  /* Тот же приём для трёх денежных состояний (правка 07.09, задание «окно действий - для
     действий»): оффер первым (та же карточка, что раньше стояла содержимым полосы), пояснение
     вторым - естественный порядок разметки уже совпадает (offer вставлен первым среди карточек
     каждого состояния), order здесь фиксирует его явно, как и у guest-done выше. */
  .pb-ws[data-ws-section][data-state="zero"] #wsAsideZeroWelcomeOffer{order:-2}
  /* Та же гоча, что у #wsAsideDoneOffer выше: [data-panel~="zero"]{display:block} перебивает hidden. */
  #wsAsideZeroWelcomeOffer[hidden]{display:none}
  .pb-ws[data-ws-section][data-state="zero"] #wsAsideZeroOffer{order:1}
  /* П6 правка оператора 09.09: при нуле обработок с живым welcome-окном плашка баланса
     («0 обработок на балансе» + тарифы 10/30) дублирует карточку оффера - лишняя. Прячем
     плашку, ПОКА оффер виден (startZeroWelcomeTile ставит/снимает [hidden] на welcome-карточке,
     второго источника правды не заводим - читаем то же состояние через adjacent sibling).
     Оффер истёк (welcome-карточка получила [hidden]) - плашка возвращается сама, иначе нулевой
     баланс останется без объяснения. id-специфичность нужна, чтобы перебить
     [data-panel~="zero"]{display:block} выше, та же гоча, что у #wsAsideZeroWelcomeOffer[hidden]. */
  #wsAsideZeroWelcomeOffer:not([hidden]) + #wsAsideZeroOffer{display:none}
  .pb-ws[data-ws-section] .pb-ws__aside-card--promo{order:-1}
  /* Бонусная карточка - СРАЗУ ПОД ОФФЕРОМ (правка оператора 20.09: в разметке она лежит
     после справочных карточек и человек видел её третьей, ниже «Как снять старое фото»).
     Стопка колонки: офферы -2, акция и бонусы -1, прочее 0+. Тот же приём и тот же довод,
     что у купона на фотокнигу ниже: место держит order, а не позиция в разметке - карточка
     живёт в трёх панелях сразу, и таскать её по трём местам разметки значит завести три
     точки рассинхрона.
     Класс .pb-ws__aside-card добавлен ЯВНО (0,4,0): без него голый .ws-bonus-invite проигрывал
     бы правилам вида .pb-ws__aside-card[data-panel="done"]{order:2} - та же гоча, на которой
     купон уезжал в конец колонки (замер 15.09). */
  .pb-ws[data-ws-section] .pb-ws__aside-card.ws-bonus-invite{order:-1}
  /* Купон на фотокнигу: строго ПОД главным оффером (решение оператора 15.09). Оффер -2,
     купон -1, прочие карточки состояния 2 и 3 - место держит order, а не позиция в разметке.
     Селектор по классу .pb-ws__aside-coupon (общему у обеих карточек-близнецов), не по id -
     см. разбор специфичности у правила [hidden] ниже. */
  /* order:1 - строго НИЖЕ любого оффера и ВЫШЕ обычных карточек состояния (2 и 3).
     -1 здесь не годится: боевой оффер вставляется скриптом с классом --promo, у которого
     тот же order:-1, и при равенстве выигрывает порядок разметки - купон вставал НАД
     оффером (замер 15.09, снимок колонки). 0 занят карточкой поддержки без data-panel. */
  /* Купон встаёт СРАЗУ под оффером. order:-1 занят офферами (класс --promo, его ставит
     promo-render.js), и при равенстве выигрывал бы порядок разметки - купон вставал НАД
     оффером (замер 15.09). Дробный order недопустим, поэтому берём 0, а карточку
     поддержки (у неё order:0 по умолчанию, отдельного правила нет) опускаем до 4:
     предложение уместнее сразу за оффером, а помощь - в конце колонки. */
  /* .pb-ws__aside-card добавлен ЯВНО: у карточки есть data-panel="done", и правило
     .pb-ws__aside-card[data-panel="done"]{order:2} ниже по файлу (0,5,0) перебивало голый
     класс (0,4,0) - купон уезжал в конец колонки. Замер 15.09 в браузере: computed order
     был 2 вместо 0 при живом правиле. Тот же приём, что у [hidden] ниже. */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__aside-card.pb-ws__aside-coupon{order:0;position:relative}
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__aside-card:not([data-panel]):not(.pb-ws__aside-card--promo):not(.pb-ws__aside-coupon){order:4}
  /* Близнец Фотолаборатории (#flAsideBookCoupon) без id-специфичности проигрывал бы
     .pb-ws__aside-card[data-panel~="done"]{display:block} (0,5,0) ниже по файлу и оставался
     видимым при hidden (найдено code review 15.09: у fl-карточки не было ВООБЩЕ ни одного
     правила, включая это, и она показывалась гостям). Класс .pb-ws__aside-card добавлен
     явно, чтобы перебить конкурента специфичностью (0,6,0), а не порядком в файле - тот же
     разбор, что у #wsAsideDoneOffer[hidden] выше. */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__aside-card.pb-ws__aside-coupon[hidden]{display:none}
  /* Фон купона - разворот фотокниги (материал оператора 15.09). Гасим саму КАРТИНКУ через
     opacity отдельного слоя, а не кладём сплошную заливку поверх неё: у заливки виден
     собственный край, и стык с рамкой читался неаккуратно (замечание оператора 15.09).
     Слой лежит под содержимым (z-index:0 против 1 у детей) и обрезан inset:1px, поэтому
     на рамку не заходит вовсе - ни картинкой, ни своим краем. */
  /* Картинка - САМ фон карточки: ни белой подложки под ней, ни слоя поверх. Заливка под
     полупрозрачным слоем читалась отдельной плашкой у края (замечание оператора 15.09).
     Кончается картинка - сразу рамка, промежуточных краёв нет. Приглушённость запечена
     в самом файле (coupon-bg.webp), а не наложением. */
  /* .pb-ws__aside-card здесь по той же причине, что у order выше: голый класс проигрывал
     правилам карточки состояния, и фон не вставал вовсе (замер 15.09 в браузере: computed
     background-image = none у близнеца Фотолаборатории). */
  /* Класс --accent добавлен в селектор ЯВНО: у карточки он есть в разметке, а правило
     .pb-ws__aside-card--accent{background:var(--pb-accent-band)} ниже по файлу имеет ту же
     специфичность и побеждало ПОРЯДКОМ. Второй конкурент - .pb-ws__aside-card[data-panel="done"]
     с белой заливкой (0,5,0). Замер 15.09 сценарием workshop-coupon-twins: computed
     background-image был none при живом правиле картинки. */
  /* Здесь только рамка и обрезка. САМ фон-картинка стоит ниже по файлу, сразу после
     показывающего правила с background:#fff - иначе белая заливка выигрывает порядком
     при равной силе (разбор там же). */
  .pb-ws[data-ws-section] .pb-ws__aside-card.pb-ws__aside-coupon{
    position:relative;border:1px solid var(--pb-line);overflow:hidden}
  .pb-ws[data-ws-section] .pb-ws__aside-coupon > *{position:relative;z-index:1}
  /* Шапка купона: заголовок по центру, управление по краям (решение оператора 15.09).
     Кнопки выведены из потока, чтобы центровка считалась по ширине карточки, а не по
     остатку места - иначе заголовок съезжает влево на длинных подписях. */
  /* Шапка в ОДНОМ ряду, как у оффера: заголовок и кнопка выровнены друг относительно
     друга по вертикали. Прежняя раскладка центрировала заголовок по карточке, а кнопку
     уводила в absolute - они переставали быть на одной линии (замечание оператора 15.09).
     Пустышка ::before шириной с кнопку возвращает заголовку центр самой КАРТОЧКИ. */
  .pb-ws[data-ws-section] .pb-ws__aside-coupon .pb-ws__coupon-head{
    display:flex;align-items:center;gap:8px;min-height:28px}
  .pb-ws[data-ws-section] .pb-ws__aside-coupon .pb-ws__coupon-head::before{content:'';flex:0 0 28px}
  .pb-ws[data-ws-section] .pb-ws__aside-coupon .pb-ws__coupon-head .pb-ws__panel-heading{
    margin:0;flex:1;text-align:center}
  .pb-ws[data-ws-section] .pb-ws__aside-coupon .pb-ws__offer-toggle,
  .pb-ws[data-ws-section] .pb-ws__aside-coupon .pb-ws__coupon-close{flex:0 0 28px}
  /* Гашение кнопок шапки: .pb-ws__offer-toggle задаёт display со своей специфичностью и
     перебивает атрибут hidden - после забора купона галочка оставалась видимой рядом с
     крестиком (третий рецидив той же гочи в этом файле, 15.09). */
  .pb-ws[data-ws-section] .pb-ws__aside-coupon .pb-ws__offer-toggle[hidden],
  .pb-ws[data-ws-section] .pb-ws__aside-coupon .pb-ws__coupon-close[hidden]{display:none}
  /* Сворачивание тем же приёмом, что у оффера: тело в grid-строке 1fr -> 0fr. */
  .pb-ws[data-ws-section] .pb-ws__aside-coupon .pb-ws__coupon-body{
    display:grid;grid-template-rows:1fr;transition:grid-template-rows .22s ease,opacity .18s ease,margin-top .22s ease;
    margin-top:10px}
  /* Единственный ребёнок тела - обёртка потока: приём 0fr работает только с одной
     grid-строкой, иначе текст и кнопка встали бы разными строками и разъехались. */
  .pb-ws[data-ws-section] .pb-ws__aside-coupon .pb-ws__coupon-inner{
    min-height:0;display:flex;flex-direction:column;gap:10px}
  /* Читаемость текста купона (замечание оператора 15.09). Подложку под текст НЕ кладём -
     это вернуло бы ту самую плашку со своим краем. Вместо неё: основной тон вместо
     приглушённого #5c4e3f и небольшой вес - на пёстром развороте тонкая буква рябит.
     Сам фон осветлён в зоне текста прямо в файле картинки (scripts/tmp/_fade-coupon-bg.mjs). */
  .pb-ws[data-ws-section] .pb-ws__aside-coupon .pb-ws__body-text{
    color:var(--pb-ink);font-weight:500}
  .pb-ws[data-ws-section] .pb-ws__aside-coupon.is-collapsed .pb-ws__coupon-body{
    grid-template-rows:0fr;opacity:0;margin-top:0;overflow:hidden}
  .pb-ws[data-ws-section] .pb-ws__aside-coupon.is-collapsed .pb-ws__offer-toggle svg{transform:rotate(180deg)}
  /* Вторичная кнопка карточки купона (решение оператора 15.09): ФОРМА как у главной кнопки
     оффера (.pb-ws__aside-link--accent: высота 52, радиус 14), но без заливки CTA - купон
     не должен спорить с офером за главное действие. Белый фон с обводкой взят у родного
     .pb-ws__btn-ghost, свой цвет не заводим. Селектор по классу --soft, а не по id: класс
     общий у обеих карточек-близнецов (ws и fl), id разные. */
  .pb-ws[data-ws-section] .pb-ws__aside-coupon .pb-ws__aside-link--soft{display:flex;align-items:center;
    justify-content:center;gap:8px;width:100%;min-height:52px;padding:0 18px;
    border:1px solid rgba(46,28,18,.18);border-radius:14px;background:#fff;
    color:var(--pb-ink);font:600 15px/1 var(--pb-font);text-decoration:none;cursor:pointer}
  .pb-ws[data-ws-section] .pb-ws__aside-coupon .pb-ws__aside-link--soft:hover{border-color:rgba(184,101,42,.5);
    background:var(--pb-accent-soft)}
  /* Гашение ПОСЛЕ правил вида и со специфичностью выше правила --soft: то задаёт display:flex
     по классу, и общий [hidden] (0,1,1) его не перебивает (рецидив той же гочи 15.09). */
  .pb-ws[data-ws-section] .pb-ws__aside-coupon .pb-ws__aside-link--soft[hidden]{display:none}
  /* Крестик есть только у состояния «купон ваш»: предложение закрывается отказом от показа,
     а не кнопкой, иначе человек теряет карточку до того, как прочитал, что ему дают. */
  .pb-ws__coupon-close{display:inline-flex;align-items:center;justify-content:center;
    flex:0 0 auto;width:28px;height:28px;margin:-8px 0;padding:0;border:0;border-radius:99px;
    background:none;color:var(--pb-ink);opacity:.55;cursor:pointer;font-size:20px;line-height:1;
    transition:background .15s ease,opacity .15s ease}
  .pb-ws__coupon-close:hover{opacity:1;background:rgba(184,101,42,.09)}
  .pb-ws__coupon-close[hidden]{display:none}
  .pb-ws[data-ws-section][data-state="window-24h"] #wsAsideW24Offer{order:-2}
  .pb-ws[data-ws-section][data-state="window-24h"] .pb-ws__aside-card[data-panel="window-24h"]{order:2}
  .pb-ws[data-ws-section][data-state="guest-used"] #wsAsideGuestUsedOffer{order:-2}
  .pb-ws[data-ws-section][data-state="guest-used"] .pb-ws__aside-card[data-panel="guest-used"]{order:2}
  /* «Скачать» теряет заливку главной кнопки и становится вровень с «Поделиться». */
  .pb-ws[data-ws-section][data-state="done"] #downloadBtn,
  .pb-ws[data-ws-section][data-state="guest-done"] #downloadBtn{
    background:#fff;border:1px solid var(--pb-line)}
  /* «Следующее» получает акцент: заливка, белая подпись и белый значок. */
  .pb-ws[data-ws-section][data-state="done"] #newPhotoBtn,
  .pb-ws[data-ws-section][data-state="guest-done"] #newPhotoBtn{
    background:var(--pb-accent);border-color:var(--pb-accent);
    transition:box-shadow .18s ease,transform .18s ease}
  /* Отклик на курсор - тот же канон, что у главной кнопки (07.09): краска стоит, работает
     тень с подъёмом. Правило нужно ОТДЕЛЬНО, потому что кнопка ряда - .pb-ws__suggest-card,
     а не .pb-ws__btn-primary: базовый канон её не достаёт, и акцентная кнопка осталась бы
     единственной в ряду без отклика (замер 07.09 это и поймал). Заливку держим явно - иначе
     .pb-ws__suggest-card:hover увёл бы фон в --pb-accent-soft. */
  @media (hover: hover) and (pointer: fine){
    .pb-ws[data-ws-section][data-state="done"] #newPhotoBtn:hover,
    .pb-ws[data-ws-section][data-state="guest-done"] #newPhotoBtn:hover{
      background:var(--pb-accent);border-color:var(--pb-accent);
      box-shadow:0 6px 14px rgba(148,77,26,.38);transform:translateY(-1px)}
  }
  .pb-ws[data-ws-section][data-state="done"] #newPhotoBtn:active,
  .pb-ws[data-ws-section][data-state="guest-done"] #newPhotoBtn:active{
    transform:translateY(0);box-shadow:0 2px 6px rgba(148,77,26,.28)}
  .pb-ws[data-ws-section][data-state="done"] #newPhotoBtn,
  .pb-ws[data-ws-section][data-state="guest-done"] #newPhotoBtn,
  .pb-ws[data-ws-section][data-state="done"] #newPhotoBtn svg,
  .pb-ws[data-ws-section][data-state="guest-done"] #newPhotoBtn svg,
  .pb-ws[data-ws-section][data-state="done"] #newPhotoBtn .pb-ws__menu-title,
  .pb-ws[data-ws-section][data-state="guest-done"] #newPhotoBtn .pb-ws__menu-title{color:#fff}
  /* Ширину полосы в «готово» больше не жмём под контент: при width:fit-content (правка 06.09,
     когда справа стояла одна кнопка) полоса села на 523px, кнопки ужались до 152 и обе белые
     подписи резались многоточием. Ряд из трёх равных кнопок требует ширины кадра. */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__panel,
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__panel{
    width:min(100%,var(--pb-dock-max))}
  /* Поле под подписи больше не нужно: обе выведены в поток строкой над рядом. Оставленный
     margin 30/29 держал бы пустоту сверху и снизу колонки действия. */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__act,
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__act{min-height:0;margin:0}
  /* Ряды «готово»: дальнейшие шаги, затем вторичные действия. Итог уехал к кнопке, поэтому
     рядов в левой зоне два, а не три. */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__panel-bottom,
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__panel-bottom{grid-column:1;grid-row:1;justify-self:stretch;width:100%;gap:8px 10px}
  /* Путей «что дальше» два (оживить, Фотолаборатория), и оба должны читаться сразу: заголовок
     занимает свою строку, карточки делят ширину поровну. Пока заголовок стоял с ними в одну
     строку, второй карточке не хватало 9px, она переносилась вниз, блок вырастал со 67 до 143
     и выдавливал нижний ряд за край полосы (замер 06.09). */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__panel-bottom .pb-ws__panel-heading,
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__panel-bottom .pb-ws__panel-heading{flex:0 0 100%;margin:0}
  /* «Готово»: главные пункты «Следующее фото / Поделиться» в первом ряду, на той же линии
     (y19) и той же высоте (56), что карточки цвета в «выбрано» - раскладка между
     состояниями не меняется (правка оператора 06.09). */
  .pb-ws[data-ws-section][data-state="done"] #resultSharePlate,
  .pb-ws[data-ws-section][data-state="guest-done"] #resultSharePlate{grid-column:1;grid-row:1;align-self:start}
  .pb-ws[data-ws-section] #downloadBtn{width:100%;min-height:64px;font-size:18px}


  /* ===== Общий ключ состояний =====
     Правка 07.09 (задание «окно действий - для действий»): офферная карточка (цена, таймер,
     пояснения, вторичные кнопки) переехала в инфопанель (#wsAsideZeroOffer/#wsAsideW24Offer/
     #wsAsideGuestUsedOffer, workshop.html выше по файлу). В полосе денежных состояний теперь
     ровно ОДНА кнопка - та же форма, что у "готово" (одна кнопка в колонке действия, без
     второго ряда контента слева), поэтому высота полосы у них общая с done/guest-done
     (min-height:116px ниже по файлу), а не auto по содержимому оффера. ДО правки высота
     плыла по факту контента (замер: zero 300 - упиралась в потолок max-height, window-24h
     254, guest-used 288, сценарий workshop-restore-layout это фиксировал); теперь колонка
     действия задаёт высоту так же, как у done/guest-done, - второго источника чисел не
     заводим, оба берут min-height:116px из одного правила ниже по файлу.
     .pb-ws__offer-card внутри полосы больше не встречается (карточка живёт в инфопанели,
     там её распускать в display:contents не нужно - #wsAsideZeroOffer/#wsAsideW24Offer
     обычные карточки инфопанели со своим фоном, как #wsAsideGuestOffer). */
  .pb-ws[data-ws-section][data-state="zero"] .pb-ws__panel,
  .pb-ws[data-ws-section][data-state="window-24h"] .pb-ws__panel,
  .pb-ws[data-ws-section][data-state="guest-used"] .pb-ws__panel{height:auto;min-height:116px}
  .pb-ws[data-ws-section][data-state="zero"] .pb-ws__cta-group[data-panel="zero"],
  .pb-ws[data-ws-section][data-state="window-24h"] .pb-ws__cta-group[data-panel="window-24h"],
  .pb-ws[data-ws-section][data-state="guest-used"] .pb-ws__cta-group[data-panel="guest-used"]{min-height:64px;margin:0}
  /* Кнопка в полосе - тот же канон, что у "Восстановить"/"Скачать" (64px, 100% ширины своей
     колонки, крупная типографика полосы): используем те же классы .pb-ws__btn-primary,
     явный список состояний ниже - тот же приём, что у done/guest-done кнопок выше по файлу. */
  .pb-ws[data-ws-section][data-state="zero"] .pb-ws__cta-group[data-panel="zero"] .pb-ws__btn-primary,
  .pb-ws[data-ws-section][data-state="window-24h"] .pb-ws__cta-group[data-panel="window-24h"] .pb-ws__btn-primary,
  .pb-ws[data-ws-section][data-state="guest-used"] .pb-ws__cta-group[data-panel="guest-used"] .pb-ws__btn-primary{
    min-height:64px;width:100%;font:700 20px/1.15 var(--pb-font)}

  /* Подсказка нейросети: в полосе она всегда раскрыта - прятать поле за строкой-раскрывашкой
     в полосе из двух рядов незачем, а плейсхолдер объясняет то же, что заголовок. Значение
     уходит в /api/restore из самой textarea (script.js:1392), data-open на это не влияет. */
  .pb-ws[data-ws-section] #ciHead{display:none}
  /* Пунктирная рамка .ci-field (workshop.html:2600) снята: внутри уже есть рамка самой
     textarea, и вокруг поля читались две рамки сразу (замечание оператора 04.09).
     min-width:0 (06.09, §6) - см. довод у #modeToggleBtn выше: грид-ребёнок без него
     раздувает колонку по content-width вместо того, чтобы сжаться. */
  .pb-ws[data-ws-section] .ci-field{display:grid;position:relative;background:transparent;border:0;overflow:visible;min-width:0}
  .pb-ws[data-ws-section] .ci-field[data-open="false"] .ci-body{display:grid}
  /* padding целиком, не только сверху: боевое правило (workshop.html) держит 0 15px 13px,
     и поле выходило на 13px выше нужного, а его рамка не совпадала по краям с рядом
     карточек под ним (замер 04.09: поле 75 при высоте самой textarea 62). */
  .pb-ws[data-ws-section] .ci-body{padding:0;gap:0;min-width:0}
  /* Размеры под спеку оператора 06.09 (§3, §5): строка текста - 28px line-height (задан в
     px, не множителем - иначе сумма высот плывёт по браузеру), поле в одну строку - 50px
     с рамкой и отступами (28 + 10 сверху + 10 снизу + 1+1 рамка = 50 - тот же расчёт держит
     потолок четырёх строк в JS ниже, CI_MAX=134). Шрифт 18px/400.
     Прежние числа (66/62, 15.5px, довод про плейсхолдер в две строки свёрнутого поля) были
     под старую цель "видно две строки плейсхолдера, пока поле закрыто" - оператор её снял:
     поле в полосе больше не закрыто НИКОГДА (см. #ciHead выше), высота считается от ОДНОЙ
     строки, а не от многострочного плейсхолдера.
     Высоту дальше ведёт скрипт (syncCiBox в workshop.html): угол ресайза и полоса
     прокрутки убраны, поле само тянется под текст до потолка в четыре строки. */
  /* padding-right 62, а не 14: в правом нижнем углу поля сидит счётчик «0/300» (.ci-foot,
     absolute), и текст затекал под него - на длинном плейсхолдере это читалось как каша из
     двух наложенных строк (замер 06.09 на скриншоте полосы). 62 = ширина счётчика 46 +
     зазор 16. Уменьшать плейсхолдер до «лишь бы не наехал» нельзя: набранный текст затекал
     бы под счётчик ровно так же. */
  /* 07.09: счётчик скрыт, пока поле пустое (правка оператора), поэтому и резерв под него
     нужен только с началом ввода. Пустому полю отдаём эти 48px обратно - иначе подсказка
     «Напишите что изменить на фото (необязательно)» (435px) не влезала в 386 и уезжала на
     вторую, невидимую строку. С первым символом .ci-body получает data-typed и отступ
     возвращается, так что набранный текст под счётчик по-прежнему не затекает. */
  /* 08.09, бюджет 134: поле в одну строку = --pb-dock-row 48 (было 50). Собирается как
     28 (строка) + 9 + 9 (отступы) + 1 + 1 (рамка). Отступ уменьшен на 1, кегль и высота
     строки не тронуты — мельчить текст в разделе для аудитории 40+ нельзя.
     Потолок роста (CI_MAX в workshop.html) не меняется: поле по-прежнему тянется до
     четырёх строк, вместе с ним растёт и полоса. */
  .pb-ws[data-ws-section] .ci-body textarea{min-height:var(--pb-dock-row);padding:9px 62px 9px 14px;border-radius:13px;
    background:var(--pb-bg);border:1px solid rgba(46,28,18,.16);font:400 18px/28px var(--pb-font);
    resize:none;overflow-y:hidden;cursor:text}
  .pb-ws[data-ws-section] .ci-body:not([data-typed="1"]) textarea{padding-right:14px}
  /* Видимый фокус (06.09, §3 - требование доступности). Без своего :focus здесь базовое
     .pb-ws .ci-body textarea:focus (выше по файлу, для панели-во-весь-экран) не побеждало:
     та же специфичность (3 класса + тег), но стоит РАНЬШЕ безусловного textarea{...} тут же
     и проигрывает порядку источников - рамка на фокусе не менялась. Это правило стоит
     ПОСЛЕ безусловного и выигрывает при равном весе. */
  .pb-ws[data-ws-section] .ci-body textarea:focus{outline:none;border-color:var(--pb-accent)}
  /* Плейсхолдер (правка оператора 06.09): при 18px подсказка не влезала в поле высотой 50 и
     обрезалась на второй строке - читалась половина фразы, то есть смысла в ней не было.
     16px + приглушённый тон: подсказка отличается от НАБРАННОГО текста (18px, полный тон) и
     не спорит с ним за внимание. Разный размер прыжка не даёт: пустое поле держит CI_MIN
     (syncCiBox, workshop.html), высота от плейсхолдера не считается вообще.
     opacity:1 - Firefox иначе накладывает свою прозрачность поверх нашего цвета. */
  .pb-ws[data-ws-section] .ci-body textarea::placeholder{font:400 16px/28px var(--pb-font);color:rgba(46,28,18,.38);opacity:1}
  /* Счётчик символов - в угол самого поля: своей строкой он добавлял полосе 19px */
  .pb-ws[data-ws-section] .ci-foot{position:absolute;right:12px;bottom:7px;padding:0 4px;background:var(--pb-bg)}

  /* Выбор цвета: две карточки в ряд, миниатюра меньше - в полосе высота дороже ширины.
     Заливка --pb-bg, а не белая: белая карточка на белой полосе теряет границу. */
  /* Две карточки делят ширину поля подсказки ровно пополам: они равнозначны, и разная
     ширина у элементов, стоящих друг под другом, читается как ошибка (правка оператора 04.09).
     min-width:0 (06.09, §6) - грид-ребёнок левой колонки полосы без него держит минимум по
     content-width и на узких окнах раздувает колонку вместо того, чтобы сжаться. */
  /* 06.09: правило переехало с #modeToggleBtn на класс - карточки стилей «Оживления» делят
     ширину так же. Колонки не фиксируем числом 2: у реставрации карточек две, у «Оживления»
     три, а равная ширина нужна в обоих случаях - auto-fit по факту детей даёт и то, и то
     без второго правила. */
  .pb-ws[data-ws-section] .pb-ws__choice-list{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:10px;min-width:0}
  /* 07.09 (замечание оператора «нижние кнопки отцентрированы, а верхние нет»): у карточек
     режима justify-content не задавался вовсе (normal), поэтому пара «значок + подпись»
     прижималась влево - поля выходили 14/46.8 против 63.3/63.3 у 3-го ряда. Центрируем тем
     же правилом: подпись не растягивается (flex у .pb-ws__label-stack уже 0 1 auto), значит
     зазор 9px внутри пары сохраняется. */
  .pb-ws[data-ws-section] .pb-ws__choice-btn{min-height:56px;min-width:0;padding:6px 12px 6px 6px;gap:10px;border-radius:14px;background:var(--pb-bg);justify-content:center}
  /* 08.09, бюджет 134: в «Восстановить» карточка садится на высоту ряда пакета (48 вместо
     56) - два ряда по 48 и есть вся левая колонка. Правило адресное, по СЕКЦИИ: в
     «Оживить» карточек три, там своя раскладка и свой запас, трогать её нечем. */
  /* 09.09: #modeToggleBtn сменил карточки на дорожку-переключатель (.pb-ws__toggle) -
     жёсткая высота ряда переехала на саму дорожку и на блок правок кадра (.pb-ws__edit)
     рядом с ней, чтобы оба сидели на одной линии ряда 1 (48px, бюджет полосы 134). */
  .pb-ws[data-ws-section="restore"] #modeToggleBtn.pb-ws__toggle,
  .pb-ws[data-ws-section] #wsDockRow3.pb-ws__edit{height:var(--pb-dock-row);min-height:0}
  /* 07.09: после решения оператора о симметрии меню и инфопанели рабочая колонка сузилась
     770→734, карточка стиля «Оживления» стала 125px, и подписи осталось 69 при нужных 77 -
     «Помахать рукой» резалось многоточием. Шрифт не мельчим (мобильный канон), колонку
     действия не трогаем: недостающие 8px забираем у собственных отступов карточки и зазора
     до значка. Правило адресное - ряд ИЗ ТРЁХ карточек; у «Восстановить» их две по 210px,
     там запас есть и трогать нечего. Замер: 3 карточки по 125, обрезанных подписей 0. */
  .pb-ws[data-ws-section] .pb-ws__choice-list:has(> :nth-child(3)) .pb-ws__choice-btn{
    padding-right:8px;padding-left:4px;gap:6px}
  /* Заголовок карточки - 16px/400, ровно как подписи нижнего ряда (.pb-ws__btn-dock): выбор
     оператора 06.09, вторым заходом приёмки. Первым заходом в тот же день стояло 18px/600
     (табличка §5) - отменено: второстепенные кнопки полосы равняются друг на друга, крупный
     текст в полосе остаётся один, на главной кнопке.
     Перенос в 2 строки, а не ellipsis (правка 06.09, круг 6): при трёх карточках стиля
     («Оживление») ширина колонки 90-92px режет «Помахать рукой»/«Поворот головы» до половины
     слова - на карточке действия человек должен прочитать название целиком, а не гадать по
     обрывку. При двух карточках («Восстановить», 210px) текст короче высоты строки не
     превышает, перенос на них визуально не виден. -webkit-line-clamp ограничивает высоту
     двумя строками (16px/1.2*2=38.4 + padding 12 = 50.4, укладывается в min-height:56 карточки
     без её роста) - третья строка обрежется многоточием, если появится более длинная подпись. */
  .pb-ws[data-ws-section] .pb-ws__choice-btn .pb-ws__label-stack{min-width:0}
  .pb-ws[data-ws-section] .pb-ws__choice-btn .pb-ws__choice-title{font:400 16px/1.2 var(--pb-font);
    display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}
  /* Пояснение карточки («Реалистичный цвет» / «Чистый чёрно-белый») уходит из вида (правка
     оператора 06.09, §2) - смысл переезжает в подсказку при наведении (attachTip, модуль
     ниже по файлу, текст берёт прямо из ЭТОГО узла - второй источник строки не заводим).
     Прячем тем же приёмом, что рельс меню (.ws-nav-tt в data-ws-nav="rail", workshop.html):
     абсолютный квадрат 1x1 с clip, а не display:none - иначе текст выпал бы из
     accessibility-дерева целиком, а не просто из вида. Было: white-space:nowrap +
     text-overflow:ellipsis (замер 04.09, подпись «Исходный тон, без повреждений» на узкой
     карточке поднимала полосу со 156 до 173) - решение того же класса задачи (не дать
     тексту раздуть карточку), просто заголовок теперь несёт её в одиночку. */
  .pb-ws[data-ws-section] .pb-ws__choice-btn .pb-ws__note-sm{
    position:absolute;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
  /* В полосе левый отступ карточки симметричен правому: квадрата миниатюры, ради которого
     он был 6px, больше нет (06.09) - иконка узкая и вплотную к краю читалась бы прижатой. */
  .pb-ws[data-ws-section] .pb-ws__choice-btn{padding-left:12px}

  /* Главная кнопка крупнее боевой: в полосе она единственная, мельчить нечего.
     20px/700 - оператор 06.09 (§5 типографики полосы), было 19px/600 (наследовалось от
     .pb-ws__btn-primary). */
  /* width:100% только там, где кнопка стоит в СВОЕЙ колонке 280 (--pb-act). В денежных
     состояниях тот же класс живёт в общей строке, и «Все тарифы» растягивалась на всю полосу. */
  .pb-ws[data-ws-section][data-state="empty"] .pb-ws__cta-group .pb-ws__btn-primary,
  .pb-ws[data-ws-section][data-state="working"] .pb-ws__cta-group .pb-ws__btn-primary,
  .pb-ws[data-ws-section][data-state="picked"] .pb-ws__cta-group .pb-ws__btn-primary{min-height:64px;width:100%;font:700 20px/1.15 var(--pb-font);
    display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px}
  /* То же правило для Оживления/Фотолаборатории: у них нет .pb-ws__cta-group (денежных
     состояний zero/window-24h/guest-used в этих разделах не бывает - кредиты уже списаны
     общим балансом), кнопка действия сидит прямо в .pb-ws__act, и без явного правила
     наследует только базовое .pb-ws__btn-primary (58px/600 18.5px) - отсюда расхождение с
     каноном 64px/700 20px (замер 06.09, _diag-dock-canon-compare). */
  .pb-ws[data-ws-section] .pb-ws__act>.pb-ws__btn-primary{min-height:64px;width:100%;font:700 20px/1.15 var(--pb-font)}
  /* Знак стоимости на кнопке (правка 04.09) был ОТМЕНЁН оператором 06.09 первым заходом
     (цена ушла в строку под кнопкой) - и ВОЗВРАЩЁН оператором 06.09 вторым заходом приёмки
     (§ 1 задачи: «на кнопке только действие, стоимость обратно на кнопку, второй строкой»).
     Узел #restoreBtnCost и раньше не удаляли из разметки - код (script.js setUploadState)
     всё это время писал число в #restoreBtnCostNum, второй источник цены как не заводили,
     так и не заводим. Кнопка - flex-column (правило выше), гость (data-free="1", ставит
     script.js) видит только строку действия: узел стоимости прячется ЦЕЛИКОМ, а не только
     звёзды - гостю списывать нечего, показывать нечего. display:none у [data-free="1"]
     выводит узел из accessibility-дерева, отдельный aria-hidden не нужен. */
  /* Вид кнопки - вариант 2 макета (mockups/workshop-cta-uiverse.html, компонент black-duck-67
     от barisdogansutcu, MIT; выбор оператора 06.09 из трёх). Заливка и радиус у нас уже были
     свои, из компонента переносится ровно то, чего не было: тёплая тень, смена тона на
     наведении и просадка на нажатии. Авторский значок молнии оператор отверг отдельно
     («иконка лишняя»), подпись остаётся голой строкой.
     Тень тёплая, не чёрная: авторская rgba(0,0,0,…) на кремовом фоне читается грязью, форма
     и сила её оставлены авторские. :not(:disabled) обязателен - без него неактивная кнопка
     пустого экрана перекрашивалась бы в акцент под курсором, а сценарий раскладки считает это
     дефектом («неактивная кнопка выглядит активной»). */
  .pb-ws[data-ws-section] #restoreBtn{box-shadow:0 4px 6px rgba(148,77,26,.14);
    transition:background-color .2s,box-shadow .2s,transform .2s}
  /* 07.09: перекраска на наведении снята (решение оператора, см. блок у .pb-ws__btn-primary).
     У этой кнопки приём тени был с самого начала - остаётся он один, только заметнее.
     Просадка scale(.95) на нажатии авторская, её не трогаем. */
  @media (hover: hover) and (pointer: fine){
    .pb-ws[data-ws-section] #restoreBtn:hover:not(:disabled){box-shadow:0 6px 14px rgba(148,77,26,.38);transform:translateY(-1px)}
  }
  .pb-ws[data-ws-section] #restoreBtn:active:not(:disabled){transform:scale(.95);box-shadow:none}
  .pb-ws[data-ws-section] #restoreBtn:disabled{box-shadow:none}
  .pb-ws[data-ws-section] .pb-ws__btn-cost{display:inline-flex;font:500 14px/1 var(--pb-font);color:rgba(255,255,255,.85)}
  .pb-ws[data-ws-section] .pb-ws__btn-cost[data-free="1"]{display:none}
  .pb-ws[data-ws-section] .pb-ws__btn-primary:disabled .pb-ws__btn-cost{color:inherit;opacity:.8}
  /* Цена под кнопкой (#restoreMetaCost) гасится здесь же - второй источник цены читался бы
     дублем рядом с ценой на кнопке. Разделитель перед ней (#restoreMetaCostSep, workshop.html)
     гасится вместе, иначе между временем и балансом/«бесплатно» повис бы одинокий «·».
     Время (#restoreMetaLine) и баланс/«бесплатно» остаются - только цена уезжает на кнопку.
     Насыщенность строки - 400 (табличка §5 задачи 06.09, первый заход), не трогаем. */
  .pb-ws[data-ws-section] #restoreMetaCost,
  .pb-ws[data-ws-section] #restoreMetaCostSep{display:none}
  .pb-ws[data-ws-section] .pb-ws__meta-line{font-weight:400}

  /* Плашки кадра в этой раскладке - круглые значки 44 (тач-цель канона) вместо пилюль с
     подписями: две подписи занимали 358px поверх фотографии, а фотография здесь и так меньше,
     чем в раскладке с панелью справа (замечание оператора 04.09). Подпись отдаёт подсказка при
     наведении, её вешает attachTip в workshop.html - без неё значок остался бы без имени. */
  .pb-ws[data-ws-section] .pb-ws__btn-overlay{width:44px;height:44px;min-height:44px;padding:0;justify-content:center;border-radius:99px}
  .pb-ws[data-ws-section] .pb-ws__btn-overlay-label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
  .pb-ws[data-ws-section] .pb-ws__btn-overlay svg{width:19px;height:19px}

  /* Предупреждение о кадре («фото боком», «лишние поля») - плашкой НАД полосой: внутри
     она съела бы ряд, а появляется редко. Работает потому, что в состояниях empty и picked
     у полосы снята прокрутка (выше). */
  .pb-ws[data-ws-section] .hdr-status{position:absolute;left:0;bottom:100%;margin-bottom:12px;max-width:560px;
    background:var(--pb-accent-band);border:1px solid var(--pb-line);box-shadow:var(--shadow-2)}

  /* OK-статус кадра переехал в правую колонку (вариант A 04.09): рисуется простой строкой
     НАД кнопкой «Восстановить фото», не всплывающей плашкой - правило выше (bottom:100%)
     остаётся в силе для WARN («фото боком», «лишние поля»), сюда он не попадает. Более
     специфичный селектор (cta-group + --ok) переопределяет position:absolute на static. */
  .pb-ws[data-ws-section] .pb-ws__cta-group[data-panel~="picked"] .hdr-status.hdr-status--ok{
    position:static;margin-bottom:8px;max-width:none;padding:0;background:none;border:0;box-shadow:none;
    font-size:13px;line-height:1.3;color:var(--pb-ok)}
  .pb-ws[data-ws-section] .pb-ws__cta-group[data-panel~="picked"] .hdr-status.hdr-status--ok svg{width:15px;height:15px;flex:0 0 15px}

  /* ===== ИНФОПАНЕЛЬ (третья зона, макет mockups/workshop-3zone.html) =====
     Своя колонка грида (--pb-info, объявлена у .pb-ws__layout выше), не выше зоны работы:
     .pb-ws__layout{align-items:stretch} тянет обе колонки на высоту workzone, а внутри
     панель скроллится сама - страница по-прежнему не растёт (тот же принцип, что у полосы
     действий и у боевой .pb-ws__panel в общей раскладке). Полоса прокрутки скрыта тем же
     приёмом, что применён в макете: scrollbar-width/-ms-overflow-style/::-webkit-scrollbar.
     padding-bottom резервирует место под значок «Связаться» (position:fixed, right:20,
     52x52) внизу: на низком окне инфопанель растянута почти во весь экран, и последняя
     карточка при прокрутке до конца иначе оказывалась ВИЗУАЛЬНО под значком (найдено 04.09,
     сценарий workshop-restore-layout, 1440×640 guest-done). 88 = 52 (значок) + 20 (его
     нижнее поле) + 16 (зазор, тот же --pb-dock-bottom, что у полосы). */
  .pb-ws[data-ws-section] .pb-ws__aside{display:flex;flex-direction:column;gap:12px;
    min-height:0;overflow-y:auto;scrollbar-width:none;-ms-overflow-style:none;padding-right:2px;padding-bottom:88px}
  .pb-ws[data-ws-section] .pb-ws__aside::-webkit-scrollbar{display:none}
  /* Видимость карточек - тот же приём, что у блоков полосы (список по data-state выше по
     файлу): включающий селектор обязан быть специфичнее общего гасящего [data-panel]{display:
     none} (базовое правило файла), иначе карточка виднелась бы во ВСЕХ состояниях сразу. */
  .pb-ws[data-ws-section][data-state="empty"] .pb-ws__aside-card[data-panel~="empty"],
  .pb-ws[data-ws-section][data-state="picked"] .pb-ws__aside-card[data-panel~="picked"],
  .pb-ws[data-ws-section][data-state="working"] .pb-ws__aside-card[data-panel~="working"],
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__aside-card[data-panel~="done"],
  .pb-ws[data-ws-section][data-state="guest-done"] .pb-ws__aside-card[data-panel~="guest-done"],
  .pb-ws[data-ws-section][data-state="error"] .pb-ws__aside-card[data-panel~="error"],
  /* Денежные состояния (правка 07.09): офферная карточка переехала из полосы сюда, список
     видимости пополнен теми же тремя data-state, что уже есть у полосы (строка 728 выше) и
     у блоков состояния - второй перечень состояний не заводим, оба списка держатся вручную,
     как держались все предыдущие state ДО этой правки. */
  .pb-ws[data-ws-section][data-state="zero"] .pb-ws__aside-card[data-panel~="zero"],
  .pb-ws[data-ws-section][data-state="window-24h"] .pb-ws__aside-card[data-panel~="window-24h"],
  .pb-ws[data-ws-section][data-state="guest-used"] .pb-ws__aside-card[data-panel~="guest-used"]{display:block;flex:0 0 auto;padding:16px;
    border-radius:16px;background:#fff;border:1px solid var(--pb-line);box-shadow:none;
    text-decoration:none;color:inherit}
  /* Карточка БЕЗ data-panel: видна во ВСЕХ состояниях раздела, свою видимость держит
     JS на внутреннем узле (hidden), не data-state секции - тот же корпус, что у карточек
     со списком состояний (fl-mode-note/fl-mode-ex в «Фотолаборатории»: подсказка режима
     живёт своим переключателем renderModeNote, не циклом empty/picked/working канона). */
  .pb-ws[data-ws-section] .pb-ws__aside-card:not([data-panel]){display:block;flex:0 0 auto;padding:16px;
    border-radius:16px;background:#fff;border:1px solid var(--pb-line);box-shadow:none;
    text-decoration:none;color:inherit}
  /* Та же гоча, что у #wsAsideDoneOffer и #wsAsideZeroWelcomeOffer выше, третий рецидив
     (замер 10.09: #wsAnimAsideZeroOffer в «Оживить» стоял с hidden=true и всё равно рисовался
     - пустой карточкой с таймером --:--:-- поверх живого оффера «Семейный 700»). Показывающее
     правило строкой выше - 0,5,0, общее .pb-ws [hidden] - 0,1,1, hidden молча проигрывает.
     Правило общее, а не по id: видимость этого корпуса ведёт JS атрибутом hidden, и следующая
     такая карточка обязана гаснуть без отдельной строки. Равная специфичность - выигрывает
     порядком, поэтому стоит СРАЗУ ПОСЛЕ показывающего. */
  .pb-ws[data-ws-section] .pb-ws__aside-card:not([data-panel])[hidden]{display:none}
  /* Фон купона - разворот фотокниги. Стоит ЗДЕСЬ, а не рядом с остальными правилами купона
     (строка ~1420), и это не небрежность: показывающее правило выше ставит background:#fff
     той же силы (0,5,0) и стоит НИЖЕ по файлу, поэтому при равенстве выигрывало порядком и
     картинка не вставала вовсе. Замер 15.09 сценарием workshop-coupon-twins: computed
     background-image = none при живом правиле с url(). Тот же приём, что у [hidden] строкой
     выше - «равная специфичность выигрывает порядком, поэтому стоит СРАЗУ ПОСЛЕ».
     Класс --accent в селекторе тоже обязателен: .pb-ws__aside-card--accent{background:
     var(--pb-accent-band)} - второй конкурент. Приглушённость запечена в сам webp, слоёв
     поверх картинки нет (решение оператора 15.09). */
  .pb-ws[data-ws-section][data-state="done"] .pb-ws__aside-card.pb-ws__aside-coupon,
  .pb-ws[data-ws-section] .pb-ws__aside-card.pb-ws__aside-coupon{
    background:url(/assets/workshop/offer-bg/coupon-bg.webp) no-repeat center 52%/100% auto;
    background-clip:padding-box}
  /* «Первое фото бесплатно» - только гостю (найдено оператором 09.09: вошедший с балансом
     видел ту же карточку, текст «попробуйте без регистрации» ему не по адресу). Прячущее
     правило для вошедшего, НЕ показывающее для гостя - канон 04.09 выше (класс
     pb-ws__guest-only на этой карточке специфичнее общего гасящего [data-panel]{display:none}
     и раньше утекал во ВСЕ состояния). Тут наоборот: правило гасит для НЕ-гостя и должно
     перебить показывающее [data-panel~="empty"] выше по файлу - равная специфичность
     (0,5,0), выигрывает порядком, поэтому стоит ПОСЛЕ него. */
  .pb-ws[data-ws-section]:not([data-guest="1"]) .pb-ws__aside-card--accent[data-panel~="empty"]{display:none}
  /* Карточка, всё содержимое которой скрыто (hidden ставит JS раздела), не должна рисовать
     пустую белую коробку: в «Фотолаборатории» до выбора операции обе карточки инфопанели
     стояли пустыми плашками (замер 06.09 - каркас есть, содержимого нет). :has даёт то же,
     что раньше давал hidden на самой обёртке, не перенося видимость обратно в разметку. */
  .pb-ws[data-ws-section] .pb-ws__aside-card:not(:has(> :not([hidden]))){display:none}
  .pb-ws[data-ws-section] .pb-ws__aside-card--accent{background:var(--pb-accent-band);border:0;box-shadow:none}
  /* Карточка без оформления: только содержимое, без фона, рамки и паддинга (16.09). Нужна там,
     где в блоке осталась одна ссылка-действие без заголовка и текста — рамка вокруг неё читается
     недоделкой на фоне соседних полноценных карточек (гость, исчерпавший попытку: пояснение
     снято решением оператора). Класс .pb-ws__aside-card в разметке ОСТАЁТСЯ — на нём держится
     видимость по data-panel (список состояний выше по файлу), снимаем только вид. */
  /* ⚠️ Селектор ОБЯЗАН нести [data-panel~="..."] и [data-state="..."]: правило видимости
     (строка ~1801) само задаёт padding/background/border, и его специфичность (два атрибута
     против одного класса) перебивает голый .pb-ws__aside-card--bare — проверено в браузере,
     карточка оставалась белой с рамкой. Та же гоча, что у #wsAsideDoneOffer и hidden выше. */
  .pb-ws[data-ws-section][data-state="guest-used"] .pb-ws__aside-card[data-panel~="guest-used"].pb-ws__aside-card--bare{
    background:none;border:0;box-shadow:none;padding:0;border-radius:0}
  /* Фон карточки оффера - лист старых снимков (материал оператора 15.09). Снимки лежат по
     краям кадра, центр светлый, поэтому текст садится в середину и остаётся читаемым.
     Картинка выбирается по размеру пакета: 3 / 10 / 30 обработок (data-offer-credits его
     ставит promo-render.js). Поверх - вуаль из того же фона карточки: без неё цифры и
     подпись теряют контраст на тёмных снимках. Вуаль в background-image, а не отдельным
     слоем, чтобы не плодить псевдоэлемент поверх содержимого. */
  /* Картинка - САМ фон карточки, как у купона: ни белой заливки под ней, ни слоёв поверх.
     Слой поверх заливки давал собственный край, и он читался белой каймой у рамки
     (замечание оператора 15.09). Приглушение и осветление верха (там бейдж и цена)
     запечены в файлы: scripts/tmp/_fade-offer-bg.mjs. Кончается картинка - сразу рамка. */
  .pb-ws[data-ws-section] .pb-ws__aside-card--accent[data-offer-bg]{
    position:relative;background-repeat:no-repeat;background-position:center 38%;
    background-size:cover;background-clip:padding-box}
  /* Рамку карточке даёт общее .pb-ws__aside-card:not([data-panel]) (border:1px var(--pb-line)):
     карточка оффера строится скриптом и data-panel не несёт, поэтому попадает под него и
     перебивает border:0 из --accent выше. На однотонной заливке линия была незаметна, с
     фоном-картинкой стала читаться рамкой (замечание оператора 15.09). Гасим только у
     карточек с фоном, у остальных линия остаётся. */
  .pb-ws[data-ws-section] .pb-ws__aside-card[data-offer-bg]{border:1px solid var(--pb-line);background-clip:padding-box}
  .pb-ws[data-ws-section] .pb-ws__aside-card--accent[data-offer-bg] > *{position:relative;z-index:1}
  .pb-ws[data-ws-section] .pb-ws__aside-card--accent[data-offer-bg]{overflow:hidden}
  .pb-ws[data-ws-section] .pb-ws__aside-card--accent[data-offer-bg="3"]{
    background-image:url(/assets/workshop/offer-bg/offer-bg-3.webp)}
  .pb-ws[data-ws-section] .pb-ws__aside-card--accent[data-offer-bg="10"]{
    background-image:url(/assets/workshop/offer-bg/offer-bg-10.webp)}
  .pb-ws[data-ws-section] .pb-ws__aside-card--accent[data-offer-bg="30"]{
    background-image:url(/assets/workshop/offer-bg/offer-bg-30.webp)}
  /* Карточка-ссылка (переход на «Оживить»/«Фотолабораторию»): тот же корпус, курсор и
     лёгкий hover, что у остальных кликабельных карточек полосы (.pb-ws__suggest-card). */
  .pb-ws[data-ws-section] .pb-ws__aside-card--link{cursor:pointer}
  .pb-ws[data-ws-section] .pb-ws__aside-card--link:hover{border-color:rgba(184,101,42,.45)}
  /* Карточка инфопанели (06.09): шапка «иконка + заголовок», текст, ссылка-действие.
     Иконка - плитка 36px, svg 20px внутри (07.09: глиф uicons-fc снят, весь набор на svg). */
  .pb-ws[data-ws-section] .pb-ws__aside-head{display:flex;align-items:center;gap:10px;margin-bottom:8px}
  .pb-ws[data-ws-section] .pb-ws__aside-head .pb-ws__panel-heading{margin:0}
  .pb-ws[data-ws-section] .pb-ws__aside-ico{flex:0 0 36px;width:36px;height:36px;display:grid;place-items:center;border-radius:10px;background:var(--pb-accent-soft);color:var(--pb-accent)}
  .pb-ws[data-ws-section] .pb-ws__aside-card--accent .pb-ws__aside-ico{background:rgba(255,255,255,.7)}
  .pb-ws[data-ws-section] .pb-ws__aside-ico svg{width:20px;height:20px}
  /* КАРТОЧКА БОНУСНОЙ ПРОГРАММЫ. Правка оператора 20.09: «нужно побольше внимания этому
     блоку». Поэтому она НАМЕРЕННО выбивается из ряда справочных карточек панели, у которых
     раскладка «значок 36px слева + заголовок справа»: здесь подарок крупный и стоит НАД
     текстом по центру - он и есть главный носитель внимания, в строке-значке такого размера
     ему места нет. Тёплую подложку значка не ставим: картинка цветная, со своей прозрачностью,
     плашка под ней читалась бы грязным пятном.
     Внизу - ссылка «Подробнее», НЕ кнопка (просьба оператора): действие здесь не совершается,
     карточка только ведёт в раздел «Баланс». Тег остался <button> - он и был, семантика
     «кнопка, которая никуда не ведёт по href, а зовёт скрипт» тут честнее ссылки-пустышки. */
  /* Компактная строка: подарок слева, справа колонка «заголовок + Подробнее». Раскладка
     на grid, а не flex-колонкой в обёртке: лишний <div> в разметке трёх близнецов - три
     места для рассинхрона, а колонок тут ровно две и они известны заранее.
     Подарок крупный (110px) и намеренно ВЫСТУПАЕТ за карточку - правка оператора 20.09.
     Выпускаем его ВВЕРХ и чуть влево, но НЕ вбок за левый край панели: у самой панели
     overflow-y:auto (строка ~2371), а это по спецификации делает `auto` и горизонтальную
     ось - вылет вбок там либо срезался бы, либо дал горизонтальную прокрутку. Вверх
     выступать безопасно: там граница карточки, а не край скроллера.
     Карточке нужен overflow:visible - общее правило офферов ставит hidden (строка ~1936),
     но оно висит на [data-offer-bg], которого здесь нет; пишем явно, чтобы правка соседнего
     селектора не обрезала подарок молча.
     Место под выступающий подарок даёт отрицательное поле самого значка, а не margin-top
     карточки: внешний отступ складывался с gap:12px панели и давал 30px против 12px у всех
     прочих карточек (замер 20.09 - «расстояние между карточками одинаковое»).
     Padding карточки СИММЕТРИЧЕН, и поля значка сверху/снизу равны по модулю: асимметрия
     любого из них сдвигает центр строки грида, и текстовый блок съезжает вниз относительно
     середины плашки (замер 20.09: было +5px при padding 20/10). */
  .pb-ws[data-ws-section] .pb-ws__aside-card.ws-bonus-invite{
    position:relative;overflow:visible;
    display:grid;grid-template-columns:96px 1fr;align-items:center;
    column-gap:10px;row-gap:2px;padding:14px;
    background:linear-gradient(180deg,#f4fbf4 0%,#fff 62%);
    border-color:rgba(46,125,50,.22)}
  .pb-ws[data-ws-section] .ws-bonus-invite__ico{
    grid-row:1 / span 2;grid-column:1;background:none;line-height:0;
    margin:-21px 0 -21px -6px;pointer-events:none}
  .pb-ws[data-ws-section] .ws-bonus-invite__ico img{width:110px;height:110px;object-fit:contain;display:block;
    transform-origin:50% 88%;animation:pb-ws-gift-wiggle 6s ease-in-out infinite}
  /* Покачивание подарка. Цикл 6 с, но само движение занимает первые ~12% - дальше пауза:
     карточка висит на экране постоянно, непрерывное качание из подсказки превращается в
     раздражитель. Амплитуда 5гр - «совсем лёгкий вигл» (просьба оператора 20.09), опора в
     точке 50%/88% (низ коробки), чтобы подарок качался как стоящий предмет, а не вращался
     вокруг своей середины. */
  @keyframes pb-ws-gift-wiggle{
    0%,12%,100%{transform:rotate(0deg)}
    3%{transform:rotate(-5deg)}
    6%{transform:rotate(4deg)}
    9%{transform:rotate(-2deg)}}
  @media (prefers-reduced-motion:reduce){
    .pb-ws[data-ws-section] .ws-bonus-invite__ico img{animation:none}}
  .pb-ws[data-ws-section] .ws-bonus-invite__title{
    grid-column:2;align-self:end;margin:0;font:800 15px/1.25 var(--pb-font);color:var(--pb-ink)}
  /* М1 (24.09): пять состояний вместо статичной плашки + «Подробнее» - подпись под заголовком
     и контурная кнопка действия. Перенесено из mockups/bonus-modules/modules.css § М1. */
  .pb-ws[data-ws-section] .ws-bonus-invite__sub{
    grid-column:2;margin:2px 0 0;font:500 13px/1.35 var(--pb-font);color:var(--pb-muted)}
  .pb-ws[data-ws-section] .ws-bonus-invite__go{
    grid-column:1 / -1;margin-top:12px;min-height:40px;padding:0 14px;border:0;border-radius:10px;
    background:#fff;color:var(--pb-accent);box-shadow:inset 0 0 0 1.5px rgba(184,101,42,.45);
    font:700 14px/1 var(--pb-font);cursor:pointer;
    display:flex;align-items:center;justify-content:center;gap:6px}
  /* Вторичная кнопка, не заливка: в том же экране уже есть главная кнопка обработки и карточка
     тарифа, плашка не должна спорить с ними за первый взгляд (правка оператора 24.09). */
  .pb-ws[data-ws-section] .ws-bonus-invite__go:hover{background:var(--bg)}
  .pb-ws[data-ws-section] .ws-bonus-invite__num{color:#2e7d32}
  @media (max-width:600px){
    .pb-ws[data-ws-section] .ws-bonus-invite__go{min-height:44px;font-size:15px}
    .pb-ws[data-ws-section] .ws-bonus-invite__sub{font-size:14px}}
  .pb-ws[data-ws-section] .pb-ws__aside-action{display:inline-flex;align-items:center;gap:6px;margin-top:10px;min-height:32px;font:600 14px/1 var(--pb-font);color:var(--pb-accent);text-decoration:none}
  .pb-ws[data-ws-section] .pb-ws__aside-action::after{content:'\2192'}
  .pb-ws[data-ws-section] .pb-ws__aside-action:hover{text-decoration:underline}
  /* Сброс браузерного вида кнопки нужен ЛЮБОМУ действию-кнопке панели, а не только
     [data-aside-crop]: у новой «Написать» (карточка помощи, 09.09) с тем же классом
     вылезала серая рамка и фон, каких нет у соседней ссылки «Показать пример». */
  /* padding НЕ обнулять: ниже по файлу (~1829) стрелка ::after выведена абсолютом к правому
     краю, а место под неё держит padding-right:16px. Голый padding:0 стирал его, и стрелка
     ложилась на последнюю букву подписи (замер 09.09: «Написать» с наездом на «ь»). */
  .pb-ws[data-ws-section] button.pb-ws__aside-action{border:0;background:none;padding-top:0;padding-bottom:0;padding-left:0;cursor:pointer}
  .pb-ws[data-ws-section] .pb-ws__aside-card .pb-ws__panel-heading{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:15px}
  .pb-ws[data-ws-section] .pb-ws__aside-card .pb-ws__body-text{margin:0}
  .pb-ws[data-ws-section] .pb-ws__aside-link{display:inline-block;margin-top:10px;padding:0;border:0;background:none;
    font:700 13.5px/1 var(--pb-font);color:var(--pb-accent);cursor:pointer}
  .pb-ws[data-ws-section] .pb-ws__aside-link--accent{display:flex;align-items:center;justify-content:center;
    width:100%;min-height:44px;margin-top:10px;border-radius:12px;background:var(--pb-accent);color:#fff;font-weight:700}
  /* Карточка оффера подтянута к попапу welcome-promo (правка оператора 09.09, спека
     .planning/features/workshop-offer-panel-canon.md - одна карточка для всех пяти состояний,
     полную копию попапа с «ЛУЧШАЯ ЦЕНА» и лучами не делаем, панель узкая). Три вещи из попапа:
     чип-плашка вместо голого заголовка, картинка искр вместо эмодзи, тёплая заливка цены. */
  /* .pb-ws__offer-head .pb-ws__offer-badge (не голый .pb-ws__offer-badge) - тот же приём, что у
     .pb-ws__aside-offer .pb-ws__panel-heading{display:block} ниже: бейдж несёт оба класса
     (pb-ws__offer-badge + pb-ws__panel-heading, канон сценария workshop-offer-panel-canon читает
     текст «Ваше предложение» по .pb-ws__panel-heading), общий .pb-ws__aside-card
     .pb-ws__panel-heading{display:flex} специфичнее голого класса и без родителя в селекторе
     переопределил бы inline-flex обратно на flex. */
  /* nowrap обязателен: в панели чип стоит в одной строке с таймером-пилюлей, места в обрез,
     и без него «ВАШЕ ПРЕДЛОЖЕНИЕ» ломалось на две строки (замер 09.09 на 1440x900), чего в
     попапе-образце нет. Буквенный разряд ужат до .04em - на .08em слово шире доступной ячейки. */
  /* Бейдж 10px и поля поуже (приёмка мокапа 10.09, mockups/offer-card-unified.html): при 11px
     и искре рядом шапке нужно было 340px из 284 доступных в панели --pb-info:318px, и таймер
     налезал на бейдж. Искра из шапки убрана - она дублировала смысл слова «предложение». */
  .pb-ws[data-ws-section] .pb-ws__offer-head .pb-ws__offer-badge{display:inline-flex;align-items:center;
    background:var(--bg-alt);color:#b8652a;font:800 10px/1 var(--pb-font);letter-spacing:.03em;
    text-transform:uppercase;padding:6px 9px;border-radius:999px;margin:0;white-space:nowrap}
  .pb-ws[data-ws-section] .pb-ws__offer-sparkle{display:none}
  /* Шапка обязана держать три элемента в ОДНУ строку при боевых 318px панели (замечание
     оператора 10.09). Замер до правки: бейдж 137 + пилюля 111 + кнопка 28 = 276, плюс два
     зазора по 8 = 292 при доступных 282, перебор 10px. Место берём из горизонтальных полей,
     а НЕ из кегля: мобильный канон запрещает мельчить шрифт, а тач-цель кнопки 28px и так
     на нижней границе. Зазоры 8 → 5 и поля пилюли 10 → 8 дают ровно те 10px. */
  .pb-ws[data-ws-section] .pb-ws__offer-head-row{gap:5px}
  .pb-ws[data-ws-section] .pb-ws__offer-head-row .pb-ws__timer-pill{padding:6px 8px}
  /* Цена оффера гостя крупно, зачёркнутая старая - в той же строке справа (правка оператора
     07.09: «главное - цена и выгода»). Раньше цена стояла в заголовке наравне со словами, а
     старая цена уезжала отдельной строкой ниже - выгода не читалась как выгода.
     Заливка (правка 09.09): тот же градиент/рамка, что у .welcome-offer-primary в попапе -
     цена читается как блок выгоды, а не как строка текста. Панель узкая - без лучей и
     отдельного бейджа «ЛУЧШАЯ ЦЕНА», только фон+рамка+паддинг вокруг существующей строки. */
  /* Приёмка мокапа 10.09 (mockups/offer-card-unified.html): цена 28px, старая 15px, справа
     плашка выгоды в ПРОЦЕНТАХ. При прежних 26/14 и растянутой плашке связка «700 ₽ 900 ₽» +
     плашка не помещалась в 252px и плашка сваливалась под цену. */
  .pb-ws[data-ws-section] .pb-ws__aside-offer-price{margin:14px 0 6px;display:flex;align-items:center;
    gap:12px;flex-wrap:wrap;row-gap:10px;
    font:800 28px/1 var(--pb-font);color:var(--pb-ink);letter-spacing:-.01em;
    background:linear-gradient(180deg,var(--bg) 0%,var(--bg-alt) 100%);border:1.5px solid var(--paper-4);
    border-radius:14px;padding:14px 16px}
  /* Цена и зачёркнутая старая — одной группой, чтобы плашка выгоды прижималась к правому краю
     всей строки, а не вставала между ними. */
  .pb-ws[data-ws-section] .pb-ws__offer-price-main{display:flex;align-items:baseline;gap:8px;
    min-width:0;flex:0 1 auto;white-space:nowrap}
  .pb-ws[data-ws-section] .pb-ws__aside-offer-price .pb-ws__aside-price-old{margin:0;font:400 15px/1.2 var(--pb-font)}
  /* Плашки нет (скидку не от чего считать) — цена по центру, иначе справа зияет пустота. */
  .pb-ws[data-ws-section] .pb-ws__aside-offer-price--solo{justify-content:center}
  /* width:max-content обязателен: без него плашка, единожды перенесённая на свою строку,
     занимает её целиком и обратно к цене уже не возвращается (замер 10.09: 250px при
     нужных 90). */
  .pb-ws[data-ws-section] .pb-ws__offer-save{margin-left:auto;flex:0 0 auto;width:max-content;
    text-align:center;background:var(--bg-alt);border:1px solid var(--paper-4);border-radius:12px;padding:6px 10px}
  .pb-ws[data-ws-section] .pb-ws__offer-save-label{display:block;font:600 10px/1.2 var(--pb-font);
    color:var(--pb-accent);white-space:nowrap}
  .pb-ws[data-ws-section] .pb-ws__offer-save-value{display:block;font:800 17px/1.15 var(--pb-font);
    color:var(--pb-accent);margin-top:1px}
  /* Строка количества: искры + «N обработок» + подпись за вертикальной чертой. Подпись
     НЕ обрезается многоточием (правка оператора 10.09) - её кегль подгоняет подогнатьПодписи
     в workshop.html, от 12px вниз, пока текст не уложится в одну строку. */
  .pb-ws[data-ws-section] .pb-ws__offer-qty{display:flex;align-items:baseline;gap:7px;margin:14px 0 0;
    flex-wrap:nowrap;font:700 15px/1.25 var(--pb-font);color:var(--pb-ink)}
  .pb-ws[data-ws-section] .pb-ws__offer-qty .restore-spark{width:17px;height:17px;flex:0 0 auto;
    color:var(--pb-accent);align-self:center}
  .pb-ws[data-ws-section] .pb-ws__offer-qty-count{flex:0 0 auto;white-space:nowrap}
  .pb-ws[data-ws-section] .pb-ws__offer-qty-note{flex:0 1 auto;min-width:0;padding-left:7px;
    border-left:1px solid var(--pb-line);font:400 12px/1.35 var(--pb-font);color:var(--pb-muted);
    white-space:nowrap}
  /* Таймер переехал из шапки в плашку цены, справа от цены (правка оператора 09.09, вторая
     итерация): margin-left:auto прижимает pill к правому краю блока цены, align-items:baseline
     родителя не годится для пилюли (у неё своя высота) - self override на center. */
  .pb-ws[data-ws-section] .pb-ws__aside-offer-price .pb-ws__timer-pill{margin-left:auto;align-self:center}
  /* Оффер по обычной цене (promo-render.js, opts.regular): в плашке главное - число обработок,
     цена и цена за штуку подстрочником; строка ниже - тезис «на что хватает» без счётчика
     и без черты (правка оператора 14.09). */
  .pb-ws[data-ws-section] .pb-ws__aside-offer-price--regular{flex-wrap:nowrap;align-items:center;
    justify-content:space-between;gap:10px;padding:16px 12px}
  /* Макет оператора 14.09 (третья итерация): одна строка - искры, число крупно, слово
     мельче, вертикальная черта, «за» и цена акцентом справа. Тезис ниже по центру. */
  .pb-ws[data-ws-section] .pb-ws__aside-offer-price--regular .pb-ws__offer-price-main{align-items:center;gap:6px;
    flex:1 1 auto;padding-right:10px;border-right:1.5px solid var(--paper-4)}
  .pb-ws[data-ws-section] .pb-ws__aside-offer-price--regular .restore-spark{width:22px;height:22px;flex:0 0 auto;color:var(--pb-accent)}
  .pb-ws[data-ws-section] .pb-ws__aside-offer-price--regular .pb-ws__offer-price-main > span{display:inline-flex;align-items:baseline;gap:5px}
  .pb-ws[data-ws-section] .pb-ws__offer-num{font:800 32px/1 var(--pb-font);color:var(--pb-ink)}
  .pb-ws[data-ws-section] .pb-ws__offer-word{font:700 15px/1.1 var(--pb-font);color:var(--pb-ink)}
  .pb-ws[data-ws-section] .pb-ws__offer-price-sub{flex:0 0 auto;display:inline-flex;align-items:baseline;gap:4px;
    font:600 15px/1 var(--pb-font);color:var(--pb-ink);white-space:nowrap}
  .pb-ws[data-ws-section] .pb-ws__offer-price-big{font:800 24px/1 var(--pb-font);color:var(--pb-accent)}
  .pb-ws[data-ws-section] .pb-ws__offer-qty--thesis{justify-content:center}
  .pb-ws[data-ws-section] .pb-ws__offer-qty--thesis .pb-ws__offer-qty-note{padding-left:0;border-left:0;text-align:center;
    font:600 14px/1.3 var(--pb-font);color:var(--pb-ink)}
  /* «Ещё за 3 фото» - одна фраза. Заголовки инфопанели по умолчанию flex (строка 1168:
     .pb-ws__aside-card .pb-ws__panel-heading{display:flex}), и слово «Ещё» со вставленным
     span'ом разъезжалось на две строки как два flex-элемента (замер 07.09). */
  /* То же для новых офферных карточек zero/window-24h/guest-used (правка 07.09) - тот же
     разъезд «Ещё» + span на две строки, второго правила не изобретаем. */
  .pb-ws[data-ws-section] .pb-ws__aside-offer .pb-ws__panel-heading{display:block}
  /* «Посмотреть все тарифы» под кнопкой «Забрать»: по центру, во всю ширину карточки, с
     линиями по бокам и шевроном. Цвет ОБЫЧНЫЙ тёмный, не акцентный (правка оператора 10.09):
     акцент оставляем главной кнопке, иначе в карточке два равных по силе призыва. */
  .pb-ws[data-ws-section] .pb-ws__aside-offer .pb-ws__aside-more{display:flex;align-items:center;
    justify-content:center;gap:8px;width:100%;margin-top:14px;
    font:600 14px/1 var(--pb-font);color:var(--pb-ink)}
  .pb-ws[data-ws-section] .pb-ws__aside-offer .pb-ws__aside-more::before,
  .pb-ws[data-ws-section] .pb-ws__aside-offer .pb-ws__aside-more::after{
    content:'';flex:1;height:1px;background:var(--pb-line)}
  .pb-ws[data-ws-section] .pb-ws__aside-offer .pb-ws__aside-more svg{width:13px;height:13px;flex:0 0 auto}
  /* Главная кнопка карточки: крупнее и со стрелкой (приёмка мокапа 10.09). */
  .pb-ws[data-ws-section] .pb-ws__aside-offer .pb-ws__aside-link--accent{gap:10px;min-height:52px;
    margin-top:14px;border-radius:14px;font:700 17px/1 var(--pb-font)}
  .pb-ws[data-ws-section] .pb-ws__aside-offer .pb-ws__aside-link--accent svg{width:18px;height:18px;flex:0 0 auto}
  .pb-ws[data-ws-section] .pb-ws__aside-more{display:block;width:100%;text-align:center;font-weight:600}
  .pb-ws[data-ws-section] .pb-ws__aside-price-old{margin:2px 0 0;font:400 13px/1.2 var(--pb-font);color:var(--pb-muted);text-decoration:line-through}
  /* fillPriceEls прячет #wsAsideGuestDoneOld через [hidden], когда скидки нет (workshop.html:
     7438-7447) - строка-обёртка скрывается вместе с ним, не оставляя пустую зачёркнутую строку. */
  .pb-ws[data-ws-section] .pb-ws__aside-price-old:has([hidden]){display:none}

  /* ─────────── СВОРАЧИВАНИЕ КАРТОЧКИ ПРЕДЛОЖЕНИЯ (правка оператора 10.09) ───────────
     Механизм перенесён из принятого мокапа mockups/offer-card-unified.html как есть.
     Три условия оттуда, каждое своей причиной:
     1. Высота едет через grid-template-rows 1fr → 0fr. display:none и height:auto
        анимировать нельзя, а max-height наугад даёт паузу в начале хода: карточки
        разной высоты.
     2. padding карточки НЕ трогаем - прежняя правка меняла его 16 → 10px, и корпус
        дёргался отдельно от содержимого.
     3. Таймер всегда в шапке, в обоих состояниях - никуда не перепрыгивает. */
  .pb-ws[data-ws-section] .pb-ws__offer-body{
    display:grid;grid-template-rows:1fr;
    transition:grid-template-rows .26s ease, opacity .18s ease}
  .pb-ws[data-ws-section] .pb-ws__offer-body > .pb-ws__offer-body-inner{overflow:hidden;min-height:0}
  .pb-ws[data-ws-section] .pb-ws__aside-offer.is-collapsed .pb-ws__offer-body{grid-template-rows:0fr;opacity:0}
  /* Тач-цель по мобильному канону даёт отрицательное поле: значок 13px, кнопка 28px,
     поля съедают прирост, чтобы шапка не стала выше из-за кнопки. */
  .pb-ws[data-ws-section] .pb-ws__offer-toggle{
    display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
    width:28px;height:28px;margin:-8px 0;padding:0;border:0;border-radius:99px;
    background:none;color:var(--pb-ink);cursor:pointer;transition:background .15s ease}
  .pb-ws[data-ws-section] .pb-ws__offer-toggle:hover{background:rgba(184,101,42,.09)}
  .pb-ws[data-ws-section] .pb-ws__offer-toggle svg{width:13px;height:13px;transition:transform .18s ease}
  /* Стрелка: вниз в развёрнутом (свернуть), ВВЕРХ в свёрнутом (развернуть). */
  .pb-ws[data-ws-section] .pb-ws__aside-offer.is-collapsed .pb-ws__offer-toggle svg{transform:rotate(180deg)}
  /* Шапка одинакова в ОБОИХ состояниях (правка оператора 10.09, повторная). Прежде эти три
     правила висели на .is-collapsed: сворачивание меняло gap строки 10 -> 8px и flex бейджа,
     шапка перекомпоновывалась, и таймер заметно сдвигался на каждом ходе - ровно то, чего
     механизм сворачивания обещал избежать (условие 3 в шапке блока). Раскладка нужна была
     ради узкой панели 318px, а не ради свёрнутого вида, поэтому держим её всегда: бейдж
     ужимается первым, таймер и кнопка держат ширину. Ничего не меняется при сворачивании -
     значит, и двигаться нечему. */
  .pb-ws[data-ws-section] .pb-ws__offer-head{min-width:0;flex:0 1 auto}
  .pb-ws[data-ws-section] .pb-ws__offer-head-row .pb-ws__timer-pill{flex:0 0 auto}
  /* Стартовое свёрнутое состояние ставится без хода: иначе на перезагрузке карточка
     разворачивалась бы и схлопывалась на глазах. Снимается на следующем кадре. */
  .ws-offer-no-anim .pb-ws__offer-body,
  .ws-offer-no-anim .pb-ws__offer-toggle svg{transition:none}

}
@media (prefers-reduced-motion: reduce){.pb-ws[data-ws-section] .pb-ws__panel{transition:none}}

/* Центровка подписи в кнопках со значком (правка оператора 06.09). Значок выводится из
   потока, иначе по центру встаёт пара «значок + подпись», а сама подпись смещается на
   половину ширины значка (замер: +11..+15px). Кнопка получает position:relative и поле
   слева под значок, чтобы текст не наезжал на него на узких ширинах. */
/* .ws-topup-btn исключена: пункт бокового меню, решение оператора 06.09 */
/* .pb-ws__btn-upload исключена (правка оператора 06.09, круг 5: «кнопка Загрузка кривая»).
   Кнопка узкая - ширина по содержимому, поля 26px, - и центровать в ней нечего: пара
   «значок + подпись» и так стоит по центру. Абсолютный значок при этом ложился ПОВЕРХ
   подписи: значок кончался на 32px, текст начинался на 26px (замер 06.09, наложение 6px).
   Приём с выводом значка из потока работает только на кнопках во всю ширину. */
.restore-plate-btn,
.crop-footer-rotate {
  position: relative;
  justify-content: center;
}
/* .pb-ws__btn-dock выведена из этой группы 07.09: по канону дополнительных кнопок значок у
   неё ведущий и в потоке, центрировать пару нечего - выравнивание задаёт правило выше. */
.pb-ws__btn-dock { position: relative; }
.restore-plate-btn > svg,
.crop-footer-rotate > svg {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
}
/* Значок вышел из потока — его gap больше не нужен, иначе подпись снова уезжает. */
/* .ws-topup-btn исключена: пункт бокового меню, решение оператора 06.09 */
/* .pb-ws__btn-upload здесь тоже нет: значок остался в потоке, gap:10px из базы держит зазор. */
.restore-plate-btn,
.crop-footer-rotate { gap: 0; }
/* 07.09, КАНОН дополнительных кнопок (решение оператора «всё должно смотреться одинаково»):
   значок ВЕДУЩИЙ - слева, В ПОТОКЕ, зазор до подписи 9px, подпись сразу за ним и выровнена
   влево. Эталон - карточки режима (.pb-ws__choice-btn), там канон уже соблюдён; отраслевое
   правило то же (Material 3: leading icon привязан к подписи, а не к краю кнопки).
   Кнопки 3-го ряда жили по другой схеме: значок absolute у левого края + подпись по центру.
   Схема тянула за собой три компенсации подряд (gap:0, симметричное поле 28px против наезда,
   медиа-исключение на узких окнах) и всё равно давала зазор 46px против 9 у карточек.
   Значок в потоке снимает и разнобой, и все три компенсации. */
/* flex:0 1 auto — подпись НЕ растягивается на всю кнопку: именно растяжение раньше отрывало
   текст от значка и давало переменную дыру между ними. Ужиматься при нехватке места она
   по-прежнему может (0 1), поэтому многоточие как страховка сохраняется. */
.pb-ws__btn-dock > span { flex: 0 1 auto; text-align: center; min-width: 0; }
/* На самой тесной ширине (1181 с развёрнутым меню кнопке достаётся 116.5px) поля кнопки
   14px с каждой стороны - это последние пиксели, которых не хватает подписи: без правила
   ниже «Обрезать»/«Заменить» снова режутся многоточием (ловит §8). Бюджет считается по
   копейкам, поэтому поджимаем поля самой кнопки, а не зазор 9px значок→подпись - он часть
   канона и не трогается. */
@media (max-width: 1279px) {
  body:not([data-ws-nav="rail"]) #wsDockRow3 .pb-ws__btn-dock { padding-left: 10px; padding-right: 4px; }
}
@media (max-width: 600px) {
  /* На мобильном у кнопок поворота подпись скрыта — значок возвращаем в центр. */
  .crop-footer-rotate > svg { position: static; transform: none; }
}

/* Ссылки-действия со стрелкой СПРАВА («Обрезать и повернуть» в инфопанели, «Обсудить
   условия»): стрелка занимает место в потоке и уводит подпись влево на 10px (замер
   06.09). Выводим её из потока к правому краю — подпись встаёт по центру. */
.pb-ws[data-ws-section] .pb-ws__aside-action,
.biz-note__cta { position: relative; justify-content: center; }
.pb-ws[data-ws-section] .pb-ws__aside-action > svg,
.biz-note__cta > svg {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
}
.biz-note__cta > svg { right: 14px; }
/* Стрелка вне потока не занимает ширины, и кнопка её не закладывает: при подписи
   «Обсудить условия» (119px) ширина вышла 165 против нужных 183, стрелка легла на текст
   (замечание оператора 09.09, замер: зазор −5px вместо 7).
   Резервируем место справа полем — подпись остаётся оптически по центру между полями,
   ради чего правило выше и делалось. 13 значок + 7 зазор к 22 базового поля. */
.biz-note__cta { padding-right: 42px; }

/* Стрелка у .pb-ws__aside-action — псевдоэлемент ::after, он тоже в потоке и уводит подпись
   влево. Выносим его к правому краю, как и <svg> у соседних кнопок. */
.pb-ws[data-ws-section] .pb-ws__aside-action { padding-right: 16px; }
.pb-ws[data-ws-section] .pb-ws__aside-action::after {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
}

/* Проигравшая карточка предложения (арбитр promo-render.js arbitrateOfferCards, правка
   оператора 10.09): в панели показываем ровно одну карточку, побеждает большая сумма
   покупки. Гасим display, а не visibility - карточка не должна оставлять зазор в стопке.
   !important здесь по необходимости, а не по привычке: видимость офферов раздают правила с
   id и с [data-panel~="..."]{display:block} (специфичность 0,5,0 и выше), они перебивают
   любой селектор по атрибуту без веса. Тот же случай, что описан у #wsAsideDoneOffer выше. */
.pb-ws[data-ws-section] .pb-ws__aside-offer[data-offer-lost],
.pb-ws__aside .pb-ws__aside-offer[data-offer-lost] { display: none !important; }

/* ===== Зум фотографии в кадре (спека .planning/features/workshop-canvas-zoom.md, 14.09) =====
   Масштабируется ТОЛЬКО сама картинка (canvas/img), а не обёртка .pb-ws__canvas-fit: значки
   обрезки/замены/скачивания и лупа — сиблинги картинки внутри той же обёртки, и на 200% они
   обязаны остаться в углах фотографии, а не уехать за край кадра (риск Р1 спеки).
   Вылет за окно режет overflow:hidden у .pb-ws__canvas (строка 219), сдвиг зажимает
   clampOffset в shared/canvas-zoom.js — два независимых слоя, как требует § 4 спеки. */
.pb-ws__canvas-canvas,
.pb-ws .result-img-wrap .pb-ws__canvas-img{transform-origin:50% 50%}
/* Курсор отвечает за то, что случится по клику: на 100% клик открывает фото крупно (лупа),
   на увеличенном область работает на перетаскивание — рука (спека § 6, С1).
   Канон один на все три раздела (решение оператора 14.09: «рука логичнее, нужна везде»).
   :is(#id) в хвосте добирает вес до id-уровня: конкуренты в style.css заданы по id
   (#previewCanvas:1780, #resultBeforeImg/#resultAfterImg:12492) и без этого перебивали руку
   обратно на лупу — замер 14.09 давал zoom-in на 200% в «Восстановить» против grab в
   Фотолаборатории, где такого id-правила нет. Кадры без конкурента (#flPreviewImg,
   #wsAnimChosenImg) добираются вторым селектором на классах — им лишний вес не нужен. */
.pb-ws__canvas .pb-ws__canvas-canvas,
.pb-ws__canvas .pb-ws__canvas-img{cursor:zoom-in}
.pb-ws__canvas[data-pannable="1"] .pb-ws__canvas-canvas,
.pb-ws__canvas[data-pannable="1"] .pb-ws__canvas-img{cursor:grab}
.pb-ws__canvas[data-pannable="1"] .pb-ws__canvas-canvas:active,
.pb-ws__canvas[data-pannable="1"] .pb-ws__canvas-img:active{cursor:grabbing}
/* Те же два состояния, но с весом id — только чтобы перебить id-правила style.css. */
.pb-ws__canvas :is(#previewCanvas, #resultBeforeImg, #resultAfterImg){cursor:zoom-in}
.pb-ws__canvas[data-pannable="1"] :is(#previewCanvas, #resultBeforeImg, #resultAfterImg){cursor:grab}
.pb-ws__canvas[data-pannable="1"] :is(#previewCanvas, #resultBeforeImg, #resultAfterImg):active{cursor:grabbing}
/* Пока фото увеличено, жест внутри кадра принадлежит перетаскиванию, а не прокрутке страницы:
   иначе браузер забирает его себе на первом же движении (pointercancel) и снимок встаёт.
   Ограничено data-pannable: на 100% страница прокручивается пальцем по фото как обычно. */
.pb-ws__canvas[data-pannable="1"] .pb-ws__canvas-canvas,
.pb-ws__canvas[data-pannable="1"] .pb-ws__canvas-img{touch-action:none;-webkit-user-drag:none;user-select:none}
/* Увеличенный результат растекается по всей коробке кадра, как превью. Обёртка результата
   несёт overflow:hidden из style.css:1795 (общий для прежней Мастерской) и на зуме срезала
   фотографию по своей УЗКОЙ колонке: замер 14.09 — 325px снимка уходило под обрез, хотя в
   коробке место было, и по бокам оставалось бежевое поле. Режет пусть коробка (overflow у
   .pb-ws__canvas, строка 219) — она и есть окно кадра. На 100% обрезка возвращается: там
   обёртка держит скруглённые углы. */
.pb-ws__canvas[data-zoomed="1"] .result-img-wrap{overflow:visible}

/* Лупа — левый нижний угол КОРОБКИ кадра (решение оператора 14.09 по ресерчу
   .planning/features/workshop-canvas-icons-research.md). Не угол фотографии, в отличие от
   .pb-ws__canvas-actions: масштаб — свойство окна просмотра, а не самого снимка, и кнопка
   обязана стоять на одном месте при любом фото (замер: привязка к фото давала разброс
   зазора до края кадра 15-492px). Так же у Figma, Miro, Canva.
   position:absolute обязателен: .pb-ws__canvas — flex с центрированием (строка 424),
   в потоке лупа уехала бы в середину кадра. */
.pb-ws__zoom{position:absolute;left:14px;bottom:14px;z-index:2}
/* Кнопка шире квадратной: несёт значок и текущее число (спека Л3 — значок без числа не
   отвечает на вопрос «а сейчас сколько»). Высота и радиус канона кадра не меняются. */
/* Фон наследуется от .pb-ws__frame-icon-btn (.78 — см. коммент там же про бежевое поле). */
.pb-ws__zoom-btn{width:auto;min-width:44px;padding:0 11px;gap:6px}
.pb-ws__zoom-value{position:relative;font:600 14px/1 var(--pb-font);font-variant-numeric:tabular-nums}
.pb-ws__zoom-menu{position:absolute;left:0;bottom:calc(100% + 8px);min-width:104px;padding:6px;display:grid;gap:2px;background:rgba(28,20,16,.92);border-radius:11px;box-shadow:var(--shadow-2)}
.pb-ws__zoom-menu[hidden]{display:none}
.pb-ws__zoom-item{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:36px;padding:0 10px;border:0;border-radius:7px;background:transparent;color:#fff;font:500 14px/1 var(--pb-font);font-variant-numeric:tabular-nums;cursor:pointer;text-align:left}
@media (hover: hover){.pb-ws__zoom-item:hover{background:var(--on-dark-hover)}}
.pb-ws__zoom-item:active{background:var(--on-dark-press)}
.pb-ws__zoom-item[aria-checked="true"]::after{content:"";width:6px;height:6px;border-radius:99px;background:#fff}
/* На телефоне колеса нет — лупа остаётся единственным способом менять масштаб, поэтому
   тач-цель пунктов списка поднимается до канонных 44px (CONVENTIONS.md § Мобильный канон). */
@media (max-width:820px){.pb-ws__zoom-item{min-height:44px}}

/* ==========================================================================
   ===== Полоса действий ниже боевого порога (жалоба оператора 17.09, живой
   замер: клиент на 1080x479, кнопка «Скачать фото»/«Восстановить» физически
   ниже нижнего края окна, страница не прокручивается — 52 из 288 комбинаций
   ширина×высота ниже трёхзонного порога min-width:1181/min-height:480
   (строка 550 этого файла) воспроизводили дефект).
   Починка опробована под флагом ?abfix=squeeze/sticky (сравнительный стенд,
   scripts/scenarios/_diag-ab-bar-sweep-baseline.mjs — база «как было»);
   оператор 17.09 выбрал squeeze боевым поведением, sticky отклонён (создавал
   скроллбары) и удалён из кода целиком. Секция ниже больше не под флагом —
   действует всегда на диапазоне окна, где боевой трёхзонный блок молчит.
   Барьер — scripts/scenarios/ab-bar-always-reachable.mjs.
   ========================================================================== */

/* ----- «Кадр ужимается» -----
   Идея: то же самое «раздел = экран», что уже работает от 1181x480 (медиа-блок
   выше), только БЕЗ порога — экран равен окну на любой ширине/высоте, кадр
   получает остаток высоты грида и сам ужимается, полоса — auto-строка внизу,
   поэтому всегда видна целиком. .ws-app/.ws-main/#view-* часть цепочки живёт
   в workshop.html (она держит .ws-app) — здесь только звенья, которые держит
   этот файл: .pb-ws и всё, что ниже него. Копия правил боевого медиа-блока
   1181/480 — числа и селекторы те же, дублирование намеренно (эта секция не
   переиспользует боевую — правку боевого блока это не задевает и наоборот).
   Гейт по диапазону — медиа-условие, не атрибут, и все медиазапросы здесь
   ПЛОСКИЕ (без вложенности — файл не проходит сборку/автопрефиксер, нативную
   вложенность @media внутри @media лучше не требовать от браузера). Каждая
   комбинация ширины/высоты покрыта РОВНО одним условием ниже — эта секция
   зеркалит боевой @media (min-width:1181px) and (min-height:480px) на всём,
   что вне него: not(min-width:1181px and min-height:480px) распадается на
   «широкий, но низкий» (min-width:1181px, max-height:479px) и «узкий, любая
   высота» (max-width:1180px) — конфликтов специфичности с боевым блоком и
   друг с другом нет, на одном окне работает ровно одно условие. */
#welcomeSideTab,
#promoSideTab,
#userOfferSideTab{display:none}
@media not all and (min-width:1181px) and (min-height:480px){
  .pb-ws{height:100%}
  .pb-ws__main{height:100%;min-height:0;padding:0}
  .pb-ws__layout{height:100%;min-height:0;align-items:stretch}
}

/* Широкий, но низкий экран (min-width:1181px, высота <480): ТРЁХЗОННАЯ сетка
   «Восстановить» (workzone слева на всю высоту, инфопанель справа шириной
   --pb-info) — та же структура, что и в бою (строки 712-747), здесь просто
   на другом диапазоне высоты. Первый заход копировал только generic-раскладку
   .pb-ws__layout{minmax(0,1fr) var(--pb-panel)} (базовое правило, строка 67) —
   панель оставалась узкой правой колонкой 372px, а полоса внутри неё пыталась
   взять свою обычную ширину 900px и уезжала вправо за окно (замер:
   create-empty 1440x479 — полоса right+207). Трёхзонная сетка — то, что
   реально держит полосу ПОД кадром на широком экране в бою; без workzone-грида
   структура отличается от эталона, а не просто «шире порог».
   max-height:479px (не просто not(min-height:480px)) обязателен: иначе эти
   правила продолжали бы действовать и НА боевом диапазоне (≥1181×≥480px),
   задваивая правила боевого блока тем же весом специфичности и завися от
   порядка объявления в каскаде — граница здесь должна быть жёсткой. */
@media (min-width:1181px) and (max-height:479px){
  .pb-ws[data-ws-section] .pb-ws__layout{grid-template-columns:minmax(0,1fr) var(--pb-info,318px);column-gap:24px;align-items:stretch}
  .pb-ws[data-ws-section] .pb-ws__workzone{display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto;row-gap:24px;min-height:0;min-width:0}
  .pb-ws[data-ws-section] .pb-ws__stage-col{grid-column:1;grid-row:1;display:grid;grid-template-rows:minmax(0,1fr) auto;min-height:0}
  .pb-ws__stage,
  .pb-ws__dropzone,
  .pb-ws__canvas{height:100%;min-height:0}
  /* Инфопанель (третья зона) в бою скрыта БАЗОВЫМ правилом (.pb-ws__aside{display:none},
     строка 400) и появляется только внутри боевого медиа-блока — копия того же
     включения, на своём диапазоне высоты. */
  .pb-ws[data-ws-section] .pb-ws__aside{display:flex;flex-direction:column;gap:12px;min-height:0;overflow-y:auto;scrollbar-width:none;-ms-overflow-style:none;padding-right:2px;padding-bottom:88px}
  .pb-ws[data-ws-section] .pb-ws__aside::-webkit-scrollbar{display:none}
  /* Полоса — ширина = ширине кадра (workzone), не своя постоянная 900px константа
     (та константа в бою тоже действует, .pb-ws__panel{width:min(100%,var(--pb-dock-max))}
     ниже по файлу, строка 748-749 — она остаётся, здесь только высота). Базовое правило
     .pb-ws__panel{height:var(--pb-canvas)} (строка 184, безусловное, 520px) держит панель
     на боевой константе и в grid-stretch не участвует, раз height задан явно, не auto —
     в бою его перебивает .pb-ws[data-ws-section] .pb-ws__panel{height:auto;
     min-height:var(--pb-dock)} того же боевого блока (строка 748). Без этой строки панель
     здесь держала 520px и на низком окне торчала за низ вместе с кнопкой. */
  .pb-ws[data-ws-section] .pb-ws__panel{height:auto;min-height:var(--pb-dock,134px);max-height:none;overflow:visible;width:min(100%,var(--pb-dock-max,900px));justify-self:center}
}

/* Узкий экран (<1181, pb-ws.css:71-75 переводит .pb-ws__layout в одну колонку
   и кладёт панель ПОД кадром обычным потоком, высоты там свободные) —
   переводим саму ОДНОКОЛОНОЧНУЮ сетку в ДВЕ СТРОКИ: кадр — minmax(0,1fr)
   (ужимается, забирает остаток), панель — auto (полная высота содержимого,
   не режется). Без этого переопределения на <1181 работал бы боевой
   pb-ws.css:74 (высота кадра min(--pb-canvas,56vh)) — на низком окне
   кадр+панель вместе всё равно не влезали бы в 100% родителя, это и есть сам
   дефект (причина — в отсутствии height:100% ниже порога, установленный
   факт). Инфопанели здесь нет (боевой .pb-ws__aside{display:none} базовое
   правило не тронуто), workzone остаётся display:contents (базовое
   правило, строка 383). Условие по ширине здесь одно и то же, что бы ни было
   с высотой — узкий экран у боевого блока не бывает шире 1180px ни при какой
   высоте, задваивания с ним нет. */
@media (max-width:1180px){
  /* ⚠️ Один трек, не два: у .pb-ws__layout на этой ширине ЕДИНСТВЕННЫЙ grid-item —
     .pb-ws__aside скрыт (.pb-ws__aside{display:none}, базовое правило, не тронуто здесь).
     Прежняя запись grid-template-rows:minmax(0,1fr) auto держала вторую строку для item'а,
     которого нет: workzone авто-размещался в первую (1fr), вторая (auto) не получала ни
     одного item'а и резолвилась в 0 — отсюда height:100% на workzone попадал в circular
     reference (auto-row зависит от контента,100% зависит от auto-row) и браузер откатывался
     к content-based высоте (630 вместо доступных 654). Замер _diag-frame-gaps-uniform 17.09,
     1024x768: низ полосы выходил на 49px от окна вместо эталонных 24. С одним треком
     workzone растягивается на всю ячейку grid (1fr = 654), внутреннее деление кадр/полоса —
     забота ЕГО собственной сетки (minmax(0,1fr) auto ниже), не layout. */
  .pb-ws__layout{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr);gap:24px}
  /* ⚠️ Селектор с .pb-ws[data-ws-section], а не голый .pb-ws__workzone: правила workzone
     выше по файлу заданы именно так (строки 575-589 и 2276), их специфичность 0-2-1 против
     0-1-0 — голое правило проигрывает каскад и молча не применяется (проверено: числа
     замера не сдвинулись ни на пиксель). Та же ловушка, что сегодня была с .ws-app. */
  .pb-ws[data-ws-section] .pb-ws__workzone{min-height:0;grid-template-rows:minmax(0,1fr) auto;row-gap:24px}
  .pb-ws__stage-col{grid-row:1;min-height:0}
  /* .pb-ws__stage — промежуточная обёртка МЕЖДУ .pb-ws__stage-col и .pb-ws__dropzone/
     .pb-ws__canvas (разметка: .pb-ws__stage-col > .pb-ws__stage#uploadArea >
     .pb-ws__canvas#uploadResult). Без height:100% на этом звене .pb-ws__canvas{height:100%}
     ниже считает проценты от АВТО-высоты .pb-ws__stage (которая сама зависит от
     содержимого — в done-состоянии это .result-img-wrap.has-ar с aspect-ratio и
     натуральным размером фото), и цепочка рвётся здесь же, где рвалась изначально у
     .ws-app. Замер до правки: create-done 1080x900, #uploadResult height=1037px вместо
     доступных 622 (.pb-ws__stage-col), картинка результата налезала на полосу действий
     снизу и перехватывала клик по «Скачать фото» (elementFromPoint возвращал
     img#resultAfterImg). */
  .pb-ws__stage,
  .pb-ws__dropzone,
  .pb-ws__canvas{height:100%;min-height:0}
  .pb-ws__panel{grid-row:2;height:auto;min-height:0;overflow:visible;align-self:end;width:auto}
}

/* «Оживить»/«Фотолаборатория»: своя цепочка через .ws-exact-card →
   .upload-section → .container → (.fl-main у Фотолаборатории), копия боевых
   правил строк 575-617 на своём диапазоне высоты, но С тем же условием по
   ширине (≥1181), что и в бою — та же причина, что у трёхзонного блока
   «Восстановить» выше: без него эти правила растягивали бы кадр «Оживить»/
   «Фотолаборатории» на flex:1 1 auto даже на узких экранах, где у эталона
   действует базовая раскладка (upload-area по контенту, pb-ws.css:71-75).
   У «Оживить»/«Фотолаборатории» инфопанели на узком экране нет вовсе (аналог
   .pb-ws__aside выше), поэтому здесь только рабочая зона — кадр и полоса. */
@media (min-width:1181px) and (max-height:479px){
  .pb-ws[data-ws-section] .pb-ws__workzone > .ws-exact-card.ws-create-card{height:100%;min-height:0;display:flex;flex-direction:column}
  .pb-ws[data-ws-section] .pb-ws__workzone .ws-exact-card.ws-create-card .upload-section{flex:1 1 auto;min-height:0;display:flex;flex-direction:column}
  .pb-ws[data-ws-section] .pb-ws__workzone .ws-exact-card.ws-create-card .upload-section > .container{flex:1 1 auto;min-height:0;width:100%;max-width:none;display:flex;flex-direction:column}
  .pb-ws[data-ws-section] .pb-ws__workzone .ws-exact-card.ws-create-card .fl-main{flex:1 1 auto;min-height:0}
  .pb-ws[data-ws-section] .pb-ws__stage,
  .pb-ws[data-ws-section] .pb-ws__dropzone,
  .pb-ws[data-ws-section] .pb-ws__canvas{height:100%;min-height:0;width:100%;flex:1 1 auto}
  .pb-ws[data-ws-section] .pb-ws__stage.upload-area{max-width:none;margin:0}
  .pb-ws[data-ws-section] .fl-main > .fl-examples,
  .pb-ws[data-ws-section] .fl-main > .restore-plate{flex:none}
}
/* Узкий экран у animate/fotolab: workzone тоже переводим в две строки, тем
   же приёмом, что у .pb-ws__layout выше — иначе .ws-exact-card (flex-column)
   продолжит расти по контенту и полоса всё равно уедет вниз. */
@media (max-width:1180px){
  .pb-ws[data-ws-section] .pb-ws__workzone{display:grid;grid-template-rows:minmax(0,1fr) auto;row-gap:24px;min-height:0}
  .pb-ws[data-ws-section] .pb-ws__workzone > .ws-exact-card.ws-create-card{grid-row:1}
  .pb-ws[data-ws-section] .pb-ws__panel{grid-row:2}
}

/* Ступени плотности (shared/ui-density.js, те же пороги и коэффициенты, что у конструктора,
   assets/album/pb-ed-embed.css): окно < 1280 или ниже 650 — compact .92, от 1920×900 — large 1.12,
   остальное — standard, прежний вид. Масштаб — zoom на хроме: шапка, меню, инфопанель, полоса
   действий. Кадр не трогаем, он вписывается в остаток. Колонки меню и инфопанели домножены на
   --ui-z там, где объявлены (workshop.html .ws-app, строка --pb-info выше). Только трёхзонная
   раскладка: ниже 1181 и на низком окне своя вёрстка. */
@media (min-width:1181px) and (min-height:480px){
  html[data-density="compact"]{--ui-z:.92}
  html[data-density="large"]{--ui-z:1.12}
  body > header.header,
  #wsSidebar,
  .pb-ws[data-ws-section] .pb-ws__aside,
  .pb-ws[data-ws-section] .pb-ws__panel{zoom:var(--ui-z,1)}
}
