/*
    The builder application's own shell and dashboard.

    Not the published sites — those are styled entirely by the block library and the site's theme, and this file is
    never loaded by them. Keeping the two apart is what lets a customer's palette be anything without the dashboard
    becoming unreadable.

    И РОВНО ЗДЕСЬ ЛЕЖИТ ЛОВУШКА, стоившая платформы формы «Контактов» и приглушённого текста. «Опубликованная
    страница этот лист не грузит» — правда, и из неё легко вывести неправду: «значит, страницы клиента он не
    касается». Касается. App.razor подключает его и странице редактора — последним из четырёх, то есть с
    правом решающего голоса при равном весе, — а на холсте редактора живёт `.sb-page`: настоящая страница
    клиента, собранная теми же компонентами библиотеки блоков. Всё, что названо здесь именем, которое есть и
    у страницы (`sb-form`, `sb-muted`, а из голых имён — `a` и `code`), достаёт до неё, и автор
    правит один вид, а публикует другой. `sb-field` консоль больше не носит (Center.SiteBuilder#116,
    PR 1/3): своё имя `ds-field` у страницы клиента не встречается, оговорка снята с ним заодно — но
    не с `sb-form`, который остался прежним именем и прежним риском.

    Лекарство — оговорка нулевого веса `X:where(:not(.sb-page *))`: снаружи страницы правило весит ровно
    столько же, сколько весило, внутри не значит ничего. Сторож — PageScopeTests.
*/

/*
    Свои имена поверх общих — не вторая палитра.

    Значения приходят из _content/Center.Design/design.css, одного на три приложения; здесь они только
    получают имена, которыми на них ссылаются полторы тысячи строк ниже и editor.css рядом. Тёмная тема
    больше не объявляется здесь совсем: --ds-* переключаются сами, и «поправил светлый, забыл тёмный» с
    этого места невозможно.

    Раньше в этом блоке стоял свой фон #f4f5f8 против #f6f7fa у сайта организации и #f6f7fb у входа. Три
    почти одинаковых серых, которые нельзя отличить глазом и нельзя переиспользовать.
*/
:root {
    --app-bg: var(--ds-bg);
    --app-panel: var(--ds-surface);
    --app-panel-alt: var(--ds-surface-alt);
    --app-line: var(--ds-line);
    --app-text: var(--ds-text);
    --app-muted: var(--ds-muted);
    --app-accent: var(--ds-accent);
    --app-accent-fill: var(--ds-accent-fill);
    --app-accent-soft: var(--ds-accent-soft);
    --app-warning: var(--ds-warning);
    --app-warning-soft: var(--ds-warning-soft);

    /* Смысловые цвета и их подложки. Подложка обязана быть парной теме: светло-зелёная плашка на тёмной
       консоли не «мягкая», а слепящая — это единственное светлое пятно на экране. */
    --app-success: var(--ds-success);
    --app-success-soft: var(--ds-success-soft);
    --app-danger: var(--ds-danger);
    --app-danger-soft: var(--ds-danger-soft);
    --app-info: var(--ds-info);
    --app-info-soft: var(--ds-info-soft);
    --app-notice-soft: var(--ds-warning-soft);
    --app-notice-text: var(--ds-warning);
}

/*
    margin/background/color для body сняты (issue #132): design.css 0.31.23 уже объявляет их для того
    же <body> теми же токенами (--ds-bg/--ds-text через --app-bg/--app-text), и SheetOverlapTests не
    прощает дубликат одного свойства одного селектора в двух листах даже при совпадающих значениях.
*/
html, body {
    -webkit-font-smoothing: antialiased;
}

/*
    КЕГЛЬ ОБЪЯВЛЕН ТЕЛУ, А КОРНЮ — НИКОГДА. Это не расстановка по вкусу, а та же ловушка, что в заголовке
    файла: лист консоли достаёт до страницы клиента.

    `rem` считается от корня ДОКУМЕНТА, а документ на странице редактора один — холст не фрейм, страница
    клиента лежит в том же дереве. Пока корню объявляли наши 15 пикселей (--ds-size), КАЖДЫЙ размер
    библиотеки блоков, записанный в rem, был на холсте на 6.25% меньше опубликованного: нижняя граница
    витринного заголовка в рамке «Телефон» — 29.3 пикселя против 31.2 на сайте (те же 1.95rem), цена в
    тарифной карточке 2.4rem — 36 против 38.4. Автор ставил отступ по одному виду, а публиковал другой,
    и предпросмотр переставал быть предпросмотром.

    Корень оставлен браузерным, а не переставлен на 16, и разница существенная. Опубликованную страницу
    собирает PageHtmlRenderer без нашей палитры — корень у неё тот, что у браузера читателя. Человеку,
    поднявшему шрифт в настройках браузера, жёсткие 16 соврали бы ровно так же, как врали 15; не сказать
    ничего — единственный способ совпасть с ним всегда.

    Консоль в кегле не меняется: тело по-прежнему 15, а от тела наследуется всё, что на странице есть.
    Меняются только rem'ы бандла BootstrapBlazor — его внутренние поля и кегли считаются от корня и
    приезжают на бутстраповские числа (12 и 14 вместо 11.25 и 13.125), то есть в нашу же шкалу.

    Сторож — CanvasRootFontTests.
*/
body { font: var(--ds-size)/var(--ds-leading) var(--ds-font); }

/*
    Правила по имени элемента — и оговорка `:where(:not(.sb-page *))` у каждого.

    Голое имя элемента совпадает вообще со всем, что так называется, а на странице редактора среди «всего»
    лежит и страница клиента. Ссылке сегодня везёт: лист темы задаёт свой цвет правилом потяжелее, и на
    холсте она выходит той же, что на сайте. Везение — не устройство: одно правило блока с иной
    специфичностью, и ссылки на холсте станут фиолетовыми, как в консоли.

    А <code> не везёт уже сейчас, и это замерено. Блок «Код» рисует <pre class="sb-code-pre"><code>: лист
    библиотеки одевает <pre>, а внутренний <code> не трогает — и его забирало правило ниже. Холст против
    сайта: поле слева 5 пикселей против 0, скругление 5 против 0, фон rgb(245,246,250) консоли против
    прозрачного. Кусок кода на холсте был плашкой, а на опубликованной странице — обычным текстом.
*/
a:where(:not(.sb-page *)) { color: var(--app-accent); text-decoration: none; }
a:hover:where(:not(.sb-page *)) { text-decoration: underline; }

/*
    Моноширинный текст берёт цвет темы, а не бутстраповский розовый.

    По умолчанию Bootstrap красит <code> в #d63384. На светлом фоне это читается, на тёмном даёт 3.70 при
    норме 4.5 — а именно в <code> у нас лежат адреса сайтов, ключи потоков и имена наборов, то есть то, что
    приходится читать по буквам и переносить руками.
*/
code:where(:not(.sb-page *)) {
    padding: 1px 5px;
    border-radius: 5px;
    background: var(--app-bg);
    color: var(--app-text);
    font-size: .92em;
}

.sb-app { display: flex; flex-direction: column; min-height: 100vh; }

/* The editor takes the whole viewport and supplies its own bar, so the layout gets out of the way entirely. */
.sb-app-fullscreen .sb-app-body { padding: 0; }

/*
    Высота токеном, а не по строке текста.

    Ссылка высотой в 20 пикселей — это цель, в которую промахиваются: WCAG 2.2 называет нижней границей 24,
    и не из вежливости, а потому что попадание пальцем и попадание мышью при треморе считаются одинаково.
    Отступы заодно дают подсветку при наведении форму, а не подчёркивание в воздухе.

    32 здесь стояло числом, и число было верным ровно для мыши. У пальца своя цифра, и она у платформы уже
    есть: --ds-tap равен 32 и становится 44 в @media (pointer: coarse). Замер на сенсорном 375 до этой
    правки: одиннадцать целей в шапке консоли, все одиннадцать мельче 44.
*/
.sb-app-link {
    display: inline-flex;
    align-items: center;
    min-height: var(--ds-tap);
    padding: 0 10px;
    border-radius: 8px;
    color: var(--app-muted);
    font-size: 14px;
    white-space: nowrap;
}

.sb-app-link:hover,
.sb-app-link.active {
    background: var(--app-bg);
    color: var(--app-text);
    text-decoration: none;
}

/* Текущий раздел — не только цветом: цвет один и тот же для «вы здесь» и «сюда можно нажать» различают
   плохо, а при дальтонизме не различают вовсе. */
.sb-app-link.active { font-weight: 600; box-shadow: inset 0 -2px 0 var(--app-accent); }

.sb-app-body { flex: 1; padding: 28px 22px 60px; }

/* The build stamp's row at the foot of the console; the stamp itself is styled by its own scoped stylesheet. */
.sb-app-stamp { display: flex; justify-content: flex-end; padding: 0 22px 12px; }

/* ---------------------------------------------------------------------------------------------------
   Dashboard
   --------------------------------------------------------------------------------------------------- */

.sb-dash { max-width: 1180px; margin-inline: auto; }

.sb-dash-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
    margin-bottom: 26px;
}

.sb-dash-head h1 { margin: 0; font-size: 28px; letter-spacing: -0.02em; }
.sb-dash-sub { margin: 6px 0 0; color: var(--app-muted); font-size: 14px; }

.sb-dash-head-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.sb-dash-filter input[type="search"] {
    width: min(240px, 70vw);
    height: 36px;
    padding: 0 12px;
    border: 1px solid var(--app-line);
    border-radius: 10px;
    background: var(--app-panel);
    color: var(--app-text);
    font: inherit;
    font-size: 14px;
}

.sb-dash-filter input[type="search"]:focus {
    outline: 2px solid color-mix(in srgb, var(--app-accent) 45%, transparent);
    outline-offset: 1px;
    border-color: var(--app-accent);
}

.sb-dash-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 16px;
}

/*
    Коробка карточки — общая (design.css 0.31.23, .ds-card): подложка, рамка, крупный радиус, отступ,
    зазор, раскладка колонкой. Здесь остаются только свои отступления от неё, и оба названы:
    Center.SiteBuilder#116 (PR 2/3) сняло дубликат, разметка несёт класс .ds-card рядом с .sb-dash-card.
*/
.sb-dash-card {
    position: relative;
    overflow: hidden;

    /*
        Тень лежит всегда, а не приезжает по наведению.

        Карточка сайта — не ссылка: нажимают в ней кнопки, а не её саму. Подъём при наведении обещал бы
        обратное, и это ровно тот случай, ради которого написано правило «наведение — только на
        нажимаемом». А приёмочное «плоское» карточка получала за то, что от фона её отделяла одна линия, —
        это лечится покоем, а не движением. Ступень md, а не sm: sm по замеру не тень, а подпорка линии, и
        именно поэтому она держится своим значением поверх .ds-card (у него своя, sm).

        Остаётся переход краски: рамка под курсором подсказывает, какая из трёх похожих карточек сейчас
        под рукой. Путь = 0, значит ступень краски.
    */
    box-shadow: var(--ds-shadow-md);
    transition: border-color var(--ds-t-tint) var(--ds-ease-out);
}

.sb-dash-card:hover {
    border-color: color-mix(in srgb, var(--app-accent) 35%, var(--app-line));
}

.sb-dash-card header { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.sb-dash-card h2 { margin: 0; font-size: 17px; letter-spacing: -0.01em; }

.sb-dash-card-title {
    display: flex;
    align-items: flex-start;
    gap: 11px;
    min-width: 0;
}

/* Один акцент на экран, не один на карточку (заказ Design KisaPs/Center.SiteBuilder#186): было —
   цвет карточки и аватара по хешу имени сайта, не несущий смысла и споривший с оттенком приложения.
   Инициалы теперь в мягком акценте пакета, тем же, что у «Редактировать». */
.sb-dash-avatar {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 11px;
    background: var(--app-accent-soft);
    color: var(--app-accent);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.02em;
}

.sb-dash-slug {
    display: block;
    margin-top: 3px;
    color: var(--app-muted);
    font-size: 11.5px;
    background: transparent;
    padding: 0;
}

.sb-dash-state {
    flex: none;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 600;
    white-space: nowrap;
}

.sb-state-draft { background: var(--app-bg); color: var(--app-muted); }
.sb-state-published { background: var(--app-success-soft); color: var(--app-success); }
.sb-state-unpublished { background: var(--app-warning-soft); color: var(--app-warning); }
.sb-state-archived { background: var(--app-danger-soft); color: var(--app-danger); }

.sb-dash-desc { margin: 0; color: var(--app-muted); font-size: 13.5px; }

.sb-dash-meta { display: flex; flex-direction: column; gap: 4px; margin: 0; font-size: 12.5px; }
.sb-dash-meta div { display: flex; justify-content: space-between; gap: 12px; }
.sb-dash-meta dt { margin: 0; color: var(--app-muted); }
.sb-dash-meta dd { margin: 0; }
.sb-dash-meta code { font-size: 12px; }

/*
    Плашка публикации на карточке сайта.

    Нейтральная во всех состояниях (заказ Design KisaPs/Center.SiteBuilder#186): было — свой цвет на
    каждое состояние публикации, второй экземпляр того же сообщения, что уже несёт ярлык в углу
    карточки (.sb-dash-state) — глаз не знал, на какой из двух смотреть. Состояние теперь говорит
    только ярлык; здесь — слово, а не цвет («снят» по-прежнему написано словом, не цветом).
*/
.sb-dash-pub {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 8px 10px;
    border: 1px solid var(--app-line);
    border-radius: 10px;
    background: var(--app-panel-alt);
    font-size: 12.5px;
}

.sb-dash-pub p { margin: 0; }

.sb-dash-pub-line { color: var(--app-text); }
.sb-dash-pub-where { color: var(--app-muted); word-break: break-all; }
.sb-dash-pub-where code { font-size: 12px; }
/* Приглушённым, не цветом тревоги: непоказанная правка ждёт нажатия кнопки рядом, а не аварии. */
.sb-dash-pub-diff { color: var(--app-muted); }

.sb-dash-pub-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

.sb-dash-card footer,
.sb-dash-card-actions {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: auto;
    padding-top: 4px;
}

.sb-dash-primary,
.sb-dash-console,
.sb-dash-more {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

.sb-dash-more {
    padding-top: 4px;
    border-top: 1px dashed var(--app-line);
}

/* Те же 24 пикселя, что и в шапке: правило одно для всей консоли, а не для того места, где о нём вспомнили. */
.sb-dash-open {
    display: inline-flex;
    align-items: center;
    min-height: 30px;
    padding: 0 8px;
    border-radius: 8px;
    font-size: 13.5px;
}

.sb-dash-open:hover { background: var(--app-bg); text-decoration: none; }

/* «Удалить» — второстепенное действие: красный текст без заливки. Красная заливка делала его самой
   заметной кнопкой карточки — ярче «Редактировать». Высота — токеном цели нажатия, как у прежней кнопки:
   на грубом указателе токен растёт до 44, и тихая кнопка не обязана быть мелкой (замечание ревью #166). */
.sb-dash-delete {
    display: inline-flex;
    align-items: center;
    min-height: var(--ds-tap);
    padding: 0 8px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--app-danger);
    font-size: 13.5px;
    cursor: pointer;
}

.sb-dash-delete:hover { background: var(--app-danger-soft); }

/* Screen-reader-only label for the filter field (no Bootstrap dependency on the dashboard chrome). */
.visually-hidden {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/*
    Отступ, цвет и колонка — общие (design.css 0.31.23, .ds-empty). Здесь остаётся только своя рамка:
    пунктир и фон панели, которых у .ds-empty нет. Разметка несёт .ds-empty рядом с .sb-dash-empty
    (Center.SiteBuilder#116, PR 2/3), а заголовок — класс словаря .ds-empty-title вместо голого <h2>.
*/
.sb-dash-empty {
    background: var(--app-panel);
    border: 1px dashed var(--app-line);
    border-radius: 14px;
}

.sb-dash-empty p { margin: 0 0 18px; }

/* ---------------------------------------------------------------------------------------------------
   Тариф

   Плашка состояния на панели и карточка на странице тарифов. Намеренно не предупреждающего цвета: пробный
   тариф — это условия, на которых человек работает, а не неполадка. Предупреждение он получит там, где
   действительно упрётся в предел, и с той же ссылкой.
   --------------------------------------------------------------------------------------------------- */

.sb-plan-bar {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 4px 10px;
    margin-bottom: 18px;
    padding: 10px 14px;
    background: var(--app-panel);
    border: 1px solid var(--app-line);
    border-radius: 10px;
    color: var(--app-muted);
    font-size: 13.5px;
}

.sb-plan-bar strong { color: var(--app-text); font-weight: 650; }
.sb-plan-more { margin-left: auto; white-space: nowrap; }

.sb-plan-card {
    max-width: 560px;
    padding: 22px 24px;
    background: var(--app-panel);
    border: 1px solid var(--app-line);
    border-radius: 14px;
}

.sb-plan-card h2 { margin: 0; font-size: 19px; }
.sb-plan-price { margin: 2px 0 16px; color: var(--app-muted); font-size: 14px; }

.sb-plan-list { margin: 0; padding-left: 20px; }
.sb-plan-list li { margin-bottom: 8px; font-size: 14px; line-height: 1.55; }

.sb-plan-note { margin: 16px 0 0; color: var(--app-muted); font-size: 13px; }

/*
    Здесь стоял светлый замок на палитру внутри модального окна — и его снятие есть починка, а не уборка.

    Замок был верен ровно до тех пор, пока окно библиотеки было всегда белым: наши подписи внутри него
    брали --app-muted, в тёмной теме это светло-серый, и на белом он давал контраст 2.58 при норме 4.5.
    Прибив тринадцать переменных к светлым числам, подписи чинили.

    Что изменилось: design.js общего пакета ставит на корень data-bs-theme рядом с data-theme, и окно
    библиотеки в тёмной теме честно стало тёмным. Замок при этом остался — и перевернул дефект с ног на
    голову: чернильный #171a22 на панели #191d25 даёт контраст 1.1, то есть подписи не «плохо читаются»,
    а не видны вовсе. Замер сделан на светлой половине, а поломка живёт на тёмной, — поэтому её и не
    заметили.

    Теперь переменных внутри окна не переопределяет никто: --ds-* уже знают про тему, панель окна приходит
    из моста тем же --ds-surface, что и панель под ним, и «половина экрана одним набором, окно поверх
    другим» больше неоткуда взяться.
*/
.sb-form-grid { display: flex; flex-direction: column; gap: 14px; }
.sb-form-grid label { display: block; margin-bottom: 5px; color: var(--app-muted); font-size: 12.5px; font-weight: 600; }
.sb-form-help { margin: 5px 0 0; color: var(--app-muted); font-size: 11.5px; }

/* ---------------------------------------------------------------------------------------------------
   Blazor's error strip
   --------------------------------------------------------------------------------------------------- */

#blazor-error-ui {
    display: none;
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 1000;
    padding: 12px 18px;
    background: var(--app-notice-soft);
    color: var(--app-notice-text);
    box-shadow: 0 -1px 6px color-mix(in srgb, var(--app-text) 18%, transparent);
}

#blazor-error-ui .dismiss {
    position: absolute;
    top: 6px;
    right: 12px;
    cursor: pointer;
}

/* Полоса предпросмотра стояла здесь и не действовала ни разу: её носит документ, отданный
   PageHtmlRenderer, а он этого листа не грузит — только бандл блоков, тему и свой лист. Правила уехали
   туда, где полоса печатается. */

/* ---------------------------------------------------------------------------------------------------
   Консоль сайта: источники данных и клиенты.

   Одни и те же примитивы, что у списка сайтов — карточка, чип, панель — потому что это один продукт, а
   не три. Ширина ограничена: таблица прогонов на широком мониторе иначе растягивается так, что глазу не
   связать начало строки с её концом.
   --------------------------------------------------------------------------------------------------- */
.sb-console {
    max-width: 1180px;
    margin: 0 auto;
    padding: 32px 24px 64px;
}

.sb-console-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 24px;
    margin-bottom: 28px;
}

.sb-console-head h1 {
    margin: 0 0 6px;
    font-size: 26px;
    font-weight: 650;
    letter-spacing: -0.02em;
}

.sb-console-sub,
.sb-console-hint {
    margin: 0;
    max-width: 68ch;
    color: var(--app-muted);
    font-size: 14px;
    line-height: 1.55;
}

.sb-console-hint { font-size: 13px; margin-bottom: 14px; }

/*
    Отступ, цвет текста и центрирование — общие (design.css 0.31.23, .ds-empty). Здесь остаётся только
    пунктирная рамка, которой у .ds-empty нет: разметка несёт оба класса разом (Center.SiteBuilder#116,
    PR 2/3).
*/
.sb-console-empty {
    border: 1px dashed var(--app-line);
    border-radius: 14px;
}

/*
    Коробка панели — общая (design.css 0.31.23, .ds-panel): подложка, рамка, радиус. Здесь остаётся только
    отступ сверху — своя раскладка страницы, а не вид самой панели, и design.css его не знает и не обещает.
    Center.SiteBuilder#116 (PR 2/3): разметка несёт класс .ds-panel вместо своего .sb-panel.
*/
.ds-panel {
    margin-top: 32px;
}

.ds-panel h2 {
    margin: 0 0 8px;
    font-size: 17px;
    font-weight: 640;
}

/* ---------------------------------------------------------------------------------------------------
   Центр запуска магазина.

   Это один обзор поверх уже существующей консоли, поэтому он использует её ширину и типографику. Cobalt
   появляется только как спокойная подложка и кромка: смысл статуса всегда напечатан словами и продублирован
   знаком, так что экран не превращается в светофор для людей, которые различают цвета иначе.
   --------------------------------------------------------------------------------------------------- */
.sb-launch {
    --sb-launch-cobalt: color-mix(in srgb, var(--app-accent) 82%, var(--app-accent-fill));
    --sb-launch-porcelain: color-mix(in srgb, var(--app-panel) 92%, var(--app-accent-soft));
    max-width: 1180px;
    margin: 0 auto;
    padding: 32px 24px 64px;
}

.sb-launch-hero {
    position: relative;
    overflow: hidden;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(240px, .42fr);
    gap: 32px;
    padding: clamp(24px, 4vw, 44px);
    border: 1px solid color-mix(in srgb, var(--sb-launch-cobalt) 28%, var(--app-line));
    border-radius: 24px;
    background:
        radial-gradient(circle at 95% 0%, color-mix(in srgb, var(--sb-launch-cobalt) 14%, transparent), transparent 48%),
        var(--sb-launch-porcelain);
    box-shadow: 0 24px 70px color-mix(in srgb, var(--app-text) 8%, transparent);
}

.sb-launch-kicker {
    margin: 0 0 10px;
    color: var(--sb-launch-cobalt);
    font-size: 12px;
    font-weight: 750;
    letter-spacing: .12em;
    text-transform: uppercase;
}

.sb-launch-hero h1 {
    max-width: 18ch;
    margin: 0;
    font-size: clamp(30px, 5vw, 52px);
    font-weight: 700;
    letter-spacing: -.045em;
    line-height: 1.04;
}

.sb-launch-intro {
    max-width: 62ch;
    margin: 18px 0 0;
    color: var(--app-muted);
    font-size: 15px;
    line-height: 1.65;
}

.sb-launch-summary {
    align-self: stretch;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 14px;
    padding: 22px;
    border: 1px solid color-mix(in srgb, var(--sb-launch-cobalt) 22%, var(--app-line));
    border-radius: 18px;
    background: color-mix(in srgb, var(--app-panel) 86%, transparent);
}

.sb-launch-summary .sb-launch-status-mark { color: var(--sb-launch-state); }

.sb-launch-overall {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    width: fit-content;
    margin: 0;
    font-size: 15px;
    font-weight: 700;
}

.sb-launch-status-mark {
    display: inline-grid;
    place-items: center;
    width: 25px;
    height: 25px;
    flex: 0 0 25px;
    border: 1px solid currentColor;
    border-radius: 50%;
    font-size: 13px;
    font-weight: 800;
    line-height: 1;
}

.sb-launch-progress {
    display: block;
    width: 100%;
    height: 10px;
    appearance: none;
    border: 0;
    border-radius: 999px;
    overflow: hidden;
    background: var(--app-bg);
    color: var(--sb-launch-cobalt);
}

.sb-launch-progress::-webkit-progress-bar { background: var(--app-bg); border-radius: 999px; }
.sb-launch-progress::-webkit-progress-value { background: var(--sb-launch-cobalt); border-radius: 999px; }
.sb-launch-progress::-moz-progress-bar { background: var(--sb-launch-cobalt); border-radius: 999px; }

.sb-launch-progress-label,
.sb-launch-summary-copy { margin: 0; color: var(--app-muted); font-size: 13px; line-height: 1.5; }
/* SB-30: адрес живого сайта — ссылкой и с копированием. Адрес — URL, поэтому здесь и нужен anywhere. */
.sb-launch-address { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; margin: 8px 0 0; font-size: 13px; }
.sb-launch-address a { min-width: 0; overflow-wrap: anywhere; font-weight: 600; }
.sb-launch-copy { padding: 2px 10px; border: 1px solid var(--app-border, currentColor); border-radius: 999px; background: transparent; color: inherit; font: inherit; cursor: pointer; }
.sb-launch-progress-label strong { color: var(--app-text); font-variant-numeric: tabular-nums; }

.sb-launch-section-head {
    display: flex;
    align-items: end;
    justify-content: space-between;
    gap: 20px;
    margin: 38px 0 16px;
}

.sb-launch-section-head h2 { margin: 0 0 5px; font-size: 22px; letter-spacing: -.02em; }
.sb-launch-section-head p { margin: 0; color: var(--app-muted); font-size: 14px; }

.sb-launch-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
    margin: 0;
    padding: 0;
    list-style: none;
}

/*
    Коробка карточки — общая (design.css 0.31.23, .ds-card: подложка, рамка, радиус, отступ); разметка несёт
    класс .ds-card рядом с .sb-launch-card (Center.SiteBuilder#116, PR 2/3). Здесь остаётся только своя
    двухколоночная раскладка (значок слева, содержимое справа) и цветная кромка состояния, которых у
    .ds-card нет и быть не может — это не карточка семьи, а её собственное устройство.
*/
.sb-launch-card {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 8px 14px;
    min-width: 0;
    border-left: 4px solid var(--sb-launch-state, var(--app-line));
}

.sb-launch-card-ready { --sb-launch-state: var(--app-success); }
.sb-launch-card-needs-action { --sb-launch-state: var(--app-danger); }
.sb-launch-card-review { --sb-launch-state: var(--app-warning); }
.sb-launch-card .sb-launch-status-mark { grid-column: 1; color: var(--sb-launch-state); }
.sb-launch-card-body { grid-column: 2; min-width: 0; container-type: inline-size; }
.sb-launch-card h3 { margin: 0; font-size: 17px; font-weight: 680; }
.sb-launch-card-status { margin: 3px 0 0; color: var(--sb-launch-state); font-size: 13px; font-weight: 700; }
.sb-launch-card-detail { margin: 10px 0 0; color: var(--app-muted); font-size: 13px; line-height: 1.55; }

.sb-launch-card-heading {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 4px 12px;
}

.sb-launch-items {
    display: grid;
    gap: 14px;
    margin: 16px 0 0;
    padding: 0;
    list-style: none;
}

.sb-launch-item {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
    align-items: end;
    padding-top: 14px;
    border-top: 1px solid var(--app-line);
}

.sb-launch-item:first-child { padding-top: 0; border-top: 0; }
.sb-launch-item-copy { min-width: 0; }
.sb-launch-item .sb-launch-card-action { max-width: 100%; overflow-wrap: anywhere; }

/* The card, rather than the viewport, determines whether copy and action fit side by side. */
@container (min-width: 440px) {
    .sb-launch-item { grid-template-columns: minmax(0, 1fr) auto; gap: 14px; }
}
.sb-launch-item-title { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin: 0; font-weight: 650; }
.sb-launch-item-mark { color: var(--sb-launch-state); font-weight: 800; }
.sb-launch-item-state { margin: 3px 0 0 18px; color: var(--sb-launch-state); font-size: 12px; font-weight: 700; }
.sb-launch-item .sb-launch-card-detail { margin-top: 7px; }

.sb-launch-required {
    padding: 1px 6px;
    border: 1px solid color-mix(in srgb, var(--app-danger) 34%, var(--app-line));
    border-radius: 999px;
    color: var(--app-danger);
    font-size: 10px;
    font-weight: 750;
    letter-spacing: .03em;
    text-transform: uppercase;
}

.sb-launch-card-action {
    justify-self: start;
    display: inline-flex;
    align-items: center;
    max-width: 100%;
    min-height: var(--ds-tap);
    font-weight: 650;
    text-align: start;
    white-space: normal;
}

.sb-launch-card-action::after { content: "\00a0→"; }

.sb-launch-card-action:focus-visible,
.sb-dash-launch:focus-visible {
    outline: 3px solid color-mix(in srgb, var(--app-accent) 36%, transparent);
    outline-offset: 3px;
    border-radius: 6px;
}

.sb-launch-skeleton {
    display: grid;
    gap: 16px;
    min-height: 420px;
}

.sb-launch-skeleton-hero,
.sb-launch-skeleton-card {
    position: relative;
    overflow: hidden;
    border: 1px solid var(--app-line);
    border-radius: 20px;
    background: var(--app-panel);
}

.sb-launch-skeleton-hero { min-height: 245px; }
.sb-launch-skeleton-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.sb-launch-skeleton-card { min-height: 148px; border-radius: 16px; }

.sb-launch-skeleton-hero::after,
.sb-launch-skeleton-card::after {
    position: absolute;
    inset: 0;
    content: "";
    background: linear-gradient(105deg, transparent 32%, color-mix(in srgb, var(--app-bg) 72%, transparent) 48%, transparent 64%);
    opacity: .72;
}

.sb-launch-fatal,
.sb-launch-missing {
    max-width: 640px;
    margin: 64px auto;
    padding: 30px;
    border: 1px solid var(--app-line);
    border-radius: 18px;
    background: var(--app-panel);
    text-align: center;
}

.sb-launch-fatal h1,
.sb-launch-missing h1 { margin: 0 0 10px; font-size: 24px; }
.sb-launch-fatal p,
.sb-launch-missing p { margin: 0 auto 20px; max-width: 54ch; color: var(--app-muted); }

.sb-launch-retry {
    min-height: var(--ds-tap);
    padding: 0 18px;
    border: 1px solid var(--sb-launch-cobalt);
    border-radius: 9px;
    background: var(--sb-launch-cobalt);
    color: var(--ds-accent-contrast);
    font: inherit;
    font-weight: 650;
    cursor: pointer;
}

.sb-launch-retry:hover { filter: brightness(.96); }
.sb-launch-retry:focus-visible { outline: 3px solid color-mix(in srgb, var(--app-accent) 36%, transparent); outline-offset: 3px; }
.sb-launch-retry:disabled { cursor: wait; opacity: .7; }

.sb-dash-launch {
    display: inline-flex;
    align-items: center;
    min-height: var(--ds-tap);
    padding: 0 12px;
    border: 1px solid color-mix(in srgb, var(--app-accent) 42%, var(--app-line));
    border-radius: 8px;
    background: var(--app-accent-soft);
    color: var(--app-accent);
    font-weight: 680;
}

.sb-dash-launch:hover { text-decoration: none; background: color-mix(in srgb, var(--app-accent-soft) 70%, var(--app-accent-fill)); }

@media (max-width: 720px) {
    .sb-console .sb-invite {
        flex-wrap: wrap;
    }

    .sb-console .sb-invite .btn {
        min-height: 44px;
    }
}

@media (max-width: 760px) {
    .sb-launch { padding: 22px 16px 48px; }
    .sb-launch-hero { grid-template-columns: 1fr; gap: 24px; padding: 24px; border-radius: 20px; }
    .sb-launch-hero h1 { max-width: none; }
    .sb-launch-grid,
    .sb-launch-skeleton-grid { grid-template-columns: 1fr; }
    .sb-launch-section-head { align-items: flex-start; flex-direction: column; gap: 8px; }
}

@media (max-width: 520px) {
    .sb-launch-item { grid-template-columns: 1fr; gap: 4px; }
    .sb-launch-card-action { justify-self: stretch; }
    .sb-launch-card { padding: 17px 15px; }
    .sb-launch-fatal,
    .sb-launch-missing { margin: 28px auto; padding: 24px 18px; }
}

/* --- карточки источников и кабинетов --- */
.sb-source-list,
.sb-cabinet-list {
    display: grid;
    gap: 12px;
}

.sb-source,
.sb-cabinet {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    padding: 18px 20px;
    background: var(--app-panel);
    border: 1px solid var(--app-line);
    border-radius: 14px;
}

/* Красная кромка, а не красный фон: состояние надо заметить, а читать всё равно текст. */
.sb-source-failing { border-left: 3px solid var(--app-danger); }
.sb-cabinet-suspended { border-left: 3px solid var(--app-warning); }

.sb-source-name,
.sb-cabinet-name { font-size: 15px; font-weight: 620; }

.sb-source-endpoint {
    margin-top: 2px;
    color: var(--app-muted);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12.5px;
    word-break: break-all;
}

.sb-cabinet-legal { margin-top: 2px; color: var(--app-muted); font-size: 13px; }

.sb-source-meta,
.sb-cabinet-meta { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }

/* .sb-chip снят целиком (Center.SiteBuilder#116, PR 2/3): чип семьи — это плашка, .ds-badge из
   design.css 0.31.23, без тона или с модификатором .ds-tone-*; своей копии больше нет. */

.sb-source-error {
    margin-top: 10px;
    padding: 9px 12px;
    background: var(--app-danger-soft);
    border-radius: 9px;
    color: var(--app-danger);
    font-size: 13px;
    line-height: 1.5;
}

.sb-source-actions,
.sb-cabinet-actions { display: flex; gap: 8px; flex-shrink: 0; }

/* --- ход разведки ---

   Обход всех наборов базы клиента — это до 1011 запросов, то есть минуты. Полоска здесь не украшение:
   без неё человек видел молчащий экран и делал единственный доступный ему вывод — сломалось.

   Элемент <progress> родной, поэтому красить его приходится через псевдоэлементы каждого движка: без
   appearance:none Chrome и Firefox рисуют системную полоску, которая мимо палитры консоли в обеих темах. */
.sb-walk-count { margin: 0 0 8px; font-size: 14px; }
.sb-walk-count strong { font-weight: 650; font-variant-numeric: tabular-nums; }

.sb-walk-bar {
    display: block;
    width: 100%;
    height: 8px;
    margin-bottom: 8px;
    appearance: none;
    border: 0;
    border-radius: 999px;
    background: var(--app-bg);
    color: var(--app-accent);
}

.sb-walk-bar::-webkit-progress-bar { background: var(--app-bg); border-radius: 999px; }
.sb-walk-bar::-webkit-progress-value { background: var(--app-accent); border-radius: 999px; }
.sb-walk-bar::-moz-progress-bar { background: var(--app-accent); border-radius: 999px; }

/* --- проверка подключения --- */
.sb-test { padding: 16px 18px; border-radius: 12px; }
.sb-test-ok { background: var(--app-success-soft); border: 1px solid var(--app-line); }
.sb-test-bad { background: var(--app-danger-soft); border: 1px solid var(--app-line); }

.sb-test-message { font-weight: 600; margin-bottom: 10px; }

/* Unlike short global form checks, this complete risk statement must wrap inside the warning card on phones. */
.sb-http-risk-ack {
    display: flex;
    align-items: flex-start;
    box-sizing: border-box;
    width: 100%;
    max-width: 100%;
    min-width: 0;
}

.sb-http-risk-ack .form-check-input {
    flex: 0 0 20px;
}

.sb-http-risk-ack .form-check-label {
    flex: 1 1 auto;
    min-width: 0;
    white-space: normal;
    overflow-wrap: anywhere;
}

.sb-test-details {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 4px 18px;
    margin: 0;
    font-size: 13px;
}

.sb-test-details dt { color: var(--app-muted); }
.sb-test-details dd { margin: 0; word-break: break-all; }

/* --- мастер подключения 1С ---
   Своя лестница шагов (.sb-wizard-*) отсюда ушла вместе с четырьмя <li> в разметке: её рисует компонент
   библиотеки, который отличает пройденный шаг от будущего не только краской. Стилей у него свои, а два
   значения, которых у него нет, стоят в bb-bridge.css. */

/* Блок «Наш адрес»: его понесут администратору 1С, поэтому он оформлен как то, что переписывают, а не
   как декорация. */
.sb-ouraddr {
    margin: 14px 0;
    padding: 12px 14px;
    background: var(--app-bg);
    border: 1px solid var(--app-line);
    border-radius: 12px;
}

.sb-ouraddr-title { font-weight: 600; font-size: 13px; margin-bottom: 8px; }
.sb-ouraddr-list { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-bottom: 8px; }
.sb-ouraddr-item { display: inline-flex; align-items: center; gap: 6px; }
.sb-ouraddr-item code { font-size: 14px; }

/* --- таблицы --- */
.sb-streams,
.sb-runs,
.sb-members {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

.sb-streams th, .sb-runs th, .sb-members th {
    padding: 8px 10px;
    border-bottom: 1px solid var(--app-line);
    color: var(--app-muted);
    font-weight: 600;
    text-align: left;
    white-space: nowrap;
}

.sb-streams td, .sb-runs td, .sb-members td {
    padding: 9px 10px;
    border-bottom: 1px solid var(--app-line);
    vertical-align: top;
}

.sb-run-bad td { background: var(--app-danger-soft); }

/* Цветом «предупреждения», а не приглушённым серым: «не пришло 833» стоит рядом со скачанными и обязано
   читаться как беда, а не как справка. */
.sb-run-missing { color: var(--app-warning); font-weight: 600; }

.sb-warnings { margin: 14px 0 0; padding-left: 20px; color: var(--app-warning); font-size: 13px; }

/* --- роли --- */
.sb-role {
    display: inline-block;
    margin-right: 5px;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 600;
}

/* Владелец выделен цветом намеренно: список, где владельца надо искать глазами, — это список, в котором
   его однажды случайно уберут. Краска владельца осталась литералом: словарь несёт четыре тона
   (success/warning/danger/info) на пять ролей, и пятого — фиолетового — среди них нет. Замена на один из
   четырёх стёрла бы различие с соседней ролью (info уже занят «менеджером»), поэтому #6d28d9 не токен, а
   измеренный пробел — заявка на седьмой тон, а не решение этой правки (Center.SiteBuilder#116, PR 3/3). */
.sb-role-owner { background: var(--app-accent-soft); color: #6d28d9; }
.sb-role-manager { background: var(--app-info-soft); color: var(--app-info); }
.sb-role-buyer { background: var(--app-success-soft); color: var(--app-success); }
.sb-role-accountant { background: var(--app-warning-soft); color: var(--app-warning); }
.sb-role-viewer { background: var(--app-bg); color: var(--app-muted); }

/*
    --- формы ---

    `:where(:not(.sb-page *))` у трёх правил ниже, и вот из-за чего.

    Этот лист подключён не только консоли: App.razor отдаёт его и странице редактора (последним из четырёх,
    то есть с правом решающего голоса при равном весе). А внутри редактора живёт `.sb-page` — страница
    клиента, нарисованная настоящей, теми же компонентами библиотеки блоков. Имена `sb-form` и `sb-field`
    носят обе стороны: у нас это форма приглашения и поля настроек, у клиента — форма блока «Контакты»,
    поля оформления заказа и записи на приём.

    Замерено живым браузером на одной и той же разметке внутри `.sb-page`, холст против серверного
    предпросмотра той же страницы: форма «Контактов» на холсте — сетка с зазором 14, на сайте — колонка с
    зазором 24. Автор расставлял поля по одной вёрстке, посетитель получал другую, и увидеть это можно было
    только положив два экрана рядом.

    Ноль веса у оговорки обязателен: правило снаружи страницы обязано весить ровно столько же, сколько
    весило, иначе, починив вид сайта, мы переверстали бы консоль. `.sb-field-hint` оговорки не просит —
    такого имени у страницы клиента нет.
*/
.sb-form:where(:not(.sb-page *)) { display: grid; gap: 14px; }
.ds-field:where(:not(.sb-page *)) { display: grid; gap: 5px; }
.ds-field:where(:not(.sb-page *)) label { font-size: 13px; font-weight: 600; color: var(--app-text); }
/* .ds-hint снят отсюда (issue #132): design.css 0.31.23 уже объявляет color/font-size для этого же
   класса, и держать здесь свой хардкод 12px вместо токена --ds-size-xs — не «то же самое», а второе
   место, где кегль подсказки разойдётся с общим при следующей подкрутке шкалы. */

/*
    У ПОЛЯ ВВОДА КОНСОЛИ ЕСТЬ РАМКА, И ОБЪЯВЛЯЕТ ЕЁ КОНСОЛЬ.

    Замер приёмки на мастере подключения 1С: `border: 0px none`, `background: transparent` у всех четырёх
    полей шага — имя, сервер, база, порт. Рамка зажигалась только по фокусу. Человек видел подписи и пустоту
    и не понимал, куда печатать.

    Откуда бралось. Своего правила о полях у консоли не было НИ ОДНОГО: их одевал чужой лист. Сперва
    editor.css (его подключает каждая непубличная страница), потом — когда он честно отгородился корнем
    `:where(.sb-editor)` — бандл библиотеки блоков, который тут же рядом и по той же причине: холсту нужна
    настоящая таблица стилей сайта, а грузится она всем экранам консоли. В ней `.sb-field input` — имя,
    которое носят обе стороны, — задаёт `border: 1px solid var(--sb-border)` и `background: var(--sb-surface)`.

    И вот несущее: `--sb-*` объявляет ТЕМА САЙТА, то есть они существуют только внутри `.sb-page`. Снаружи
    объявление с неразрешимой `var()` становится невалидным на этапе вычисления значения, и свойство берёт
    не значение по умолчанию, а `unset`: `border-style` — `none`, ширина — 0, фон — прозрачный. Не «рамка
    похуже», а «рамки нет вовсе». Ровно тот же механизм, что стоил консоли рамок в прошлый раз (заголовок
    editor.css), — сменился только лист-источник.

    Вес. `.sb-field input` у библиотеки — (0,1,1), и бандл её грузится ВЫШЕ нас; значит, чтобы спорить, нам
    хватает ровно такого же веса, и мы его не превышаем: обе оговорки и отвод флажков завёрнуты в `:where()`,
    который веса не добавляет. Перевесить было бы легко и неверно — лишний вес здесь спорил бы уже не с
    библиотекой, а с правилами, которые придут после.

    Две оговорки, и обе несущие:
      • `.sb-page *` — внутри холста поля клиента одевает тема сайта, и это единственный верный там вид;
        правило консоли, дотянувшееся туда, означало бы «правлю один вид, публикую другой» (заголовок листа).
      • `.sb-editor *` — у панелей редактора поля свои, объявленные в editor.css своей палитрой. Мы грузимся
        после него и без этой оговорки переодели бы их в консольные: 12.5 → 14 по кеглю и 6/8 → 9/12 по
        полям, то есть инспектор стал бы выше окна.

    Center.SiteBuilder#116 (PR 1/3) переименовал разметку консоли `.sb-field` → `.ds-field`: имя
    больше не общее с библиотекой блоков, и спора весов, описанного выше, с этого момента нет —
    `.ds-field input` и `.sb-field input` разные селекторы. Оговорки `:where()` оставлены как есть:
    без них ничего не меняется, а снимать их — отдельная уборка, не эта правка.

    Флажки и переключатели отведены: их рисует браузер своим виджетом, рамка и заливка ему не указ, а
    ширина 100% превратила бы квадратик в полосу во всю строку — ровно та беда, что уже случалась в
    «Свойствах страницы» редактора и описана там же.
*/
.ds-field:where(:not(.sb-page *)):where(:not(.sb-editor *))
    :is(input, select, textarea):where(:not([type="checkbox"], [type="radio"], [type="range"], [type="file"])) {
    width: 100%;
    min-width: 0;

    /* Рост цели — тот же, что у полей BootstrapBlazor рядом (bb-bridge): поле мастера и поле настроек на
       соседних экранах обязаны быть одного роста, иначе консоль выглядит собранной из двух разных форм. */
    min-height: var(--ds-tap);
    padding: 9px 12px;
    border: 1px solid var(--app-line);
    border-radius: var(--ds-radius-sm);
    background: var(--app-panel);
    color: var(--app-text);
    font: inherit;
    font-size: var(--ds-size-sm);
}

.ds-field:where(:not(.sb-page *)):where(:not(.sb-editor *)) textarea { resize: vertical; }

/* Подсветка фокуса — рамкой в цвет акцента, кольцо рисует общее правило bb-bridge. Ровно так же, как у
   `.form-control`: два вида фокуса на соседних полях одного экрана человек читает как две разные формы. */
.ds-field:where(:not(.sb-page *)):where(:not(.sb-editor *)) :is(input, select, textarea):focus {
    border-color: var(--app-accent);
}

.sb-invite {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-top: 16px;
    padding-top: 16px;
    border-top: 1px solid var(--app-line);
}

.sb-invite-token {
    margin-top: 14px;
    padding: 12px 14px;
    background: var(--app-success-soft);
    border: 1px solid var(--app-warning);
    border-radius: 10px;
}

.sb-invite-token-label { font-size: 12.5px; color: var(--app-warning); margin-bottom: 6px; line-height: 1.5; }
.sb-invite-token code { font-size: 12.5px; word-break: break-all; }


/* --- вход для клиентов --- */
.sb-console-head-actions { display: flex; gap: 8px; flex-shrink: 0; }

.sb-portal {
    margin-bottom: 22px;
    padding: 14px 18px;
    border-radius: 12px;
    font-size: 13.5px;
    line-height: 1.55;
}

/* Не красным: ненастроенный вход — это состояние, а не поломка. Магазину, торгующему в розницу без
   регистрации, он и не нужен. */
.sb-portal-off { background: var(--app-bg); border: 1px solid var(--app-line); color: var(--app-muted); }
.sb-portal-on { background: var(--app-success-soft); border: 1px solid var(--app-line); color: var(--app-success); }

.sb-portal code {
    padding: 1px 5px;
    background: color-mix(in srgb, var(--app-text) 6%, transparent);
    border-radius: 5px;
    font-size: 12.5px;
}

.sb-portal-uris { margin: 8px 0 0; padding-left: 20px; }
.sb-portal-uris li { margin-top: 2px; }

/* Идентификаторы клиента и адреса возврата — свёрнуты: нужны только для разговора с поддержкой. */
.sb-portal-details { margin-top: 10px; }
.sb-portal-details summary { cursor: pointer; font-size: 13px; color: var(--app-muted); }
.sb-portal-details[open] summary { margin-bottom: 6px; }

.sb-portal-switch {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 14px 0 10px;
    font-size: 14px;
}

/*
    Тумблер: вид рисуется на самом <input>, а не на соседнем <div>.

    Это не про экономию разметки. Пока полосу с кружком рисовал отдельный элемент, настоящего поля рядом не
    было вовсе — и вместе с ним не было фокуса, пробела, объявления вслух и состояния «выключен» для тех, кто
    не смотрит на экран. Стилизованный input сохраняет всё это даром: браузер сам даёт кольцо фокуса, сам
    переключает по пробелу, сам называет положение.
*/
.sb-switch { cursor: pointer; }

.sb-switch input[type="checkbox"] {
    appearance: none;
    flex: none;
    position: relative;
    width: 44px;
    height: 26px;
    margin: 0;
    border: 1px solid var(--app-line);
    border-radius: 999px;
    background: var(--app-bg);
    cursor: inherit;
    transition: background-color var(--ds-t-tint) var(--ds-ease-out), border-color var(--ds-t-tint) var(--ds-ease-out);
}

.sb-switch input[type="checkbox"]::after {
    content: "";
    position: absolute;
    inset-block-start: 3px;
    inset-inline-start: 3px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--app-muted);
    transition: translate var(--ds-t-move) var(--ds-ease-out), background-color var(--ds-t-tint) var(--ds-ease-out);
}

/* Заливка берёт -fill, а кружок поверх неё — -contrast: в тёмной теме это разные цвета, и осветлённый
   под чтение индиго под белым кружком даёт 3.6 при норме 4.5. См. design.css. */
.sb-switch input[type="checkbox"]:checked {
    border-color: var(--app-accent-fill);
    background: var(--app-accent-fill);
}

.sb-switch input[type="checkbox"]:checked::after {
    translate: 18px 0;
    background: var(--ds-accent-contrast);
}

.sb-switch input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--app-accent);
    outline-offset: 2px;
}

.sb-switch:has(input[type="checkbox"]:disabled) {
    cursor: not-allowed;
    opacity: .55;
}

/* --- каталог --- */
.sb-catalog-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 16px;
    flex-wrap: wrap;
}

.sb-catalog-bar > :first-child { min-width: min(280px, 100%); flex: 1 1 280px; }
.sb-catalog-bar-right { margin-left: auto; display: flex; align-items: center; gap: 8px; }

.sb-catalog { width: 100%; border-collapse: collapse; font-size: 14px; }
.sb-catalog th {
    padding: 8px 10px;
    text-align: left;
    font-weight: 600;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--app-muted);
    border-bottom: 1px solid var(--app-line);
}

.sb-catalog td { padding: 10px; border-bottom: 1px solid var(--app-line); vertical-align: middle; }
.sb-catalog-pick { width: 32px; }
.sb-catalog-name { font-weight: 500; }
.sb-catalog-slug { font-size: 12px; color: var(--app-muted); }

/* Скрытый товар приглушён, а не перечёркнут: он существует и продаётся по договору, просто не в списке. */
.sb-catalog-hidden { opacity: 0.55; }

.sb-catalog-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    margin-top: 18px;
    font-size: 13px;
    color: var(--app-muted);
}

/* Вкладки «Товары» / «Разделы» на странице каталога (issue #106). */
.sb-console-tabs {
    display: flex;
    gap: 4px;
    margin-bottom: 16px;
    border-bottom: 1px solid var(--app-line);
}

.sb-console-tab {
    padding: 8px 14px;
    border: none;
    background: none;
    font: inherit;
    font-weight: 500;
    color: var(--app-muted);
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}

.sb-console-tab.active { color: var(--app-text); border-bottom-color: var(--app-accent); }

.sb-catalog-actions { display: flex; flex-wrap: wrap; gap: 6px; }

/* .sb-chip-warn/-edited/-new сняты целиком (Center.SiteBuilder#116, PR 2/3): тон плашки — общий механизм
   design.css, .ds-tone-warning и .ds-tone-accent, а не свой класс на каждое состояние. «Непрочитанное»
   (было .sb-chip-new) взяло акцентный тон, а не предупреждающий, тем же порядком, что и раньше: новая
   заявка — хорошая новость, а не тревога. */

/*
    Поля заявки. Имена полей задаёт автор формы, поэтому длина у них любая, а перенос строк в значении —
    обычное дело: в «Сообщении» посетитель пишет абзацами, и склеенный в строку абзац теряет смысл.
*/
.sb-submission-fields { display: grid; gap: 10px; margin: 12px 0 0; }
.sb-submission-fields dt { color: var(--app-muted); font-size: 12.5px; }
.sb-submission-fields dd { margin: 2px 0 0; }
.sb-submission-value { white-space: pre-wrap; overflow-wrap: anywhere; }
/* Тот же случай, что и у форм выше: `sb-muted` — это ещё и тон «приглушённый» у текстового блока клиента.
   Замер на холсте против сайта: rgb(95,102,120) нашей консоли вместо rgb(77,96,112) темы сайта. */
.sb-muted:where(:not(.sb-page *)) { color: var(--app-muted); }

/* Столбцы с числом и временем: без ограничения таблица отдаёт минутам столько же места, сколько названию. */
.sb-catalog .sb-narrow { width: 110px; }

.sb-date {
    padding: 6px 10px;
    border: 1px solid var(--app-line);
    border-radius: 8px;
    background: var(--app-panel);
    color: var(--app-text);
    font: inherit;
    font-size: 13px;
}

/* --- правка товара --- */
.sb-source-value {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-top: 6px;
    padding: 6px 10px;
    background: var(--app-bg);
    border-left: 2px solid var(--app-line);
    border-radius: 0 6px 6px 0;
    font-size: 12.5px;
}

.sb-source-label { color: var(--app-muted); flex-shrink: 0; }
.sb-source-text { color: var(--app-muted); overflow-wrap: anywhere; }

.ds-panel-revert { background: var(--app-danger-soft); }

.sb-attrs, .sb-variants { width: 100%; border-collapse: collapse; font-size: 14px; }
.sb-attrs th, .sb-variants th {
    padding: 6px 8px;
    text-align: left;
    font-size: 12px;
    color: var(--app-muted);
    border-bottom: 1px solid var(--app-line);
}

.sb-attrs td, .sb-variants td { padding: 6px 8px; border-bottom: 1px solid var(--app-line); }

/* --- заказы --- */
.ds-panel h3 {
    margin: 22px 0 8px;
    font-size: 13.5px;
    font-weight: 640;
}

.sb-order-columns {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 0 28px;
}

/* Let each panel fit its grid track; wide tables scroll inside their existing ds-table-wrap. */
.sb-order-columns > .ds-panel { min-width: 0; }

/* Итог справа и жирный: глаз менеджера ищет одну цифру, и это она. */
.sb-order-totals {
    display: grid;
    grid-template-columns: max-content max-content;
    gap: 4px 18px;
    justify-content: end;
    margin: 12px 0 0;
    font-size: 13px;
}

.sb-order-totals dt { color: var(--app-muted); }
.sb-order-totals dd { margin: 0; text-align: right; }
.sb-order-totals dt:last-of-type, .sb-order-totals dd:last-of-type { font-weight: 650; }

/* --- галерея --- */

/* Приём файлов: прозрачный input поверх всей области. Так перетаскивание работает силами самого браузера,
   без JS-interop, который в Blazor Server — лишний круг до сервера и обратно ради того, что уже умеет <input>. */
.sb-dropzone {
    position: relative;
    margin-bottom: 18px;
    padding: 26px;
    border: 2px dashed var(--app-line);
    border-radius: 12px;
    background: var(--app-bg);
    text-align: center;
    transition: border-color var(--ds-t-tint) var(--ds-ease-out),
                background var(--ds-t-tint) var(--ds-ease-out);
}

.sb-dropzone:hover { border-color: var(--app-accent); background: var(--app-accent-soft); }
.sb-dropzone-busy { border-color: var(--app-accent); background: var(--app-accent-soft); }

.sb-dropzone-input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

.sb-dropzone-face { pointer-events: none; display: grid; gap: 4px; }
.sb-dropzone-face b { font-size: 15px; color: var(--app-text); }
.sb-dropzone-face span { font-size: 13px; color: var(--app-muted); }

.sb-upload-errors {
    margin: 0 0 16px;
    padding: 10px 14px 10px 30px;
    background: var(--app-danger-soft);
    border: 1px solid var(--app-line);
    border-radius: 10px;
    color: var(--app-danger);
    font-size: 13px;
}

.sb-gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
    gap: 16px;
}

.sb-gallery-item {
    display: grid;
    gap: 6px;
    padding: 10px;
    border: 1px solid var(--app-line);
    border-radius: 12px;
    background: var(--app-panel);
}

.sb-gallery-primary { border-color: var(--app-accent); box-shadow: 0 0 0 1px var(--app-accent) inset; }

.sb-gallery-thumb {
    position: relative;
    aspect-ratio: 1;
    border-radius: 8px;
    overflow: hidden;
    background: var(--app-bg);
}

.sb-gallery-thumb img { width: 100%; height: 100%; object-fit: contain; }

.sb-gallery-noimage {
    display: grid;
    place-items: center;
    height: 100%;
    font-size: 12px;
    color: var(--app-muted);
}

.sb-gallery-badge {
    position: absolute;
    top: 6px;
    left: 6px;
    padding: 2px 7px;
    border-radius: 20px;
    background: var(--app-accent-fill);
    color: var(--ds-accent-contrast);
    font-size: 11px;
}

.sb-gallery-variants { display: grid; gap: 3px; font-size: 12.5px; }
.sb-gallery-variant { display: flex; align-items: center; gap: 6px; cursor: pointer; }
.sb-gallery-actions { display: flex; flex-wrap: wrap; gap: 4px; }
.sb-gallery-actions .btn { padding: 2px 8px; font-size: 12px; }


/* --- объём сопоставления --- */
.sb-schema-scope { margin: 12px 0 16px; }

.sb-schema-other { margin-top: 14px; font-size: 13px; }
.sb-schema-other summary { cursor: pointer; color: var(--app-muted); }
/* Свёрнутый <details> прячет через content-visibility, а строки схемы интерактивны (Открыть/Скрыть/
   Вернуть). Теперь, когда группа надёжно сворачивается вручную, свёрнутое достижимо по-настоящему —
   без этой строки поиск по странице находил бы спрятанные строки. Тот же приём, что у палитры/групп
   стилей в editor.css. */
.sb-schema-other:not([open]) > *:not(summary) { display: none; }

.sb-schema-other-list {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 10px;
    max-height: 220px;
    overflow-y: auto;
}

.sb-schema-other-list code {
    padding: 2px 7px;
    background: var(--app-bg);
    border-radius: 5px;
    font-size: 12px;
}

/*
    Галочки и переключатели — не меньше 24 пикселей.

    Стандартный form-check-input у Bootstrap — 15×15. WCAG 2.2 называет нижней границей 24, и не из
    вежливости: попадание пальцем на телефоне и попадание мышью при треморе считаются одинаково. Пятнадцать
    пикселей — это цель, в которую промахиваются, а промах по галочке «скрыть товар» стоит дороже промаха
    по ссылке: он молча меняет данные.
*/
.form-check-input {
    width: 20px;
    height: 20px;
    margin-top: 0.1em;
}

/* Сама галочка остаётся 20 — крупнее она выглядит неуклюже, — а нажимаемая область растёт за счёт
   подписи и невидимого поля вокруг. */
.form-check,
.form-check-label {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-height: 28px;
    cursor: pointer;
}

/* Кольцо фокуса у галочки здесь не пишется: его возвращает bb-bridge.css вместе с остальными полями
   библиотеки, и токеном --ds-focus-ring, а не переписанным от руки «2px solid». Копия совпадала с
   оригиналом значение в значение — то есть расходиться начала бы молча, с первой правки токена. */

/* ---------------------------------------------------------------------------------------------------
   Галерея шаблонов в окне «Новый сайт»
   ---------------------------------------------------------------------------------------------------

   Миниатюра рисуется из самого шаблона: полосы главной страницы в их тонах, окрашенные его палитрой. Это не
   скриншот и не претендует им быть — по ней не прочитать текст. Она отвечает на другие вопросы, которые
   человек задаёт до нажатия: длинная страница или короткая, чередуются ли полосы, какого цвета будет сайт.

   Скриншоты были бы точнее и врали бы через неделю: их пришлось бы перевыпускать руками после каждой правки
   шаблона, а не перевыпущенный скриншот хуже, чем никакого. Здесь расходиться нечему.
   --------------------------------------------------------------------------------------------------- */

.sb-tpl-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 10px;
}

/* Ячейка галереи: карточка выбора и ссылка «посмотреть» под ней. Ссылка вне кнопки, потому что внутри
   <button> ссылке быть нельзя, а посмотреть шаблон целиком — не то же самое, что его выбрать. */
.sb-tpl-cell {
    display: grid;
    gap: 4px;
    align-content: start;
}

.sb-tpl-look {
    justify-self: start;
    padding: 4px 6px;
    color: var(--app-muted);
    font-size: 12px;
    text-decoration: none;
    border-radius: 6px;
}

.sb-tpl-look:hover { color: var(--app-accent); text-decoration: underline; }

.sb-tpl-look:focus-visible {
    outline: 2px solid var(--app-accent);
    outline-offset: 2px;
}

.sb-tpl {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 8px;
    text-align: left;
    background: var(--app-panel);
    border: 1px solid var(--app-line);
    border-radius: 10px;
    cursor: pointer;
    /* Миниатюра шаблона — кнопка выбора, поэтому наведение ей положено; меняется только краска рамки. */
    transition: border-color var(--ds-t-tint) var(--ds-ease-out);
}

.sb-tpl:hover { border-color: color-mix(in srgb, var(--app-accent) 45%, var(--app-line)); }

/* Выбранный — рамкой и внутренним контуром, а не одним лишь цветом: выбор обязан быть виден и в оттенках
   серого, и тому, кто не различает синий. */
.sb-tpl-on {
    border-color: var(--app-accent);
    box-shadow: inset 0 0 0 1px var(--app-accent);
}

.sb-tpl:focus-visible {
    outline: 2px solid var(--app-accent);
    outline-offset: 2px;
}

.sb-tpl-shot {
    display: block;
    overflow: hidden;
    border-radius: 6px;
    border: 1px solid var(--app-line);
}

.sb-tpl-page {
    display: flex;
    flex-direction: column;
    height: 84px;
    background: #fff;
}

/* Полосы делят высоту поровну и не сжимаются в ноль: страница из восьми полос должна отличаться от страницы
   из трёх, а не превращаться в сплошную заливку. */
.sb-tpl-band {
    display: block;
    flex: 1 1 0;
    min-height: 5px;
}

.sb-tpl-surface { background: #fff; }
.sb-tpl-alt { background: var(--t-alt, #f2f4f8); }
.sb-tpl-primary { background: var(--t-primary, #4f46e5); }
.sb-tpl-dark { background: #12141c; }

/* Первая полоса — обложка: показываем в ней заголовок и кнопку, иначе миниатюра читается как полосатый флаг. */
.sb-tpl-band:first-child {
    position: relative;
    flex-grow: 1.6;
}

.sb-tpl-band:first-child::before,
.sb-tpl-band:first-child::after {
    content: "";
    position: absolute;
    left: 14%;
    border-radius: 2px;
}

.sb-tpl-band:first-child::before {
    top: 30%;
    width: 52%;
    height: 5px;
    background: color-mix(in srgb, var(--t-text, #12141c) 62%, transparent);
}

.sb-tpl-band:first-child::after {
    top: 58%;
    width: 24%;
    height: 7px;
    background: var(--t-primary, #4f46e5);
}

/* На обложке поверх основного цвета штрихи тем же цветом не видны — там они светлые. */
.sb-tpl-primary:first-child::before { background: color-mix(in srgb, #fff 72%, transparent); }
.sb-tpl-primary:first-child::after { background: var(--t-accent, #f59e0b); }

.sb-tpl-blank { background: repeating-linear-gradient(135deg, #fff 0 8px, #f6f7fb 8px 16px); }
.sb-tpl-blank .sb-tpl-band:first-child::before,
.sb-tpl-blank .sb-tpl-band:first-child::after { display: none; }

.sb-tpl-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.sb-tpl-name {
    color: var(--app-text);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.25;
}

.sb-tpl-note { color: var(--app-muted); font-size: 11.5px; }

/*
    Образец шрифта в миниатюре.

    Первая полоса миниатюры показывает не только цвет обложки, но и набор: две буквы тем же шрифтом, которым
    будут заголовки сайта. Различить антикву и гротеск на этом кегле легко, а без образца шаблоны отличались бы
    в галерее одной краской — то есть наполовину.
*/
.sb-tpl-type {
    position: absolute;
    left: 14%;
    top: 26%;
    font-family: var(--t-heading, inherit);
    font-size: 15px;
    font-weight: 650;
    line-height: 1;
    color: color-mix(in srgb, var(--t-text, #12141c) 78%, transparent);
}

/* На залитой обложке те же буквы светлые: тёмным по основному цвету их не видно. */
/* Заголовок обложки вместо двух букв: строка длинная, поэтому у неё своя ширина и обрезка по двум строкам —
   миниатюра в сто пятьдесят пикселей не место для абзаца, но название сайта в ней читается. */
.sb-tpl-line {
    right: 14%;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    font-size: 9px;
    line-height: 1.25;
    text-wrap: balance;
}
.sb-tpl-primary .sb-tpl-type,
.sb-tpl-dark .sb-tpl-type { color: color-mix(in srgb, #fff 88%, transparent); }

/* Образец занимает место строки-заголовка, поэтому сама полоска-заголовок убирается. */
.sb-tpl-band:first-child:has(.sb-tpl-type)::before { display: none; }

/*
    --- администрирование платформы ---

    Плашка «вы в чужом сайте». Предупреждающая, а не декоративная: консоль клиента выглядит ровно как своя, и
    единственное, что отделяет показательную правку от правки на живом сайте, — это то, что человек помнит,
    где он. Отсюда цвет предупреждения и текст, который называет и владельца, и то, что идёт запись.
*/
.sb-assist {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 4px 10px;
    padding: 9px 22px;
    background: var(--app-notice-soft);
    color: var(--app-notice-text);
    border-bottom: 1px solid var(--app-line);
    font-size: 13.5px;
}

.sb-assist strong { font-weight: 650; }
.sb-assist-exit { margin-left: auto; color: inherit; text-decoration: underline; white-space: nowrap; }

/*
    Над редактором — плавающая: редактор занимает весь экран и считает высоту сам, полоса сверху сдвинула бы
    холст. Снизу по центру, чтобы не накрыть панель инструментов, и выше плашки сборки: та — справка, эта —
    предупреждение.
*/
.sb-assist-float {
    position: fixed;
    left: 50%;
    bottom: 12px;
    transform: translateX(-50%);
    z-index: 80;
    /* 100% от окна, а не 100vw: vw считает ширину вместе с полосой прокрутки, и плашка вылезала за правый
       край ровно на её толщину — то есть давала странице вторую полосу прокрутки, горизонтальную. */
    max-width: min(720px, calc(100% - 32px));
    border: 1px solid var(--app-line);
    border-radius: 10px;
    box-shadow: 0 6px 20px color-mix(in srgb, var(--app-text) 18%, transparent);
}

/* Поиск на экране платформы — обычная форма браузера: страница статическая, запрос живёт в адресе. */
.sb-admin-search {
    flex: 1 1 280px;
    min-width: 240px;
    padding: 7px 11px;
    border: 1px solid var(--app-line);
    border-radius: 8px;
    background: var(--app-panel);
    color: var(--app-text);
    font: inherit;
    font-size: 14px;
}

.sb-admin-button {
    min-height: 34px;
    padding: 0 14px;
    border: 1px solid var(--app-line);
    border-radius: 8px;
    background: var(--app-panel);
    color: var(--app-text);
    font: inherit;
    font-size: 14px;
    cursor: pointer;
}

.sb-admin-button:hover { background: var(--app-bg); }

/* ---------------------------------------------------------------------------------------------------
   ТАБЛИЦА ЖИВЁТ В ОБЁРТКЕ

   Девятнадцать таблиц консоли и админки, и ни одна из них не влезает в 375 — в них по пять-семь колонок,
   и меньше их не станет: колонка «Прогон / Взято / Ошибки / Когда» перестаёт отвечать на вопрос, если из
   неё что-нибудь убрать. Значит, вопрос не «как ужать таблицу», а «кто прокручивается».

   Без обёртки прокручивается ДОКУМЕНТ: страница /admin на 375 меряла 612 пикселей, и вместе с таблицей
   вбок ездили шапка, заголовок и подвал — то есть ломалась вся страница, а не одна её часть. С обёрткой
   прокручивается только таблица, а страница стоит.

   Правило: любая <table> в консоли и админках лежит внутри .ds-table-wrap. Голая таблица в разметке —
   падение сторожа, а не «мелочь, которую видно на телефоне».

   Класс общий по имени (ds-*), но объявлен здесь: пакет заморожен на время перерисовки. Переезжает в
   design.css вместе с админкой сервера идентификации, у которой уже есть своя копия под другим именем.
   --------------------------------------------------------------------------------------------------- */
.ds-table-wrap {
    min-width: 0;
    max-width: 100%;
    overflow-x: auto;
}

/* Список наборов в окне «Добавить поток» прокручивается сам. Ответ поиска — до полусотни строк, и без
   собственной высоты они уводят кнопку «Добавить» за нижний край: до неё пришлось бы прокручивать страницу
   под модальным окном, а на телефоне это и вовсе не получается. */
.sb-stream-choices { max-height: 44vh; overflow-y: auto; }

/* ---------------------------------------------------------------------------------------------------
   УЗКИЙ ЭКРАН

   Два порога, и оба измеренные, а не круглые.

   860 — ширина, на которой панели консоли перестают жить в две колонки: .sb-order-columns держит колонки
   от 260, .sb-form-cols — от 230, и ниже 860 вторая колонка получает меньше, чем нужно её содержимому.
   720 — ширина, на которой шапка консоли перестаёт помещаться в строку.

   Один блок на весь лист, а не правила по местам: правила телефона уже были рассыпаны по файлу, и
   следующий узкий экран правился бы в четвёртом месте.
   --------------------------------------------------------------------------------------------------- */
@media (max-width: 860px) {
    /* Одна колонка, а не две по 130 пикселей. auto-fit сам бы этого не сделал: он укладывает столько
       колонок, сколько влезает по минимуму, и две колонки по 260 «влезают» в 540 при поле в 24. */
    .sb-order-columns,
    .sb-form-cols { grid-template-columns: 1fr; }

    .sb-console { padding: 22px 16px 48px; }
    .ds-panel { padding: 18px 16px; }

    /* Поле во всю ширину: поле в 240 посреди колонки в 343 читается как незаполненная форма, а не как
       поле, которому столько и надо. */
    .sb-form-cols .form-control,
    .sb-form-cols .form-select,
    .ds-field .form-control,
    .ds-field .form-select { width: 100%; }
}

@media (max-width: 720px) {
    .sb-console-head { flex-direction: column; }
    .sb-console-head-actions { flex-direction: column; align-items: stretch; }
    .sb-source-actions { width: 100%; flex-wrap: wrap; }
    .sb-source, .sb-cabinet { flex-direction: column; align-items: stretch; }

    /*
        Ряд периода переносится, а не вылезает за край.

        Ряд с самого начала получал здесь всю ширину строки, но не получал разрешения переноситься — а два
        поля даты плюс «Показать» в неё не помещаются: поле типа date браузер рисует по содержимому и уже
        своего содержимого не делает, сколько ему ни говори. Замер на статистике при 375: правый край ряда
        на 399 при окне 375, то есть вся страница ездит вбок сама по себе, без единого раскрытого слоя.
        Родительский .sb-catalog-bar переносится с самого начала — здесь не хватало ровно этой строки.
    */
    .sb-catalog-bar-right { margin-left: 0; width: 100%; flex-wrap: wrap; }
    .sb-gallery-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }

    /* Ряд действий строки — в столбец: четыре кнопки по 44 в строку требуют 176 плюс промежутки, а
       столбец «Что делать» на 375 получает от таблицы около сотни. */
    .sb-row-actions { flex-direction: column; align-items: stretch; }

    .sb-dash-head-actions { width: 100%; }
    .sb-dash-filter input[type="search"] { width: 100%; }
}

/* --- экран источника ---
   Классы ниже разметка использовала с самого начала, а правил под них не было ни одного: «колонки»
   рисовались одной колонкой, крошки — обычным текстом, предупреждение о сертификате ничем не отличалось
   от абзаца. Словарь sb-* держится на том, что каждый класс из разметки здесь описан. */
.sb-crumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-bottom: 6px;
    font-size: 12.5px;
    color: var(--app-muted);
}

.sb-crumbs a { color: var(--app-muted); text-decoration: none; }
.sb-crumbs a:hover { color: var(--app-accent); text-decoration: underline; }

/* auto-fit, а не жёсткие две колонки: тот же лист обслуживает и узкую консоль, и широкий монитор. */
.sb-form-cols {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 14px;
}

.sb-form-flags { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; margin-top: 14px; }

/*
    Предложение готовой разметки. Панель внутри панели — отсюда своя рамка акцентом: это не ещё один
    раздел настроек, а единственная кнопка, которая делает за человека работу, которую он делать не умеет.
    Акцент, а не жёлтый: жёлтым платформа говорит «осторожно», а здесь она говорит «нажмите сюда».
*/
.sb-offer {
    margin: 0 0 18px;
    border-color: color-mix(in srgb, var(--ds-accent) 40%, transparent);
    background: color-mix(in srgb, var(--ds-accent) 6%, var(--app-panel));
}

.sb-offer h3 { margin: 0 0 6px; font-size: 16px; }

.sb-offer-review { margin: 6px 0 14px; padding-left: 20px; color: var(--app-muted); font-size: 13px; }

/* «Образец обновился» — предупреждение, а не подсказка: человек читает панель образца по диагонали и, не
   выделив эту строку, пролистает её вместе с описанием. Ровно так он и пролистал бы весть о том, что его
   покупатель до сих пор видит в карточке GUID. */
.sb-offer-stale {
    margin: 0 0 10px;
    padding: 8px 10px;
    border-left: 3px solid var(--ds-warning);
    border-radius: var(--ds-radius-sm);
    background: color-mix(in srgb, var(--ds-warning) 12%, transparent);
    font-size: 13px;
}

.sb-offer-review li { margin: 3px 0; }

.sb-warn {
    margin: 10px 0 0;
    padding: 10px 12px;
    border: 1px solid color-mix(in srgb, var(--app-warning) 35%, transparent);
    border-radius: 10px;
    background: var(--app-warning-soft);
    color: var(--app-warning);
    font-size: 13px;
    line-height: 1.5;
}

/* Выключенный поток остаётся читаемым: строка гаснет, но не исчезает — её сюда и пришли включать. */
.sb-row-off td { color: var(--app-muted); }
.sb-row-off code { opacity: .7; }

.sb-row-actions { display: flex; flex-wrap: wrap; gap: 4px; white-space: nowrap; }

.sb-product-editor { display: flex; flex-direction: column; gap: 4px; }

/* ---------------------------------------------------------------------------------------------------
   ДИАЛОГИ СПИСКА САЙТОВ

   Само окно, его появление, лист снизу на телефоне и ловушку фокуса даёт общий пакет (.ds-dialog). Здесь
   только то, чего у него быть не может: разрушительное действие в ряду кнопок и подпись под полем.
   --------------------------------------------------------------------------------------------------- */

/*
    Заголовок окна — настоящий <h2>, а не строка нужного кегля.

    Читалка объявляет открытое окно по его заголовку; без заголовка человек слышит «диалог» и дальше сам
    догадывается, какой именно из шести. Кегль и насыщенность задаёт .ds-dialog-head, поэтому у самого
    заголовка снимаются собственные поля и набор — иначе он приносит с собой отступы браузера и рвёт
    полосу шапки надвое.
*/
.ds-dialog-head h2 { margin: 0; font: inherit; letter-spacing: -0.01em; }

/*
    Свой класс, не ds-btn-danger.

    До Center.Design 0.31.13 общий пакет был собран для страниц, где удалять нечего, и красной кнопки в
    нём не было — сплошная заливка родилась здесь под тем же именем. С 0.31.13 design.css завёл
    .ds-btn-danger сам, но другим приёмом: мягкий фон и обводка, а не сплошная заливка (KisaPs/Center#709
    заодно перевело шапку на общий блок учётной записи — заявка KisaPs/Center.SiteBuilder#91 — и версия
    вслед за ней поднялась с 0.31.6). Один лист один селектор (см. SheetOverlapTests): совпадение имени
    красило бы порядком подключения листов, а не тем, кто прав. Смена вида — самостоятельное решение
    поверх этого выноса, не побочный эффект правки шапки, поэтому кнопка осталась прежней, только под
    своим именем.

    Краска берётся из темы, поэтому в тёмной половине она не «ярче», а другая: см. --app-danger.
*/
.sb-btn-danger {
    background: var(--app-danger);
    border-color: var(--app-danger);
    color: var(--app-panel);
}

.sb-btn-danger:hover { filter: brightness(1.1); }

.sb-dialog-note { margin: 0 0 14px; color: var(--app-muted); font-size: 13.5px; line-height: 1.55; }
.sb-dialog-note:last-child { margin-bottom: 0; }

/* ---------------------------------------------------------------------------------------------------
   СТАТИСТИКА

   График рисует библиотека; здесь остаются только числа над ним.
   --------------------------------------------------------------------------------------------------- */

.sb-stat-figure {
    margin: .2rem 0;
    font-size: 27px;
    font-weight: 600;

    /*
        Моноширинные цифры — обязательны, а не «аккуратнее».

        Число на плитке набегает счётчиком, и в пропорциональном наборе «1» уже «8»: каждая смена разряда
        двигает всё число вправо-влево, и плитка на глазах дёргается. Это же правило действует и когда
        счётчик погашен просьбой «поменьше движения»: числа в колонке должны стоять столбиком.
    */
    font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------------------------------------
   ОТКАЗ: НЕТ СТРАНИЦЫ, НЕ ВЫШЛО

   Две страницы, у которых один вид и разный текст. Обе — про то, куда идти дальше, а не про то, что
   случилось: человеку, попавшему на несуществующий адрес, интересна дорога, а не диагноз.
   --------------------------------------------------------------------------------------------------- */

.sb-stop {
    max-width: 640px;
    margin: 0 auto;
    padding: 64px 24px;
    text-align: center;
}

.sb-stop-code {
    display: block;
    margin-bottom: 10px;
    color: var(--app-muted);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.sb-stop h1 { margin: 0 0 12px; font-size: 26px; font-weight: 650; letter-spacing: -0.02em; }
.sb-stop p { margin: 0 0 10px; color: var(--app-muted); line-height: 1.6; }

.sb-stop-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    justify-content: center;
    margin-top: 22px;
}

/* Номер запроса — то единственное, ради чего эту страницу цитируют в поддержку, поэтому он выделен и
   ломается в любом месте: на 375 идентификатор в 30 знаков иначе раздвинул бы страницу. */
.sb-stop-ref {
    display: inline-block;
    margin-top: 18px;
    padding: 8px 12px;
    background: var(--app-panel);
    border: 1px solid var(--app-line);
    border-radius: 10px;
    color: var(--app-muted);
    font-size: 12.5px;
}
