/**
 * Unified Component Library
 * ONE variant per component type
 * Uses CSS variables from /css/dynamic.css
 * Date: 2026-02-06
 *
 * This file provides standardized, reusable components that use CSS variables
 * for easy theming. All components follow the same design patterns.
 */

/* ==================== BUTTONS ==================== */

/* ===== Buttons — base .btn (перенесено из base.css, TD-64 W13.1) =====
   Канон кнопки. Варианты-цвета (.btn-primary и т.д.) генерируются в /css/dynamic.css (тема). */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    gap: var(--button-gap, 10px);
    min-height: var(--button-min-height, 72px);
    min-width: var(--button-min-height, 72px);
    padding: var(--button-padding-y, 10px) var(--button-padding-x-right, 35px) var(--button-padding-y, 10px) var(--button-padding-x-left, 35px);
    border: 1px solid transparent;
    border-radius: var(--border-radius);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    text-align: center;
    text-decoration: none;
    transition: var(--transition-fast);
    -webkit-appearance: none;
    appearance: none;
}

/* .btn-outline — «голый» outline (ButtonVariant::Outline).
   Тема (dynamic.css) объявляет ему `border: 1px solid var(--btn-outline-border)`, но базовое
   правило `.btn { border: 1px solid transparent }` выше по файлу имеет ТУ ЖЕ специфичность (0,1,0)
   и грузится ПОЗЖЕ темы, поэтому перебивало рамку в прозрачную — outline-кнопка выглядела как
   голый текст во всех ~297 вызовах. Возвращаем рамку на тех же токенах темы (TASK-603). */
.btn-outline {
    border: var(--border-width-thin) solid var(--btn-outline-border, var(--color-border));
}

/* Confirm — semantic confirm action (own tokens, editable independently from success) */
.btn-confirm,
.ui-button--confirm {
    background-color: var(--button-confirm-background, var(--button-success-background));
    color: var(--button-confirm-text, var(--button-primary-text, var(--color-white, #fff)));
    border: var(--border-width-thin) solid var(--button-confirm-border, transparent);
    box-shadow: var(--button-confirm-shadow, none);
    padding: var(--spacing-sm) var(--spacing-lg);
    border-radius: var(--border-radius);
    cursor: pointer;
    transition: var(--transition-fast);
    font-weight: var(--font-weight-semibold);
}
.btn-confirm:hover,
.ui-button--confirm:hover {
    background-color: var(--button-confirm-hover, var(--button-success-hover));
    color: var(--button-confirm-hover-text, var(--color-white, #fff));
    border-color: var(--button-confirm-hover-border, transparent);
    box-shadow: var(--button-confirm-hover-shadow, none);
}
.btn-confirm:active,
.ui-button--confirm:active {
    background-color: var(--button-confirm-active, var(--button-success-hover));
    color: var(--button-confirm-active-text, var(--color-white, #fff));
    border-color: var(--button-confirm-active-border, transparent);
    box-shadow: var(--button-confirm-active-shadow, none);
}

/* Unconfirm — semantic revert action */
.btn-unconfirm,
.ui-button--unconfirm {
    background-color: var(--button-unconfirm-background, var(--button-warning-background));
    color: var(--button-unconfirm-text, var(--button-primary-text, var(--color-white, #fff)));
    border: var(--border-width-thin) solid var(--button-unconfirm-border, transparent);
    box-shadow: var(--button-unconfirm-shadow, none);
    padding: var(--spacing-sm) var(--spacing-lg);
    border-radius: var(--border-radius);
    cursor: pointer;
    transition: var(--transition-fast);
    font-weight: var(--font-weight-semibold);
}
.btn-unconfirm:hover,
.ui-button--unconfirm:hover {
    background-color: var(--button-unconfirm-hover, var(--button-warning-hover));
    color: var(--button-unconfirm-hover-text, var(--color-white, #fff));
    border-color: var(--button-unconfirm-hover-border, transparent);
    box-shadow: var(--button-unconfirm-hover-shadow, none);
}
.btn-unconfirm:active,
.ui-button--unconfirm:active {
    background-color: var(--button-unconfirm-active, var(--button-warning-hover));
    color: var(--button-unconfirm-active-text, var(--color-white, #fff));
    border-color: var(--button-unconfirm-active-border, transparent);
    box-shadow: var(--button-unconfirm-active-shadow, none);
}

/* Disabled state modifier — use with btn btn-secondary btn--disabled */
.btn--disabled {
    opacity: 0.5;
    cursor: default;
    pointer-events: none;
}

/* Native disabled state — покрывает <button disabled> (JS/DTO ui_button disabled:true без href).
   Общий канон вместо page-CSS дублей (TD-61 D1). */
.btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;
}

/* a11y: ссылка-кнопка в disabled-состоянии (href + aria-disabled) не кликабельна (TD-64 W13.1) */
.btn[aria-disabled="true"],
.header-btn[aria-disabled="true"] {
    pointer-events: none;
}

/* Disabled-вид кнопки шапки. `.header-btn` — самодостаточная база и класс `.btn` НЕ несёт,
   поэтому `.btn:disabled` выше на неё не распространяется: до этого правила заблокированная
   кнопка выглядела полностью активной (сплошная заливка варианта), а `<button disabled>`
   вдобавок отзывался на :hover, т.к. pointer-events снимался только у [aria-disabled].
   Набор деклараций — тот же, что у `.btn-icon:disabled` (секция icon-кнопок ниже), с которой
   `_edit_button`/`_delete_button` мигрировали на `.header-btn`: одинаковое состояние должно
   выглядеть одинаково, каким бы компонентом кнопка ни была собрана. */
.header-btn:disabled,
.header-btn[aria-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;
}

/* Button size modifiers */
.btn-sm {
    min-height: unset;
    padding: var(--spacing-sm) var(--spacing-md);
    font-size: var(--button-font-size-sm);
}

.btn-lg {
    /* large на шаг выше дефолта (--button-min-height): иерархия sm < md(дефолт) < lg.
       Раньше .btn-lg наследовал базовый min-height → визуально совпадал с дефолтом (TD-63). */
    min-height: calc(var(--button-min-height, 72px) + var(--spacing-sm, 8px));
    padding: var(--button-padding-lg);
    font-size: var(--button-font-size-lg);
}

/* Кнопка на swagger-card (library-страницы): светлый фон карточки + акцентный текст,
   без рамки. Консолидация 5 идентичных page-CSS правил `.swagger-card .btn`
   (TD-61 P-C — доработка > кастом). Применять как companion: class="btn ... btn-on-swagger-card". */
.btn-on-swagger-card {
    background: var(--color-card-background);
    color: var(--color-hero-gradient-start);
    border: none;
}

/* Responsive layout-утилита кнопки (TD-61 P-D — консолидация page-CSS @media-копий C1).
   Mobile-first по канону grid.css (docs/architecture/responsive-breakpoints.md):
   базовое состояние = мобильное (full-width), на ≥768px (md) кнопка возвращается
   к штатной ширине. Применять как companion: class="btn ... btn-full-mobile". */
.btn-full-mobile {
    width: 100%;
}

@media (min-width: 768px) {
    .btn-full-mobile {
        width: auto;
    }
}

/* .form-actions .btn — компаунд-канон (TD-61 P-D). Консолидация 3 идентичных page-CSS копий
   (admin-settings.css, admin-styles.css, admin-translations-edit-key.css — единственные,
   где `.form-actions` реально в markup; остальные ~18 page-CSS дублей были dead CSS
   на _form_icon_buttons и удалены без замены). Компаунд-в-канон вместо companion-класса
   на markup — единый селектор безопаснее точечной правки 3 шаблонов, .form-actions уже
   централизован (флекс-колонка на мобиле → row+auto-width на ≥768px). */
.form-actions .btn {
    width: 100%;
}

@media (min-width: 768px) {
    .form-actions .btn {
        width: auto;
    }
}

/* .guide-nav-row .btn — компаунд-канон (TD-61 P-D). Консолидация ~29 page-CSS копий
   (9 в скоупе P-D + guide/howto ×20). ВАЖНО: это НЕ responsive-утилита — браузер-проверка
   (2026-07-08, guide/communications и guide/expenses) подтвердила flex:1 на ВСЕХ ширинах
   (350px на 1440px desktop, 158px на 375px mobile) — равное распределение 2 кнопок в ряд —
   текущее намеренное поведение guide-страниц, НЕ mobile-only. Поэтому unconditional
   (без @media-сброса на ≥768: тот дал бы визуальную регрессию десктопа). Поведение 1:1, дедуп. */
.guide-nav-row .btn {
    flex: 1;
    text-align: center;
}

/* Icon Buttons (circular buttons) */
.btn-circle, .ui-button--circle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: none;
    cursor: pointer;
    transition: var(--transition-fast);
}

.btn-circle-add, .ui-button--add {
    background: linear-gradient(135deg, var(--color-info) 0%, var(--color-link) 100%);
    color: var(--button-primary-text);
}

.btn-circle-edit, .ui-button--edit {
    background: linear-gradient(135deg, var(--color-gradient-start) 0%, var(--color-gradient-end) 100%);
    color: var(--button-primary-text);
}

.btn-circle-delete, .ui-button--delete {
    background: linear-gradient(135deg, var(--color-error) 0%, var(--color-danger-dark, #c0392b) 100%);
    color: var(--button-primary-text);
}

.btn-circle-refresh, .ui-button--refresh {
    background: linear-gradient(135deg, var(--color-warning) 0%, var(--color-warning) 100%);
    color: var(--button-primary-text);
}

.btn-circle:hover, .ui-button--circle:hover {
    transform: scale(1.1);
    box-shadow: var(--shadow-button);
}

/* ==================== CARDS ==================== */

.card-unified,
.ui-card {
    background: var(--layout-card-background);
    border-radius: var(--border-radius-large);
    box-shadow: var(--shadow-card);
    padding: var(--spacing-padding);
    transition: var(--transition-default);
}

.card-unified:hover,
.ui-card:hover {
    box-shadow: var(--shadow-card-hover);
}

/* Базовый .card — перенесён из base.css (TD-61 B-core: component-override лейаута → канон) */
.card {
    background: var(--color-card-background);
    border-radius: var(--border-radius-lg);
    box-shadow: var(--shadow-card);
    padding: 20px;
    margin-bottom: 16px;
}

/* Card-варианты геометрии — консолидация page-CSS companion-паддингов (TD-61 P-C).
   Companion-классы (.role-card / .X-form .card / .detail-card …) сохраняют свою
   уникальную специфику (flex/gap), а паддинг задаётся вариантом вместо копий в page-CSS. */
.card--list-item { padding: var(--spacing-md); }        /* 16px — admin-index справочники (list-карточки) */
.card--compact { padding: 14px var(--spacing-md); }     /* 14px 16px — компактные form-страницы */
.card--detail { padding: var(--spacing-lg); }           /* 24px — detail/show-страницы */

.card-header, .ui-card__header {
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-bold);
    margin-bottom: var(--spacing-md);
    color: var(--color-text);
}

/* Flex-бар шапка карточки (заголовок + действия в ряд, с разделителем) — консолидация
   page-CSS конвенции «.card-header как flex-контейнер» (TD-61 P-C). Это СТРУКТУРНЫЙ бар,
   отличный от канон .card-header (заголовок-текст); внутренний заголовок — .card-title. */
.card-header--bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--spacing-md);
    padding-bottom: 12px;
    border-bottom: var(--border-width-thin) solid var(--color-border);
}

.card-body, .ui-card__body {
    color: var(--color-text-secondary);
}

.card-footer, .ui-card__footer {
    margin-top: var(--spacing-md);
    padding-top: var(--spacing-md);
    border-top: var(--border-width-thin) solid var(--color-border-light);
}

/* ==================== ALERTS ==================== */

/* Legacy .alert / .alert-danger — структурная база сырого HTML-alert вне ui_alert
   (class="alert alert-{type}"). Перенесено из base.css (TD-64): все стили alert
   живут одним блоком здесь. Объявлено ДО .ui-alert — каскад сохранён (.ui-alert
   перекрывает общие свойства для элементов ui_alert, несущих оба класса). */
.alert {
    padding: var(--spacing-md);
    border-radius: var(--border-radius);
    margin-bottom: var(--spacing-md);
    font-size: var(--font-size-sm);
}

.alert-danger {
    background-color: var(--color-error-bg);
    color: var(--color-error);
}

.alert-unified,
.ui-alert {
    width: 100%;
    padding: var(--spacing-md) var(--spacing-md);
    border-radius: var(--border-radius);
    margin-bottom: var(--spacing-md);
    display: flex;
    align-items: center;
    gap: var(--spacing-md);
    font-size: var(--font-size-sm);
}

/* Контур по типу (TD-64 follow-up #1): полный 1px (--border-width-thin) цветом типа
   --color-{type}. Толстый левый акцент здесь НЕ задаётся — он только у selection-required
   (см. ниже), для ЛЮБОГО типа в его собственном цвете. Новые --border-{type} токены НЕ
   заводим — это был бы дубль поверх --color-{type}. */
.alert-unified-success,
.ui-alert--success {
    background-color: var(--color-success-bg);
    color: var(--color-success);
    border: var(--border-width-thin) solid var(--color-success);
}

.alert-unified-error,
.ui-alert--error {
    background-color: var(--color-error-bg);
    color: var(--color-error);
    border: var(--border-width-thin) solid var(--color-error);
}

.alert-unified-warning,
.ui-alert--warning {
    background-color: var(--color-warning-bg);
    color: var(--color-warning);
    border: var(--border-width-thin) solid var(--color-warning);
}

.alert-unified-info,
.ui-alert--info {
    background-color: var(--color-info-bg);
    color: var(--color-info);
    border: var(--border-width-thin) solid var(--color-info);
}

.alert-unified-icon,
.ui-alert__icon {
    font-size: var(--font-size-xl);
    flex-shrink: 0;
}

.alert-unified-content,
.ui-alert__content {
    flex: 1;
}

/* ===== Alert component (TD-64) — канон-стили, перенос из pages/receipts.css ===== */

/* Цвета .alert-danger (как и .alert-info/success/warning) генерируются в dynamic.css
   из настроек стилей — см. StyleController. Здесь только структурные стили компонента. */

/* Подсказка под заголовком alert (hint-поле AlertConfig) */
.alert-hint {
    margin: var(--spacing-xs) 0 0 0;
    font-size: var(--font-size-sm);
}

/* Кнопка закрытия dismissible-alert: прижата вправо (alert — flex), не сжимается.
   Внешний вид — брендбук btn-icon btn-xs btn-icon-{type} (разметка в _alert.html.twig). */
.alert-close {
    margin-left: auto;
    flex-shrink: 0;
}

/* Семантический компаньон selection-required (rootClasses от AlertConfig).
   ЕДИНСТВЕННЫЙ носитель толстого левого акцента: расширяет левую грань 1px-контура до
   --border-width-thick. Цвет акцента — собственный цвет типа (наследуется от border
   .ui-alert--{type}), поэтому работает для ЛЮБОГО типа, не только warning.
   Объявлено ПОСЛЕ правил типов → border-left-width перекрывает 1px из их border-шортхенда. */
.selection-required-alert {
    border-left-width: var(--border-width-thick);
    /* фон/цвет/контур — от .ui-alert--{type}; margin-bottom — от базового .ui-alert */
}

/* slideDown-анимация для ui-alert (JS-тоггл validation-error) */
@keyframes slideDown {
    from { opacity: 0; transform: translateY(-10px); }
    to   { opacity: 1; transform: translateY(0); }
}

.ui-alert--animated {
    animation: slideDown 0.3s ease-out;
}

/* ===== Flash Messages (перенесено из base.css — TD-85; канон .ui-flash-message) ===== */
.ui-flash-messages {
    margin-bottom: 16px;
}

.ui-flash-message {
    padding: 12px 16px;
    border-radius: var(--border-radius);
    margin-bottom: 8px;
    font-size: var(--font-size-sm);
}

.ui-flash-message--success {
    background-color: var(--color-success-bg);
    color: var(--color-success);
}

.ui-flash-message--error, .ui-flash-message--danger {
    background-color: var(--color-error-bg);
    color: var(--color-error);
}

.ui-flash-message--warning {
    background-color: var(--color-warning-bg);
    color: var(--color-warning);
}

.ui-flash-message--info {
    background-color: var(--color-info-bg);
    color: var(--color-info);
}

.ui-flash-message a {
    color: inherit;
    font-weight: var(--font-weight-semibold);
    text-decoration: underline;
}

.ui-flash-message a:hover {
    text-decoration: none;
}

/* ui-dismissible controller: анимация скрытия перед удалением из DOM */
.ui-dismissing {
    opacity: 0;
    max-height: 0;
    margin-top: 0;
    margin-bottom: 0;
    padding-top: 0;
    padding-bottom: 0;
    overflow: hidden;
    transition: opacity var(--transition-default), max-height var(--transition-default), margin var(--transition-default), padding var(--transition-default);
}

/* ===== /Alert component (TD-64) ===== */

/* ==================== BADGES ==================== */

.badge-unified,
.ui-badge {
    display: inline-block;
    padding: var(--spacing-xs) var(--spacing-sm);
    border-radius: var(--border-radius-small);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
}

.ui-badge--primary {
    background-color: var(--color-badge-primary);
    color: var(--button-primary-text);
}

.ui-badge--success {
    background-color: var(--color-badge-success);
    color: var(--color-success);
}

/* Заливной зелёный статус: белый текст на насыщенном фоне — для бейджа, который
   в карточке главный и обязан читаться издалека (TASK-598/599). Светлый `--success`
   остаётся для перечислительных бейджей в таблицах и списках. */
.ui-badge--success-solid {
    background-color: var(--color-success-strong);
    color: var(--color-white);
}

.ui-badge--warning {
    background-color: var(--color-badge-warning);
    color: var(--color-warning);
}

.ui-badge--danger {
    background-color: var(--color-badge-danger);
    color: var(--button-primary-text);
}

.ui-badge--info {
    background-color: var(--color-badge-info);
    color: var(--color-info);
}

.ui-badge--secondary {
    background-color: var(--color-secondary-bg, #e9ecef);
    color: var(--color-text-secondary, #6c757d);
}

.ui-badge--light {
    background-color: var(--color-light-bg, #f8f9fa);
    color: var(--color-text-muted, #6c757d);
}

.ui-badge--country {
    background-color: var(--color-badge-country, #e8f4fd);
    color: var(--color-badge-country-text, #0c5460);
}

.ui-badge--group {
    background-color: var(--color-badge-group, #f0e6ff);
    color: var(--color-badge-group-text, #6f42c1);
}

.ui-badge--telegram {
    background-color: var(--color-badge-telegram, #e3f2fd);
    color: var(--color-badge-telegram-text, #0088cc);
}

.ui-badge--one-time {
    background-color: var(--color-badge-one-time, #fff3cd);
    color: var(--color-badge-one-time-text, #856404);
}

/* TD-64 W4 — доменные роли (палитра по смыслу, спека §2.3-A) */
.ui-badge--landlord {
    background-color: var(--color-badge-landlord, var(--color-badge-success));
    color: var(--color-success);
}
.ui-badge--landlord_agent {
    background-color: var(--color-badge-landlord-agent, var(--color-badge-primary));
    color: var(--button-primary-text);
}
.ui-badge--tenant {
    background-color: var(--color-badge-tenant, var(--color-badge-info));
    color: var(--color-info);
}
.ui-badge--tenant_agent {
    background-color: var(--color-badge-tenant-agent, var(--color-secondary-bg, #e9ecef));
    color: var(--color-text-secondary, #6c757d);
}
.ui-badge--maintenance {
    background-color: var(--color-badge-maintenance, var(--color-badge-warning));
    color: var(--color-warning);
}

.ui-badge--sm {
    font-size: var(--font-size-xs, 0.75rem);
    padding: var(--spacing-xs) var(--spacing-sm);
}

/* ==================== FORMS ==================== */

/* TD-85 Волна 2: select/textarea переиспользуют ЕДИНУЮ базу form-control
   (границы/радиус/шрифт/фон/цвет/focus/disabled) через групповой селектор — «доработка
   > дубль». Элемент-специфика (стрелка select, resize textarea) — отдельными companion-
   правилами ниже. padding канон 8×16 (как form-input в Волне 1). */
.form-control-unified, .ui-form-control,
.form-select-unified, .ui-form-select,
.form-textarea-unified, .ui-form-textarea {
    width: 100%;
    padding: var(--spacing-sm) var(--spacing-md);
    border: var(--border-width-thin) solid var(--color-input-border);
    border-radius: var(--border-radius);
    font-family: var(--font-family);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-normal);
    line-height: var(--line-height-normal);
    letter-spacing: 0;
    background: var(--color-input-bg);
    color: var(--color-text);
    transition: var(--transition-fast);
}

.form-control-unified:focus, .ui-form-control:focus,
.form-select-unified:focus, .ui-form-select:focus,
.form-textarea-unified:focus, .ui-form-textarea:focus {
    outline: none;
    border-color: var(--color-input-focus);
    box-shadow: var(--shadow-input-focus);
}

.form-control-unified:disabled, .ui-form-control:disabled,
.form-select-unified:disabled, .ui-form-select:disabled,
.form-textarea-unified:disabled, .ui-form-textarea:disabled {
    background: var(--form-disabled-background);
    color: var(--form-disabled-text);
    cursor: not-allowed;
}

.form-control-unified::placeholder, .ui-form-control::placeholder,
.form-textarea-unified::placeholder, .ui-form-textarea::placeholder {
    color: var(--form-placeholder);
    font-family: var(--font-family);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-normal);
    line-height: var(--line-height-normal);
}

/* Select — база выше + собственная стрелка/appearance. Стрелка — SVG в background-image
   (CSS var внутри data-URI не поддерживается): fill #898E97 = значение --color-text-muted. */
.form-select-unified, .ui-form-select {
    -webkit-appearance: none;
    appearance: none;
    padding-right: 40px;
    cursor: pointer;
    background-repeat: no-repeat;
    background-position: right var(--spacing-md) center;
    /* @brandbook-ignore-next BB-RAW-COLOR — глиф стрелки select в data-URI SVG (background-image не берёт CSS-переменную); #898E97 = значение --color-text-muted */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23898E97' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
}

/* Textarea — база выше + вертикальный ресайз + min-height (паритет с удаляемым legacy
   .form-textarea из base.css/components.css, где было min-height: 80px). */
.form-textarea-unified, .ui-form-textarea {
    resize: vertical;
    min-height: 80px;
}

/* Checkbox-обёртка (layout-компонент, не form-control). */
.form-check-unified, .ui-form-check {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    cursor: pointer;
}

.form-check-unified input[type="checkbox"], .ui-form-check input[type="checkbox"] {
    width: 20px;
    height: 20px;
    accent-color: var(--color-primary);
}

.form-check-unified label, .ui-form-check label {
    color: var(--color-text-secondary);
    font-size: var(--font-size-sm);
}

/* Компактный размер form-control — узкие inline-поля (property-wizard value-inputs).
   Консолидация page-CSS `.form-control-sm` (TD-61 P-C — доработка > кастом).
   На узком мобиле снимает max-width (см. @media ниже). */
.form-control-unified--sm, .ui-form-control--sm {
    padding: 6px 10px;
    font-size: var(--font-size-sm);
    width: 100%;
    max-width: 200px;
}

@media (max-width: 575px) {
    .form-control-unified--sm, .ui-form-control--sm {
        max-width: 100%;
    }
}

.form-label-unified, .ui-form-label {
    display: block;
    margin-bottom: var(--spacing-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-secondary);
    font-size: var(--font-size-sm);
}

.form-group-unified, .ui-form-group {
    margin-bottom: var(--spacing-md);
}

.form-help-text {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    margin-top: var(--spacing-xs);
}

.form-error-text {
    font-size: var(--font-size-sm);
    color: var(--color-error);
    margin-top: var(--spacing-xs);
}

/* Input Group — append-обёртка «контрол + примыкающая кнопка» (DTO-компонент
   ui_input_group, TASK-551 — канонизация временной раскладки
   ui-side-rail__chat-input-group панели чата поддержки). Контрол и append-кнопка —
   ПРЯМЫЕ дети `.ui-input-group` (партиал не оборачивает содержимое блоков в
   дополнительный div, см. templates/components/ui/input_group/_input_group.html.twig)
   — визуальная стыковка держится ПОЗИЦИОННЫМИ селекторами (:first-child/:last-child),
   а не именем класса контрола/кнопки: компонент не обязан знать, что именно внутри.

   display: grid, НЕ flex (живая проверка в брендбуке поймала регрессию первой
   flex-версии) — append-кнопка квадратная только через `aspect-ratio: 1` на
   элементе, чья высота получена через `align-items/self: stretch` от ВЫСОТЫ
   КОНТЕЙНЕРА, а высота контейнера сама вычисляется «снизу вверх» из высоты
   контрола (textarea произвольной высоты). Это классическая циклическая
   зависимость flex-алгоритма («automatic stretch + aspect-ratio при auto/
   intrinsic cross-size контейнера») — в панели чата она молча резолвилась
   верно только потому, что дальний предок (`.ui-side-rail__panel--chat`)
   задаёт СЕБЕ definite `height: min(480px, …)`, из-за чего вся высота по
   цепочке становится definite «случайно». В демо `readme.twig` такого предка
   нет (обёртка `.bb-render` — intrinsic height) — та же группа с ТЕМИ ЖЕ
   правилами давала 16px×66px кнопку (ширина = ширине иконки, аспект-рейшо не
   срабатывал). Живой замер (`getBoundingClientRect`) подтвердил: у CSS Grid
   ('display:grid; grid-template-columns: minmax(0,1fr) auto') того же
   алгоритмического цикла нет — track-sizing разрешает высоту строки ДО того,
   как align-items:stretch раздаёт её элементам, поэтому aspect-ratio у
   append-кнопки резолвится детерминированно НЕЗАВИСИМО от того, есть ли у
   дальних предков definite-height. Component не должен зависеть от случайных
   свойств своего далёкого окружения — тот же принцип, что и у скругления/
   цвета рамки ниже (не зависеть от того, что внутри/вокруг).

   minmax(0, 1fr) на первой колонке — grid-нативный аналог `min-width: 0` у
   flex: без него широкий контрол (длинный текст) распирал бы родительский
   контейнер горизонтальным скроллом (эта регрессия уже случалась в чате
   поддержки при вёрстке временной раскладки).

   ⚠️ Вторая колонка — `auto`, а НЕ размер-от-aspect-ratio (кросс-браузерная
   регрессия, живая проверка заказчика в Firefox 153): первая версия оставляла
   ширину append-кнопки полностью на откуп `width:auto + aspect-ratio:1`, и
   Chrome действительно выводил её из растянутой высоты (66px → 66px). Firefox
   так НЕ делает — в grid-алгоритме ширина auto-трека разрешается ДО того, как
   `align-items:stretch` раздаёт высоту строки, поэтому на момент подсчёта
   ширины опоры нет вовсе: `width` уже не фиксированный (правило группы отменило
   собственные `--button-icon-size-*` кнопки), а единственное содержимое — svg с
   шириной `36%` от ещё неизвестной ширины. Трек схлопывался в вертикальную
   полоску ~8px (кнопка превращалась в узкую планку у края textarea).

   Поэтому квадратность держится на ЯВНОЙ ширине, равной высоте строки
   (`--ui-input-group-append-size`, см. правило append ниже), а `aspect-ratio`
   оставлен лишь как страховка для случая, когда высота почему-то не определена.
   Опора на явный размер не зависит от порядка «сначала ширина или сначала
   высота» и одинаково резолвится во всех движках. */
.ui-input-group {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: stretch;
}

/* Контрол (первый ребёнок) — занимает первую (эластичную) колонку.

   Скругление ЗАДАНО ЯВНО с обеих сторон, а не оставлено «как получится» у
   исходного контрола (заказчик, живая проверка): внутренний угол (сторона стыка)
   обнулён, ВНЕШНИЙ угол — явно `var(--border-radius)`, тот же токен, что и у
   append-кнопки ниже. Без этого явного дублирования внешний вид группы зависел
   бы от радиуса, который случайно достался конкретному контролу (у textarea/input
   он и так `var(--border-radius)` через form-control-unified, но группа не должна
   на это полагаться — контролом может быть что угодно). */
.ui-input-group > *:first-child {
    min-width: 0;
    border-top-left-radius: var(--border-radius);
    border-bottom-left-radius: var(--border-radius);
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}

/* Append-кнопка (последний ребёнок, при 2+ прямых детях группы) — квадратная:
   ширина равна собственной высоте (aspect-ratio: 1), а высота растянута по контролу
   (align-items: stretch у группы), поэтому кнопка всегда совпадает с контролом по
   высоте, сколько бы строк он ни занимал.

   border-top-right-radius/border-bottom-right-radius: var(--border-radius) — ЯВНО
   заданный внешний радиус (заказчик, живая проверка после первой версии): append —
   как правило ui_icon_button(), канон-форма которой КРУГ (`.btn-icon{border-radius:
   50%}`, specificity 0,1,0). Наше правило зануляло только ЛЕВЫЕ (стыковые) углы —
   правые оставались 50% (полукруг), а слева у контрола — `var(--border-radius)`
   (~8-16px), что вместе давало D-образную фигуру вместо единого скруглённого
   прямоугольника. specificity этого правила — 0,3,0 (класс + :last-child +
   :not(:first-child)), уверенно бьёт border-radius:50% (0,1,0) независимо от
   порядка источников — группа теперь ЗАДАЁТ форму внешних углов, а не наследует
   её от того, что оказалось внутри (круглая кнопка, квадратная, пилюля — не важно).

   border-color: var(--color-input-border) — ЯВНО заданный цвет рамки в состоянии
   покоя (заказчик, живая проверка): у контрола рамка `--color-input-border`
   (form-control-unified), у `.btn-icon`/`.btn-icon-primary` — СВОЙ токен
   `--button-icon-border`/`--button-icon-primary-border`. Разные цвета на двух
   половинах одного контура читались как «сшитые из кусков», а не единый контрол.
   specificity 0,3,0 бьёт `.btn-icon`(0,1,0)/`.btn-icon-primary`(0,1,0) — рамка
   append-элемента ВСЕГДА совпадает с рамкой контрола в покое, независимо от
   цвет-варианта кнопки (побочный эффект: собственный `:hover`/`:active` цвет
   рамки кнопки (0,2,0) тоже перебивается — принятый компромисс, это лишь
   косметика наведения на кнопку, не часть контракта компонента).

   Скругление и рамка со стороны стыка (левая) — по-прежнему обнулены/перекрыты
   отрицательным отступом (без двойной рамки на стыке).

   width: auto — без него aspect-ratio НЕ работает: append-кнопка обычно
   ui_icon_button() с собственным ФИКСИРОВАННЫМ `width` (напр. `.btn-icon.btn-sm
   { width: var(--button-icon-size-sm) }`, специфичность 0,2,0). aspect-ratio
   подставляет размер ТОЛЬКО в действительно `auto`-измерение — раз width уже не
   auto (задан явно классом кнопки), браузер его не трогает. `width: auto` здесь
   имеет более высокую специфичность (0,3,0 — класс + 2 псевдокласса) и отменяет
   фиксированную ширину кнопки, после чего aspect-ratio: 1 вычисляет ширину = высоте.

   ⚠️ `height: 100%`, а НЕ `height: auto` (кросс-браузерная регрессия, живая проверка
   заказчика в Firefox 153 — кнопка схлопывалась в вертикальную полоску ~8px). При
   `height:auto` высота кнопки становится ОПРЕДЕЛЁННОЙ только после того, как
   `align-self:stretch` растянет её по строке грида, — а ширину auto-трека Firefox
   считает РАНЬШЕ этого момента, и `aspect-ratio` на тот момент не из чего вычислять
   (собственный `--button-icon-size-*` кнопки уже отменён `width:auto`). Chrome
   разрешал ту же цепочку в обратном порядке и потому проблему не показывал —
   классическое «работает у меня». `height: 100%` берёт высоту напрямую от строки
   грида (строка уже определена track-sizing'ом ДО раздачи размеров элементам), даёт
   `aspect-ratio` определённую опору и резолвится одинаково в обоих движках.
   `align-self: stretch` оставлен: он и формирует ту самую высоту строки, от которой
   берётся 100%. */
.ui-input-group > *:last-child:not(:first-child) {
    aspect-ratio: 1;
    width: auto;
    height: 100%;
    align-self: stretch;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
    border-top-right-radius: var(--border-radius);
    border-bottom-right-radius: var(--border-radius);
    border-color: var(--color-input-border);
    margin-left: calc(-1 * var(--border-width-thin));
}

/* Иконка append-кнопки — размер В ПРОЦЕНТАХ от стороны кнопки, НЕ фиксированный px
   (заказчик, живая проверка). Причина того же класса, что и D-образная кнопка выше:
   `.btn-icon svg`/`.btn-icon.btn-sm svg` (assets/styles/components.css, ~3684/~3709,
   16px/14px) рассчитаны на НЕРАСТЯНУТУЮ круглую кнопку 28-36px — append-кнопка внутри
   группы растянута по высоте контрола (align-self: stretch, произвольная высота: 66px
   в панели чата, ~42px в компактной search-группе readme.twig), и та же иконка
   занимает там ~20% высоты вместо родных ~50% — читается «потерянной».

   Это generic-свойство КОМПОНЕНТА (append всегда растянут произвольно), а не
   частный случай чата — поэтому правило здесь, а не в call-site и НЕ в глобальном
   `.btn-icon svg` (тот общий на все icon-кнопки проекта, трогать нельзя). Фиксированный
   px не подходит: один размер не может одновременно быть «около трети» у 66px-кнопки
   И не вылезать за пределы у ~30-42px-кнопки (однострочные контролы, напр. search-демо
   readme.twig) — при разной высоте append нужна ПРОПОРЦИЯ, не константа. Готового
   токена под эту пропорцию в проекте нет — заводить новый ради единственного правила
   избыточно (тут не переиспользуемый цвет/отступ, а форм-фактор одного компонента),
   поэтому голое число С ОБОСНОВАНИЕМ здесь же, в докблоке.

   36% — эмпирически: даёт ~24px при 66px (чат, целевой ориентир заказчика — «около
   трети высоты») и ~15px при 42px (search-демо, не крупнее исходных 14px sm-иконки,
   не вылезает за пределы кнопки). specificity 0,3,1 (класс + 2 псевдокласса + svg)
   уверенно бьёт `.btn-icon.btn-sm svg` (0,2,1) и `.btn-icon svg` (0,1,1) при любом
   размере кнопки — не голый px, поэтому вне шкалы BB-ICON-SIZE-RAW ([12,16,20,24,32,
   48,64]px), но и не безграничный: 36% на append-кнопке, которая САМА ограничена
   aspect-ratio:1 и высотой контрола, не может убежать за разумные пределы. */
.ui-input-group > *:last-child:not(:first-child) svg {
    width: 36%;
    height: 36%;
}

/* Единый focus-ring на ВСЮ группу (заказчик, живая проверка): нативный `:focus`
   контрола красит рамку/box-shadow ТОЛЬКО вокруг самого контрола — append-кнопка
   в это состояние не попадает, контур получается «наполовину подсвеченным» со
   ступенькой на стыке. Решение — group-level `:focus-within`:
     1) box-shadow контрола на фокусе ГЛУШИТСЯ (см. правило `:first-child:focus`
        ниже, specificity 0,3,0 бьёт `.form-control-unified:focus` 0,2,0) —
        вместо него `.ui-input-group:focus-within` даёт ОДИН box-shadow на весь
        прямоугольник (тот же токен `--shadow-input-focus`, что и у одиночного
        form-control — контрол внутри группы визуально ведёт себя как ОДИН
        укрупнённый контрол, а не как два независимых);
     2) border-color контрола на фокусе — родной `:focus` (уже даёт нужный
        `--color-input-focus`, трогать не нужно);
     3) border-color append-кнопки синхронизируется на фокусе отдельным правилом
        `:focus-within > *:last-child` ниже (specificity 0,4,0 — заведомо выше
        собственных цвет-вариантов кнопки).
   border-radius на самой группе — чтобы box-shadow повторял скруглённый силуэт
   (у `.ui-input-group` самого по себе нет видимого фона/рамки, только тень). */
.ui-input-group:focus-within {
    border-radius: var(--border-radius);
    box-shadow: var(--shadow-input-focus);
}

.ui-input-group > *:first-child:focus {
    box-shadow: none;
}

.ui-input-group:focus-within > *:last-child:not(:first-child) {
    border-color: var(--color-input-focus);
}

/* ==================== MODALS ==================== */

.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: var(--color-overlay-50);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: var(--z-modal);
}

.modal-content {
    background: var(--modal-content-background);
    border-radius: var(--border-radius-large);
    box-shadow: var(--shadow-modal);
    max-width: 600px;
    width: 90%;
    max-height: 90vh;
    overflow-y: auto;
}

.modal-header {
    padding: var(--spacing-lg);
    background: var(--modal-header-background);
    border-bottom: var(--border-width-thin) solid var(--modal-header-border);
    border-radius: var(--border-radius-large) var(--border-radius-large) 0 0;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.modal-title, .ui-modal__title {
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    margin: 0;
}

.modal-close, .ui-modal__close {
    background: none;
    border: none;
    font-size: var(--font-size-3xl);
    cursor: pointer;
    color: var(--color-text-muted);
    transition: var(--transition-fast);
    padding: 0;
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.modal-close:hover, .ui-modal__close:hover {
    color: var(--color-text);
}

.modal-body {
    padding: var(--spacing-lg);
}

.modal-footer {
    padding: var(--spacing-md) var(--spacing-lg);
    background: var(--modal-footer-background);
    border-top: var(--border-width-thin) solid var(--modal-footer-border);
    border-radius: 0 0 var(--border-radius-large) var(--border-radius-large);
    display: flex;
    justify-content: flex-end;
    gap: var(--spacing-md);
}

/* ==================== TABLES ==================== */

.table-unified,
.ui-table {
    width: 100%;
    border-collapse: collapse;
}

/* Таблица внутри карточки - убираем отступы */

.table-unified thead,
.ui-table thead {
    background: var(--color-table-header);
}

.table-unified th,
.ui-table th {
    padding: var(--spacing-md) var(--spacing-md);
    text-align: left;
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    border-bottom: var(--border-width-thin) solid var(--color-table-border);
}

.table-unified th:first-child {
    padding-left: var(--spacing-padding);
}

.table-unified td,
.ui-table td {
    padding: var(--spacing-md) var(--spacing-md);
    border-bottom: var(--border-width-thin) solid var(--color-table-border);
    color: var(--color-text);
}

.table-unified td:first-child {
    padding-left: var(--spacing-padding);
}

.table-unified.table-hover tbody tr:hover,
.ui-table.table-hover tbody tr:hover {
    background: var(--color-table-hover);
}

.table-unified.table-striped tbody tr:nth-child(even),
.ui-table.table-striped tbody tr:nth-child(even) {
    background: var(--color-table-stripe);
}

.table-unified tbody tr:last-child td {
    border-bottom: none;
}

/* Подсветка текущего месяца в таблице графика доходов */
.ui-table__row--current {
    background: var(--color-info-bg, rgba(59, 130, 246, 0.06));
}

.ui-table__row--current:hover {
    background: var(--color-table-hover);
}

/*
 * Stacked (карточный) вид таблицы на мобиле — opt-in через
 * TableConfig::$stackedOnMobile (класс-модификатор .ui-table--stacked
 * подмешивается tableClasses()). TASK-550 M3: до этого приём был page-local
 * копией в assets/styles/pages/income.css (вторая копия того же паттерна,
 * что и assets/styles/pages/user-calendar.css:474-479, .balance-table) —
 * централизован сюда, чтобы новые страницы просто включали флаг, а не
 * копировали CSS.
 *
 * thead скрыт через display:none (НЕ visually-hidden-технику): CSS Display
 * Level 3 снимает implicit table/row/cell ARIA-роли у ЛЮБОГО элемента с
 * display:block независимо от видимости самого узла — держать thead в
 * a11y-дереве, скрытым только визуально, смысла не имеет (M12 review).
 * Подписи полей и так озвучиваются через data-label/::before на каждой
 * ячейке (ниже) — это единственный работающий источник подписи после потери
 * table-семантики.
 */
@media (max-width: 767px) {
    .ui-table--stacked thead {
        display: none;
    }

    .ui-table--stacked,
    .ui-table--stacked tbody,
    .ui-table--stacked tr,
    .ui-table--stacked td {
        display: block;
        width: 100%;
    }

    .ui-table--stacked tbody tr {
        margin-bottom: var(--spacing-sm);
        /* Подсветка текущего месяца (.ui-table__row--current) красится через
           background на <tr> — при display:block продолжает работать как фон
           всей карточки. */
        border: var(--border-width-thin) solid var(--color-table-border);
        border-radius: var(--border-radius-md);
        overflow: hidden;
    }

    .ui-table--stacked tbody tr:last-child {
        margin-bottom: 0;
    }

    /* Ячейка — 2-колоночный grid (label|value) вместо table-cell раскладки. */
    .ui-table--stacked td {
        display: grid;
        grid-template-columns: 40% 1fr;
        align-items: baseline;
        gap: var(--spacing-sm);
        padding: var(--spacing-xs) var(--spacing-sm);
        border-bottom: var(--border-width-thin) solid var(--color-table-border);
        font-size: var(--font-size-xs);
    }

    /* Выравнивание первой ячейки со следующими (M4 review): без явного override
       .table-unified td:first-child (выше, --spacing-padding=16px, специфичность
       (0,2,1)) перебивает padding-left ТОЛЬКО у первой td каждой карточки —
       колонка подписей визуально съезжает на 8px относительно остальных строк.
       Тот же (0,2,1) + более позднее место в файле = побеждает по source order. */
    .ui-table--stacked td:first-child {
        padding-left: var(--spacing-sm);
    }

    .ui-table--stacked td:last-child {
        border-bottom: none;
    }

    /* Подпись поля — из data-label на <td> (задаётся call-site'ом, дублирует
       переводы headers). Атрибутный квалификатор [data-label] (M6 review) —
       без него будущая ячейка без data-label получит пустой grid-item шириной
       40% под подпись. */
    .ui-table--stacked td[data-label]::before {
        content: attr(data-label);
        display: block;
        font-weight: var(--font-weight-semibold);
        color: var(--color-text-secondary);
    }

    .ui-table--stacked td[data-label=""]::before {
        display: none;
    }
}

/* ==================== PAGINATION ==================== */

.pagination-unified, .ui-pagination {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: var(--spacing-sm);
    margin-top: var(--spacing-lg);
}

.pagination-item {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 36px;
    height: 36px;
    padding: 0 var(--spacing-md);
    border-radius: var(--border-radius);
    text-decoration: none;
    color: var(--color-text);
    background: var(--color-card-background);
    border: var(--border-width-thin) solid var(--color-border);
    transition: var(--transition-fast);
}

.pagination-item:hover {
    background: var(--color-primary);
    color: var(--button-primary-text);
    border-color: var(--color-primary);
}

.pagination-item.active {
    background: var(--color-primary);
    color: var(--button-primary-text);
    border-color: var(--color-primary);
}

.pagination-item.disabled {
    background: var(--color-disabled-bg);
    color: var(--color-disabled);
    cursor: not-allowed;
    pointer-events: none;
}

/* ==================== LISTS ==================== */

.list-unified, .ui-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.list-item-unified, .ui-list-item {
    padding: var(--spacing-md) var(--spacing-md);
    border-bottom: var(--border-width-thin) solid var(--color-border-light);
    transition: var(--transition-fast);
}

.list-item-unified:hover, .ui-list-item:hover {
    background: var(--color-table-hover);
}

.list-item-unified:last-child, .ui-list-item:last-child {
    border-bottom: none;
}

/* ==================== DROPDOWNS ==================== */

.dropdown-unified, .ui-dropdown {
    position: relative;
    display: inline-block;
}

.dropdown-toggle {
    background: var(--color-card-background);
    border: var(--border-width-thin) solid var(--color-border);
    padding: var(--spacing-sm) var(--spacing-md);
    border-radius: var(--border-radius);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-sm);
    transition: var(--transition-fast);
}

.dropdown-toggle:hover {
    background: var(--color-table-hover);
}

/* TASK-526b: блок .header-btn.dropdown-toggle удалён как избыточный — он дублировал
   базовые декларации .header-btn теми же токенами. Перебивать .dropdown-toggle не нужно:
   при равной специфичности (0,1,0) .header-btn объявлена ниже по файлу и выигрывает каскадом. */

.dropdown-menu {
    position: absolute;
    top: 100%;
    left: 0;
    min-width: 200px;
    background: var(--color-card-background);
    border: var(--border-width-thin) solid var(--color-border);
    border-radius: var(--border-radius);
    box-shadow: var(--shadow-dropdown);
    margin-top: var(--spacing-xs);
    z-index: var(--z-dropdown);
    display: none;
}

.dropdown-unified.active .dropdown-menu, .ui-dropdown.active .dropdown-menu {
    display: block;
}

.dropdown-item {
    display: block;
    padding: var(--spacing-sm) var(--spacing-md);
    color: var(--color-text);
    text-decoration: none;
    transition: var(--transition-fast);
    border-bottom: var(--border-width-thin) solid var(--color-border-light);
}


.dropdown-item:hover {
    background: var(--color-table-hover);
}

/* Keyboard focus indicator for menu navigation (ArrowUp/Down). :focus-visible
   keeps it off for mouse interaction while staying visible for keyboard users. */
.dropdown-item:focus-visible {
    outline: var(--border-width-normal) solid var(--color-primary);
    outline-offset: -2px;
    background: var(--color-table-hover);
}

.dropdown-item:last-child {
    border-bottom: none;
}

/* Submit/button used as a dropdown item (e.g. delete form inside a menu):
   reset native button chrome so it matches <a class="dropdown-item">. */
button.dropdown-item {
    width: 100%;
    box-sizing: border-box;
    text-align: left;
    background: none;
    border: none;
    border-bottom: var(--border-width-thin) solid var(--color-border-light);
    font: inherit;
    cursor: pointer;
}

/* A menuitem wrapped in a <form> is never :last-child of the menu, so drop the
   separator when the form is the last menu child. */
.dropdown-menu > form:last-child .dropdown-item {
    border-bottom: none;
}

/* Текущий выбор в меню (DropdownItem.current → aria-current): язык интерфейса,
   выбранный период и т.п. Нативный <select> подсвечивал выбранное сам — здесь
   роль подсветки берёт на себя модификатор. */
.dropdown-item--current {
    background: var(--color-primary-bg);
    color: var(--color-primary);
    font-weight: var(--font-weight-semibold);
}

/* Длинное меню (месяцы, языки, справочники) — прокрутка внутри меню вместо
   роста на весь экран. Модификатор канона: ставится на .dropdown-menu через
   scoped-правило страницы или обёртку компонента. */
.dropdown-menu--scrollable {
    max-height: 40vh;
    overflow-y: auto;
}

.dropdown-item--danger {
    color: var(--color-danger, #dc3545);
}

.dropdown-item--danger:hover {
    background: var(--color-danger-bg, #fff5f5);
    color: var(--color-danger, #dc3545);
}

/* ==================== TABS ==================== */

.tabs-unified,
.tabs-nav,
.ui-tabs {
    display: flex;
    border-bottom: var(--border-width-normal) solid var(--color-border);
    gap: 0;
}

.tab-item, .tab-btn, .ui-tabs__item {
    padding: var(--spacing-md) var(--spacing-lg);
    background: transparent;
    border: none;
    border-bottom: var(--border-width-normal) solid transparent;
    margin-bottom: -2px;
    cursor: pointer;
    color: var(--color-text-muted);
    font-size: var(--font-size-base);
    transition: var(--transition-fast);
    text-decoration: none;
}

.tab-item:hover, .tab-btn:hover, .ui-tabs__item:hover {
    color: var(--color-text);
    background: var(--color-table-hover);
}

.tab-item.active, .tab-btn.active, .ui-tabs__item--active {
    color: var(--color-primary);
    border-bottom-color: var(--color-primary);
    font-weight: var(--font-weight-semibold);
}

.tab-count, .ui-tabs__count {
    display: inline-block;
    margin-left: var(--spacing-sm);
    padding: var(--spacing-xs) var(--spacing-sm);
    font-size: var(--font-size-xs, 11px);
    background: var(--color-background);
    border-radius: var(--border-radius-sm);
    color: var(--color-text-muted);
}

.tab-content {
    display: none;
    padding: var(--spacing-lg) 0;
}

.tab-content.active {
    display: block;
}

/* Канон-панель tabs (js-режим, не-crm). Механика видимости .active — как .tab-content/.crm-tab-content. */
.ui-tabpanel {
    display: none;
}

.ui-tabpanel.active {
    display: block;
}

/* ==================== TOOLTIPS ==================== */

.tooltip-unified, .ui-tooltip-unified {
    position: relative;
    display: inline-block;
}

.tooltip-text {
    visibility: hidden;
    background-color: var(--color-secondary);
    color: var(--button-primary-text);
    text-align: center;
    border-radius: var(--border-radius);
    padding: var(--spacing-sm) var(--spacing-md);
    position: absolute;
    z-index: var(--z-tooltip);
    bottom: 125%;
    left: 50%;
    transform: translateX(-50%);
    white-space: nowrap;
    font-size: var(--font-size-sm);
    opacity: 0;
    transition: opacity var(--transition-fast);
}

.tooltip-text::after {
    content: "";
    position: absolute;
    top: 100%;
    left: 50%;
    margin-left: -5px;
    border-width: 5px;
    border-style: solid;
    border-color: var(--color-secondary) transparent transparent transparent;
}

.tooltip-unified:hover .tooltip-text, .ui-tooltip-unified:hover .tooltip-text {
    visibility: visible;
    opacity: 1;
}

/* ==================== LOADING SPINNER ==================== */

.spinner-unified,
.ui-spinner {
    display: inline-block;
    width: 40px;
    height: 40px;
    border: 4px solid var(--lazy-spinner-base);
    border-top-color: var(--lazy-spinner-active);
    border-radius: 50%;
    animation: spinner-rotate 0.8s linear infinite;
}

.spinner-small {
    width: 20px;
    height: 20px;
    border-width: 2px;
}

.spinner-large {
    width: 60px;
    height: 60px;
    border-width: 6px;
}

@keyframes spinner-rotate {
    to {
        transform: rotate(360deg);
    }
}

.ui-spinner-center {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 120px;
}

/* turbo-frame — кастом-элемент, по умолчанию inline; фреймы и плейсхолдеры
   рассчитаны на блочный поток (единственное bare-правило по кастом-элементу, TD-93). */
turbo-frame { display: block; }

/* ==================== TOAST NOTIFICATIONS ==================== */
/* Временные всплывающие уведомления. JS-утилита: /js/modules/toast.js → window.showToast(msg, type)
   Типы: success, error, warning. Позиция: top-right по умолчанию, bottom-center через --bottom-center. */

.toast-stack {
    position: fixed;
    z-index: var(--z-toast);
    display: flex;
    flex-direction: column;
    gap: var(--spacing-sm);
    pointer-events: none;
    max-width: 380px;
}

.toast-stack--top    { top: 80px; right: 20px; }
.toast-stack--bottom { bottom: 20px; left: 50%; transform: translateX(-50%); }

.toast {
    position: relative;
    padding: var(--spacing-md) var(--spacing-lg);
    border-radius: var(--border-radius);
    color: var(--button-primary-text);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    box-shadow: var(--shadow-lg);
    animation: toastIn 0.3s ease forwards;
    pointer-events: auto;
    max-width: 340px;
}

.toast--success  { background: var(--color-success); }
.toast--error    { background: var(--color-error); }
/* Warning uses dark text for WCAG AA contrast (yellow bg + white text fails). */
.toast--warning  { background: var(--color-warning); color: var(--color-text, #111); }
.toast--info     { background: var(--color-info, var(--color-primary)); }

.toast--bottom-center {
    animation: toastInBottom 0.3s ease forwards;
}

.toast--hide {
    animation: toastOut 0.3s ease forwards;
}

.toast--bottom-center.toast--hide {
    animation: toastOutBottom 0.3s ease forwards;
}

@keyframes toastIn {
    from { transform: translateX(100%); opacity: 0; }
    to   { transform: translateX(0);    opacity: 1; }
}
@keyframes toastOut {
    from { transform: translateX(0);    opacity: 1; }
    to   { transform: translateX(100%); opacity: 0; }
}
@keyframes toastInBottom {
    from { transform: translateX(-50%) translateY(20px); opacity: 0; }
    to   { transform: translateX(-50%) translateY(0);    opacity: 1; }
}
@keyframes toastOutBottom {
    from { transform: translateX(-50%) translateY(0);    opacity: 1; }
    to   { transform: translateX(-50%) translateY(20px); opacity: 0; }
}

/* ==================== PROGRESS BAR ==================== */

.progress-unified, .ui-progress {
    width: 100%;
    height: 8px;
    background: var(--color-background);
    border-radius: var(--border-radius);
    overflow: hidden;
}

.progress-bar {
    height: 100%;
    background: var(--color-primary);
    transition: width var(--transition-default);
}

.progress-bar-success {
    background: var(--color-success);
}

.progress-bar-warning {
    background: var(--color-warning);
}

.progress-bar-danger {
    background: var(--color-error);
}

/* ==================== DIVIDER ==================== */

.divider-unified, .ui-divider {
    height: 1px;
    background: var(--color-border);
    margin: var(--spacing-lg) 0;
}

.divider-unified-text, .ui-divider__text {
    display: flex;
    align-items: center;
    text-align: center;
    margin: var(--spacing-lg) 0;
}

.divider-unified-text::before, .ui-divider__text::before,
.divider-unified-text::after, .ui-divider__text::after {
    content: '';
    flex: 1;
    border-bottom: var(--border-width-thin) solid var(--color-border);
}

.divider-unified-text span, .ui-divider__text span {
    padding: 0 var(--spacing-md);
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

/* ==================== FOOTER ==================== */

.footer-copyright {
    color: var(--color-text-muted);
    opacity: 0.7;
}

/* ==================== BREADCRUMBS ==================== */
/* Централизованный компонент _breadcrumbs.html.twig (обёртка <nav class="app-breadcrumbs">
   в _app_welcome_banner / на страницах). CSS перенесён сюда из app-base.css (TD-61) —
   канон грузится везде, включая external-лейаут; страницам переопределять запрещено. */

.app-breadcrumbs {
    font-size: var(--font-size-lg);
    font-family: 'Inter', sans-serif;
    line-height: 1em;
    color: var(--color-text-secondary);
    margin-bottom: 12px;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0;
}

.app-breadcrumbs a {
    color: inherit;
    text-decoration: none;
}

.app-breadcrumbs a:hover {
    color: var(--color-info);
}

.app-breadcrumbs__divider {
    margin: 0 6px;
    opacity: .5;
}

@media (max-width: 767px) {
    .app-breadcrumbs {
        display: none;
    }
}

/* ==================== UTILITY CLASSES ==================== */

.text-center {
    text-align: center;
}

.text-right {
    text-align: right;
}

.text-left {
    text-align: left;
}

.text-muted {
    color: var(--color-text-muted);
}

.text-primary {
    color: var(--color-primary);
}

.text-success {
    color: var(--color-success);
}

.text-warning {
    color: var(--color-warning);
}

.text-danger {
    color: var(--color-error);
}

.bg-primary {
    background-color: var(--color-primary);
}

.bg-success {
    background-color: var(--color-success);
}

.bg-warning {
    background-color: var(--color-warning);
}

.bg-danger {
    background-color: var(--color-error);
}

/* .lqip-bg — статическая часть LQIP-плейсхолдера (preview/_picture.html.twig).
   Динамический background-image ставит common--lqip-background контроллер
   (TD-51 style-src nonce migration — без inline style="..."). */
.lqip-bg {
    background-size: cover;
    background-repeat: no-repeat;
}

/* .d-none — утилита скрытия (член display-семейства d-flex/d-block/.../d-none).
   !important бьёт component-display (напр. .ui-alert{display:flex}) и заменяет собой
   удалённые многоклассовые правила .X.hidden (.messages-badge/.s3-modal-overlay/.add-user-step) —
   их специфичность была без !important, поэтому глобальный .d-none перекрывает с запасом. TD-67. */
.d-none {
    display: none !important;
}

.d-block {
    display: block;
}

.d-inline {
    display: inline;
}

.d-inline-block {
    display: inline-block;
}

/* Button width utilities */
.btn-auto {
    display: inline-flex !important;
    width: auto !important;
}

/* display: flex (не block, TASK-552 фикс) — .btn задаёт inline-flex + align-items/
   justify-content:center для вертикального/горизонтального центрирования содержимого
   (components.css:15-18). block !important убивал flex-контекст целиком: без него
   align-items/justify-content не действуют, и текст кнопки высотой
   --button-min-height (72px) прижимался к верхнему краю вместо центра. flex
   сохраняет и растягивание на 100% ширины (width:100% ниже), и центрирование —
   единственное отличие от .btn в этом модификаторе теперь именно ширина. */
.btn-block {
    display: flex !important;
    width: 100% !important;
}

.d-flex {
    display: flex;
}

.d-inline-flex {
    display: inline-flex;
}

/* Sizing utilities — статические 100%-размеры (TD-51 W0).
   Только для КОНСТАНТНОГО 100%; динамические проценты (progress-bars и т.п.)
   ставятся через Stimulus (element.style.setProperty), не через utility. */
.w-100 {
    width: 100%;
}

.h-100 {
    height: 100%;
}

.mw-100 {
    max-width: 100%;
}

/* Interaction utility (TD-51 W0) */
.pointer-events-none {
    pointer-events: none;
}

/* Interaction / layout utilities (TD-51 W2 — частые inline-паттерны) */
.cursor-pointer {
    cursor: pointer;
}

.flex-fill {
    flex: 1;
}

.ms-auto {
    margin-left: auto;
}

.flex-wrap {
    flex-wrap: wrap;
}

/* Icon scale utility (TD-51 W6b — inline-block + scale() for oversized icon emphasis) */
.icon-scale-2x {
    display: inline-block;
    transform: scale(2);
}

.icon-scale-2-5x {
    display: inline-block;
    transform: scale(2.5);
}

.flex-column {
    flex-direction: column;
}

.justify-center {
    justify-content: center;
}

.justify-between {
    justify-content: space-between;
}

.align-center {
    align-items: center;
}

.align-end {
    align-items: flex-end;
}

.gap-xs {
    gap: var(--spacing-xs);
}

.gap-sm {
    gap: var(--spacing-sm);
}

.gap-md {
    gap: var(--spacing-md);
}

.gap-lg {
    gap: var(--spacing-lg);
}

.mt-sm { margin-top: var(--spacing-sm); }
.mt-md { margin-top: var(--spacing-md); }
.mt-lg { margin-top: var(--spacing-lg); }

.mb-sm { margin-bottom: var(--spacing-sm); }
.mb-md { margin-bottom: var(--spacing-md); }
.mb-lg { margin-bottom: var(--spacing-lg); }

.pt-sm { padding-top: var(--spacing-sm); }
.pt-md { padding-top: var(--spacing-md); }
.pt-lg { padding-top: var(--spacing-lg); }

.pb-sm { padding-bottom: var(--spacing-sm); }
.pb-md { padding-bottom: var(--spacing-md); }
.pb-lg { padding-bottom: var(--spacing-lg); }

/* ==================== CURRENCY DISPLAY ==================== */

/**
 * Secondary currency display (shown in brackets after main amount)
 * Usage: <span class="currency-secondary">(40 084.43 ₴)</span>
 */
.currency-secondary {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    opacity: 0.8;
    font-weight: var(--font-weight-normal);
}

/**
 * Secondary currency on dark backgrounds (e.g., total cards)
 * Automatically applies white color with opacity
 */
.total-card .currency-secondary,
.card-dark .currency-secondary {
    color: var(--color-white);
    opacity: 0.85;
    font-size: var(--font-size-sm);
}

/* ==================== AMOUNT CARDS (Централизованная карточка суммы) ==================== */

.amount-card-unified, .ui-amount-card {
    display: flex;
    flex-direction: column;
    padding: var(--spacing-lg) var(--spacing-xl);
    border-radius: var(--border-radius-lg);
    box-shadow: var(--shadow-card);
    min-width: 250px;
}

.amount-card-primary, .ui-amount-card--primary {
    background: linear-gradient(135deg, var(--color-gradient-start) 0%, var(--color-gradient-end) 100%);
    color: var(--color-white);
    box-shadow: var(--shadow-primary);
}

.amount-card-secondary, .ui-amount-card--secondary {
    background: var(--color-card-background);
    color: var(--color-text);
    border: var(--border-width-thin) solid var(--color-border);
}

.ui-amount-card--success {
    background: linear-gradient(135deg, var(--color-success) 0%, var(--color-success) 100%);
    color: var(--color-white, #fff);
    box-shadow: var(--shadow-success);
}

.ui-amount-card--danger {
    background: linear-gradient(135deg, var(--color-danger) 0%, var(--color-error) 100%);
    color: var(--color-white, #fff);
    box-shadow: var(--shadow-error);
}

.ui-amount-card--warning {
    background: linear-gradient(135deg, var(--color-warning) 0%, var(--color-warning) 100%);
    color: var(--color-white, #fff);
    box-shadow: var(--shadow-warning);
}

.amount-card-label, .ui-amount-card__label {
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-medium);
    opacity: 0.95;
}

.amount-card-value, .ui-amount-card__value {
    font-size: var(--font-size-xl);
    font-weight: var(--font-weight-bold);
}

.ui-amount-card__currency {
    opacity: 0.85;
    font-size: var(--font-size-sm);
}

/* TASK-000535: опциональные иконка-чип и сноска карточки (появляются только
   когда call-site передал icon/caption — см. _amount_card.html.twig). */
.ui-amount-card__head {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
}

.ui-amount-card__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--border-radius-md);
    background: var(--color-white-alpha-20);
    flex-shrink: 0;
}

.ui-amount-card__caption {
    margin-top: var(--spacing-xs);
    font-size: var(--font-size-sm);
    opacity: 0.9;
}

.ui-amount-card__caption--up {
    color: var(--color-success-strong);
}

.ui-amount-card__caption--down {
    color: var(--color-warning-strong);
}

/* На градиентных вариантах собственный цвет сноски потерялся бы на фоне —
   наследуем цвет карточки и играем только прозрачностью. */
.ui-amount-card--primary .ui-amount-card__caption,
.ui-amount-card--success .ui-amount-card__caption,
.ui-amount-card--danger .ui-amount-card__caption,
.ui-amount-card--warning .ui-amount-card__caption {
    color: inherit;
    opacity: 0.85;
}

/* Вторичная валюта внутри amount-card */
.amount-card-primary .currency-secondary,
.ui-amount-card--primary .currency-secondary,
.ui-amount-card--primary .ui-amount-card__currency {
    color: var(--color-white);
    opacity: 0.85;
    font-size: var(--font-size-sm);
}

.amount-card-secondary .currency-secondary,
.ui-amount-card--secondary .currency-secondary,
.ui-amount-card--secondary .ui-amount-card__currency {
    color: var(--color-text-muted);
    opacity: 0.85;
    font-size: var(--font-size-sm);
}

/* ==================== EXPORT BUTTONS ==================== */

.export-buttons-wrapper {
    text-align: right;
    margin: var(--spacing-md) 0;
}

/* ===== Filter Row ===== */
.filter-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacing-md);
    align-items: flex-end;
}

.filter-group {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-xs);
    min-width: 130px;
}

.filter-group label {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted, #6c757d);
    font-weight: var(--font-weight-medium);
}

.filter-actions {
    display: flex;
    gap: var(--spacing-sm);
    align-items: flex-end;
}

/* Строка поиска над полями фильтра (TASK-000589, actionsLayout независим —
   включается наличием searchName). Иконка внутри рамки, инпут без своей рамки.

   ⚠️ Имя `filter-row__search`, а НЕ `filter-search`: последнее уже занято как
   page-класс ячейки фильтра (user/custom_contragents, user|admin/service_providers)
   — глобальное правило с таким именем перекрасило бы чужие страницы. */
.filter-row__search {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    margin-bottom: var(--spacing-md);
    padding: var(--spacing-sm) var(--spacing-md);
    border: 1px solid var(--color-input-border);
    border-radius: var(--border-radius-md);
    background: var(--color-input-bg);
    color: var(--color-text-muted);
}

/* Инпут снимает собственную рамку — её несёт обёртка; поэтому фокус-индикация
   тоже уходит на обёртку (иначе фокус не виден вообще — WCAG 2.4.7).
   Тот же приём, что у `.ui-input-group:focus-within` выше. */
.filter-row__search:focus-within {
    border-color: var(--color-input-focus);
    box-shadow: var(--shadow-input-focus);
}

.filter-row__search input {
    flex: 1;
    border: none;
    background: transparent;
    padding: 0;
    box-shadow: none;
}

/* Split-раскладка: отдельная строка под полями — reset слева, submit справа. */
.filter-actions--split {
    justify-content: space-between;
    align-items: center;
    margin-top: var(--spacing-md);
}

/* margin-left:auto, а не только space-between: без resetUrl (валидная конфигурация)
   `.filter-actions__end` остаётся единственным ребёнком и уехал бы к flex-start. */
.filter-actions__end {
    display: flex;
    gap: var(--spacing-sm);
    align-items: center;
    margin-left: auto;
}

.filter-reset {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-link);
    text-decoration: none;
}

/* «Більше фільтрів» — toggle (TASK-000589 A2): reveals c.moreLabelKey-guarded
   fields_more. Полноценная кнопка (Figma 878:12481 «Договори», 876:10585 «Типи
   витрат») — білий фон/сіра рамка/темний текст, врівень по висоті з «Застосувати»
   поруч. Геометрія скопійована з .btn-apply (~L2836: компактна фільтр-кнопка,
   auto-геометрія замість базового .btn 72px), кольори — з існуючої групи
   --button-secondary-* (та сама, що й у .btn-secondary), нових токенів не заведено.
   Раніше був link-style (колір --color-link, без chrome) — розходився з макетом.
   Chevron, що обертається на .is-expanded — той самий приём, що .ui-row-expander
   (TD-64). Toggled by components--filter-row Stimulus controller
   (assets/controllers/components/filter-row_controller.js). */
.filter-more-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-sm);
    padding: var(--spacing-sm) var(--spacing-md);
    background: var(--button-secondary-background, #fff);
    color: var(--button-secondary-text, #333);
    border: var(--border-width-thin) solid var(--button-secondary-border, #ddd);
    border-radius: var(--border-radius);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    transition: var(--transition-fast);
}

.filter-more-toggle:hover {
    background: var(--button-secondary-hover, #f8f9fa);
    color: var(--button-secondary-hover-text, #333);
    border-color: var(--button-secondary-hover-border, #ccc);
}

.filter-more-toggle svg {
    transition: transform var(--transition-duration-default);
}

.filter-more-toggle.is-expanded svg {
    transform: rotate(180deg);
}

/* Раскрываемая строка доп. полей: тот же `.filter-row` (display:flex), только
   с отступом сверху от видимых полей. Скрытие/показ — `.d-none` (TD-67), а НЕ
   `[hidden]`: авторский `.filter-row{display:flex}` всегда бьёт UA-правило
   `[hidden]{display:none}` независимо от специфичности/порядка (решает только
   происхождение каскада — author побеждает user-agent), поэтому `!important`
   в `.d-none` — единственный надёжный способ спрятать этот блок. */
.filter-row--more {
    margin-top: var(--spacing-md);
}

@media (max-width: 767px) {
    .filter-row {
        flex-direction: column;
    }
    .filter-group {
        width: 100%;
    }
    /* TASK-541: порядок по мобильному макету (Figma 979-10699, 875-8040) —
       reset-ссылка НАД кнопкой, submit тянется на всю ширину. */
    .filter-actions--split {
        flex-direction: column;
        align-items: stretch;
        gap: var(--spacing-sm);
    }
    .filter-actions__end {
        flex-direction: column;
        align-items: center;
        margin-left: 0;
    }
    .filter-reset {
        justify-content: center;
    }
    .filter-more-toggle {
        justify-content: center;
    }
}

/* ═══ Date Range (TASK-000589, Figma 875-8040) — единое поле діапазону дат ═══
   Два input[type=date] + разделитель «—» + иконка календаря в общей рамке.
   Тот же приём, что у .filter-row__search: рамку несёт обёртка, фокус —
   через :focus-within (инпуты снимают собственную рамку/фокус-обводку). */
.ui-date-range {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-sm);
    padding: var(--spacing-sm) var(--spacing-md);
    border: var(--border-width-thin) solid var(--color-input-border);
    border-radius: var(--border-radius-md);
    background: var(--color-input-bg);
    color: var(--color-text-muted);
}

.ui-date-range:focus-within {
    border-color: var(--color-input-focus);
    box-shadow: var(--shadow-input-focus);
}

.ui-date-range__input {
    border: none;
    background: transparent;
    padding: 0;
    box-shadow: none;
    color: inherit;
    font-size: var(--font-size-sm);
}

.ui-date-range__sep {
    color: var(--color-text-muted);
}

.ui-date-range__icon {
    display: inline-flex;
    align-items: center;
    color: var(--color-text-muted);
}

/* ===== Table Responsive ===== */
.table-responsive {
    overflow-x: auto;
    overflow-y: visible;
    -webkit-overflow-scrolling: touch;
}

/* ===== UI Icons ===== */
.ui-icon { flex-shrink: 0; vertical-align: middle; }

/* ===== Document thumbnails (components/_document_thumb.html.twig) =====
   Миниатюра изображения в списках документов вместо generic-иконки.
   Размеры совпадают с иконочным вариантом того же size, чтобы строка списка
   не «прыгала» между картинкой и иконкой; cover — потому что пресет doc-thumb
   квадратный (96×96), а исходники произвольных пропорций. */
.doc-thumb {
    flex-shrink: 0;
    object-fit: cover;
    border-radius: var(--border-radius-sm);
    background: var(--color-background);
}
.doc-thumb--sm { width: 24px; height: 24px; }
.doc-thumb--md { width: 32px; height: 32px; }
.doc-thumb--xl { width: 44px; height: 44px; }
.ui-icon--missing { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; background: var(--color-warning-bg); color: var(--color-warning); border-radius: 50%; font-size: var(--font-size-xs); font-weight: var(--font-weight-bold); }
/* Цвет задаём через color → currentColor: работает и для line (stroke), и для solid (fill). */
.ui-icon--primary { color: var(--color-primary); }
.ui-icon--success { color: var(--color-success); }
.ui-icon--warning { color: var(--color-warning); }
.ui-icon--danger { color: var(--color-danger); }
.ui-icon--error { color: var(--color-error); }
.ui-icon--info { color: var(--color-info); }
.ui-icon--muted { color: var(--color-text-muted); }
.ui-icon--white { color: var(--color-white, #fff); }
.ui-icon--secondary { color: var(--color-secondary); }
.ui-icon--blue { color: var(--color-info, #4F7CF7); }

/* ===== UI Pictograms ===== */
.ui-pictogram { display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; flex-shrink: 0; }
.ui-pictogram--emoji { font-style: normal; line-height: 1; }
.ui-pictogram--image { width: 1.2em; height: 1.2em; object-fit: contain; }
.ui-pictogram--sm { font-size: 0.85em; }
.ui-pictogram--lg { font-size: 1.3em; }
.ui-pictogram--xl { font-size: 1.8em; }

/* ===== UI Confirmation Status (inline) ===== */
.ui-confirmation-status { display: inline-flex; align-items: center; gap: var(--spacing-xs); font-size: var(--font-size-xs); }
.ui-conf-party { display: inline-flex; align-items: center; gap: var(--spacing-xs); }
.ui-conf-label { color: var(--color-text-muted); font-weight: var(--font-weight-medium); }
.ui-conf-sep { color: var(--color-text-muted); opacity: 0.4; margin: 0 2px; }
.ui-conf-icon { font-weight: var(--font-weight-bold); font-size: var(--font-size-sm); }
.ui-conf-icon--confirmed { color: var(--color-success); }
.ui-conf-icon--rejected { color: var(--color-danger); }
.ui-conf-icon--unknown { color: var(--color-warning); font-size: var(--font-size-xs); }

/* ===== UI Confirmation Block (full) ===== */
.ui-confirmation-block { background: var(--color-background); padding: var(--spacing-md) var(--spacing-md); border-radius: var(--border-radius-sm); border: var(--border-width-thin) solid var(--color-border); display: flex; flex-direction: column; gap: var(--spacing-sm); }
.ui-confirmation-block--locked { opacity: 0.85; }
.ui-conf-my { display: flex; align-items: center; justify-content: space-between; }
.ui-conf-my-label { font-size: var(--font-size-xs); font-weight: var(--font-weight-semibold); }
.ui-conf-my-label--confirmed { color: var(--color-success); }
.ui-conf-my--locked { opacity: 0.9; }
.ui-conf-toggle-form { display: inline-flex; }
.ui-conf-other { display: flex; align-items: center; gap: var(--spacing-sm); font-size: var(--font-size-xs); color: var(--color-text-muted); }

/* ===== UI Empty State ===== */
.empty-state { text-align: center; padding: var(--spacing-2xl) var(--spacing-lg); }
/* Компактный вид (TD-86): без card-обёртки и дефолтной иконки — для узких блоков.
   Селектор из двух классов, чтобы перебивать padding .empty-state независимо от порядка правил. */
.empty-state.ui-empty--inline { padding: var(--spacing-md) var(--spacing-sm); }
.empty-icon { font-size: var(--font-size-8xl); margin-bottom: var(--spacing-md); opacity: 0.5; }
.empty-message { color: var(--color-text-muted); margin-bottom: var(--spacing-md); font-size: var(--font-size-sm); }
.empty-state-title { font-size: var(--font-size-base); font-weight: var(--font-weight-semibold); color: var(--color-secondary); margin: 0 0 var(--spacing-sm); }
.empty-hint { font-size: var(--font-size-xs); color: var(--color-text-muted); opacity: 0.8; margin-bottom: var(--spacing-md); }

/* ===== Warning Boxes ===== */
.warning-box, .ui-warning-box { padding: var(--spacing-md) var(--spacing-md); border-radius: var(--border-radius, 8px); border: var(--border-width-thin) solid var(--color-warning); background: var(--color-warning-bg); color: var(--color-warning); font-size: var(--font-size-sm); margin-bottom: var(--spacing-md); }
.warning-box.warning-error, .ui-warning-box--error { border-color: var(--color-error); background: var(--color-error-bg); color: var(--color-error); }
.warning-box.warning-info, .ui-warning-box--info { border-color: var(--color-info); background: var(--color-info-bg); color: var(--color-info); }
.ui-warning-box--warning { border-color: var(--color-warning); background: var(--color-warning-bg); color: var(--color-warning); }
.warning-content { display: flex; align-items: flex-start; gap: var(--spacing-sm); }
.ui-warning-box__heading { display: flex; align-items: center; gap: var(--spacing-sm); }
.warning-icon, .ui-warning-box__icon { font-size: var(--font-size-lg); flex-shrink: 0; }
.warning-text, .ui-warning-box__text { flex: 1; }
.ui-warning-box__reasons { margin: var(--spacing-sm) 0 0; padding-left: var(--spacing-lg); }

/* ===== Form Extensions (.form-select/.form-textarea/.form-check мигрированы на канон
   .form-select-unified/.form-textarea-unified/.form-check-unified выше — TD-85 Волна 2) ===== */
.form-hint { font-size: var(--font-size-xs); color: var(--color-text-muted); margin-top: var(--spacing-xs); }
.form-error-text { font-size: var(--font-size-xs); color: var(--color-error); margin-top: var(--spacing-xs); }

/* ── Layout Components ── */
.ui-page-layout { display: flex; width: 100%; margin: 0 auto; }
.ui-page-layout--with-sidebar { gap: var(--spacing-xl, 24px); }
.ui-page-layout__sidebar { flex: 0 0 260px; }
.ui-page-layout__content { flex: 1; min-width: 0; }
@media (max-width: 767px) {
    .ui-page-layout--with-sidebar { flex-direction: column; }
    .ui-page-layout__sidebar { flex: none; width: 100%; }
}

.ui-section { margin-bottom: var(--spacing-lg, 24px); }
.ui-section--sm { margin-bottom: var(--spacing-sm, 8px); }
.ui-section--md { margin-bottom: var(--spacing-md, 16px); }
.ui-section--lg { margin-bottom: var(--spacing-lg, 24px); }
.ui-section--xl { margin-bottom: var(--spacing-xl, 32px); }
.ui-section__title { font-size: var(--font-size-lg, 18px); font-weight: var(--font-weight-bold, 700); margin-bottom: var(--spacing-md, 16px); color: var(--color-text); }

/* ── Tooltip ── */
.ui-tooltip { position: relative; cursor: help; }
.ui-tooltip::after { content: attr(data-tooltip); position: absolute; z-index: var(--z-tooltip); padding: 6px 10px; border-radius: var(--border-radius-small, 4px); background: var(--color-text-primary, #333); color: var(--color-white, #fff); font-size: var(--font-size-xs); white-space: nowrap; pointer-events: none; opacity: 0; transition: opacity var(--transition-duration-fast); }
.ui-tooltip:hover::after { opacity: 1; }
.ui-tooltip[data-tooltip-position="top"]::after { bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: var(--spacing-sm); }
.ui-tooltip[data-tooltip-position="bottom"]::after { top: 100%; left: 50%; transform: translateX(-50%); margin-top: var(--spacing-sm); }
.ui-tooltip[data-tooltip-position="left"]::after { right: 100%; top: 50%; transform: translateY(-50%); margin-right: var(--spacing-sm); }
.ui-tooltip[data-tooltip-position="right"]::after { left: 100%; top: 50%; transform: translateY(-50%); margin-left: var(--spacing-sm); }
.ui-tooltip--multiline::after { white-space: pre-line; max-width: 220px; width: max-content; text-align: left; line-height: var(--line-height-normal); }

/* ══════ Missing ui-* BEM aliases ══════ */

/* ui-button aliases for btn-action */
.ui-button { display: inline-flex; align-items: center; justify-content: center; padding: var(--spacing-sm) var(--spacing-lg); border: none; border-radius: var(--border-radius); font-size: var(--font-size-base); font-weight: var(--font-weight-semibold); cursor: pointer; transition: var(--transition-fast); text-decoration: none; gap: var(--spacing-sm); }
.ui-button--primary { background-color: var(--button-primary-background); color: var(--button-primary-text); border: var(--border-width-thin) solid var(--button-primary-border, transparent); box-shadow: var(--button-primary-shadow, none); }
.ui-button--primary:hover { background-color: var(--button-primary-hover); color: var(--button-primary-hover-text, var(--button-primary-text)); }
.ui-button--success { background-color: var(--button-success-background); color: var(--button-success-text); border: var(--border-width-thin) solid var(--button-success-border, transparent); box-shadow: var(--button-success-shadow, none); }
.ui-button--success:hover { background-color: var(--button-success-hover); color: var(--button-success-hover-text, var(--button-success-text)); }
.ui-button--danger { background-color: var(--button-danger-background); color: var(--button-danger-text); border: var(--border-width-thin) solid var(--button-danger-border, transparent); box-shadow: var(--button-danger-shadow, none); }
.ui-button--danger:hover { background-color: var(--button-danger-hover); color: var(--button-danger-hover-text, var(--button-danger-text)); }
.ui-button--warning { background-color: var(--button-warning-background); color: var(--button-warning-text); border: var(--border-width-thin) solid var(--button-warning-border, transparent); box-shadow: var(--button-warning-shadow, none); }
.ui-button--warning:hover { background-color: var(--button-warning-hover); color: var(--button-warning-hover-text, var(--button-warning-text)); }
.ui-button--secondary { background-color: var(--button-secondary-background); color: var(--button-secondary-text); border: var(--border-width-thin) solid var(--button-secondary-border, transparent); box-shadow: var(--button-secondary-shadow, none); }
.ui-button--secondary:hover { background-color: var(--button-secondary-hover); color: var(--button-secondary-hover-text, var(--button-secondary-text)); }
.ui-button--outline { background: transparent; color: var(--btn-outline-text, var(--color-primary)); border: var(--border-width-thin) solid var(--btn-outline-border, var(--color-primary)); }
.ui-button--outline:hover { background: var(--btn-outline-hover-bg, var(--color-primary)); color: var(--color-white, #fff); }
.ui-button--sm { padding: var(--spacing-sm) var(--spacing-md); font-size: var(--font-size-xs); }
.ui-button--lg { padding: 12px 28px; font-size: var(--font-size-base); }
.ui-button--disabled, .ui-button:disabled { opacity: 0.5; pointer-events: none; }

/* ui-empty alias for empty-state */
.ui-empty { text-align: center; padding: var(--spacing-2xl) var(--spacing-lg); }
.ui-empty__icon { font-size: var(--font-size-8xl); margin-bottom: var(--spacing-md); opacity: 0.5; }
.ui-empty__message { color: var(--color-text-muted); margin-bottom: var(--spacing-md); font-size: var(--font-size-sm); }
.ui-empty__title { font-size: var(--font-size-base); font-weight: var(--font-weight-semibold); color: var(--color-secondary); margin: 0 0 var(--spacing-sm); }
.ui-empty__hint { font-size: var(--font-size-xs); color: var(--color-text-muted); opacity: 0.8; margin-bottom: var(--spacing-md); }

/* ui-modal alias for modal-overlay/modal-content */
.ui-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: var(--color-overlay-50); display: flex; align-items: center; justify-content: center; z-index: var(--z-modal); }
.ui-modal__content { background: var(--modal-content-background); border-radius: var(--border-radius-large); box-shadow: var(--shadow-modal); max-width: 600px; width: 90%; max-height: 90vh; overflow-y: auto; }
.ui-modal__header { padding: var(--spacing-lg); background: var(--modal-header-background); border-bottom: var(--border-width-thin) solid var(--modal-header-border); border-radius: var(--border-radius-large) var(--border-radius-large) 0 0; display: flex; justify-content: space-between; align-items: center; }
.ui-modal__body { padding: var(--spacing-lg); }
.ui-modal__footer { padding: var(--spacing-md) var(--spacing-lg); border-top: var(--border-width-thin) solid var(--color-border); display: flex; align-items: center; justify-content: flex-end; gap: var(--spacing-sm); }
.ui-modal--sm .ui-modal__content { max-width: 400px; }
.ui-modal--lg .ui-modal__content { max-width: 800px; }

/* ui-modal__header-icon — круглая иконка-бейдж слева от заголовка (TASK-603, напр. "недостаточно средств") */
/* flex:1 на заголовке — ТОЛЬКО когда в шапке есть иконка-бейдж (TASK-603).
   Глобально на .modal-title его ставить нельзя: 5 модалок используют слот modal_header
   (admin/users, billing/history, crm/tasks, currency_rates, _financial_blocks) — там
   растянутый заголовок отрывает доп. контролы шапки от title и прижимает их к крестику. */
.ui-modal__header:has(.ui-modal__header-icon) .ui-modal__title { flex: 1; }
.ui-modal__header-icon { display: flex; align-items: center; justify-content: center; flex-shrink: 0; width: var(--button-icon-size-xl); height: var(--button-icon-size-xl); border-radius: 50%; background: var(--color-warning-bg); color: var(--color-warning); margin-right: var(--spacing-md); }

/* ui-print-popup — попап печати документа */
.ui-print-popup { max-width: 480px; }

/* ui-toggle alias for toggle-switch */
.ui-toggle { position: relative; display: inline-block; width: 44px; height: 24px; }
.ui-toggle__input { opacity: 0; width: 0; height: 0; }
.ui-toggle__slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: var(--color-border); border-radius: 24px; transition: var(--transition-fast); }
.ui-toggle__slider::before { content: ''; position: absolute; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: var(--color-white, #fff); border-radius: 50%; transition: var(--transition-fast); }
.ui-toggle__input:checked + .ui-toggle__slider { background-color: var(--color-success); }
.ui-toggle__input:checked + .ui-toggle__slider::before { transform: translateX(20px); }
.ui-toggle__input:disabled + .ui-toggle__slider { opacity: .5; }
.ui-toggle--info .ui-toggle__input:checked + .ui-toggle__slider { background-color: var(--color-info); }
.ui-toggle--danger .ui-toggle__input:checked + .ui-toggle__slider { background-color: var(--color-error); }

/* ui-badge-zone — tariff zone badges */
.zone-badge, .ui-badge-zone { display: inline-block; padding: var(--spacing-xs) var(--spacing-sm); border-radius: var(--border-radius-sm); font-size: var(--font-size-xs, 11px); font-weight: var(--font-weight-semibold); text-transform: uppercase; }
.zone-total, .ui-badge-zone--total { background: var(--color-info-bg); color: var(--color-info); }
.zone-day, .ui-badge-zone--day { background: var(--color-warning-bg); color: var(--color-warning); }
.zone-night, .ui-badge-zone--night { background: var(--color-background); color: var(--color-info); }
.zone-peak, .ui-badge-zone--peak { background: var(--color-error-bg); color: var(--color-error); }
.zone-half_peak, .ui-badge-zone--half_peak { background: var(--color-warning-bg); color: var(--color-warning); }
.zone-badge.single, .ui-badge-zone--single { background: var(--color-info-bg); color: var(--color-info); }
.zone-badge.two, .ui-badge-zone--two { background: var(--color-warning-bg); color: var(--color-warning-dark, var(--color-warning)); }
.zone-badge.three, .ui-badge-zone--three { background: var(--color-success-bg); color: var(--color-success); }

/* ui-confirmation shorthand alias */
.ui-confirmation { background: var(--color-background); padding: var(--spacing-md) var(--spacing-md); border-radius: var(--border-radius-sm); border: var(--border-width-thin) solid var(--color-border); display: flex; flex-direction: column; gap: var(--spacing-sm); }

/* ui-logo */
.ui-logo { display: inline-block; vertical-align: middle; }
.ui-logo--icon { height: 32px; }
.ui-logo--full { height: 32px; }

/* Page subtitle */
.page-subtitle { margin: 2px 0 0; font-size: var(--font-size-xs); color: var(--color-text-muted); }

/* Detail item label/value pairs */
.detail-label { font-size: var(--font-size-xs); color: var(--color-text-muted); font-weight: var(--font-weight-medium); }
.detail-value { font-size: var(--font-size-sm); color: var(--color-text); }
.detail-value--bold { font-weight: var(--font-weight-semibold); }

/* Confirmation panel (extends ui-confirmation) */
.confirm-row { display: flex; align-items: center; justify-content: space-between; }
.confirm-label { font-size: var(--font-size-xs); font-weight: var(--font-weight-semibold); }
.confirm-label--success { color: var(--color-success); }
.confirm-label--secondary { color: var(--color-secondary); }
.confirm-status { display: flex; align-items: center; gap: var(--spacing-sm); font-size: var(--font-size-xs); }
.confirm-status--success { color: var(--color-success); }
.confirm-status--error { color: var(--color-error); }
.confirm-status--muted { color: var(--color-text-muted); }

/* Info header color variants */
.info-header--error { background: var(--color-error); }
.info-header--warning { background: var(--color-warning); }
.info-header--info { background: var(--color-info); }
.info-header--success { background: var(--color-success); }

/* Stat icon gradient variants */
.stat-icon--hero { background: linear-gradient(135deg, var(--color-hero-gradient-start) 0%, var(--color-hero-gradient-end) 100%); }
.stat-icon--warning-error { background: linear-gradient(135deg, var(--color-warning) 0%, var(--color-error) 100%); }
.stat-icon--info { background: linear-gradient(135deg, var(--color-info) 0%, var(--color-info) 100%); }
.stat-icon--success-info { background: linear-gradient(135deg, var(--color-success) 0%, var(--color-info) 100%); }
.stat-icon--error-warning { background: linear-gradient(135deg, var(--color-error) 0%, var(--color-warning) 100%); }
.stat-icon--neutral { background: linear-gradient(135deg, var(--color-info-bg) 0%, var(--color-background) 100%); }

/* Round action icon button */
.action-icon-round { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 50%; border: none; cursor: pointer; text-decoration: none; }
.action-icon-round--success { background: var(--color-success-bg); color: var(--color-success); }
.action-icon-round--info { background: var(--color-info-bg); color: var(--color-primary); }

/* Text hint/secondary utilities */
.text-hint { font-size: var(--font-size-xs); color: var(--color-text-muted); }
.text-hint-sm { font-size: var(--font-size-xxs); color: var(--color-text-muted); }
.text-hint-xs { font-size: var(--font-size-xs); color: var(--color-text-muted); }
.text-secondary-sm { font-size: var(--font-size-xs); color: var(--color-text-secondary); }

/* Empty state centered (for inline empty blocks) */
.empty-block-centered { padding: 40px; text-align: center; color: var(--color-text-muted); }

/* List item icon (proposals, communications) */
.list-item-icon { width: 48px; height: 48px; border-radius: var(--border-radius-sm); background: var(--color-background); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }

/* Desc tabs (locale switcher in property show) */
.desc-tabs { display: flex; gap: var(--spacing-xs); margin-bottom: var(--spacing-md); }
.desc-tab { padding: var(--spacing-xs) var(--spacing-sm); font-size: var(--font-size-xs); }
.desc-tab.active { background: var(--color-primary); color: var(--color-white, #fff); border-color: var(--color-primary); }

/* Link utilities */
.link-primary { color: var(--color-primary); text-decoration: none; }
.link-primary:hover { text-decoration: underline; }

/* Form validation states (used by expense-provider-selector.js and similar) */
.input-invalid { border-color: var(--color-error) !important; border-width: 2px !important; }
.input-valid { border-color: var(--color-success) !important; border-width: 2px !important; }

/* User/inviter autocomplete (components--user-autocomplete) — global styles.
   Перенесено из templates/admin/_autocomplete_styles.html.twig для глобальной доступности
   (брендбук, попап контактов, админ-фильтр инвайтеров). */
.autocomplete-group { min-width: 280px; }
.autocomplete-wrapper { position: relative; }
.autocomplete-wrapper .form-control-unified { padding-right: 30px; }
.autocomplete-clear {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    font-size: var(--font-size-lg);
    color: var(--color-disabled);
    cursor: pointer;
    padding: 0 var(--spacing-xs);
    line-height: 1;
}
.autocomplete-clear:hover { color: var(--color-text); }
.autocomplete-dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--color-card-background);
    border: var(--border-width-thin) solid var(--color-border);
    border-top: none;
    border-radius: 0 0 6px 6px;
    max-height: 300px;
    overflow-y: auto;
    z-index: var(--z-dropdown);
    display: none;
    box-shadow: var(--shadow-dropdown, 0 4px 12px rgba(0,0,0,0.15));
}
.autocomplete-dropdown.show { display: block; }
.autocomplete-item {
    padding: var(--spacing-sm) var(--spacing-md);
    cursor: pointer;
    border-bottom: var(--border-width-thin) solid var(--color-background);
}
.autocomplete-item:last-child { border-bottom: none; }
.autocomplete-item:hover { background: var(--color-background); }
.autocomplete-item .item-name {
    font-weight: var(--font-weight-medium);
    color: var(--color-secondary);
}
.autocomplete-item .item-details {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    margin-top: var(--spacing-xs);
}
.autocomplete-empty {
    padding: var(--spacing-md);
    color: var(--color-text-muted);
    font-style: italic;
    text-align: center;
}
.autocomplete-loading {
    padding: var(--spacing-md);
    text-align: center;
    color: var(--color-text-muted);
}

/* Autocomplete dropdown item modifiers (used by expense-provider-selector.js and user autocomplete) */
.autocomplete-item--empty { color: var(--color-text-muted); }
.autocomplete-item--invite { background-color: var(--color-success-bg); font-weight: bold; }
.autocomplete-item--contragent { background-color: var(--color-info-bg); font-weight: bold; }

/* TASK-434: незарегистрированный контакт — недоступен для выбора */
.autocomplete-item--disabled {
    cursor: default;
    opacity: 0.6;
}
.autocomplete-item--disabled:hover { background: transparent; }
.autocomplete-item--disabled .item-name { color: var(--color-text-muted); }
.autocomplete-item-badge {
    display: inline-block;
    margin-top: var(--spacing-xs);
    padding: 1px 8px;
    font-size: var(--font-size-xxs);
    border-radius: var(--border-radius-sm);
    color: var(--color-text-muted);
    background: var(--color-background);
}

/* Currency equivalent preview (used by receipt-form.js) */
.equivalent-amount-positive { color: var(--color-success); font-weight: var(--font-weight-semibold); }

/* ==================== INTEGRATION BUTTONS ==================== */
/* Integration buttons — Telegram, Google, Swagger etc.
   Previously duplicated via inline <style> across login/register/share pages. */

.btn-google,
.ui-button--integration-google {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-sm);
    width: 100%;
    padding: var(--spacing-sm);
    font-weight: var(--font-weight-medium);
    background: var(--button-google-background, var(--color-card-background));
    color: var(--button-google-text, var(--color-text));
    border: var(--border-width-thin) solid var(--button-google-border, var(--color-border));
    box-shadow: var(--button-google-shadow, none);
    border-radius: var(--border-radius);
    cursor: pointer;
    transition: var(--transition-fast);
    text-decoration: none;
}
.btn-google:hover,
.ui-button--integration-google:hover {
    background: var(--button-google-hover, var(--color-background));
    color: var(--button-google-hover-text, var(--color-text));
    border-color: var(--button-google-hover-border, var(--color-primary));
    box-shadow: var(--button-google-hover-shadow, none);
    text-decoration: none;
}
.btn-google:active,
.ui-button--integration-google:active {
    background: var(--button-google-active, var(--color-background));
    color: var(--button-google-active-text, var(--color-text));
    border-color: var(--button-google-active-border, var(--color-primary));
    box-shadow: var(--button-google-active-shadow, none);
}

.btn-telegram,
.ui-button--integration-telegram {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-sm);
    width: 100%;
    padding: var(--spacing-sm);
    font-weight: var(--font-weight-medium);
    background: var(--button-telegram-background, var(--color-card-background));
    color: var(--button-telegram-text, var(--color-info));
    border: var(--border-width-thin) solid var(--button-telegram-border, var(--color-info));
    box-shadow: var(--button-telegram-shadow, none);
    border-radius: var(--border-radius);
    cursor: pointer;
    transition: var(--transition-fast);
    text-decoration: none;
}
.btn-telegram:hover,
.ui-button--integration-telegram:hover {
    background: var(--button-telegram-hover, var(--color-info));
    color: var(--button-telegram-hover-text, var(--button-primary-text, var(--color-white, #fff)));
    border-color: var(--button-telegram-hover-border, var(--color-info));
    box-shadow: var(--button-telegram-hover-shadow, none);
    text-decoration: none;
}
.btn-telegram:active,
.ui-button--integration-telegram:active {
    background: var(--button-telegram-active, var(--color-primary-hover));
    color: var(--button-telegram-active-text, var(--color-white, #fff));
    border-color: var(--button-telegram-active-border, var(--color-primary-hover));
    box-shadow: var(--button-telegram-active-shadow, none);
}
.btn-telegram:hover svg path,
.ui-button--integration-telegram:hover svg path {
    fill: var(--button-telegram-hover-text, var(--button-primary-text, var(--color-white, #fff)));
}

.btn-swagger,
.ui-button--integration-swagger {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-sm);
    padding: var(--spacing-sm) var(--spacing-md);
    background: var(--button-swagger-background, var(--color-success));
    color: var(--button-swagger-text, var(--color-white, #fff));
    border: var(--border-width-thin) solid var(--button-swagger-border, var(--color-success));
    box-shadow: var(--button-swagger-shadow, none);
    border-radius: var(--border-radius);
    text-decoration: none;
    font-weight: var(--font-weight-medium);
    transition: var(--transition-fast);
}
.btn-swagger:hover,
.ui-button--integration-swagger:hover {
    background: var(--button-swagger-hover, var(--color-success-dark, var(--color-success)));
    color: var(--button-swagger-hover-text, var(--color-white, #fff));
    border-color: var(--button-swagger-hover-border, var(--color-success-dark, var(--color-success)));
    box-shadow: var(--button-swagger-hover-shadow, none);
    text-decoration: none;
}
.btn-swagger:active,
.ui-button--integration-swagger:active {
    background: var(--button-swagger-active, var(--color-success-dark, var(--color-success)));
    color: var(--button-swagger-active-text, var(--color-white, #fff));
    border-color: var(--button-swagger-active-border, var(--color-success-dark, var(--color-success)));
    box-shadow: var(--button-swagger-active-shadow, none);
}

/* ==================== PRINT BUTTONS ==================== */
/* Compact print-trigger buttons used in contract/expense lists. */

.btn-print-block,
.ui-button--print {
    background: var(--button-print-background, transparent);
    color: var(--button-print-text, var(--color-text));
    border: var(--border-width-thin) solid var(--button-print-border, var(--color-border));
    box-shadow: var(--button-print-shadow, none);
    border-radius: var(--border-radius-sm);
    cursor: pointer;
    padding: var(--spacing-xs) var(--spacing-sm);
    font-size: var(--font-size-xs);
    opacity: 0.5;
    transition: var(--transition-fast);
}
.btn-print-block:hover:not(:disabled),
.ui-button--print:hover:not(:disabled) {
    background: var(--button-print-hover, var(--color-background));
    color: var(--button-print-hover-text, var(--color-text));
    border-color: var(--button-print-hover-border, var(--color-border));
    box-shadow: var(--button-print-hover-shadow, none);
    opacity: 1;
}
.btn-print-block:disabled,
.ui-button--print:disabled {
    cursor: not-allowed;
}
/* Active state — e.g. key-shaped icon for unlocked print */
.btn-print-block.btn-print-keys,
.ui-button--print.ui-button--print-active {
    opacity: 1;
    cursor: pointer;
    background: var(--button-print-active, var(--color-info-bg));
    color: var(--button-print-active-text, var(--color-primary));
    border-color: var(--button-print-active-border, var(--color-primary));
    box-shadow: var(--button-print-active-shadow, none);
}

/* ==================== AI ACTION BUTTONS ==================== */
/* AI-triggered buttons with spinning icon on loading state. */

.btn-ai-generate,
.btn-ai-translate,
.btn-ai-description,
.ui-button--ai {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-sm);
    margin-top: var(--spacing-sm);
    padding: var(--spacing-sm) var(--spacing-md);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    background: var(--button-ai-background, var(--color-background));
    color: var(--button-ai-text, var(--color-primary));
    border: var(--border-width-thin) solid var(--button-ai-border, var(--color-primary));
    box-shadow: var(--button-ai-shadow, none);
    border-radius: var(--border-radius-sm);
    cursor: pointer;
    transition: var(--transition-fast);
}
.btn-ai-generate:hover,
.btn-ai-translate:hover,
.btn-ai-description:hover,
.ui-button--ai:hover {
    background: var(--button-ai-hover, var(--color-primary));
    color: var(--button-ai-hover-text, var(--button-primary-text, var(--color-white, #fff)));
    border-color: var(--button-ai-hover-border, var(--color-primary));
    box-shadow: var(--button-ai-hover-shadow, none);
}
.btn-ai-generate:active,
.btn-ai-translate:active,
.btn-ai-description:active,
.ui-button--ai:active {
    background: var(--button-ai-active, var(--color-primary-hover));
    color: var(--button-ai-active-text, var(--color-white, #fff));
    border-color: var(--button-ai-active-border, var(--color-primary-hover));
    box-shadow: var(--button-ai-active-shadow, none);
}
.btn-ai-generate:disabled,
.btn-ai-translate:disabled,
.btn-ai-description:disabled,
.ui-button--ai:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.btn-ai-generate .ai-icon,
.btn-ai-translate .ai-icon,
.btn-ai-description .ai-icon,
.ui-button--ai .ai-icon {
    font-size: var(--font-size-sm);
}
.btn-ai-generate.loading .ai-icon,
.btn-ai-translate.loading .ai-icon,
.btn-ai-description.loading .ai-icon,
.ui-button--ai.loading .ai-icon {
    animation: ai-spin 1s linear infinite;
}
@keyframes ai-spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

/* ==================== SEMANTIC ALIAS BUTTONS ==================== */
/* Semantic aliases to existing variants — kept for template compatibility.
   New code should use .btn .btn-primary/.btn-danger/.btn-outline directly. */

/* Create — alias of btn-primary */
.btn-create,
.ui-button--create {
    background-color: var(--button-primary-background);
    color: var(--button-primary-text, var(--color-white, #fff));
    border: var(--border-width-thin) solid var(--button-primary-border, transparent);
    padding: var(--spacing-sm) var(--spacing-md);
    border-radius: var(--border-radius);
    cursor: pointer;
    font-weight: var(--font-weight-medium);
    transition: var(--transition-fast);
}
.btn-create:hover,
.ui-button--create:hover {
    background-color: var(--button-primary-hover);
}

/* Remove / Clear / Disconnect / Detach — alias of btn-danger text style */
.btn-remove, .btn-remove-block, .btn-clear, .btn-clear-selection,
.btn-disconnect, .btn-detach,
.ui-button--remove {
    background: transparent;
    color: var(--color-error);
    border: var(--border-width-thin) solid var(--color-error);
    padding: var(--spacing-sm) var(--spacing-md);
    border-radius: var(--border-radius);
    cursor: pointer;
    font-size: var(--font-size-xs);
    transition: var(--transition-fast);
}
.btn-remove:hover, .btn-remove-block:hover, .btn-clear:hover, .btn-clear-selection:hover,
.btn-disconnect:hover, .btn-detach:hover,
.ui-button--remove:hover {
    background: var(--color-error);
    color: var(--color-white, #fff);
}

/* Connect — alias of btn-success */
.btn-connect,
.ui-button--connect {
    background-color: var(--button-success-background);
    color: var(--button-success-text, var(--color-white, #fff));
    border: var(--border-width-thin) solid var(--button-success-border, transparent);
    padding: var(--spacing-sm) var(--spacing-md);
    border-radius: var(--border-radius);
    cursor: pointer;
    font-weight: var(--font-weight-medium);
    transition: var(--transition-fast);
}
.btn-connect:hover,
.ui-button--connect:hover {
    background-color: var(--button-success-hover);
}

/* Test / Show-map / Show-password / Close-selector / Secondary-action / Save-icon / Cancel-icon / Cancel-sm
   — aliases of btn-secondary */
.btn-test, .btn-show-map, .btn-show-password, .btn-close-selector,
.btn-secondary-action, .btn-save-icon, .btn-cancel-icon, .btn-cancel-sm,
.ui-button--alias-secondary {
    background-color: var(--button-secondary-background, var(--color-card-background));
    color: var(--button-secondary-text, var(--color-text));
    border: var(--border-width-thin) solid var(--button-secondary-border, var(--color-border));
    padding: var(--spacing-sm) var(--spacing-md);
    border-radius: var(--border-radius);
    cursor: pointer;
    transition: var(--transition-fast);
}
.btn-test:hover, .btn-show-map:hover, .btn-show-password:hover, .btn-close-selector:hover,
.btn-secondary-action:hover, .btn-save-icon:hover, .btn-cancel-icon:hover, .btn-cancel-sm:hover,
.ui-button--alias-secondary:hover {
    background-color: var(--button-secondary-hover, var(--color-background));
}

/* Outline variants (btn-outline-{variant}) — deprecated, use btn + btn-outline + btn-{variant} */
.btn-outline-primary, .btn-outline-secondary, .btn-outline-success,
.btn-outline-danger, .btn-outline-warning {
    background: transparent;
    padding: var(--spacing-sm) var(--spacing-md);
    border-radius: var(--border-radius);
    cursor: pointer;
    transition: var(--transition-fast);
}
.btn-outline-primary { color: var(--color-primary); border: var(--border-width-thin) solid var(--color-primary); }
.btn-outline-primary:hover { background: var(--color-primary); color: var(--color-white, #fff); }
.btn-outline-secondary { color: var(--color-secondary); border: var(--border-width-thin) solid var(--color-secondary); }
.btn-outline-secondary:hover { background: var(--color-secondary); color: var(--color-white, #fff); }
.btn-outline-success { color: var(--color-success); border: var(--border-width-thin) solid var(--color-success); }
.btn-outline-success:hover { background: var(--color-success); color: var(--color-white, #fff); }
.btn-outline-danger { color: var(--color-error); border: var(--border-width-thin) solid var(--color-error); }
.btn-outline-danger:hover { background: var(--color-error); color: var(--color-white, #fff); }
.btn-outline-warning { color: var(--color-warning); border: var(--border-width-thin) solid var(--color-warning); }
.btn-outline-warning:hover { background: var(--color-warning); color: var(--color-white, #fff); }
/* disabled outline (TD-64 W13.5): паритет с legacy .btn-{variant}-outline:disabled — opacity/cursor + отмена hover-заливки */
.btn-outline-primary:disabled, .btn-outline-secondary:disabled, .btn-outline-success:disabled,
.btn-outline-danger:disabled, .btn-outline-warning:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.btn-outline-primary:disabled:hover { background: transparent; color: var(--color-primary); }
.btn-outline-secondary:disabled:hover { background: transparent; color: var(--color-secondary); }
.btn-outline-success:disabled:hover { background: transparent; color: var(--color-success); }
.btn-outline-danger:disabled:hover { background: transparent; color: var(--color-error); }
.btn-outline-warning:disabled:hover { background: transparent; color: var(--color-warning); }

/* Text-only danger (used for inline unlink) */
.btn-danger-text {
    background: none;
    border: none;
    color: var(--color-error);
    cursor: pointer;
    padding: var(--spacing-xs) var(--spacing-sm);
    font-size: var(--font-size-xs);
    text-decoration: underline;
}
.btn-danger-text:hover {
    color: var(--color-error-dark, var(--color-error));
    text-decoration: none;
}

/* Validation error highlight — added via JS on submit when a required button-field is missing */
.btn-error-highlight {
    border-color: var(--color-error) !important;
    box-shadow: 0 0 0 2px var(--color-error-bg);
    color: var(--color-error);
    transition: border-color var(--transition-duration-default), box-shadow var(--transition-duration-default);
}

/* ==================== FILTER BUTTONS (.btn-apply / .btn-reset) ==================== */
/* Используются на страницах charges/receipts/payments/translations (фильтры таблиц).
   Полностью редактируемые через Brand Book popup (data-variant="apply" / "reset"). */

.btn-apply,
.ui-button--apply {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* TD-86 Wave3: снимаем базовую .btn-геометрию (width:100% / min-height:72px / min-width:72px),
       чтобы канон ui_button({special:'apply'}) = «.btn .btn-apply» рендерил компактную фильтр-кнопку,
       а не растянутую мобайл-кнопку. auto (не 0) = initial-значение голого .btn-apply: во flex-ряду
       (.filter-actions) кнопка не ужимается уже контента, как и в bare-рендере. */
    width: auto;
    min-width: auto;
    min-height: auto;
    gap: var(--spacing-sm);
    padding: var(--spacing-sm) var(--spacing-md);
    background: var(--button-apply-background, var(--color-primary));
    color: var(--button-apply-text, var(--color-white, #fff));
    border: var(--border-width-thin) solid var(--button-apply-border, var(--color-primary));
    box-shadow: var(--button-apply-shadow, none);
    border-radius: var(--border-radius);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    transition: var(--transition-fast);
    text-decoration: none;
}
.btn-apply:hover,
.ui-button--apply:hover {
    background: var(--button-apply-hover, var(--color-primary-hover));
    color: var(--button-apply-hover-text, var(--color-white, #fff));
    border-color: var(--button-apply-hover-border, var(--color-primary-hover));
    box-shadow: var(--button-apply-hover-shadow, none);
}
.btn-apply:active,
.ui-button--apply:active {
    background: var(--button-apply-active, var(--color-primary-hover));
    color: var(--button-apply-active-text, var(--color-white, #fff));
    border-color: var(--button-apply-active-border, var(--color-primary-hover));
    box-shadow: var(--button-apply-active-shadow, none);
}
.btn-apply:disabled,
.ui-button--apply:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.btn-reset,
.ui-button--reset {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* TD-86 Wave3: снимаем базовую .btn-геометрию (auto = initial голого .btn-reset) — см. .btn-apply выше. */
    width: auto;
    min-width: auto;
    min-height: auto;
    gap: var(--spacing-sm);
    padding: var(--spacing-sm) var(--spacing-md);
    background: var(--button-reset-background, var(--color-card-background));
    color: var(--button-reset-text, var(--color-text-muted));
    border: var(--border-width-thin) solid var(--button-reset-border, var(--color-border));
    box-shadow: var(--button-reset-shadow, none);
    border-radius: var(--border-radius);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    transition: var(--transition-fast);
    text-decoration: none;
}
.btn-reset:hover,
.ui-button--reset:hover {
    background: var(--button-reset-hover, var(--color-background));
    color: var(--button-reset-hover-text, var(--color-secondary));
    border-color: var(--button-reset-hover-border, var(--color-secondary));
    box-shadow: var(--button-reset-hover-shadow, none);
    text-decoration: none;
}
.btn-reset:active,
.ui-button--reset:active {
    background: var(--button-reset-active, var(--color-disabled));
    color: var(--button-reset-active-text, var(--color-secondary));
    border-color: var(--button-reset-active-border, var(--color-secondary));
    box-shadow: var(--button-reset-active-shadow, none);
}

/* .btn-link — text-link style button (Back to settings, Cancel, etc) */
.btn-link,
.ui-button--link {
    display: inline-flex;
    align-items: center;
    /* TD-86 Wave3: снимаем базовую .btn-геометрию — канон ui_button({variant:'link'}) = «.btn .btn-link»
       рендерит компактную текст-ссылку, а не растянутую 72px-кнопку (прецедент: reset_password/_description).
       auto (не 0) = initial голого .btn-link. NB: компактность зашита в класс; link'у, которому нужна
       full-width геометрия, потребуется отдельная ось (widthMode) — сейчас таких нет. */
    width: auto;
    min-width: auto;
    min-height: auto;
    gap: var(--spacing-xs);
    padding: var(--spacing-xs) var(--spacing-sm);
    background: var(--button-link-background, transparent);
    color: var(--button-link-text, var(--color-primary));
    border: var(--border-width-thin) solid var(--button-link-border, transparent);
    box-shadow: var(--button-link-shadow, none);
    font-size: var(--font-size-sm);
    cursor: pointer;
    text-decoration: none;
    transition: var(--transition-fast);
}
.btn-link:hover,
.ui-button--link:hover {
    background: var(--button-link-hover, transparent);
    color: var(--button-link-hover-text, var(--color-primary-hover));
    border-color: var(--button-link-hover-border, transparent);
    box-shadow: var(--button-link-hover-shadow, none);
    text-decoration: underline;
}
.btn-link:active,
.ui-button--link:active {
    background: var(--button-link-active, transparent);
    color: var(--button-link-active-text, var(--color-primary-hover));
    border-color: var(--button-link-active-border, transparent);
}

/* ==================== PAGE-SPECIFIC BUTTON ALIASES ==================== */
/* Классы, которые раньше определялись inline в <style> блоках отдельных страниц.
   Теперь все наследуют CSS-переменные Brand Book и редактируются через Brand Book popup
   (меняешь соответствующий --button-*-* токен — меняются везде). */

/* Print family — alias of btn-print-block (editable via "print" variant) */
.btn-print, .btn-print-all-addendums, .btn-print-contract,
.btn-acceptance-act, .btn-return-act {
    background: var(--button-print-background, transparent);
    color: var(--button-print-text, var(--color-text));
    border: var(--border-width-thin) solid var(--button-print-border, var(--color-border));
    box-shadow: var(--button-print-shadow, none);
    border-radius: var(--border-radius-sm);
    cursor: pointer;
    padding: var(--spacing-xs) var(--spacing-sm);
    font-size: var(--font-size-xs);
    transition: var(--transition-fast);
}
.btn-print:hover, .btn-print-all-addendums:hover, .btn-print-contract:hover,
.btn-acceptance-act:hover, .btn-return-act:hover {
    background: var(--button-print-hover, var(--color-background));
    color: var(--button-print-hover-text, var(--color-text));
    border-color: var(--button-print-hover-border, var(--color-border));
    box-shadow: var(--button-print-hover-shadow, none);
}

/* Delete inline aliases — alias of btn-icon-danger (editable via "icon-danger" variant) */
.btn-delete-receipt, .btn-delete-charge, .btn-delete-sm {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    background: var(--button-icon-danger-background, var(--color-error-bg));
    color: var(--button-icon-danger-text, var(--color-error));
    border: var(--border-width-thin) solid var(--button-icon-danger-border, var(--color-error-bg));
    box-shadow: var(--button-icon-danger-shadow, none);
    border-radius: var(--border-radius-sm);
    cursor: pointer;
    transition: var(--transition-fast);
}
.btn-delete-receipt:hover, .btn-delete-charge:hover, .btn-delete-sm:hover {
    background: var(--button-icon-danger-hover, var(--color-error));
    color: var(--button-icon-danger-hover-text, var(--color-white, #fff));
    border-color: var(--button-icon-danger-hover-border, var(--color-error));
}

/* Save/Cancel inline aliases — alias of btn-icon-save / btn-icon-cancel */
.btn-save-icon, .btn-save-sm {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    background: var(--button-icon-save-background, var(--color-primary));
    color: var(--button-icon-save-text, var(--color-white, #fff));
    border: var(--border-width-thin) solid var(--button-icon-save-border, var(--color-primary));
    box-shadow: var(--button-icon-save-shadow, none);
    border-radius: var(--border-radius-sm);
    cursor: pointer;
    transition: var(--transition-fast);
}
.btn-save-icon:hover, .btn-save-sm:hover {
    background: var(--button-icon-save-hover, var(--color-card-background));
    color: var(--button-icon-save-hover-text, var(--color-primary));
}

.btn-cancel-icon, .btn-cancel-sm {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    background: var(--button-icon-cancel-background, var(--color-card-background));
    color: var(--button-icon-cancel-text, var(--color-text-muted));
    border: var(--border-width-thin) solid var(--button-icon-cancel-border, var(--color-border));
    box-shadow: var(--button-icon-cancel-shadow, none);
    border-radius: var(--border-radius-sm);
    cursor: pointer;
    transition: var(--transition-fast);
}
.btn-cancel-icon:hover, .btn-cancel-sm:hover {
    background: var(--button-icon-cancel-hover, var(--color-error-bg));
    color: var(--button-icon-cancel-hover-text, var(--color-error));
    border-color: var(--button-icon-cancel-hover-border, var(--color-error));
}

/* Confirm small variants — alias of btn-confirm */
.btn-confirm-inline, .btn-confirm-small {
    background-color: var(--button-confirm-background, var(--button-success-background));
    color: var(--button-confirm-text, var(--color-white, #fff));
    border: var(--border-width-thin) solid var(--button-confirm-border, transparent);
    padding: var(--spacing-xs) var(--spacing-sm);
    font-size: var(--font-size-xs);
    border-radius: var(--border-radius);
    cursor: pointer;
    transition: var(--transition-fast);
}
.btn-confirm-inline:hover, .btn-confirm-small:hover {
    background-color: var(--button-confirm-hover, var(--button-success-hover));
}

/* Create variations — alias of btn-primary */
.btn-create-first, .btn-copy-contract {
    background-color: var(--button-primary-background);
    color: var(--button-primary-text, var(--color-white, #fff));
    border: var(--border-width-thin) solid var(--button-primary-border, transparent);
    padding: var(--spacing-sm) var(--spacing-md);
    border-radius: var(--border-radius);
    cursor: pointer;
    font-weight: var(--font-weight-medium);
    transition: var(--transition-fast);
}
.btn-create-first:hover, .btn-copy-contract:hover {
    background-color: var(--button-primary-hover);
}

/* Edit inline actions — alias of btn-warning */
.btn-edit-amount {
    background: transparent;
    color: var(--button-warning-background);
    border: none;
    cursor: pointer;
    padding: var(--spacing-xs) var(--spacing-sm);
    font-size: var(--font-size-xs);
    text-decoration: underline;
}
.btn-edit-amount:hover {
    color: var(--button-warning-hover);
    text-decoration: none;
}

/* Property show page specials — aliases using primary/secondary tokens */
.btn-messages, .btn-handshake, .btn-negotiations-list,
.btn-proposal, .btn-proposals-list, .btn-partner-invite,
.btn-expense-link, .btn-help-demo {
    background-color: var(--button-primary-background);
    color: var(--button-primary-text, var(--color-white, #fff));
    border: var(--border-width-thin) solid var(--button-primary-border, transparent);
    padding: var(--spacing-sm) var(--spacing-md);
    border-radius: var(--border-radius);
    cursor: pointer;
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-xs);
    text-decoration: none;
    transition: var(--transition-fast);
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-sm);
}
.btn-messages:hover, .btn-handshake:hover, .btn-negotiations-list:hover,
.btn-proposal:hover, .btn-proposals-list:hover, .btn-partner-invite:hover,
.btn-expense-link:hover, .btn-help-demo:hover {
    background-color: var(--button-primary-hover);
    text-decoration: none;
}

/* .action-icon (+ action-confirm/btn-unconfirm/action-confirmed/action-delete) — УДАЛЕНО (TD-63):
   мёртвый класс (0 использований в шаблонах/JS; комментарий о adjustments/billing устарел —
   места давно мигрированы на companion-классы). Канон icon-кнопки = .btn-icon. */

/* ==================== ACTION BUTTONS ==================== */
/* Action buttons — large CTA buttons used in charges/expenses/penalties/admin/calendar.
   Previously defined inline across 8 templates with broken --color-gradient-* vars. */
.action-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-sm);
    padding: 0.625rem 1.25rem;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    line-height: var(--line-height-normal);
    min-height: 38px;
    border: none;
    border-radius: var(--border-radius-sm);
    cursor: pointer;
    transition: var(--transition-default, all 0.25s cubic-bezier(0.4, 0, 0.2, 1));
    text-decoration: none;
    box-shadow: var(--shadow-sm, 0 2px 4px rgba(0, 0, 0, 0.1));
    white-space: nowrap;
}
.action-btn:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-md, 0 4px 12px rgba(0, 0, 0, 0.15));
    text-decoration: none;
}
.action-btn:active { transform: translateY(0); }
.action-btn svg { flex-shrink: 0; }

.action-btn-icon {
    padding: 0.625rem;
    min-width: 38px;
    justify-content: center;
    gap: 0;
}

.action-btn-primary {
    background: var(--button-primary-background, var(--color-primary));
    color: var(--button-primary-text, var(--color-white, #fff));
}
.action-btn-primary:hover {
    background: var(--button-primary-hover, var(--color-primary-hover));
    color: var(--button-primary-hover-text, var(--color-white, #fff));
}

.action-btn-warning {
    background: var(--button-warning-background, var(--color-warning));
    color: var(--button-warning-text, var(--color-white, #fff));
}
.action-btn-warning:hover {
    background: var(--button-warning-hover, var(--color-warning-hover, var(--color-warning)));
    color: var(--button-warning-hover-text, var(--color-white, #fff));
}

.action-btn-success {
    background: var(--button-success-background, var(--color-success));
    color: var(--button-success-text, var(--color-white, #fff));
}
.action-btn-success:hover {
    background: var(--button-success-hover, var(--color-success-hover, var(--color-success)));
    color: var(--button-success-hover-text, var(--color-white, #fff));
}

/* Activate — alias of btn-warning */
.btn-activate {
    background-color: var(--button-warning-background);
    color: var(--button-warning-text, var(--color-white, #fff));
    border: var(--border-width-thin) solid var(--button-warning-border, transparent);
    padding: var(--spacing-sm) var(--spacing-md);
    border-radius: var(--border-radius);
    cursor: pointer;
    transition: var(--transition-fast);
}
.btn-activate:hover {
    background-color: var(--button-warning-hover);
}

/* Disabled state for all legacy aliases */
.btn-disabled,
.btn-delete-receipt:disabled, .btn-delete-charge:disabled,
.btn-save-icon:disabled, .btn-cancel-icon:disabled,
.btn-confirm-inline:disabled, .btn-confirm-small:disabled,
.btn-messages:disabled, .btn-create-first:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;
}

/* Extra-small size modifier (btn-xs — contracts/show etc.) */
.btn-xs {
    padding: var(--spacing-xs) var(--spacing-sm);
    font-size: var(--font-size-xxs);
}

/* ==================== FORM ACTIONS (Save/Cancel bar) ==================== */
/* Центральный block для new/edit страниц — используется через
   {% include 'components/_form_actions.html.twig' %}.
   Правило Brand Book: не ставить отдельный back-link, если есть Cancel. */
.form-actions {
    display: flex;
    gap: var(--spacing-md);
    margin-top: var(--spacing-lg);
    flex-wrap: wrap;
    align-items: center;
}
.form-actions--end { justify-content: flex-end; }
.form-actions--start { justify-content: flex-start; }
.form-actions--center { justify-content: center; }
.form-actions--between { justify-content: space-between; }
.form-actions .btn { min-width: 120px; }

@media (max-width: 575px) {
    .form-actions .btn { min-width: 0; flex: 1; }
}

/* .copy-btn / .btn-copy — compact button in code blocks (guide templates, 90+ uses)
   Fully editable via Brand Book (data-variant="copy") */
.copy-btn,
.btn-copy,
.ui-button--copy {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-xs);
    background: var(--button-copy-background, rgba(0,0,0,0.15));
    color: var(--button-copy-text, var(--color-white, #fff));
    border: var(--border-width-thin) solid var(--button-copy-border, transparent);
    box-shadow: var(--button-copy-shadow, none);
    border-radius: var(--border-radius-sm);
    padding: var(--spacing-xs) var(--spacing-sm);
    font-size: var(--font-size-xs);
    cursor: pointer;
    transition: background var(--transition-duration-default);
}
.copy-btn:hover,
.btn-copy:hover,
.ui-button--copy:hover {
    background: var(--button-copy-hover, rgba(0,0,0,0.3));
    color: var(--button-copy-hover-text, var(--color-white, #fff));
    border-color: var(--button-copy-hover-border, transparent);
    box-shadow: var(--button-copy-hover-shadow, none);
}
.copy-btn:active,
.btn-copy:active,
.ui-button--copy:active {
    background: var(--button-copy-active, rgba(0,0,0,0.45));
    color: var(--button-copy-active-text, var(--color-white, #fff));
    border-color: var(--button-copy-active-border, transparent);
    box-shadow: var(--button-copy-active-shadow, none);
}

/* ═══ User Remove Button ═══ */
.btn-user-remove {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: none;
    cursor: pointer;
    font-size: var(--font-size-sm);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background-color: var(--color-background);
    color: var(--color-error);
}
.btn-user-remove:hover {
    background-color: var(--color-error);
    color: var(--button-primary-text);
}

/* ═══ Tenant Info Block ═══ */
.tenant-info-block {
    display: flex; align-items: center; gap: var(--spacing-sm);
    font-size: var(--font-size-xs); color: var(--color-text-muted);
    padding: var(--spacing-xs) 0;
}
.tenant-info-icon { font-size: var(--font-size-xs); flex-shrink: 0; }
.tenant-info-name { font-weight: var(--font-weight-medium); color: var(--color-text); }
.tenant-info-date { white-space: nowrap; }
.tenant-info-open { font-style: italic; }

/* ═══ Photo Carousel ═══ */
.photo-carousel {
    position: relative;
    user-select: none;
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
    padding: 0 var(--spacing-lg) var(--spacing-md);
}
.photo-carousel__track-wrap {
    overflow: hidden;
    border-radius: var(--border-radius-md);
    width: 100%;
    min-width: 0;
}
.photo-carousel__track {
    display: flex;
    gap: var(--spacing-sm);
    transition: transform var(--transition-duration-slow) cubic-bezier(.4,0,.2,1);
    will-change: transform;
}
.photo-carousel__item {
    flex: 0 0 var(--carousel-item-width, 180px);
    width: var(--carousel-item-width, 180px);
    aspect-ratio: 1;
    border-radius: var(--border-radius-md);
    overflow: hidden;
    background: var(--color-table-bg);
    position: relative;
    cursor: pointer;
    transition: transform var(--transition-duration-default), box-shadow var(--transition-duration-default);
}
.photo-carousel__item:hover {
    transform: scale(1.02);
    box-shadow: var(--shadow-card-hover);
}
/* Lazy-image wrapper must fill the square item so the inner img can cover it. */
.photo-carousel__item .lazy-img-wrapper {
    display: block;
    width: 100%;
    height: 100%;
}
.photo-carousel__item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.photo-carousel__caption {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    padding: var(--spacing-xs) var(--spacing-sm);
    background: linear-gradient(var(--carousel-overlay-dark, rgba(0,0,0,0.65)), transparent);
    color: var(--color-white, #fff);
    font-size: var(--font-size-xxs);
    font-weight: var(--font-weight-medium);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.photo-carousel__type {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    padding: var(--spacing-xs) var(--spacing-sm);
    background: linear-gradient(transparent, var(--carousel-overlay-dark, rgba(0,0,0,0.65)));
    color: var(--color-white, #fff);
    font-size: var(--font-size-xxs);
}
/* Prev/next — нейтральные overlay icon-кнопки (.btn-icon.btn-icon-overlay):
   форма/размер/фон/тень/scale-hover — из канона; вертикальное центрирование +
   совмещение со scale-hover — из канон-класса .btn-icon-overlay--centered (в разметке);
   companion держит ТОЛЬКО позиционирование и глиф-типографику (юникод ‹ ›). */
.photo-carousel__btn {
    position: absolute;
    top: 50%;
    z-index: 2;
    font-size: var(--font-size-xl);
    font-weight: var(--font-weight-bold);
}
.photo-carousel__btn:disabled {
    opacity: 0.25;
    cursor: default;
}
.photo-carousel__btn--prev { left: 0; }
.photo-carousel__btn--next { right: 0; }
.photo-carousel__dots {
    display: flex;
    justify-content: center;
    gap: var(--spacing-sm);
    margin-top: var(--spacing-sm);
}
.photo-carousel__dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--color-table-border);
    transition: background var(--transition-duration-default), transform var(--transition-duration-default);
    cursor: pointer;
}
.photo-carousel__dot.active {
    background: var(--color-primary);
    transform: scale(1.3);
}

/* ── Photo Carousel: Show All button + full grid ── */
.photo-carousel__all-wrap {
    margin-top: var(--spacing-sm);
    text-align: center;
}
.photo-carousel__show-all-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-sm);
    padding: var(--spacing-sm) var(--spacing-md);
    border: var(--border-width-thin) solid var(--color-primary);
    border-radius: var(--border-radius-md);
    background: transparent;
    color: var(--color-primary);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    transition: background var(--transition-duration-default), color var(--transition-duration-default);
}
.photo-carousel__show-all-btn:hover {
    background: var(--color-primary);
    color: var(--color-white);
}
.photo-carousel__grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: var(--spacing-sm);
}
.photo-carousel__grid-item {
    position: relative;
    aspect-ratio: 1;
    border-radius: var(--border-radius-md);
    overflow: hidden;
    background: var(--color-table-bg);
    cursor: pointer;
    transition: transform var(--transition-duration-default), box-shadow var(--transition-duration-default);
}
.photo-carousel__grid-item:hover {
    transform: scale(1.02);
    box-shadow: var(--shadow-card-hover);
}
.photo-carousel__grid-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* ── Dashboard Property Card (_property_dashboard_card.html.twig) ─────────── */
.dashboard-property-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: var(--spacing-md);
    margin-bottom: var(--spacing-lg);
}
.dashboard-property-card {
    display: block;
    padding: 0;
    background: var(--color-card-background);
    border: var(--border-width-thin) solid var(--color-border);
    border-radius: var(--border-radius-sm);
    text-decoration: none;
    color: inherit;
    transition: var(--transition-default);
    overflow: hidden;
    position: relative;
    height: 300px;
    width: 100%;
    cursor: pointer;
}
.dashboard-property-card:hover {
    border-color: var(--color-primary);
    box-shadow: var(--shadow-card-hover);
    transform: translateY(-1px);
}
.dashboard-property-card__body {
    padding: var(--spacing-md);
    position: absolute;
    width: 100%;
    bottom: 0;
    left: 0;
    box-sizing: border-box;
    background: var(--color-card-background-overlay, rgba(255, 255, 255, 0.9));
    z-index: 10;
}
.dashboard-property-card__body .tenant-info-block { margin-top: var(--spacing-sm); }
.property-card-cover { width: 100%; height: 100%; position: absolute; top: 0; left: 0; }
.property-card-cover img { width: 100%; height: 100%; display: block; object-fit: fill; }
.property-card-cover .lazy-img-wrapper { width: 100%; height: 100%; }
.property-card-cover--placeholder { background: var(--color-input-bg, #f4f4f4); }
.property-card-cover--placeholder img { object-fit: contain; padding: var(--spacing-lg); box-sizing: border-box; opacity: 0.35; }
.property-card-header { display: flex; align-items: center; gap: var(--spacing-sm); margin-bottom: 10px; }
.property-color-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
.property-card-name { flex: 1; font-weight: var(--font-weight-semibold); font-size: var(--font-size-sm); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.property-card-total { font-weight: var(--font-weight-bold); font-size: var(--font-size-sm); white-space: nowrap; }
.property-card-breakdown { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--spacing-sm); text-align: center; }
.property-card-col { display: flex; flex-direction: column; align-items: center; gap: var(--spacing-xs); }
.property-card-icon { font-size: var(--font-size-sm); }
.property-card-amount { font-weight: var(--font-weight-semibold); font-size: var(--font-size-xs); }
.property-card-badges { display: flex; gap: var(--spacing-sm); flex-wrap: wrap; margin-top: var(--spacing-sm); }

@media (max-width: 767px) {
    .dashboard-property-cards { grid-template-columns: 1fr; }
}
@media (min-width: 768px) and (max-width: 991px) {
    .dashboard-property-cards { grid-template-columns: repeat(2, 1fr); }
}

/* ── Contract item (_contract_item.html.twig) ── */
.contracts-list { display: flex; flex-direction: column; gap: var(--spacing-md); }

.contract-item {
    display: flex;
    /* TASK-000536 (Figma 878:12481): статус / інфо / сводный блок / ряд дій — один ряд,
       вирівняний по центру. flex-start лишало сводный блок и ряд дій вертикального
       центрування відносно назви+meta+дат у сусідній колонці. */
    align-items: center;
    /* Найдено браузерной проверкой на ~1440px (частая ширина ноутбука): без wrap 4 колонки
       (status/info/summary/actions) не помещались в бюджет карточки, и .contract-info
       (min-width:0) схлопывался вплоть до переноса заголовка по одному слову — сводный
       блок визуально наезжал на текст. wrap даёт честную вторую строку вместо сжатия текста
       в нуль; .contract-info ниже держит пол в 280px, чтобы вторую строку получал ряд
       действий/сводка, а не заголовок договора. */
    flex-wrap: wrap;
    gap: var(--spacing-md);
    padding: var(--spacing-md);
    background: var(--color-card-background);
    border-radius: var(--border-radius-sm);
    box-shadow: var(--shadow-card);
    border-left: 4px solid var(--color-border-light);
}

.contract-item.active { border-left-color: var(--color-success); background: var(--color-background); }
.contract-item.expired { border-left-color: var(--color-error); background: var(--color-background); }
.contract-item.future { border-left-color: var(--color-primary); background: var(--color-background); }
.contract-item.deactivated { border-left-color: var(--color-text-muted); background: var(--color-background); opacity: 0.85; }
/* TASK-614: черновик — договор ещё не введён в действие. Приглушён, но без opacity
   расторгнутого: он не «списан», а ждёт решения владельца. */
.contract-item.draft { border-left-color: var(--color-border); background: var(--color-background); }

/* min-width:280px — пол ширины: при нехватке места переносится ряд дій/сводка (flex-wrap
   на .contract-item), а не заголовок договора внутри этой колонки. */
.contract-info { flex: 1 1 280px; min-width: 280px; }

.contract-name {
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-base);
    color: var(--color-secondary);
    text-decoration: none;
    display: block;
    margin-bottom: var(--spacing-sm);
}
.contract-name:hover { color: var(--color-primary); }

.contract-meta {
    display: flex;
    gap: var(--spacing-md);
    flex-wrap: wrap;
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    margin-bottom: var(--spacing-xs);
}

.contract-tenant { font-weight: var(--font-weight-medium); color: var(--color-secondary); display: inline-flex; align-items: center; gap: var(--spacing-xs); }

/* Аватар орендаря в карточке договора (TASK-000536). Базовый вид кружка-заглушки берётся
   из общего .app-avatar-fallback; здесь только размер под строку меты — селектор из двух
   классов перебивает базовый по специфичности, поэтому !important не нужен. */
.contract-tenant__avatar { flex-shrink: 0; display: inline-flex; }
.contract-tenant__avatar-img { width: 24px; height: 24px; border-radius: 50%; object-fit: cover; display: block; }
.contract-tenant__avatar .app-avatar-fallback { width: 24px; height: 24px; font-size: var(--font-size-xs); }

.contract-type {
    background: var(--color-info-bg);
    color: var(--color-primary-hover);
    padding: var(--spacing-xs) var(--spacing-sm);
    border-radius: var(--border-radius-sm);
    font-size: var(--font-size-xs);
}

.contract-dates { font-size: var(--font-size-xs); color: var(--color-text-muted); }
.contract-author { font-size: var(--font-size-xs); color: var(--color-text-muted); }
.open-ended { font-style: italic; color: var(--color-success); }

.contract-status { flex-shrink: 0; }

/* Сводный блок карточки (TASK-000536, Figma 878:12481): «Орендна плата» + «Наступний
   платіж», дом смещён вправо, слева пустой фон под текст. Отдельная колонка ряда
   (сестра .contract-info), а НЕ вложенный блок — рендерится ТОЛЬКО когда контроллер
   передал currency (_contract_item.html.twig, `only`-include без переменной у
   стороннего call-site — брендбук-демо — оставляет карточку без блока, обратная
   совместимость). */
.contract-summary {
    position: relative;
    display: flex;
    align-items: center;
    flex-shrink: 0;
    /* Ближе к пропорциям макета (878:12481, ~320×150 на дизайн-ширине 1920) — на 230×108
       (прежнее значение) иллюстрация читалась миниатюрой и обрезалась по краям блока
       (coordination-фикс). Перенос ряда действий на вторую строку на ~1440px (flex-wrap
       у .contract-item) — уже штатный и достаточный ответ на нехватку места, поэтому
       расширять блок здесь безопасно. */
    width: 300px;
    min-height: 150px;
    padding: var(--spacing-md) var(--spacing-lg);
    background: linear-gradient(135deg, var(--color-info-bg) 0%, var(--color-background) 100%);
    border-radius: var(--border-radius-md);
    overflow: hidden;
}
.contract-summary__text { position: relative; z-index: 1; }
.contract-summary__label,
.contract-summary__next-label { font-size: var(--font-size-xs); color: var(--color-text-muted); }
.contract-summary__amount { font-size: var(--font-size-xl); font-weight: var(--font-weight-bold); color: var(--color-text); margin: var(--spacing-xs) 0 var(--spacing-sm); }
.contract-summary__next-label { margin-top: var(--spacing-xs); }
.contract-summary__next-value { font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); color: var(--color-text); }
.contract-summary__dash { color: var(--color-text-muted); font-weight: var(--font-weight-normal); }
/* Иллюстрация — ПОЛНЫЙ фоновый слой блока (не обрезанная иконка в углу, coordination-фикс):
   object-fit:contain вписывает исходник 680×383 целиком (дом, крыша, гараж, дерево, кусты —
   ничего не обрезается), object-position:right прижимает сцену к правому краю — слева
   освобождается место под текст ровно так, как в макете («дом смещён вправо, слева пустой
   фон под текст», и сам исходник уже так скомпонован). z-index текста выше — картинка
   декоративна и лежит НИЖЕ него. */
.contract-summary__illustration { position: absolute; inset: 0; pointer-events: none; }
.contract-summary__illustration-img { display: block; width: 100%; height: 100%; object-fit: contain; object-position: right center; }

/* TASK-000536: ряд действий — отдельная колонка (сестра .contract-info/.contract-summary),
   выровнена вправо честным flex, а не float. float:right внутри flex-карточки ставил бы
   кнопки в поток текста рядом с названием договора; в макете это отдельный ряд у правого края. */
.contract-actions { display: flex; gap: var(--spacing-sm); flex-wrap: wrap; align-items: center; justify-content: flex-end; flex-shrink: 0; }
.contract-actions .btn { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; padding: 0; flex-shrink: 0; }

/* Blocks summary */
.contract-blocks { margin-top: var(--spacing-sm); }
.blocks-toggle { font-size: var(--font-size-xs); color: var(--color-primary); cursor: pointer; user-select: none; padding: 4px 0; }
.blocks-mini-table { width: 100%; border-collapse: collapse; margin-top: var(--spacing-sm); font-size: var(--font-size-xs); }
.blocks-mini-table th, .blocks-mini-table td { padding: 6px 8px; text-align: left; border-bottom: var(--border-width-thin) solid var(--color-border-light); }
.blocks-mini-table th { background: var(--color-background); color: var(--color-text-muted); font-weight: var(--font-weight-medium); font-size: var(--font-size-xs); }
.block-name { font-weight: var(--font-weight-medium); color: var(--color-secondary); }
.block-country { font-size: var(--font-size-xxs); color: var(--color-text-muted); }
.block-date { color: var(--color-text-muted); white-space: nowrap; }
.block-actions { width: 40px; text-align: center; }
.bool-yes { color: var(--color-success); font-weight: var(--font-weight-medium); }
.bool-no { color: var(--color-error); font-weight: var(--font-weight-medium); }

.print-all-addendums-container { margin-top: var(--spacing-sm); display: flex; justify-content: flex-end; }

/* User contracts row layout (inside .card) */
.contract-row { padding: var(--spacing-md) var(--spacing-md); }
.contract-row.utilities-landlord { background: var(--color-background); border-color: var(--color-border-light); }
.contract-row.utilities-landlord .property-link,
.contract-row.utilities-landlord .meta-value { color: var(--color-text-muted); }
.contract-row.utilities-tenant { background: var(--color-success-bg); border-color: var(--color-success); }
.contract-row-top { display: flex; align-items: center; justify-content: space-between; gap: var(--spacing-md); flex-wrap: wrap; }
.contract-row-right { display: flex; align-items: center; gap: var(--spacing-sm); flex-shrink: 0; }
.contract-row-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--spacing-sm);
    margin-top: var(--spacing-sm);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}
.meta-sep { color: var(--color-border); }
.meta-item { display: flex; gap: var(--spacing-xs); align-items: center; }
.meta-label { color: var(--color-text-muted); }
.meta-value { color: var(--color-text); font-weight: var(--font-weight-medium); }

.property-link { color: var(--color-secondary); text-decoration: none; font-weight: var(--font-weight-medium); padding-left: var(--spacing-sm); }
.property-link:hover { color: var(--color-primary); }

/* TD-51 W2: static border width/style for property-color links/tags — the
   per-property color itself is set by the `common--property-color` Stimulus
   controller (data-common--property-color-color-value="{{ property.color }}"),
   replacing `style="border-left: 3px solid {{ property.color }}"`. */
.property-color-border { border-left-width: 3px; border-left-style: solid; border-left-color: transparent; }

.modal-actions--top-gap { margin-top: var(--spacing-md); }
.back-link { margin-top: var(--spacing-lg); }
.inline-form { display: inline; margin: 0; }

@media (max-width: 767px) {
    .contract-item { flex-direction: column; align-items: flex-start; }
    .contract-status { width: 100%; }
    /* TASK-000589 review (m2): десктопный пол min-width:280px (см. .contract-info выше,
       ~L3565) на узком мобильном (320px) не сброшен — 320 − 32 (--app-content-padding-x-mobile,
       по 16px с каждой стороны) − 32 (padding var(--spacing-md) карточки, тоже с двух сторон)
       = 256px доступно, меньше пола в 280px → карточка выезжает за экран горизонтальным
       скроллом. В колонке (flex-direction: column выше) пол по ширине не нужен вовсе. */
    .contract-info { min-width: 0; flex-basis: auto; }
    .contract-summary { width: 100%; }
    .contract-actions { margin-left: 0; width: 100%; justify-content: flex-start; }
}

@media (max-width: 575px) {
    .meta-sep { display: none; }
    .contract-row-meta { flex-direction: column; align-items: flex-start; gap: var(--spacing-xs); }
    /* coordination-фикс: на узком мобильном декоративная иллюстрация прячется — текстовые
       строки «Орендна плата»/«Наступний платіж» важнее и не должны делить с ней место. */
    .contract-summary__illustration { display: none; }
}


/* NB: мёртвый дубль базового .btn-icon (40px, border-radius-sm) + legacy .btn-icon-delete
   удалены (TD-63 кат. A): полностью затенялись каноном — .btn-icon (круг 36px, ~стр.3180)
   и .btn-icon-delete (danger-вариант, ~стр.3360), оба позже по каскаду. */

/* file-input-hidden: visually hidden file inputs triggered by labels */
.file-input-hidden { display: none; }

/* Avatar upload widget — settings page personal info card */
.avatar-upload-widget {
    display: flex;
    align-items: center;
    gap: var(--spacing-lg);
    padding: var(--spacing-md) 0;
    margin-bottom: var(--spacing-md);
    border-bottom: var(--border-width-thin) solid var(--color-border);
}
.avatar-upload-preview {
    flex-shrink: 0;
    width: 128px;
    height: 128px;
    border-radius: 50%;
    overflow: hidden;
    background: var(--color-table-header);
    border: var(--border-width-normal) solid var(--color-border);
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
}
.avatar-upload-preview > div,
.avatar-upload-preview picture {
    display: block;
    width: 100%;
    height: 100%;
}
.avatar-upload-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    display: block;
}
.avatar-upload-fallback {
    font-size: var(--font-size-5xl);
    font-weight: var(--font-weight-bold);
    color: var(--color-text-muted);
    text-transform: uppercase;
    user-select: none;
}
.avatar-upload-actions {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-sm);
}
.avatar-upload-label {
    font-weight: var(--font-weight-medium, 500);
    font-size: var(--font-size-base, 14px);
    color: var(--color-text);
}
.avatar-upload-buttons {
    display: flex;
    gap: var(--spacing-sm);
    flex-wrap: wrap;
}
.avatar-upload-btn {
    cursor: pointer;
}
.avatar-upload-error {
    font-size: var(--font-size-xs);
    color: var(--color-error, #C30A41);
    margin-top: var(--spacing-xs);
}

/* ── Settings page ── */

.settings-container {
    max-width: 600px;
}
.page-title {
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-semibold);
    color: var(--color-secondary);
    margin: 0 0 var(--spacing-lg) 0;
}
.settings-form .card {
    margin-bottom: var(--spacing-md);
}
.card-title {
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
    color: var(--color-secondary);
    margin: 0 0 var(--spacing-md) 0;
    padding-bottom: var(--spacing-sm);
    border-bottom: var(--border-width-thin) solid var(--color-border-light);
}
/* .card-title--plain (TD-61 B-guard): заголовок карточки без разделителя/отступа снизу —
   встречался идентично в 5 page-CSS (admin-penalties/properties-penalties/repair-compensations/
   communications-show, admin-users-roles). Канонизирован как модификатор вместо bare-дублей. */
.card-title--plain {
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
    color: var(--color-secondary);
    margin: 0 0 var(--spacing-md) 0;
    padding-bottom: 0;
    border-bottom: none;
}
/* .card-title--lg-flush (TD-61 B-guard): крупный заголовок без отступа/разделителя —
   встречался идентично в admin-lt-adjustments-show/admin-adjustments-show. */
.card-title--lg-flush {
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
    color: var(--color-secondary);
    margin: 0;
    padding-bottom: 0;
    border-bottom: none;
}
/* .card-title--text-color (TD-61 B-guard): вариант .card-title--plain с --color-text
   вместо --color-secondary — встречался идентично в admin-tenant-assets-show/
   properties-tenant-assets-show. */
.card-title--text-color {
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text, var(--color-secondary));
    margin: 0 0 var(--spacing-md) 0;
    padding-bottom: 0;
    border-bottom: none;
}
.form-hint {
    display: block;
    margin-top: var(--spacing-xs);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}
.form-error {
    display: block;
    margin-top: var(--spacing-xs);
    font-size: var(--font-size-xs);
    color: var(--color-error);
}
.input-empty-warning {
    border-color: var(--color-warning);
    animation: pulse-warning 2s infinite;
}
.input-empty-warning:focus {
    border-color: var(--color-warning);
    box-shadow: var(--shadow-warning-focus);
    animation: none;
}
.form-hint-warning {
    color: var(--color-warning);
    font-weight: var(--font-weight-medium);
}
@keyframes pulse-warning {
    0%, 100% { border-color: var(--color-warning); }
    50% { border-color: var(--color-background); }
}
.phone-input-wrapper {
    display: flex;
    align-items: center;
    border: var(--border-width-thin) solid var(--color-border);
    border-radius: var(--border-radius-sm);
    overflow: hidden;
    background: var(--color-card-background);
    gap: 0;
}
.phone-input-wrapper:focus-within {
    border-color: var(--color-primary);
    box-shadow: var(--shadow-primary-focus);
}
.phone-prefix {
    padding: var(--spacing-md);
    background: var(--color-background);
    color: var(--color-secondary);
    font-weight: var(--font-weight-semibold);
    font-size: var(--font-size-base);
    border-right: var(--border-width-thin) solid var(--color-border);
    white-space: nowrap;
}
.phone-segment {
    width: 44px;
    border: none !important;
    text-align: center;
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-medium);
    padding: var(--spacing-md) var(--spacing-xs);
    background: transparent;
    margin-left: var(--spacing-sm);
}
.phone-segment-sm {
    width: 32px;
}
.phone-segment:focus {
    outline: none;
    background: var(--color-info-bg);
}
.phone-segment:first-of-type {
    margin-left: 0;
}
.phone-input-wrapper.error,
.tax-input-wrapper.error {
    border-color: var(--color-error);
}
.phone-input-wrapper.error:focus-within,
.tax-input-wrapper.error:focus-within {
    box-shadow: var(--shadow-error-focus);
}
.form-control-unified.input-error {
    border-color: var(--color-error);
}
.form-control-unified.input-error:focus {
    box-shadow: var(--shadow-error-focus);
}

/* TD-51 W2: replaces style="text-transform:uppercase|lowercase" on code-input fields (admin/*-new forms). */
.form-input-uppercase { text-transform: uppercase; }
.form-input-lowercase { text-transform: lowercase; }

/* TD-51 W2: replaces style="border:0;border-top:1px solid var(--color-border);margin:20px 0" on <hr> dividers.
   margin 20px — намеренно off-scale (сохранение исходной вёрстки); НЕ приводить к токену. */
.hr-divider { border: 0; border-top: var(--border-width-thin) solid var(--color-border); margin: 20px 0; }

.tax-input-wrapper {
    display: flex;
    align-items: center;
    border: var(--border-width-thin) solid var(--color-border);
    border-radius: var(--border-radius-sm);
    overflow: hidden;
    background: var(--color-card-background);
    width: fit-content;
}
.tax-input-wrapper:focus-within {
    border-color: var(--color-primary);
    box-shadow: var(--shadow-primary-focus);
}
.tax-segment {
    width: 60px;
    border: none !important;
    text-align: center;
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-medium);
    padding: var(--spacing-md) var(--spacing-xs);
    background: transparent;
    margin-left: var(--spacing-sm);
}
.tax-segment:first-child {
    margin-left: var(--spacing-md);
}
.tax-segment:last-child {
    margin-right: var(--spacing-md);
}
.tax-segment-sm {
    width: 40px;
}
.tax-segment:focus {
    outline: none;
    background: var(--color-info-bg);
}

.card-input-wrapper {
    display: flex;
    align-items: center;
    border: var(--border-width-thin) solid var(--color-border);
    border-radius: var(--border-radius-sm);
    overflow: hidden;
    background: var(--color-card-background);
    width: fit-content;
}
.card-input-wrapper:focus-within {
    border-color: var(--color-primary);
    box-shadow: var(--shadow-primary-focus);
}
.card-segment {
    width: 60px;
    border: none !important;
    text-align: center;
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-medium);
    padding: var(--spacing-md) var(--spacing-xs);
    background: transparent;
    margin-left: var(--spacing-sm);
}
.card-segment:first-child {
    margin-left: var(--spacing-md);
}
.card-segment:last-child {
    margin-right: var(--spacing-md);
}
.card-segment:focus {
    outline: none;
    background: var(--color-info-bg);
}

.subsection-title {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text-muted);
    margin: var(--spacing-lg) 0 var(--spacing-md) 0;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.radio-group {
    display: flex;
    gap: var(--spacing-lg);
    flex-wrap: wrap;
}
.radio-label {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    cursor: pointer;
    font-size: var(--font-size-sm);
}
.radio-label input[type="radio"] {
    width: 18px;
    height: 18px;
    cursor: pointer;
}

.checkbox-label {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    cursor: pointer;
    font-size: var(--font-size-sm);
}
.checkbox-label input[type="checkbox"] {
    width: 18px;
    height: 18px;
    cursor: pointer;
}

.requisites-ukraine-card {
    border-left: 4px solid var(--color-primary);
}

.connected-account {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--spacing-md) 0;
    border-bottom: var(--border-width-thin) solid var(--color-border);
}
.connected-account:last-child {
    border-bottom: none;
}
.connected-account-actions {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    min-width: 84px;
    justify-content: flex-end;
}
.connected-account-info {
    display: flex;
    align-items: center;
    gap: var(--spacing-md);
}
.connected-account-icon {
    flex-shrink: 0;
}
.connected-account-details {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-xs);
}
.connected-account-name {
    font-weight: var(--font-weight-medium);
    color: var(--color-secondary);
}
.connected-account-status {
    font-size: var(--font-size-xs);
}
.connected-account-status.connected {
    color: var(--color-success);
}
.connected-account-status.not-connected {
    color: var(--color-text-muted);
}
.connected-account-status.warning {
    color: var(--color-warning);
}
.btn-activate {
    background: var(--color-warning-bg);
    color: var(--color-warning-dark);
}
.btn-activate:hover {
    background: var(--color-background);
}
.telegram-activation-warning {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    padding: var(--spacing-sm) var(--spacing-md);
    background: var(--color-warning-bg);
    border-radius: var(--border-radius-sm);
    font-size: var(--font-size-xs);
    color: var(--color-warning-dark);
    margin-top: var(--spacing-md);
}
.telegram-activation-warning a {
    color: var(--color-info);
    font-weight: var(--font-weight-medium);
}
/* ============================================================================
   Icon buttons (.btn-icon) — ЕДИНЫЙ КАНОН (TD-63: перенесено из header-buttons.css).
   Форма = КРУГ (border-radius:50%); цвета на токенах --button-icon-* (редактируются
   через брендбук-редактор / /admin/styles). Размер: .btn-sm = круг 28px (ниже).
   ============================================================================ */
.btn-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--button-icon-size-md, 36px);
    height: var(--button-icon-size-md, 36px);
    padding: 0;
    border-radius: 50%;
    background: var(--button-icon-background, var(--color-card-background));
    color: var(--button-icon-text, var(--color-text-muted));
    border: var(--border-width-thin) solid var(--button-icon-border, var(--color-border));
    box-shadow: var(--button-icon-shadow, none);
    cursor: pointer;
    transition: var(--transition-fast);
    text-decoration: none;
    line-height: 1;
}
.btn-icon:hover {
    background: var(--button-icon-hover, var(--color-card-background));
    color: var(--button-icon-hover-text, var(--color-primary));
    border-color: var(--button-icon-hover-border, var(--color-primary));
    box-shadow: var(--button-icon-hover-shadow, none);
    text-decoration: none;
}
.btn-icon:active {
    background: var(--button-icon-active, var(--color-background));
    color: var(--button-icon-active-text, var(--color-primary));
    border-color: var(--button-icon-active-border, var(--color-primary));
    box-shadow: var(--button-icon-active-shadow, none);
}
.btn-icon svg {
    display: block;
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

/* Плоская icon-кнопка (TD-63 шаг 5, категория D) — раскладка иконки без фона/рамки/фикс-формы.
   Для паразитов, бравших .btn-icon только ради display:flex (clear/close/burger и т.п.);
   размер/позицию задаёт companion-класс. */
.btn-icon-ghost {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    background: none;
    border: none;
    color: var(--color-text-muted);
    cursor: pointer;
    transition: var(--transition-fast);
    line-height: 1;
}
.btn-icon-ghost:hover {
    color: var(--color-primary);
}
.btn-icon-ghost svg {
    display: block;
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

/* File-preview remove × — компактный flat-контрол удаления на чипах файлов/вложений.
   База вида — .btn-icon-ghost (bg/border:none, muted, flat). Companion даёт компактную
   глиф-геометрию + delete-аффорданс (hover→danger). Единый канон (был дублирован inline
   в 5 шаблонах/page-CSS, TD-63 дрейф). Тёмный кружок-удаления в чате — отдельный класс
   .msg-attach-remove (conversation), коллизия имён устранена. */
.btn-icon-ghost.file-preview-remove {
    font-size: var(--font-size-lg);
    padding: 0 var(--spacing-xs);
    border-radius: var(--border-radius-sm);
}
.btn-icon-ghost.file-preview-remove:hover {
    color: var(--color-danger, var(--color-error));
}

/* CRM delete/unlink icon-кнопки: ghost-база (.btn-icon-ghost) + danger-аффорданс на hover.
   Единый канон (был дублирован inline на CRM-страницах, TD-63 дрейф crm-icon-btn). */
.btn-icon-ghost.crm-icon-danger:hover {
    color: var(--color-danger, var(--color-error));
}

/* Icon button size modifier (ось размера ⟂ форме — radius наследуется от .btn-icon).
   Маленькая icon-кнопка = .btn-icon.btn-sm = круг 28px. */
/* Extra-small size — круг 20px (TD-63 шаг 5, для мелких overlay-кнопок) */
.btn-icon.btn-xs {
    width: var(--button-icon-size-xs, 20px);
    height: var(--button-icon-size-xs, 20px);
    padding: 0; /* обнулить padding общего .btn-xs в icon-контексте */
}
.btn-icon.btn-xs svg {
    width: 12px;
    height: 12px;
}
.btn-icon.btn-sm {
    width: var(--button-icon-size-sm, 28px);
    height: var(--button-icon-size-sm, 28px);
    padding: 0; /* обнулить padding общего .btn-sm — иначе border-box распирает в овал */
}
.btn-icon.btn-sm svg {
    width: 14px;
    height: 14px;
}
/* Large size — круг 40px (канон крупной круглой кнопки действия, TD-63 консолидация круг-40) */
.btn-icon.btn-lg {
    width: var(--button-icon-size-lg, 40px);
    height: var(--button-icon-size-lg, 40px);
    min-height: unset; /* сбросить min-height общего .btn-lg в icon-контексте — иначе круг растягивается в овал (TD-63) */
    padding: 0; /* обнулить padding общего .btn-lg в icon-контексте */
}
.btn-icon.btn-lg svg {
    width: 20px;
    height: 20px;
}
/* Extra-large size — 52px (крупная квадрат-карточка-кнопка действия, напр. документы объекта; TD-63 категория B) */
.btn-icon.btn-xl {
    width: var(--button-icon-size-xl, 52px);
    height: var(--button-icon-size-xl, 52px);
    min-height: unset; /* сбросить min-height общего .btn-lg/.btn-xl в icon-контексте */
    padding: 0;
}
.btn-icon.btn-xl svg {
    width: 24px;
    height: 24px;
}
/* XXL size — 72px (большой круг, напр. btn-icon-only; TD-63 ось размера) */
.btn-icon.btn-xxl {
    width: var(--button-icon-size-xxl, 72px);
    height: var(--button-icon-size-xxl, 72px);
    min-height: unset; /* сбросить min-height общего размера в icon-контексте */
    padding: 0;
}
.btn-icon.btn-xxl svg {
    /* svg 20px намеренно (совпадает с .btn-icon-only svg) — нулевая регрессия иконки при будущей консолидации btn-icon-only на ось размера; НЕ «исправлять» на 24px ради консистентности с xl */
    width: 20px;
    height: 20px;
}

/* ============================================================================
   Icon button SHAPE modifier (ось формы ⟂ размеру/цвету — TD-63 категория B).
   .btn-icon = круг (база); .btn-icon-rounded-* переопределяет круг на
   ПРЯМОУГОЛЬНЫЙ скруглённый квадрат по шкале токенов брендбука «Тіні та
   заокруглення» (--border-radius-sm 4px / --border-radius 8px / -lg 12px).
   Для квадратных icon-кнопок действий (фильтр/таблица/тулбар), где круг не
   уместен. Размер по-прежнему задаёт .btn-sm/.btn-lg, цвет — .btn-icon-{variant}.
   ============================================================================ */
.btn-icon-rounded-sm {
    border-radius: var(--border-radius-sm);
}
.btn-icon-rounded {
    border-radius: var(--border-radius);
}
.btn-icon-rounded-lg {
    border-radius: var(--border-radius-lg);
}

/* ⚠️ Цвет-варианты ниже задают ТОЛЬКО background/color/border-color/box-shadow —
   НЕ добавлять сюда border-radius: форму задаёт ось формы выше (.btn-icon-rounded-*).
   Border-radius в цвет-варианте вернул бы круг поверх .btn-icon-rounded-* (равная
   специфичность 0,1,0, цвет-вариант идёт позже → победил бы). */
/* Primary variant */
.btn-icon-primary {
    background: var(--button-icon-primary-background, var(--color-info-bg));
    color: var(--button-icon-primary-text, var(--color-primary));
    border-color: var(--button-icon-primary-border, var(--color-info-bg));
    box-shadow: var(--button-icon-primary-shadow, none);
}
.btn-icon-primary:hover {
    background: var(--button-icon-primary-hover, var(--color-primary));
    color: var(--button-icon-primary-hover-text, var(--color-white, #fff));
    border-color: var(--button-icon-primary-hover-border, var(--color-primary));
    box-shadow: var(--button-icon-primary-hover-shadow, none);
}
.btn-icon-primary:active {
    background: var(--button-icon-primary-active, var(--color-primary-hover));
    color: var(--button-icon-primary-active-text, var(--color-white, #fff));
    border-color: var(--button-icon-primary-active-border, var(--color-primary-hover));
    box-shadow: var(--button-icon-primary-active-shadow, none);
}

/* Secondary / Outline variant */
.btn-icon-secondary,
.btn-icon-outline {
    background: var(--button-icon-secondary-background, var(--color-background));
    color: var(--button-icon-secondary-text, var(--color-text-muted));
    border-color: var(--button-icon-secondary-border, var(--color-border));
    box-shadow: var(--button-icon-secondary-shadow, none);
}
.btn-icon-secondary:hover,
.btn-icon-outline:hover {
    background: var(--button-icon-secondary-hover, var(--color-secondary));
    color: var(--button-icon-secondary-hover-text, var(--color-white, #fff));
    border-color: var(--button-icon-secondary-hover-border, var(--color-secondary));
    box-shadow: var(--button-icon-secondary-hover-shadow, none);
}
.btn-icon-secondary:active,
.btn-icon-outline:active {
    background: var(--button-icon-secondary-active, var(--color-secondary-hover));
    color: var(--button-icon-secondary-active-text, var(--color-white, #fff));
    border-color: var(--button-icon-secondary-active-border, var(--color-secondary-hover));
    box-shadow: var(--button-icon-secondary-active-shadow, none);
}

/* Danger / Delete variant */
.btn-icon-danger,
.btn-icon-delete {
    background: var(--button-icon-danger-background, var(--color-error-bg));
    color: var(--button-icon-danger-text, var(--color-error));
    border-color: var(--button-icon-danger-border, var(--color-border));
    box-shadow: var(--button-icon-danger-shadow, none);
}
.btn-icon-danger:hover,
.btn-icon-delete:hover {
    background: var(--button-icon-danger-hover, var(--color-error));
    color: var(--button-icon-danger-hover-text, var(--color-white, #fff));
    border-color: var(--button-icon-danger-hover-border, var(--color-error));
    box-shadow: var(--button-icon-danger-hover-shadow, none);
}
.btn-icon-danger:active,
.btn-icon-delete:active {
    background: var(--button-icon-danger-active, var(--color-error-dark, var(--color-error)));
    color: var(--button-icon-danger-active-text, var(--color-white, #fff));
    border-color: var(--button-icon-danger-active-border, var(--color-error-dark, var(--color-error)));
    box-shadow: var(--button-icon-danger-active-shadow, none);
}

/* Save variant — primary filled form action */
.btn-icon-save {
    background: var(--button-icon-save-background, var(--color-primary));
    color: var(--button-icon-save-text, var(--button-primary-text, var(--color-white, #fff)));
    border-color: var(--button-icon-save-border, var(--color-primary));
    box-shadow: var(--button-icon-save-shadow, none);
}
.btn-icon-save:hover {
    background: var(--button-icon-save-hover, var(--color-card-background));
    color: var(--button-icon-save-hover-text, var(--color-primary));
    border-color: var(--button-icon-save-hover-border, var(--color-primary));
    box-shadow: var(--button-icon-save-hover-shadow, none);
}
.btn-icon-save:active {
    background: var(--button-icon-save-active, var(--color-primary-hover));
    color: var(--button-icon-save-active-text, var(--color-white, #fff));
    border-color: var(--button-icon-save-active-border, var(--color-primary-hover));
    box-shadow: var(--button-icon-save-active-shadow, none);
}

/* Cancel variant — error on hover */
.btn-icon-cancel {
    background: var(--button-icon-cancel-background, var(--color-card-background));
    color: var(--button-icon-cancel-text, var(--color-text-muted));
    border-color: var(--button-icon-cancel-border, var(--color-border));
    box-shadow: var(--button-icon-cancel-shadow, none);
}
.btn-icon-cancel:hover {
    background: var(--button-icon-cancel-hover, var(--color-error-bg));
    color: var(--button-icon-cancel-hover-text, var(--color-error));
    border-color: var(--button-icon-cancel-hover-border, var(--color-error));
    box-shadow: var(--button-icon-cancel-hover-shadow, none);
}
.btn-icon-cancel:active {
    background: var(--button-icon-cancel-active, var(--color-error));
    color: var(--button-icon-cancel-active-text, var(--color-white, #fff));
    border-color: var(--button-icon-cancel-active-border, var(--color-error));
    box-shadow: var(--button-icon-cancel-active-shadow, none);
}

/* Warning variant */
.btn-icon-warning {
    background: var(--button-icon-warning-background, var(--color-warning-bg, #fff8e1));
    color: var(--button-icon-warning-text, var(--color-warning));
    border-color: var(--button-icon-warning-border, var(--color-border));
    box-shadow: var(--button-icon-warning-shadow, none);
}
.btn-icon-warning:hover {
    background: var(--button-icon-warning-hover, var(--color-warning));
    color: var(--button-icon-warning-hover-text, var(--color-white, #fff));
    border-color: var(--button-icon-warning-hover-border, var(--color-warning));
    box-shadow: var(--button-icon-warning-hover-shadow, none);
}
.btn-icon-warning:active {
    background: var(--button-icon-warning-active, var(--color-warning-dark, var(--color-warning)));
    color: var(--button-icon-warning-active-text, var(--color-white, #fff));
    border-color: var(--button-icon-warning-active-border, var(--color-warning-dark, var(--color-warning)));
    box-shadow: var(--button-icon-warning-active-shadow, none);
}

/* Success / Action / Refresh / Add variant */
.btn-icon-success,
.btn-icon-action,
.btn-icon-refresh,
.btn-icon-add {
    background: var(--button-icon-success-background, var(--color-success-bg));
    color: var(--button-icon-success-text, var(--color-success));
    border-color: var(--button-icon-success-border, var(--color-success-bg));
    box-shadow: var(--button-icon-success-shadow, none);
}
.btn-icon-action:hover,
.btn-icon-refresh:hover,
.btn-icon-add:hover {
    background: var(--button-icon-success-hover, var(--color-success));
    color: var(--button-icon-success-hover-text, var(--color-white, #fff));
    border-color: var(--button-icon-success-hover-border, var(--color-success));
    box-shadow: var(--button-icon-success-hover-shadow, none);
}
.btn-icon-action:active,
.btn-icon-refresh:active,
.btn-icon-add:active {
    background: var(--button-icon-success-active, var(--color-success-dark, var(--color-success)));
    color: var(--button-icon-success-active-text, var(--color-white, #fff));
    border-color: var(--button-icon-success-active-border, var(--color-success-dark, var(--color-success)));
    box-shadow: var(--button-icon-success-active-shadow, none);
}
/* Refresh spins on hover */
.btn-icon-refresh:hover {
    transform: rotate(90deg);
}

/* Info variant */
.btn-icon-info {
    background: var(--button-icon-info-background, var(--color-info-bg));
    color: var(--button-icon-info-text, var(--color-info));
    border-color: var(--button-icon-info-border, var(--color-info-bg));
    box-shadow: var(--button-icon-info-shadow, none);
}
.btn-icon-info:hover {
    background: var(--button-icon-info-hover, var(--color-info));
    color: var(--button-icon-info-hover-text, var(--color-white, #fff));
    border-color: var(--button-icon-info-hover-border, var(--color-info));
    box-shadow: var(--button-icon-info-hover-shadow, none);
}
.btn-icon-info:active {
    background: var(--button-icon-info-active, var(--color-info-dark, var(--color-info)));
    color: var(--button-icon-info-active-text, var(--color-white, #fff));
    border-color: var(--button-icon-info-active-border, var(--color-info-dark, var(--color-info)));
    box-shadow: var(--button-icon-info-active-shadow, none);
}

/* ============================================================================
   Overlay icon button (TD-63 шаг 5) — icon-кнопка ПОВЕРХ медиа (фото/превью).
   Зачем отдельный модификатор: обычные цвет-варианты (.btn-icon-danger и т.п.)
   дают СВЕТЛЫЙ тон по умолчанию — на фотографии он не виден. Overlay даёт
   сплошной card-фон для контраста + тень-отрыв + scale на hover.

   Оси (ортогонально):
     размер  — .btn-sm / .btn-xs / base / .btn-lg
     цвет    — .btn-icon-{danger|warning|primary|info|success}
   Цвет-вариант на overlay = ЦВЕТНАЯ ИКОНКА на card-фоне → ЗАЛИТЫЙ цвет + белая
   иконка на hover (compound 0,2,0 надёжно бьёт светлый вариант 0,1,0 ниже).

   Reveal (opacity:0 + показ по hover контейнера) — НЕ часть кнопки: задаётся
   контейнером (напр. .photo-card:hover .btn-icon-overlay { opacity: 1 }), т.к.
   зависит от селектора контейнера и нужен не везде (drive-бейдж виден всегда).
   ============================================================================ */
.btn-icon-overlay {
    background: var(--color-card-background);
    color: var(--color-text-muted);
    border: none;
    box-shadow: var(--shadow-overlay);
}
.btn-icon-overlay:hover {
    transform: scale(1.08);
    border: none; /* страховка инварианта: цвет-вариант не должен вернуть рамку на hover */
    box-shadow: var(--shadow-overlay);
}
/* Цвет на overlay = те же редактируемые токены --button-icon-{variant}-* что и у
   обычных вариантов (тема из /admin/styles следует за overlay). Отличие overlay —
   только сплошной card-фон по умолчанию (вместо светлого тона варианта). */
.btn-icon-overlay.btn-icon-danger {
    background: var(--color-card-background);
    color: var(--button-icon-danger-text, var(--color-error));
}
.btn-icon-overlay.btn-icon-danger:hover {
    background: var(--button-icon-danger-hover, var(--color-error));
    color: var(--button-icon-danger-hover-text, var(--color-white, #fff));
}
.btn-icon-overlay.btn-icon-warning {
    background: var(--color-card-background);
    color: var(--button-icon-warning-text, var(--color-warning));
}
.btn-icon-overlay.btn-icon-warning:hover {
    background: var(--button-icon-warning-hover, var(--color-warning));
    color: var(--button-icon-warning-hover-text, var(--color-white, #fff));
}
.btn-icon-overlay.btn-icon-primary {
    background: var(--color-card-background);
    color: var(--button-icon-primary-text, var(--color-primary));
}
.btn-icon-overlay.btn-icon-primary:hover {
    background: var(--button-icon-primary-hover, var(--color-primary));
    color: var(--button-icon-primary-hover-text, var(--color-white, #fff));
}
.btn-icon-overlay.btn-icon-info {
    background: var(--color-card-background);
    color: var(--button-icon-info-text, var(--color-info));
}
.btn-icon-overlay.btn-icon-info:hover {
    background: var(--button-icon-info-hover, var(--color-info));
    color: var(--button-icon-info-hover-text, var(--color-white, #fff));
}
.btn-icon-overlay.btn-icon-success {
    background: var(--color-card-background);
    color: var(--button-icon-success-text, var(--color-success));
}
.btn-icon-overlay.btn-icon-success:hover {
    background: var(--button-icon-success-hover, var(--color-success));
    color: var(--button-icon-success-hover-text, var(--color-white, #fff));
}

/* Вертикально-центрированная overlay-кнопка (prev/next в абсолюте top:50%): несёт
   translateY-центрирование И совмещает его со scale-hover оверлея — чтобы viewer'ы
   (lightbox/gallery/carousel) НЕ дублировали это правило у себя. Контейнер задаёт
   только position/top/left/right. :hover (0,2,0) идёт ПОСЛЕ .btn-icon-overlay:hover
   (0,2,0) → выигрывает по порядку каскада, сохраняя translateY под scale. */
.btn-icon-overlay--centered {
    transform: translateY(-50%);
}
.btn-icon-overlay--centered:hover {
    transform: translateY(-50%) scale(1.08);
}

/* Disabled state */
.btn-icon:disabled,
.btn-icon.btn-icon-disabled {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;
}

/* Form actions row — обёртка для compact save/cancel icon-кнопок (_form_icon_buttons.html.twig).
   Перенесено из header-buttons.css (TD-63). */
.form-actions-icons,
.ui-form-actions-icons {
    display: flex;
    justify-content: flex-end;
    gap: var(--spacing-sm);
    margin-top: var(--spacing-md);
}

/* Stat badge — счётчик элементов в шапке списков. Перенесено из header-buttons.css (TD-63). */
.stat-badge, .ui-badge--stat {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 40px;
    min-width: 40px;
    padding: 0 var(--spacing-sm);
    margin: 0;
    vertical-align: middle;
    background: var(--color-background, #f0f2f5);
    color: var(--color-text-muted);
    border: var(--border-width-thin) solid var(--color-border);
    border-radius: var(--border-radius);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-bold);
    line-height: 32px;
    box-shadow: none;
    cursor: default;
    text-align: center;
}
.stat-badge:hover, .ui-badge--stat:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-card-hover);
}
/* Stat badge warning — для непрочитанных сообщений */
.stat-badge-warning, .ui-badge--stat-warning {
    background: var(--color-badge-warning, #fff8e1);
    color: var(--color-warning-dark, #e65100);
}

/* TD-64 W4 — полная палитра stat-вариантов */
.stat-badge-primary, .ui-badge--stat-primary { background: var(--color-badge-primary); color: var(--button-primary-text); }
.stat-badge-success, .ui-badge--stat-success { background: var(--color-badge-success); color: var(--color-success); }
.stat-badge-info, .ui-badge--stat-info { background: var(--color-badge-info); color: var(--color-info); }
.stat-badge-danger, .ui-badge--stat-danger { background: var(--color-badge-danger); color: var(--button-primary-text); }
.stat-badge-secondary, .ui-badge--stat-secondary { background: var(--color-secondary-bg, #e9ecef); color: var(--color-text-secondary, #6c757d); }

/* TD-64 W4 — кликабельный badge-индикатор (бывш. property-badge) */
.ui-badge--action {
    cursor: pointer;
    border: none;
    transition: box-shadow var(--transition-duration-default);
}
.ui-badge--action-danger:hover { box-shadow: var(--shadow-error); }
.ui-badge--action-warning:hover { box-shadow: var(--shadow-warning); }

/* Icon + Text button (.btn-with-text) — иконка + видимый текст (вместо .btn-icon, без
   паразитного овала). Авто-ширина по контенту. Перенесено из header-buttons.css (TD-63). */
.btn-with-text {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-sm);
    padding: var(--spacing-sm) var(--spacing-md);
    height: auto;
    width: auto;
    background: var(--button-icon-background, var(--color-card-background));
    color: var(--button-icon-text, var(--color-text-muted));
    border: var(--border-width-thin) solid var(--button-icon-border, var(--color-border));
    border-radius: var(--border-radius, 8px);
    cursor: pointer;
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    line-height: 1;
    text-decoration: none;
    transition: color var(--transition-duration-fast), opacity var(--transition-duration-fast);
    box-shadow: none;
}
.btn-with-text:hover {
    color: var(--button-icon-hover-text, var(--color-primary));
    border-color: var(--button-icon-hover-border, var(--color-primary));
    background: var(--button-icon-background, var(--color-card-background));
    text-decoration: none;
    opacity: 0.9;
}
.btn-with-text:active {
    color: var(--button-icon-active-text, var(--color-primary));
    border-color: var(--button-icon-active-border, var(--color-primary));
    opacity: 0.8;
}
.btn-with-text svg {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

/* ============================================================================
   Icon-only button (circle on .btn) — отдельный ЛЕГИТИМНЫЙ компонент «circle»
   (брендбук-секция). По ADR-0008 — самодостаточная база, НЕ сводится на ось .btn-icon
   (TD-63 #1 закрыт won't-do: ось .btn-icon светлая, залитого вида не даёт).
   Перенесено из header-buttons.css (TD-63). Цвета — общие токены --button-{variant}-* (FILLED).
   Размер 72px (= --button-min-height; ось --button-icon-size-xxl даёт ту же величину).
   Double specificity (.btn.btn-icon-only) defeats later .btn rules in base.css. ============ */
.btn-icon-only,
.btn.btn-icon-only,
a.btn.btn-icon-only {
    width: var(--button-min-height, 72px);
    height: var(--button-min-height, 72px);
    min-height: unset;
    padding: 0;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    line-height: 1;
}
.btn-icon-only svg,
.btn.btn-icon-only svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}
/* Компактный размер — icon-only кнопка в одном ряду с .btn-icon/.header-btn
   (шапка страницы): без него .btn-icon-only тянет --button-min-height (72px). */
.btn-sm.btn-icon-only,
.btn.btn-sm.btn-icon-only,
a.btn.btn-sm.btn-icon-only {
    width: var(--button-icon-size-md, 36px);
    height: var(--button-icon-size-md, 36px);
    min-width: var(--button-icon-size-md, 36px);
}
.btn-sm.btn-icon-only svg,
.btn.btn-sm.btn-icon-only svg {
    width: 16px;
    height: 16px;
}
.btn.btn-icon-only.btn-primary {
    background-color: var(--button-primary-background);
    color: var(--button-primary-text, var(--color-white, #fff));
    border: var(--border-width-normal) solid var(--button-primary-border, transparent);
    box-shadow: var(--button-primary-shadow, none);
}
.btn.btn-icon-only.btn-primary:hover {
    background-color: var(--button-primary-hover);
    border-color: var(--button-primary-hover-border, transparent);
    box-shadow: var(--button-primary-hover-shadow, none);
}
.btn.btn-icon-only.btn-primary:active {
    background-color: var(--button-primary-active, var(--button-primary-hover));
    border-color: var(--button-primary-active-border, transparent);
    box-shadow: var(--button-primary-active-shadow, none);
}
.btn.btn-icon-only.btn-secondary {
    background-color: var(--button-secondary-background, var(--color-white, #fff));
    color: var(--button-secondary-text, var(--color-text, #333));
    border: var(--border-width-normal) solid var(--button-secondary-border, var(--color-border, #ddd));
    box-shadow: var(--button-secondary-shadow, none);
}
.btn.btn-icon-only.btn-secondary:hover {
    background-color: var(--button-secondary-hover, var(--color-background));
    border-color: var(--button-secondary-hover-border, var(--color-border, #ccc));
    box-shadow: var(--button-secondary-hover-shadow, none);
}
.btn.btn-icon-only.btn-secondary:active {
    background-color: var(--button-secondary-active, var(--button-secondary-hover));
    border-color: var(--button-secondary-active-border, var(--color-border, #bbb));
    box-shadow: var(--button-secondary-active-shadow, none);
}
.btn.btn-icon-only.btn-danger {
    background-color: var(--button-danger-background);
    color: var(--button-danger-text, var(--color-white, #fff));
    border: var(--border-width-normal) solid var(--button-danger-border, transparent);
    box-shadow: var(--button-danger-shadow, none);
}
.btn.btn-icon-only.btn-danger:hover {
    background-color: var(--button-danger-hover);
    border-color: var(--button-danger-hover-border, transparent);
    box-shadow: var(--button-danger-hover-shadow, none);
}
.btn.btn-icon-only.btn-success {
    background-color: var(--button-success-background);
    color: var(--button-success-text, var(--color-white, #fff));
    border: var(--border-width-normal) solid var(--button-success-border, transparent);
    box-shadow: var(--button-success-shadow, none);
}
.btn.btn-icon-only.btn-success:hover {
    background-color: var(--button-success-hover);
    border-color: var(--button-success-hover-border, transparent);
    box-shadow: var(--button-success-hover-shadow, none);
}
.btn.btn-icon-only.btn-warning {
    background-color: var(--button-warning-background);
    color: var(--button-warning-text, var(--color-white, #fff));
    border: var(--border-width-normal) solid var(--button-warning-border, transparent);
    box-shadow: var(--button-warning-shadow, none);
}
.btn.btn-icon-only.btn-warning:hover {
    background-color: var(--button-warning-hover);
    border-color: var(--button-warning-hover-border, transparent);
    box-shadow: var(--button-warning-hover-shadow, none);
}
.btn-icon-only:disabled,
.btn.btn-icon-only:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ============================================================================
   Header buttons (.header-btn) — глобальный компонент «кнопка заголовка страницы»
   (page-header action: help/print/invite/menu). Перенесено из header-buttons.css (TD-63).
   Размер задаёт --header-btn-size (52px, макет Welcome Banner — TASK-526b); цвета базы
   на токенах --button-header-* (вариант "header" в брендбуке, редактируется через
   брендбук-редактор), цвета вариантов — на канон-переменных --color-*. Используется в ~19 шаблонах.
   ============================================================================ */
/* --header-btn-size объявлена на общем селекторе, а не только внутри .header-btn:
   переменную читают ещё три места, которые НЕ являются потомками .header-btn —
   переключатель языка (.language-switcher-toggle, канон-секция ниже в этом файле),
   текстовые .btn-sm/.ui-button--sm в .page-header-right (ниже в этом файле) и кнопка
   входа в шапке лендинга (.lp3-btn-login, assets/styles/pages/landing-base.css).
   Двум последним переменная объявлена на них самих, а не на контейнере: обе читают её
   собственным правилом, и при переносе разметки в другой контейнер высота не должна
   молча пропадать. Значение 52px объявлено ровно один раз здесь; все остальные места
   читают var() без литерального fallback-дубля (TASK-526b). */
.app-welcome-banner__actions, .page-header-right, .header-btn,
.language-switcher-toggle, .lp3-btn-login { --header-btn-size: 52px; }
.header-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* width НЕ фиксируем: голые/with-text кнопки растут по контенту;
       квадратные .header-btn.icon-only берут ту же переменную как width. */
    height: var(--header-btn-size);
    min-width: var(--header-btn-size);
    flex-shrink: 0;
    margin: 0;
    vertical-align: top;
    background: var(--button-header-background, var(--color-primary));
    color: var(--button-header-text, var(--color-white, #fff));
    border: var(--border-width-thin) solid var(--button-header-border, transparent);
    /* TASK-526b: единый радиус 8px у всех форм (макет) — прежнее деление
       «круг у .icon-only / pill у .with-text» ушло вместе с круглыми кнопками. */
    border-radius: var(--border-radius-sm);
    cursor: pointer;
    transition: var(--transition-default, all 0.2s ease);
    text-decoration: none;
    box-shadow: var(--button-header-shadow);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-normal);
    /* 1.25 → строка 20px: padding 16 + 20 + 16 = ровно 52px высоты кнопки.
       При 1.5 (24px) контент выходил за padding и вертикальный отступ не работал. */
    line-height: var(--line-height-tight);
}
.header-btn:hover {
    background: var(--button-header-hover, var(--color-primary-hover, var(--color-primary)));
    box-shadow: var(--button-header-hover-shadow);
    color: var(--button-header-hover-text, var(--color-white, #fff));
    border-color: var(--button-header-hover-border, transparent);
    text-decoration: none;
}
.header-btn:active {
    background: var(--button-header-active, var(--color-primary-hover, var(--color-primary)));
    color: var(--button-header-active-text, var(--color-white, #fff));
    border-color: var(--button-header-active-border, transparent);
    box-shadow: var(--button-header-active-shadow);
}
/* Кнопка с текстом и иконкой (property menu).
   .header-btn--with-text — BEM-модификатор, который ui_header_button вешает
   автоматически при заданном text (TASK-456: «+ Новий варіант» по макету —
   зазор между иконкой и текстом, горизонтальный padding). */
.header-btn.with-text,
.header-btn--with-text {
    width: auto;
    padding: 0 var(--spacing-md);
    gap: var(--spacing-sm);
}
.header-btn.with-text svg {
    width: 24px;
    height: 24px;
}
/* Квадратная icon-кнопка 52×52 (TASK-526b: была круглой 40×40).
   Симметричный padding + квадратный бокс ОБЯЗАТЕЛЬНЫ, чтобы бокс не распирало.
   Легаси space-класс .header-btn.icon-only остаётся для _help_button/billing-topbar;
   нативный BEM .header-btn--icon-only — для ui_header_button (TD-64). */
.header-btn.icon-only, .header-btn--icon-only {
    box-sizing: border-box;
    width: var(--header-btn-size);
    height: var(--header-btn-size);
    min-width: var(--header-btn-size);
    padding: var(--spacing-sm);
}
.header-btn.icon-only svg, .header-btn--icon-only svg {
    width: 24px;
    height: 24px;
}
/* Outline variant — белая плашка с СИНЕЙ рамкой (в макете так же оформлен outline-бейдж:
   border 1px #4F7CF7). Раньше был прозрачным и выпадал из ряда кнопок шапки; серая рамка
   сделала бы его неотличимым от default, поэтому отличие несёт именно рамка, а не только
   цвет иконки — часть S3-иконок игнорирует currentColor (жёсткий stroke на <path>).
   Форму 52×52/радиус задаёт .header-btn--icon-only; --primary/--danger форму НЕ дублируют.
   (TD-64 W13.6c: legacy дуал-класс .header-btn.ui-button--outline удалён — только нативный BEM.) */
.header-btn--outline {
    box-sizing: border-box;
    width: var(--header-btn-size);
    height: var(--header-btn-size);
    min-width: var(--header-btn-size);
    padding: 0;
    background: var(--color-white, #fff);
    color: var(--color-primary);
    border: var(--border-width-thin) solid var(--color-primary);
}
.header-btn--outline:hover {
    background: var(--color-primary-bg);
    color: var(--color-primary);
    border-color: var(--color-primary);
}
/* Primary variant (TD-64 W13.6) — нативный header-btn--primary вместо legacy .ui-button--primary дуал-класса.
   TASK-526b: берёт канон-синий --color-primary, а не оранжевый --button-primary-background:
   в макете primary-кнопка шапки («Редагувати») — #4F7CF7. */
.header-btn--primary {
    background-color: var(--color-primary);
    color: var(--color-white, #fff);
    border: var(--border-width-thin) solid transparent;
    box-shadow: none;
}
.header-btn--primary:hover {
    background-color: var(--color-primary-hover);
    color: var(--color-white, #fff);
    border-color: transparent;
}
/* Базовый .header-btn:active (0,2,0) перебивает вариант (0,1,0) — без этих правил
   синяя кнопка при нажатии перекрашивалась в светлую вторичную. */
.header-btn.header-btn--primary:active {
    background-color: var(--color-primary-hover);
    color: var(--color-white, #fff);
    border-color: transparent;
}
/* Danger variant (TD-64 W13.6). TASK-526b: как и --primary, опирается на канон-переменные,
   а не на группу --button-danger-* — чтобы у всех вариантов компонента был ОДИН источник цвета. */
.header-btn--danger {
    background-color: var(--color-error);
    color: var(--color-white, #fff);
    border: var(--border-width-thin) solid transparent;
    box-shadow: none;
}
.header-btn--danger:hover {
    background-color: var(--color-error-dark);
    color: var(--color-white, #fff);
    border-color: transparent;
}
.header-btn.header-btn--danger:active {
    background-color: var(--color-error-dark);
    color: var(--color-white, #fff);
    border-color: transparent;
}
/* Text buttons in page-header — та же высота, что у .header-btn (TASK-526b: 40 → 52).
   --header-btn-size объявлена и на .page-header-right, и на .app-welcome-banner__actions
   (см. секцию Header buttons выше), но правило высоты изначально было написано только для
   первой зоны — в welcome-баннере текстовые кнопки оставались 41px рядом с квадратами 52px. */
.page-header-right .btn-sm,
.page-header-right .ui-button--sm,
.app-welcome-banner__actions .btn,
.app-welcome-banner__actions .ui-button--sm {
    height: var(--header-btn-size);
    line-height: 1;
    display: inline-flex;
    align-items: center;
    vertical-align: top;
}
/* Геометрия ряда кнопок welcome-баннера, добиваемая поверх высоты. Три сброса базы .btn
   (секция BUTTONS в начале файла), из-за которых текстовая кнопка выпадала из ряда:
     width: 100%           → растягивалась на всю строку шапки, отжимая иконочные кнопки вниз;
     min-height/min-width  → 72px, выше ряда 52px (.btn-sm гасит только min-height, и лишь у себя);
     border-radius         → --border-radius (16px) против 8px у .header-btn и переключателя языка.
   Селектор берёт .btn целиком, а не только .btn-sm: в баннере есть вызовы ui_button() без size.
   .page-header-right сюда намеренно не включён — там своя, более старая раскладка, и её вид
   менять не требовалось. Переключатель языка задаёт ту же геометрию сам (канон-секция ниже),
   поэтому в баннере оба блока дают совпадающий результат. */
.app-welcome-banner__actions .btn,
.app-welcome-banner__actions .ui-button--sm {
    width: auto;
    min-width: 0;
    min-height: 0;
    border-radius: var(--border-radius-sm);
}
/* ============================================================================
   Language switcher toggle (_layout/_language_switcher.html.twig) — единый вид
   во всех лейаутах: welcome-баннер app-лейаута, топбар base.html.twig, шапка
   лендинга (lp3) и external-лейаут. До этого правило жило page-scoped в
   assets/styles/pages/app-base.css под .app-welcome-banner__actions, поэтому на
   гостевых страницах переключатель оставался дефолтной .btn.btn-secondary.btn-sm
   (радиус 16px, min-width 72px, шрифт 14px) — вид зависел от лейаута, хотя шаблон
   один. Сбросы width/min-* гасят базу .btn (width: 100%, min-height/min-width 72px),
   которая иначе распирает кнопку в шапке.
   ============================================================================ */
.language-switcher-toggle {
    width: auto;
    min-width: 0;
    min-height: 0;
    height: var(--header-btn-size);
    padding: var(--spacing-md);
    gap: 12px;
    border: var(--border-width-thin) solid var(--color-input-border);
    border-radius: var(--border-radius-sm);
    background: var(--color-white);
    color: var(--color-text);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-normal);
    line-height: var(--line-height-tight);
}
.language-switcher-toggle:hover {
    background: var(--color-primary-bg);
    border-color: var(--color-primary);
    color: var(--color-primary);
}
/* SVG всегда stroke currentColor */
.header-btn svg {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.btn-connect {
    background: var(--color-success-bg);
    color: var(--color-success);
}
.btn-connect:hover {
    background: var(--color-success);
    color: var(--color-white, #fff);
}
.btn-disconnect {
    background: var(--color-background);
    color: var(--color-error);
}
.btn-disconnect:hover {
    background: var(--color-error);
    color: var(--color-white, #fff);
}
.btn-test {
    background: var(--color-info-bg);
    color: var(--color-info);
}
.btn-test:hover {
    background: var(--color-info);
    color: var(--color-white, #fff);
}
.btn-test.sending {
    opacity: 0.6;
    pointer-events: none;
}
.btn-test.success {
    background: var(--color-success-bg);
    color: var(--color-success);
}
.btn-test.error {
    background: var(--color-background);
    color: var(--color-error);
}

.password-requirements {
    background: var(--color-info-bg, #e8f4fd);
    border: var(--border-width-thin) solid var(--color-info-border, #b3d9f2);
    border-left: var(--border-width-thick) solid var(--color-info);
    border-radius: var(--border-radius-sm);
    padding: var(--spacing-md) var(--spacing-md);
    margin-bottom: var(--spacing-md);
    font-size: var(--font-size-xs);
}
.password-requirements ul {
    margin: 0;
    padding-left: var(--spacing-lg);
    color: var(--color-text-secondary, #555);
}
.password-requirements li {
    margin-bottom: var(--spacing-xs);
}
.password-input-wrapper {
    position: relative;
    display: flex;
    align-items: center;
}
.password-input-wrapper .form-control-unified {
    padding-right: 44px;
}
.password-toggle {
    position: absolute;
    right: var(--spacing-sm);
    /* TD-86: кнопка стала ui_icon_button (base .btn-icon = 36px круг). Снимаем фикс-геометрию,
       чтобы остаться content-sized absolute-eye внутри инпута (bg/border уже сняты behavior:ghost). */
    width: auto;
    height: auto;
    background: none;
    border: none;
    cursor: pointer;
    padding: var(--spacing-sm);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-text-muted);
}
.password-toggle:hover {
    color: var(--color-secondary);
}
.password-toggle svg {
    fill: currentColor;
}

.danger-zone {
    margin-top: var(--spacing-xl);
    border: var(--border-width-thin) solid var(--color-error-bg);
    border-left: var(--border-width-thick) solid var(--color-error);
    background: linear-gradient(135deg, var(--color-error-bg) 0%, var(--color-background) 100%);
}
.danger-title {
    color: var(--color-error);
    border-bottom-color: var(--color-error-bg);
}
.danger-text {
    color: var(--color-text-secondary, #555);
    font-size: var(--font-size-sm);
    margin-bottom: var(--spacing-md);
}
.btn-danger-outline {
    background: transparent;
    color: var(--color-error);
    border: var(--border-width-thin) solid var(--color-error);
    padding: var(--spacing-sm) var(--spacing-lg);
    border-radius: var(--border-radius-sm);
    width: auto;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    transition: var(--transition-default);
}
.btn-danger-outline:hover {
    background: var(--color-error);
    color: var(--color-white, #fff);
}
.btn-danger-outline:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.btn-danger-outline:disabled:hover {
    background: transparent;
    color: var(--color-error);
}
.btn-warning-outline {
    background: transparent;
    color: var(--color-warning);
    border: var(--border-width-thin) solid var(--color-warning);
    padding: var(--spacing-sm) var(--spacing-lg);
    border-radius: var(--border-radius-sm);
    width: auto;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    transition: var(--transition-default);
}
.btn-warning-outline:hover {
    background: var(--color-warning);
    color: var(--color-white, #fff);
}
.danger-section {
    margin-bottom: var(--spacing-lg);
    padding-bottom: var(--spacing-lg);
    border-bottom: var(--border-width-thin) solid var(--color-error-bg);
}
.danger-section:last-child {
    margin-bottom: 0;
    padding-bottom: 0;
    border-bottom: none;
}
.danger-subtitle {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--color-warning-dark);
    margin: 0 0 var(--spacing-sm) 0;
}
.danger-section-delete .danger-subtitle {
    color: var(--color-error);
}
.alert-warning {
    background: var(--color-warning-bg);
    color: var(--color-warning-dark);
    padding: var(--spacing-md) var(--spacing-md);
    margin-bottom: var(--spacing-md);
    font-size: var(--font-size-sm);
}
.alert-warning p {
    margin: 0;
}
.alert-warning .mt-2 {
    margin-top: var(--spacing-sm);
}
.bindings-list {
    margin-top: var(--spacing-md);
}
.bindings-list ul {
    margin: var(--spacing-sm) 0 0 0;
    padding-left: var(--spacing-lg);
}
.bindings-list li {
    margin-bottom: var(--spacing-sm);
}
.badge {
    display: inline-block;
    padding: var(--spacing-xs) var(--spacing-sm);
    font-size: var(--font-size-xxs);
    font-weight: var(--font-weight-semibold);
    border-radius: var(--border-radius-sm);
    margin-left: var(--spacing-sm);
}
.badge-secondary {
    background: var(--color-disabled);
    color: var(--color-white, #fff);
}

.modal-sorry {
    font-size: var(--font-size-sm);
    color: var(--color-text);
    margin: 0 0 var(--spacing-md) 0;
    line-height: var(--line-height-normal);
}
.modal-info {
    background: var(--color-background);
    border: var(--border-width-thin) solid var(--color-background);
    border-radius: var(--border-radius-sm);
    padding: var(--spacing-md);
    margin-bottom: var(--spacing-md);
}
.modal-info h4 {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--color-warning);
    margin: 0 0 var(--spacing-md) 0;
}
.modal-info ul {
    margin: 0;
    padding-left: var(--spacing-lg);
}
.modal-info li {
    font-size: var(--font-size-sm);
    color: var(--color-warning);
    margin-bottom: var(--spacing-sm);
    line-height: var(--line-height-normal);
}
.modal-info li:last-child {
    margin-bottom: 0;
}
.modal-question {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-secondary);
    margin: 0;
    text-align: center;
}

.notification-channels-hint {
    font-size: var(--font-size-sm);
    color: var(--color-text-secondary, #555);
    margin-bottom: var(--spacing-md);
    background: var(--color-info-bg, #e8f4fd);
    border-left: var(--border-width-thick) solid var(--color-info);
    border-radius: var(--border-radius-sm);
    padding: var(--spacing-sm) var(--spacing-md);
}
.notification-channel-group {
    margin-bottom: var(--spacing-md);
}
.notification-channel {
    display: flex;
    align-items: center;
    gap: var(--spacing-md);
    padding: var(--spacing-md) var(--spacing-md);
    border: var(--border-width-thin) solid var(--color-border);
    border-radius: var(--border-radius-sm);
    background: var(--color-background);
    cursor: pointer;
    transition: var(--transition-default);
}
.notification-channel:hover {
    border-color: var(--color-primary);
    background: var(--color-info-bg);
}
.notification-channel input[type="checkbox"] {
    width: 20px;
    height: 20px;
    cursor: pointer;
}
.notification-channel.channel-disabled {
    opacity: 0.6;
    cursor: not-allowed;
    background: var(--color-background);
}
.notification-channel.channel-disabled:hover {
    border-color: var(--color-border);
    background: var(--color-background);
}
.channel-icon {
    flex-shrink: 0;
}
.channel-name {
    font-weight: var(--font-weight-medium);
    color: var(--color-secondary);
}

.notification-preferences-section {
    margin-top: var(--spacing-lg);
    padding-top: var(--spacing-md);
    border-top: var(--border-width-thin) solid var(--color-border-light);
}
.notification-prefs-hint {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    margin-bottom: var(--spacing-md);
}
.notification-group {
    border: var(--border-width-thin) solid var(--color-border);
    border-radius: var(--border-radius-sm);
    margin-bottom: var(--spacing-sm);
    overflow: hidden;
}
.notification-group-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--spacing-sm) var(--spacing-md);
    background: var(--color-background);
    cursor: pointer;
    user-select: none;
    transition: background var(--transition-duration-default);
}
.notification-group-header:hover {
    background: var(--color-info-bg);
}
.notification-group-name {
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-sm);
    color: var(--color-secondary);
}
.notification-group-controls {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
}
.notification-group-select {
    font-size: var(--font-size-xs);
    padding: var(--spacing-xs) var(--spacing-sm);
    border: var(--border-width-thin) solid var(--color-border);
    border-radius: var(--border-radius-sm);
    background: var(--color-card-background);
    color: var(--color-text);
    cursor: pointer;
}
.notification-group-chevron {
    color: var(--color-text-muted);
    transition: transform var(--transition-duration-default);
}
.notification-group.expanded .notification-group-chevron {
    transform: rotate(180deg);
}
.notification-group-body {
    padding: 0 var(--spacing-md);
    background: var(--color-card-background);
}
.notification-type-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--spacing-sm) 0;
    border-bottom: var(--border-width-thin) solid var(--color-border-light);
    font-size: var(--font-size-xs);
}
.notification-type-row:last-child {
    border-bottom: none;
}
.notification-type-name {
    color: var(--color-text);
    flex: 1;
}
.notification-type-channels {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
}
.notification-type-checkbox {
    display: flex;
    align-items: center;
    gap: var(--spacing-xs);
    cursor: pointer;
    color: var(--color-text-muted);
}
.notification-type-checkbox input:checked + svg {
    color: var(--color-primary);
}
.notification-type-checkbox input:disabled + svg {
    opacity: 0.4;
}
.notification-type-checkbox input[type="checkbox"] {
    width: 16px;
    height: 16px;
}
.notification-type-checkbox.channel-na {
    opacity: 0.3;
    cursor: not-allowed;
}
.notification-mandatory-badge {
    display: flex;
    align-items: center;
}
.notification-prefs-actions {
    margin-top: var(--spacing-md);
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--spacing-sm);
}
.notification-prefs-status {
    font-size: var(--font-size-xs);
}
.notification-prefs-status.success {
    color: var(--color-success);
}
.notification-prefs-status.error {
    color: var(--color-error);
}

@media (min-width: 768px) {
    .page-title {
        font-size: var(--font-size-4xl);
    }
}

.card-title.accordion-title {
    cursor: pointer;
    display: flex;
    justify-content: space-between;
    align-items: center;
    user-select: none;
    transition: color var(--transition-duration-fast);
    margin-bottom: 0;
    border-bottom: none;
    padding-bottom: 0;
}
.card-title.accordion-title:hover {
    color: var(--color-primary);
}
.accordion-chevron {
    flex-shrink: 0;
    margin-left: var(--spacing-sm);
    color: var(--color-text-muted);
    transition: transform var(--transition-duration-default) ease;
    display: flex;
    align-items: center;
}
.card.accordion-open .accordion-chevron {
    transform: rotate(0deg);
}
.card.accordion-closed .accordion-chevron {
    transform: rotate(-90deg);
}
.card.accordion-open .accordion-title {
    border-bottom: var(--border-width-thin) solid var(--color-border-light);
    margin-bottom: var(--spacing-md);
    padding-bottom: var(--spacing-sm);
}
.accordion-body {
    display: grid;
    grid-template-rows: 1fr;
    transition: grid-template-rows var(--transition-duration-slow) ease;
}
.card.accordion-closed .accordion-body {
    grid-template-rows: 0fr;
}
.accordion-body-inner {
    overflow: hidden;
}

/* === Photo Lightbox — fullscreen image viewer (paired with ui photo carousel) === */
.ui-photo-lightbox {
    display: none;
    position: fixed;
    inset: 0;
    background: var(--color-overlay-95);
    z-index: var(--z-lightbox);
    align-items: center;
    justify-content: center;
}

.ui-photo-lightbox--open {
    display: flex;
}

.ui-photo-lightbox__content {
    position: relative;
    max-height: 90vh;
    display: flex;
    align-items: center;
    justify-content: center;
}

.ui-photo-lightbox__image {
    max-width: 90vw;
    max-height: 85vh;
    object-fit: contain;
    border-radius: var(--border-radius-sm);
    box-shadow: 0 10px 50px var(--color-overlay-50);
}

.ui-photo-lightbox__close,
.ui-photo-lightbox__nav {
    position: fixed;
    background: var(--color-white-alpha-10);
    border: none;
    border-radius: 50%;
    color: var(--color-white, #fff);
    cursor: pointer;
    transition: background var(--transition-duration-slow), transform var(--transition-duration-default);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: calc(var(--z-lightbox) + 1);
}

.ui-photo-lightbox__close {
    top: 20px;
    right: 20px;
    width: 50px;
    height: 50px;
    font-size: var(--font-size-5xl);
}

.ui-photo-lightbox__close:hover {
    background: var(--color-white-alpha-20);
    transform: scale(1.1);
}

.ui-photo-lightbox__nav {
    top: 50%;
    transform: translateY(-50%);
    width: 60px;
    height: 60px;
    font-size: var(--font-size-3xl);
}

.ui-photo-lightbox__nav:hover {
    background: var(--color-white-alpha-25);
    transform: translateY(-50%) scale(1.1);
}

.ui-photo-lightbox__nav--disabled {
    opacity: 0.3;
    cursor: default;
}

.ui-photo-lightbox__nav--disabled:hover {
    background: var(--color-white-alpha-10);
    transform: translateY(-50%);
}

.ui-photo-lightbox__nav--prev {
    left: 20px;
}

.ui-photo-lightbox__nav--next {
    right: 20px;
}

.ui-photo-lightbox__counter {
    position: fixed;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    background: var(--color-overlay-70);
    color: var(--color-white, #fff);
    padding: var(--spacing-sm) var(--spacing-lg);
    border-radius: var(--border-radius-lg);
    font-size: var(--font-size-sm);
    z-index: calc(var(--z-lightbox) + 1);
}

.ui-photo-lightbox__caption {
    position: fixed;
    bottom: 70px;
    left: 50%;
    transform: translateX(-50%);
    color: var(--color-white, #fff);
    font-size: var(--font-size-sm);
    text-align: center;
    max-width: 80%;
    text-shadow: 0 2px 4px var(--color-overlay-50);
    z-index: calc(var(--z-lightbox) + 1);
}

@media (max-width: 767px) {
    .ui-photo-lightbox__nav {
        width: 50px;
        height: 50px;
        font-size: var(--font-size-xl);
    }

    .ui-photo-lightbox__nav--prev {
        left: 10px;
    }

    .ui-photo-lightbox__nav--next {
        right: 10px;
    }

    .ui-photo-lightbox__close {
        top: 10px;
        right: 10px;
        width: 44px;
        height: 44px;
    }
}

/* ── btn-faded — полупрозрачная кнопка (0.8 → 1 при hover, плавная анимация) ── */
.btn-faded {
    opacity: 0.8;
    transition: opacity var(--transition-duration-slow) ease;
}
.btn-faded:hover,
.btn-faded:focus-visible {
    opacity: 1;
    animation: btn-faded-pulse 0.3s ease forwards;
}

@keyframes btn-faded-pulse {
    0%   { opacity: 0.8; }
    60%  { opacity: 1.0; }
    100% { opacity: 1.0; }
}

/* ── btn-bg-* — кнопки с фиксированным фоном/бордером/цветом текста ── */
/* Двойной селектор .btn.btn-bg-* обеспечивает приоритет над .btn { border: none } из dynamic.css */

/* Белый фон, синяя рамка, синий текст */
.btn.btn-bg-white-blue {
    background: var(--color-white, #ffffff);
    border: var(--border-width-thin) solid var(--dash-icon-blue, #4F7CF7);
    color: var(--dash-icon-blue, #4F7CF7);
}
.btn.btn-bg-white-blue:hover { background: var(--color-primary-bg, #F5F8FF); }

/* Белый фон, зелёная рамка, зелёный текст */
.btn.btn-bg-white-green {
    background: var(--color-white, #ffffff);
    border: var(--border-width-thin) solid var(--dash-icon-green, #34A853);
    color: var(--dash-icon-green, #34A853);
}
.btn.btn-bg-white-green:hover { background: var(--color-success-bg, #eaf7ee); }

/* Оранжевый фон, оранжевая рамка, белый текст */
.btn.btn-bg-orange {
    background: var(--color-warning, #F97316);
    border: var(--border-width-thin) solid var(--color-warning, #F97316);
    color: var(--color-white, #ffffff);
}
.btn.btn-bg-orange:hover { background: var(--color-orange-hover); border-color: var(--color-orange-hover); }

/* Белый фон, белая рамка, тёмный текст */
.btn.btn-bg-white {
    background: var(--color-white, #ffffff);
    border: var(--border-width-thin) solid var(--color-white, #ffffff);
    color: var(--color-text, #141C30);
}
.btn.btn-bg-white:hover { background: var(--color-bg-hover, #f5f5f5); }

/* CRM-style tabs — global (вынесено из inline <style> crm-show шаблонов для
   переиспользования: карточка контакта /my/contacts/{id} и др.). */
.crm-tabs { margin-top: var(--spacing-md); }
.crm-tabs-nav { display: flex; gap: 0; border-bottom: var(--border-width-normal) solid var(--color-border); flex-wrap: wrap; }
.crm-tab-btn {
    padding: var(--spacing-sm) var(--spacing-md);
    border: none;
    background: none;
    cursor: pointer;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-text-muted);
    border-bottom: var(--border-width-normal) solid transparent;
    margin-bottom: -2px;
    transition: var(--transition-fast);
}
.crm-tab-btn:hover { color: var(--color-primary); }
.crm-tab-btn.active { color: var(--color-primary); border-bottom-color: var(--color-primary); font-weight: var(--font-weight-semibold); }
.crm-tab-content,
.ui-tabpanel--boxed {
    display: none;
    padding: var(--spacing-md);
    background: var(--color-card-background);
    border: var(--border-width-thin) solid var(--color-border);
    border-top: none;
    border-radius: 0 0 8px 8px;
}
.crm-tab-content.active,
.ui-tabpanel--boxed.active { display: block; }

/* ── Split button (основная кнопка + примыкающий dropdown-toggle) ── */
.split-btn { position: relative; display: inline-flex; }
.split-btn__group { display: inline-flex; align-items: stretch; border-radius: var(--border-radius); overflow: hidden; }

/* Сбрасываем глобальные .btn свойства (width:100%, min-height:72px, min-width:72px, огромный padding)
   чтобы кнопки внутри группы имели компактный инлайн-размер */
.split-btn__group .btn {
    width: auto;
    min-width: unset;
    min-height: unset;
    padding: var(--spacing-sm) var(--spacing-md);
}

.split-btn__main { border-top-right-radius: 0 !important; border-bottom-right-radius: 0 !important; }
.split-btn__toggle {
    display: inline-flex; align-items: center; justify-content: center;
    /* Перебиваем глобальный .dropdown-toggle (background, border, padding, border-radius, min-width) */
    background: inherit;
    border: none;
    border-left: var(--border-width-thin) solid var(--color-divider-on-color) !important;
    padding: 0 var(--spacing-sm) !important;
    width: auto !important;
    min-width: unset !important;
    min-height: unset !important;
    border-radius: 0 !important;
    border-top-left-radius: 0 !important;
    border-bottom-left-radius: 0 !important;
    border-top-right-radius: var(--border-radius) !important;
    border-bottom-right-radius: var(--border-radius) !important;
    cursor: pointer;
}
/* Перебиваем hover глобального .dropdown-toggle — toggle берёт hover от варианта кнопки */
.split-btn__toggle:hover { background: inherit; }

/* ── Цвета вариантов: main и toggle наследуют один и тот же вариант ──
   Перебиваем конфликт от .dropdown-toggle (background:card-bg, border:color-border).
   Для secondary/info border-left делаем полупрозрачный тёмный divider. */
.split-btn__group .btn-primary,
.split-btn__group .btn-primary:hover { background-color: var(--button-primary-background); color: var(--button-primary-text) !important; border-color: var(--button-primary-border, transparent); }
.split-btn__group .btn-primary:hover { background-color: var(--button-primary-hover); }
.split-btn__toggle.btn-primary,
.split-btn__toggle.btn-primary:hover { background-color: var(--button-primary-background); border-left-color: var(--color-divider-on-color) !important; }
.split-btn__toggle.btn-primary:hover { background-color: var(--button-primary-hover); }

.split-btn__group .btn-success { background-color: var(--button-success-background); color: var(--button-success-text, #fff) !important; border-color: var(--button-success-border, transparent); }
.split-btn__group .btn-success:hover { background-color: var(--button-success-hover); }
.split-btn__toggle.btn-success { background-color: var(--button-success-background); border-left-color: var(--color-divider-on-color) !important; }
.split-btn__toggle.btn-success:hover { background-color: var(--button-success-hover); }

.split-btn__group .btn-danger { background-color: var(--button-danger-background); color: var(--button-danger-text, #fff) !important; border-color: var(--button-danger-border, transparent); }
.split-btn__group .btn-danger:hover { background-color: var(--button-danger-hover); }
.split-btn__toggle.btn-danger { background-color: var(--button-danger-background); border-left-color: var(--color-divider-on-color) !important; }
.split-btn__toggle.btn-danger:hover { background-color: var(--button-danger-hover); }

.split-btn__group .btn-warning { background-color: var(--button-warning-background); color: var(--button-warning-text, #fff) !important; border-color: var(--button-warning-border, transparent); }
.split-btn__group .btn-warning:hover { background-color: var(--button-warning-hover); }
.split-btn__toggle.btn-warning { background-color: var(--button-warning-background); border-left-color: var(--color-divider-on-color) !important; }
.split-btn__toggle.btn-warning:hover { background-color: var(--button-warning-hover); }

.split-btn__group .btn-secondary { background-color: var(--button-secondary-background); color: var(--button-secondary-text, #333) !important; border-color: var(--button-secondary-border, #ddd); }
.split-btn__group .btn-secondary:hover { background-color: var(--button-secondary-hover); }
.split-btn__toggle.btn-secondary { background-color: var(--button-secondary-background); border-left-color: var(--color-divider-on-light) !important; }
.split-btn__toggle.btn-secondary:hover { background-color: var(--button-secondary-hover); }

/* btn-info: канон .btn-info (DynamicCssDumperService) уже даёт корректный info-цвет;
   здесь только toggle-divider (см. secondary выше — тот же паттерн на светлом фоне). */
.split-btn__toggle.btn-info { background-color: var(--button-info-background, var(--color-info-bg)); border-left-color: var(--color-divider-on-light) !important; }
.split-btn__toggle.btn-info:hover { background-color: var(--button-info-hover, var(--color-info)); color: var(--button-info-hover-text, #fff) !important; }

/* меню */
.split-btn__menu {
    position: absolute; top: calc(100% + 5px); right: 0; left: auto;
    min-width: 180px; background: var(--color-card-background);
    border: var(--border-width-thin) solid var(--color-border); border-radius: var(--border-radius);
    box-shadow: var(--shadow-dropdown); z-index: var(--z-dropdown);
    display: none; overflow: visible;
    /* Сбрасываем глобальный .dropdown-menu margin-top (4px — уже учтён через top:calc) */
    margin-top: 0;
}
/* common--dropdown вешает .active на this.element (корневой .split-btn) */
.split-btn.active .split-btn__menu { display: block; }

/* placement / align модификаторы */
.split-btn__menu--top   { top: auto; bottom: calc(100% + 5px); }
.split-btn__menu--start { right: auto; left: 0; }

/* размеры toggle (основная часть наследует .btn-sm/.btn-lg на .split-btn__main) */
.split-btn--sm .split-btn__group .btn { padding: var(--spacing-sm) var(--spacing-md); }
.split-btn--sm .split-btn__toggle { padding: 0 var(--spacing-sm) !important; font-size: var(--button-font-size-sm); }
.split-btn--lg .split-btn__group .btn { padding: var(--spacing-md) var(--spacing-lg); }
.split-btn--lg .split-btn__toggle { padding: 0 12px !important; font-size: var(--button-font-size-lg); }

/* пункты меню — вложенные правила под .split-btn__menu не ломают глобальные .dropdown-item */
.split-btn__menu .dropdown-item { display: flex; align-items: center; gap: var(--spacing-sm); padding: var(--spacing-sm) var(--spacing-md); }
/* pointer-events:none — блокирует клик на кнопке, но tooltip внутри должен работать */
.split-btn__menu .dropdown-item--disabled { color: var(--color-text-muted); cursor: not-allowed; pointer-events: none; }
.split-btn__menu .dropdown-item--disabled .ui-tooltip { pointer-events: auto; cursor: default; }
.split-btn__menu .dropdown-item--danger { color: var(--color-error); }

/* ============================================================
   ui-collapsible — сворачиваемая секция (details/summary)
   ============================================================ */
/* ⚠️ ИСПРАВЛЕНО (TASK-552 регресс-фикс, п.4): предыдущая версия этого докблока
   утверждала, что `flex: 1 1 auto` на .ui-collapsible__title «переносится ВНУТРИ
   себя, как обычный текст, до того как flex-wrap на родителе успевает
   сработать» — ЭТО БЫЛО ФАКТИЧЕСКИ НЕВЕРНО, эмпирически опровергнуто headless
   Chrome (см. TASK-552 ревью раунд 2): `flex: 1 1 auto` — это `flex-basis: auto`,
   т.е. базовый размер элемента ДО переноса равен его max-content ширине (ширина
   ПОЛНОСТЬЮ развёрнутого текста в одну строку). На узкой рельсе (320px) эта
   ширина вместе с шевроном+gap превышает доступную ширину строки — flex-wrap:wrap
   на родителе переносит ВЕСЬ .ui-collapsible__title на НОВУЮ flex-строку целиком
   (а не переносит текст ВНУТРИ себя на месте), поэтому шеврон визуально повисал
   один в первой строке, а весь заголовок уходил под него.
   Фикс — `flex-basis: 0` (не `auto`) на .ui-collapsible__title: базовый размер
   ДО переноса становится нулевым, элемент остаётся на той же flex-строке, что
   и шеврон, растягивается на остаток ширины (`flex-grow:1`) и ТОЛЬКО ТЕПЕРЬ
   собственный текст переносится изнутри как обычный inline-контент. Селектор
   остаётся УЗКИМ — `.ui-collapsible__summary > .ui-collapsible__title` (класс
   дефолтного слота партиала _collapsible.html.twig, покрывает дефолтный
   `{% block summary %}` И все ~100 брендбук-переопределений), а НЕ `*:last-child`
   (была более ранняя регрессия — *:last-child матчил ПОСЛЕДНИЙ узел income
   call-site'а, templates/properties/income/index.html.twig: <a>+ui_badge+2×<span>,
   и растягивал не того получателя). `flex-wrap: wrap` на .ui-collapsible__summary
   остаётся ОБЯЗАТЕЛЬНЫМ (не трогать) — без него income-слот (несколько узлов,
   не .ui-collapsible__title) не может перенести узел на новую строку и
   схлопывается в узкие колонки; income не затронут сменой basis, т.к. там нет
   класса .ui-collapsible__title вообще (свои классы income-section-*) — тест
   CollapsibleSummaryTitleSelectorCanonTest фиксирует обе границы. align-items
   остаётся flex-start (не center) — на многострочном заголовке center увёл бы
   шеврон в геометрический центр ВСЕГО блока текста; выравнивание шеврона с
   ПЕРВОЙ строкой текста (а не со всем блоком) сделано точечным margin-top на
   самом .ui-collapsible__icon ниже — так же безопасно для однострочных
   заголовков (~100 call-site'ов) и income. */
.ui-collapsible__summary {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--spacing-sm, 0.5rem);
    cursor: pointer;
    list-style: none;
    padding: var(--spacing-xs) var(--spacing-sm);
}

.ui-collapsible__summary > .ui-collapsible__title {
    flex: 1 1 0%;
    min-width: 0;
}

.ui-collapsible__summary::-webkit-details-marker {
    display: none;
}

/* Шеврон: › (вправо) когда свёрнут, ⌄ (вниз) когда развёрнут.
   margin-top: 0.5em (TASK-552 п.4) — align-items:flex-start на родителе
   выравнивает шеврон по верхнему краю summary, а не по центру первой строки
   текста; при line-height:1.5 разница между высотой строки (1.5em) и высотой
   шеврона (0.5em) — ровно 1em, половина (0.5em) сверху центрирует шеврон
   ИМЕННО против первой строки заголовка (не против всего многострочного блока —
   это и есть требуемое поведение flex-start). Проверено эмпирически headless
   Chrome на однострочном и многострочном заголовке (узкая рельса 320px) —
   без нудж шеврон визуально «висел» выше текста. */
.ui-collapsible__icon {
    display: inline-block;
    width: 0.5em;
    height: 0.5em;
    margin-top: 0.5em;
    border-right: var(--border-width-normal) solid var(--color-text-secondary, #5F6670);
    border-bottom: var(--border-width-normal) solid var(--color-text-secondary, #5F6670);
    transform: rotate(-45deg);
    transition: transform var(--transition-duration-fast) ease, border-color var(--transition-duration-fast) ease;
    flex-shrink: 0;
    margin-right: var(--spacing-xs, 0.25rem);
}

.ui-collapsible[open] .ui-collapsible__icon {
    transform: rotate(45deg);
}

/* Подчеркнуть кликабельность шапки (аккордеон) */
.ui-collapsible__summary:hover {
    background-color: var(--color-table-hover, #f8f9fa);
    border-radius: var(--border-radius-sm);
}

.ui-collapsible__summary:hover .ui-collapsible__icon {
    border-color: var(--color-primary, #4F7CF7);
}

.ui-collapsible__content {
    padding: var(--spacing-sm, 0.5rem);
}

/* Модификатор: аккордеон в виде карточки */
.ui-collapsible--card {
    border: var(--border-width-thin) solid var(--color-border, #D7DFF2);
    border-radius: var(--border-radius-large, 12px);
    background: var(--color-card-background, #fff);
    padding: var(--spacing-md, 1rem) var(--spacing-lg, 1.5rem);
    box-shadow: var(--shadow-card, 0 2px 8px rgba(0, 0, 0, 0.08));
}

/* TASK-544: на мобиле собственный padding карточки-аккордеона съедает ширину,
   которой на 390px и так мало (внутренние блоки несут свои отступы). Обнуляем —
   рамка/фон/скругление модификатора сохраняются, меняется только внутренний отступ. */
@media (max-width: 767px) {
    .ui-collapsible--card {
        padding: 0;
    }
}

/* Модификатор: bordered — рамка вокруг секции + подчёркнутый заголовок */
.ui-collapsible--bordered {
    margin-bottom: var(--spacing-xs);
    scroll-margin-top: var(--spacing-sm);
    border: var(--border-width-thin) solid var(--color-border);
    border-radius: var(--border-radius-sm);
}
.ui-collapsible--bordered .ui-collapsible__summary {
    background: var(--color-background);
    border-bottom: var(--border-width-thin) solid var(--color-border);
}

/* Option / Selection Card (макрос _option_card.html.twig, TD-63) */
.charge-type-option { display: flex; gap: var(--spacing-md); align-items: flex-start; padding: var(--spacing-md) var(--spacing-md); border-radius: var(--border-radius-sm); text-decoration: none; color: inherit; cursor: pointer; border: var(--border-width-thin) solid var(--color-border); background: var(--color-card-background); text-align: left; font-family: inherit; transition: border-color var(--transition-duration-fast), box-shadow var(--transition-duration-fast); }
.charge-type-option:hover { border-color: var(--color-primary); box-shadow: var(--shadow-card-hover, var(--shadow-sm)); text-decoration: none; color: inherit; }
.charge-type-option-icon { font-size: var(--font-size-3xl); flex-shrink: 0; margin-top: var(--spacing-xs); }
.charge-type-option strong { display: block; font-size: var(--font-size-sm); margin-bottom: var(--spacing-xs); color: var(--color-secondary); }
.charge-type-option p { margin: 0; font-size: var(--font-size-xs); line-height: var(--line-height-normal); color: var(--color-text-muted); }
.charge-type-onetime:hover { border-color: var(--color-warning); }
.charge-type-regular:hover { border-color: var(--color-info); }
.charge-type-disabled { opacity: 0.6; cursor: default; }
.charge-type-disabled:hover { border-color: var(--color-border); box-shadow: none; }
.charge-type-disabled a { color: var(--color-primary); text-decoration: underline; }

/* Контракт-печать: опции макета — вынос inline-style из _print_language_form.html.twig (TD-63 §C) */
.layout-options { display: none; margin-top: var(--spacing-md); padding-top: var(--spacing-md); border-top: var(--border-width-thin) solid var(--color-border); }
.layout-options .print-hint { margin-bottom: var(--spacing-sm); }
.layout-option { display: flex; align-items: center; margin-bottom: var(--spacing-sm); cursor: pointer; }
.layout-option:last-of-type { margin-bottom: 0; }
.layout-option input[type="radio"] { margin-right: var(--spacing-sm); }

/* PDF progress bar — начальное состояние ширины (вынос inline из _print_popup_batch.html.twig, TD-63 §C) */
.pdf-progress-bar { width: 0; }

/* ==================== SEGMENTED CONTROL (ui_segmented_control) ==================== */
.ui-segmented {
    display: inline-flex;
    flex-wrap: wrap;
    gap: var(--spacing-xs);
    padding: var(--spacing-xs);
    background: var(--color-background);
    border: var(--border-width-normal) solid var(--color-border);
    border-radius: var(--border-radius-lg);
}
.ui-segmented__item {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    text-decoration: none;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    padding: var(--spacing-xs) var(--spacing-md);
    border-radius: var(--border-radius-md);
    transition: color var(--transition-fast), background var(--transition-fast);
}
.ui-segmented__item:hover:not(.ui-segmented__item--active):not(.ui-segmented__item--empty) {
    color: var(--color-text);
    background: var(--color-table-hover);
}
.ui-segmented__item--active {
    background: var(--color-primary);
    color: var(--color-white);
    font-weight: var(--font-weight-semibold);
}
.ui-segmented__item--empty {
    opacity: 0.45;
}
.ui-segmented__count {
    font-size: var(--font-size-xs);
    font-variant-numeric: tabular-nums;
    background: var(--color-table-hover);
    color: var(--color-text-muted);
    padding: 0 var(--spacing-xs);
    border-radius: var(--border-radius-sm);
}
.ui-segmented__item--active .ui-segmented__count {
    background: var(--color-white-alpha-20);
    color: var(--color-white);
}
button.ui-segmented__item {
    appearance: none;
    border: none;
    background: transparent;
    font: inherit;
    margin: 0;
    cursor: pointer;
}
.ui-segmented__label {
    display: inline;
}

/* ==================== ALPHABET FILTER (ui_alphabet_filter) ==================== */
.ui-alphabet {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacing-xs);
}
.ui-alphabet__item {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 28px;
    text-align: center;
    text-decoration: none;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    padding: var(--spacing-xs) var(--spacing-sm);
    border-radius: var(--border-radius-sm);
    transition: color var(--transition-fast), background var(--transition-fast);
}
.ui-alphabet__item:hover:not(.ui-alphabet__item--active) {
    color: var(--color-text);
    background: var(--color-table-hover);
}
.ui-alphabet__item--active {
    background: var(--color-primary);
    color: var(--color-white);
    font-weight: var(--font-weight-semibold);
}

/* ═══ Row Expander — chevron toggle button (TD-64 ui_row_expander) ═══ */
.ui-row-expander {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    cursor: pointer;
    padding: var(--spacing-xs);
    border-radius: var(--border-radius-sm);
    color: var(--color-text-muted);
    transition: color var(--transition-duration-default);
}
.ui-row-expander:hover {
    color: var(--color-primary);
}
.ui-row-expander svg {
    transition: transform var(--transition-duration-default);
}
.ui-row-expander.is-expanded svg {
    transform: rotate(180deg);
}

/* ═══ Drive badge (TD-79 ui_drive_badge) — Google Drive link badge ═══ */
.ui-drive-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    padding: 2px var(--spacing-sm);
    border-radius: var(--border-radius-sm);
    background: var(--color-info-bg);
    color: var(--color-info);
    font-size: var(--font-size-xxs);
    font-weight: var(--font-weight-semibold);
    text-decoration: none;
    transition: var(--transition-fast);
    white-space: nowrap;
}
.ui-drive-badge:hover {
    background: var(--color-info);
    color: var(--color-white);
}
.ui-drive-badge svg {
    flex-shrink: 0;
}

/* ═══ Calendar indicator (TD-79 ui_calendar_indicator) — legend glyph badge ═══ */
.ui-calendar-indicator {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--border-radius-sm);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-bold);
    color: var(--color-white);
    flex-shrink: 0;
}
.ui-calendar-indicator--income {
    background: var(--color-success);
}
.ui-calendar-indicator--expense {
    background: var(--color-danger);
}
.ui-calendar-indicator--events {
    background: var(--color-warning);
}
.ui-calendar-indicator--balance {
    background: var(--color-error-dark);
}
.ui-calendar-indicator--sm {
    width: 24px;
    height: 20px;
    font-size: var(--font-size-xs);
}
.ui-calendar-indicator--lg {
    width: 32px;
    height: 32px;
    font-size: var(--font-size-base);
}

/* ═══ Chip (TD-79 ui_chip) — removable-tag ═══ */
.ui-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    padding: var(--spacing-xs) var(--spacing-sm);
    border-radius: var(--border-radius-large);
    background: var(--color-table-hover);
    color: var(--color-text);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    line-height: 1;
}
.ui-chip__dismiss {
    margin-left: var(--spacing-xs);
}

/* Ширина, которую страницы обязаны держать СВОБОДНОЙ у правого края, чтобы
   `position: fixed` рельса не легла поверх их контента (TASK-570).

   Почему токен, а не литерал в лендинге: сама `.ui-side-rail` ширину НЕ объявляет —
   она авто-выводится из `writing-mode: vertical-rl` (line-height строки + боковые
   padding'и, замер headless Chrome: 37px, стабильно между локалями, т.к. вертикальный
   текст растёт в ВЫСОТУ, а не в ширину). Поэтому «поделиться» настоящей шириной
   нельзя — можно только зарезервировать заведомо достаточную полосу и держать её в
   одном месте. Прецедент — `--app-content-padding-x` (TASK-562): там два потребителя
   одного отступа разъезжались, пока значение дублировалось литералами.

   Объявлено на `:root` в components.css (а не внутри `.ui-side-rail`): custom property
   наследуется ВНИЗ по DOM, а `.lp3-container` рельсе не потомок — из блока рельсы
   лендинг его бы не увидел. components.css грузится на КАЖДОЙ странице, включая
   публичный лендинг, поэтому `:root` здесь доступен обоим.

   `--spacing-2xl` (48px) — ближайший существующий токен шкалы, покрывающий 37px с
   запасом на случай, если вырастет иконка или line-height таба. Потребители —
   `CONSUMERS` в `SideRailReserveCanonTest`. */
:root {
    --side-rail-reserve: var(--spacing-2xl);
}

/* ═══ Side Rail (TASK-552 ui_side_rail) — floating right-edge accordion tabs ═══
   Разметка/DTO: templates/components/ui/side_rail/_side_rail.html.twig,
   src/Dto/Component/SideRail/SideRailConfig.php. Логика (открытие/автопоказ/
   sessionStorage) — Stimulus components--side-rail, отдельный этап (ЕЩЁ НЕ
   РЕАЛИЗОВАН); разметка уже несёт нужные data-controller/data-action/data-*-target.

   .ui-side-rail--demo — компаньон-модификатор ТОЛЬКО для витрины брендбука
   (передаётся через extraClasses), снимает fixed-позиционирование, иначе демо-
   примеры на /admin/brandbook легли бы поверх страницы. */
/* Позиция рельсы (TASK-551 UI-фикс п.3): якорь табов и открывающейся панели —
   НИЗ рельсы у нижнего края вьюпорта (не вертикальный центр, как было в
   TASK-552), по прецеденту большинства чат-виджетов (Intercom/Zendesk и
   аналоги). `bottom: var(--side-rail-bottom, var(--side-rail-rest-bottom))` —
   значение пишет `components--side-rail_controller.js`
   (`_syncRailPosition()`), тот же паттерн, что `--property-menu-top`/
   `--property-menu-bottom` (assets/styles/components/property-menu.css +
   property-menu_controller.js): контроллер измеряет фактическую высоту
   `.ui-side-rail__tabs` (зависит от числа табов — 1..3, роли/локали
   посетителя) и высоту вьюпорта, клампит отступ так, чтобы верх стека табов
   не ушёл выше `--side-rail-edge` от верхнего края (иначе на низких
   вьюпортах при 3 табах — анонимный посетитель: помощь+чат+регистрация —
   стек уезжал бы за экран). `--side-rail-rest-bottom` — и дефолт покоя
   (когда места достаточно), и CSS-фолбэк на случай, если JS ещё не
   отработал первый кадр (до `connect()` либо при отключённом JS).

   align-items: flex-end (не center, как было в TASK-552) — `.ui-side-rail__panels`
   нулевой высоты, align-items им и управляет якорем, от которого растёт панель
   (см. .ui-side-rail__panel ниже): якорь встаёт вровень с НИЗОМ стека табов —
   то есть с той же точкой `bottom`, что несёт сама `.ui-side-rail`. В TASK-552
   именно эта комбинация ломала раскладку (ревью §1) — flex-end сдвигал якорь
   ниже вертикального центра, а max-height панели всё ещё считал бюджет
   симметрично от центра вьюпорта, и панель вылезала за нижний край. Теперь
   формула max-height у `.ui-side-rail__panel` асимметрична (растёт ТОЛЬКО
   вверх от `bottom`, см. ниже) — flex-end здесь корректен по построению.

   .ui-side-rail--demo — компаньон-модификатор ТОЛЬКО для витрины брендбука
   (передаётся через extraClasses), снимает fixed-позиционирование, иначе демо-
   примеры на /admin/brandbook легли бы поверх страницы. */
.ui-side-rail {
    position: fixed;
    right: 0;
    z-index: var(--z-sticky);
    display: flex;
    align-items: flex-end;
    /* Дизайн-токены геометрии — читает components--side-rail_controller.js
       (getComputedStyle на этом элементе), единственный источник истины
       (см. докблок выше и property-menu.css --property-menu-* — тот же приём). */
    --side-rail-edge: var(--spacing-lg);
    --side-rail-rest-bottom: var(--spacing-2xl);
    bottom: var(--side-rail-bottom, var(--side-rail-rest-bottom));
}

.ui-side-rail--demo {
    position: relative;
    right: auto;
    bottom: auto;
    z-index: auto;
}

.ui-side-rail__tabs {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-sm);
}

/* Вертикальная закладка у правого края (TASK-552 UI-фикс п.4): весь контент таба
   (иконка + текст) повёрнут на -90°, текст читается снизу вверх. Выбран
   `writing-mode: vertical-rl` + `transform: rotate(180deg)`, а НЕ голый
   `transform: rotate(-90deg)` на исходной горизонтальной коробке — у чистого
   transform box-модель (width/height) не меняется, он только поворачивает уже
   посчитанный layout визуально, поэтому автоматический авто-размер (ширина
   чипа = высота строки текста, высота = длина текста, требование задачи) не
   получить без ручных фиксированных px. `writing-mode: vertical-rl` меняет
   direction компоновки на уровне layout (не paint) — content-box авто-считается
   как для вертикального текста (inline-axis идёт сверху вниз), и это ДАЁТ
   авто-swap width/height бесплатно. Побочный эффект vertical-rl — направление
   строки «сверху вниз» (не то, что нужно); `rotate(180deg)` разворачивает
   готовый (уже правильно посчитанный по размеру) блок on paint.

   ⚠️ ИСПРАВЛЕНО (регресс-фикс, ревью раунд 2): предыдущая версия этого докблока
   утверждала, что «border-radius/border-right:none продолжают адресовать
   ФИЗИЧЕСКИЕ стороны бокса — radius/border не участвуют в transform». ЭТО БЫЛО
   ФАКТИЧЕСКИ НЕВЕРНО, эмпирически опровергнуто headless Chrome (скриншот таба
   у правого края: скругления оказались СПРАВА, у кромки экрана, прямой угол —
   СЛЕВА, к центру экрана — ровно наоборот требуемому). `transform: rotate()`
   поворачивает ВЕСЬ отрисованный бокс целиком, включая рамки и скругления —
   свойство paint-уровня, а не «физическая сторона», как box-shadow/background.
   Правильный канон «скругление у центра экрана (слева), прямой угол у кромки
   (справа)» после поворота на 180° требует объявить радиус/рамку на
   ПРОТИВОПОЛОЖНОЙ стороне относительно того, что выглядело бы правильным для
   горизонтальной коробки БЕЗ поворота — border-radius на правых углах +
   border-right (а не border-left), которые после rotate(180deg) визуально
   встают на левую/правую сторону как надо. */
.ui-side-rail__tab {
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-sm);
    padding: var(--spacing-md) var(--spacing-sm);
    background: var(--color-card-background);
    color: var(--color-text);
    /* Ревью TASK-551 m10: таб-ссылка (tab.href, _side_rail.html.twig) рендерится как <a>, а
       не <button> — без явного text-decoration UA-дефолт даёт подчёркивание (визуально
       вертикальное — writing-mode: vertical-rl + rotate(180deg) выше). Кнопочный таб не
       затронут (у <button> подчёркивания и так нет), правило общее для обоих вариантов
       разметки — просто заранее гасит дефолт браузера на случай <a>. */
    text-decoration: none;
    border: var(--border-width-thin) solid var(--color-border);
    border-left: none;
    border-radius: 0 var(--border-radius-large) var(--border-radius-large) 0;
    box-shadow: var(--shadow-card);
    cursor: pointer;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    transition: border-color var(--transition-duration-fast) ease, color var(--transition-duration-fast) ease;
}

/* Outline-стиль (TASK-552 UI-фикс, ревью раунд 2 п.3): покой — белая карточка
   (--color-card-background) с серой рамкой (--color-border) — НЕ прозрачный
   outline, канон брендбука для этого чипа. Ховер/expanded — фон НЕ заливается
   (осознанно, по решению пользователя): меняются только рамка и текст/иконка
   на --color-warning (единый существующий токен, переиспользован — без нового
   сырого значения). .ui-side-rail__tab--expanded уравнен с :hover (не отдельный
   --color-primary, как было раньше) — оба состояния читаются как «активный/
   открытый таб» одним визуальным языком; текущий открытый таб и наведённый
   курсором таб — семантически одно и то же «внимание сюда». */
.ui-side-rail__tab:hover,
.ui-side-rail__tab--expanded {
    border-color: var(--color-warning);
    color: var(--color-warning);
}

/* :focus-visible (TASK-552 UI-фикс, ревью раунд 2 п.3) — клавиатурный фокус
   отсутствовал; паттерн как у .dropdown-item:focus-visible (components.css
   ~941) — outline внутрь через отрицательный offset, не съезжает под скруглённый
   угол чипа. */
.ui-side-rail__tab:focus-visible {
    outline: var(--border-width-normal) solid var(--color-warning);
    outline-offset: -2px;
}

.ui-side-rail__tab-label {
    white-space: nowrap;
}

/* Контр-поворот иконки (TASK-552 UI-фикс, ревью раунд 2 п.2) — БЕЗ этого правила
   иконка (ui_icon внутри таба) рисуется искажённой: `writing-mode: vertical-rl`
   на предке САМ ПО СЕБЕ не поворачивает вложенный <svg> (это replaced-элемент,
   не текстовые глиффы — эмпирически проверено headless Chrome отдельным
   прогоном), но `rotate(180deg)` на предке визуально разворачивает ВЕСЬ
   отрисованный поддокумент целиком, включая иконку — гифт-бокс превращается в
   перевёрнутую вверх ногами картинку (по факту неотличимую от искажённой на
   маленьком размере — визуально читается как «перекручена»). Тот же паттерн
   контр-поворота уже применялся для .ui-side-rail__badge ниже (по той же
   причине) — переиспользован, а не продублирован заново: `writing-mode:
   horizontal-tb` возвращает иконке нормальный (не вертикальный) поток, `rotate
   (180deg)` гасит родительский rotate(180deg) (180+180 визуально нейтрализуются),
   иконка рисуется в обычной, неповёрнутой ориентации — как на горизонтальном
   чипе. Текст (.ui-side-rail__tab-label) КОНТР-ПОВОРОТ НЕ получает и продолжает
   читаться снизу вверх (требование задачи, подтверждено пользователем) —
   инвертируется только иконка. */
.ui-side-rail__tab .ui-icon {
    writing-mode: horizontal-tb;
    transform: rotate(180deg);
}

/* Контр-поворот (TASK-552 п.4): .ui-side-rail__tab целиком повёрнут
   (writing-mode: vertical-rl + rotate(180deg) — см. докблок выше), и это визуально
   разворачивает ВЕСЬ отрисованный поддокумент, включая .ui-side-rail__badge
   (непрочитанные сообщения чата, заложено под будущий таб) — цифра внутри без
   контр-поворота рисуется зеркально-перевёрнутой (проверено скриншотом headless
   Chrome). `writing-mode: horizontal-tb` возвращает бейджу горизонтальный поток
   текста (иначе унаследует vertical-rl от предка — цифра ляжет боком), а
   `rotate(180deg)` компенсирует rotate(180deg) предка — 180+180 визуально гасится,
   бейдж рисуется как обычный неповёрнутый кружок с прямой цифрой. */
.ui-side-rail__badge {
    writing-mode: horizontal-tb;
    transform: rotate(180deg);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 var(--spacing-xs);
    border-radius: var(--border-radius-large);
    background: var(--color-danger);
    color: var(--color-white);
    font-size: var(--font-size-xxs);
    font-weight: var(--font-weight-bold);
    line-height: 1;
}

.ui-side-rail__panels {
    position: relative;
    width: 0;
    height: 0;
}

/* Выезд из-за правого края: translateX(100%) полностью прячет панель за кромкой
   viewport (right:0 у .ui-side-rail__panel), раскрытие — translateX(0). Композитные
   свойства (transform+opacity), без пересчёта раскладки (TASK-552 спека §Анимация).

   bottom:0 (без top) — панель растёт ВВЕРХ от якоря (низ стека табов, см. докблок
   .ui-side-rail выше), а не центрируется, как было в TASK-552. max-height считает
   ТОЛЬКО пространство НАД якорем: `100dvh - --side-rail-bottom - --side-rail-edge`
   — верхний край панели никогда не поднимется выше `--side-rail-edge` от верхнего
   края вьюпорта. `--side-rail-bottom` — то же значение (px), что контроллер пишет
   на `.ui-side-rail` (см. выше); CSS custom property наследуется вниз по DOM без
   повторного JS-замера на самой панели. Симметричный вариант (TASK-552, центр
   вьюпорта) вылезал за нижний край, стоило рельсе сместиться от центра (ревью §1:
   1366×768 — на 59px) — асимметричный «только вверх» корректен для ЛЮБОЙ позиции
   якоря по построению: контроллер отвечает лишь за то, чтобы сам якорь (низ стека
   табов) не ушёл выше верхнего края (TASK-551 UI-фикс п.3). */
.ui-side-rail__panel {
    position: absolute;
    right: 0;
    bottom: 0;
    width: min(320px, calc(100vw - var(--spacing-lg) * 2));
    /* Панель растёт ПО КОНТЕНТУ; ограничение — только доступная высота вьюпорта над
       якорем рельсы. Жёсткого лимита (был `min(480px, …)`) намеренно НЕТ: панель помощи
       с формой обращения выше 480px, и лимит давал вертикальный скролл с обрезанной
       кнопкой отправки. Скролл остаётся только как аварийный — когда контент реально
       не помещается в экран (низкий вьюпорт), иначе панель ушла бы за кромку. */
    max-height: calc(100dvh - var(--side-rail-bottom, var(--side-rail-rest-bottom)) - var(--side-rail-edge));
    overflow-y: auto;
    background: var(--color-card-background);
    border-radius: var(--border-radius-large);
    box-shadow: var(--shadow-modal);
    padding: var(--spacing-lg);
    transform: translateX(100%);
    opacity: 0;
    transition: transform var(--transition-duration-default) ease, opacity var(--transition-duration-default) ease;
}

.ui-side-rail__panel--expanded {
    transform: translateX(0);
    opacity: 1;
}

/* Кнопка закрытия панели (TASK-552 UI-фикс п.3) — правый верхний угол панели,
   .ui-side-rail__panel уже установлен position:absolute → служит containing block
   без дополнительных правил. Панельный контент (см. _side_rail.html.twig) идёт
   ПОСЛЕ кнопки в DOM — abs-позиционирование выводит её из потока, поэтому порядок
   в разметке не сдвигает текст панели; padding-right у самой кнопки не нужен,
   т.к. .ui-side-rail__panel уже имеет padding:var(--spacing-lg) по всем сторонам. */
.ui-side-rail__panel-close {
    position: absolute;
    top: var(--spacing-sm);
    right: var(--spacing-sm);
}

/* Мобильное скрытие .ui-side-rail__tab-label (было здесь до TASK-552 п.4) УДАЛЕНО:
   правило экономило ГОРИЗОНТАЛЬНУЮ ширину узкого вьюпорта, когда таб был широким
   горизонтальным чипом (текст растягивал чип вширь). После поворота -90° таб —
   узкая вертикальная полоска НЕЗАВИСИМО от viewport (ширина ≈ высота строки текста,
   а не длина текста) — сценарий, который правило чинило, физически не существует:
   скрывать label больше нечего экономить, а с ним пропадала бы единственная
   подпись таба на мобильном (оставалась голая иконка) — без исходной причины
   правило не оправдано, снято. */
@media (max-width: 767px) {
    .ui-side-rail__panel {
        width: min(320px, calc(100vw - var(--spacing-md) * 2));
    }
}

@media (prefers-reduced-motion: reduce) {
    .ui-side-rail__panel {
        transition: none;
    }
}

@media print {
    .ui-side-rail {
        display: none;
    }
}

/* Скрыть рельсу, пока открыт мобильный flyout-сайдбар (класс на #appLayout —
   assets/controllers/layout/app-sidebar_controller.js — чужой контроллер не трогаем).
   :has() на body, а не прямой descendant-селектор: место интеграции рельсы в
   base_app_v3.html.twig — отдельный этап, вложенность в #appLayout не гарантирована;
   :has() на общем предке (body) работает независимо от того, где рельса окажется. */
body:has(#appLayout.app-layout--mobile-open) .ui-side-rail {
    display: none;
}

/* ═══ Side Rail — панель онлайн-чата (TASK-551, этап 4, п.0.3) ═══
   `.ui-side-rail__panel--chat` — модификатор панели ИМЕННО таба чата
   (`SideRailTab.panelClass`), НЕ применяется к другим панелям (помощь, оффер).

   ⚠️ КРИТИЧНО — почему только `:not([hidden])`: панель скрывается через
   `panel.hidden = true` (side-rail_controller.js, `_hidePanel()`), и это
   работает ТОЛЬКО потому, что у `.ui-side-rail__panel` (базовый селектор) нет
   собственного `display` — действует UA-правило `[hidden]{display:none}`.
   Авторское правило `display: flex` БЕЗ `:not([hidden])` перебило бы это
   UA-правило независимо от специфичности (author origin побеждает UA origin
   для любого свойства, объявленного автором, кроме случаев с `!important` в
   UA-таблице, которых здесь нет) — панель перестала бы скрываться СОВСЕМ, даже
   с атрибутом `hidden` в разметке. `:not([hidden])` заставляет правило
   применяться, только когда атрибут ОТСУТСТВУЕТ — тогда `display` вообще не
   переопределён автором, пока панель скрыта, и единственное действующее
   правило — UA `[hidden]{display:none}`. Проверено эмпирически (не только по
   спецификации) — см. отчёт задачи, живая проверка в браузере через
   getComputedStyle().

   flex-колонка вместо блочного скролла всей панели (базовый
   `.ui-side-rail__panel` имеет `overflow-y: auto` на ВСЮ панель — поле ввода
   уезжало бы вместе с историей сообщений). `overflow-y: hidden` здесь —
   скролл переносится на `.ui-side-rail__chat-messages` (единственный
   прокручиваемый узел). `height` (не только унаследованный `max-height`) —
   чат стабильного размера независимо от количества сообщений, та же формула
   «только вверх от якоря», что у `max-height` базового `.ui-side-rail__panel`
   (см. докблок там, TASK-551 UI-фикс п.3). */
.ui-side-rail__panel--chat:not([hidden]) {
    display: flex;
    flex-direction: column;
    overflow-y: hidden;
    /* overflow-x: hidden (живая проверка после ревью, заказчик прислал
       скриншот горизонтального скролла) — страховка на уровне панели:
       горизонтальной прокрутки здесь быть не должно НИКОГДА, независимо от
       того, что именно внутри (email-поле анонима сегодня, что угодно ещё
       завтра) откажется сжаться в ширину `.ui-side-rail__panel` (`min(320px,
       calc(100vw - …))`). Основной фикс — min-width: 0 по цепочке form/
       email-field/email-input ниже; это — defense-in-depth поверх него. */
    overflow-x: hidden;
    height: min(480px, calc(100dvh - var(--side-rail-bottom, var(--side-rail-rest-bottom)) - var(--side-rail-edge)));
}

/* flex: 1 1 auto (TASK-551 UI-фикс п.2) — без этого `.ui-side-rail__chat`
   (единственный flex-item `.ui-side-rail__panel--chat`) занимает только
   высоту своего контента, а НЕ всю доступную высоту панели: при короткой
   переписке блок оставался коротким, и под формой оставалось пустое
   пространство до низа панели (заметно на растущей — TASK-551 UI-фикс п.3 —
   высоте панели). `flex: 1 1 auto` растягивает контейнер до полной высоты
   `.ui-side-rail__panel--chat`; далее ленту (`.ui-side-rail__chat-messages`,
   flex: 1 1 auto) забирает освободившееся место — статус/форма/уведомление о
   закрытии остаются естественной высоты и прижаты к НИЗУ контейнера (растёт
   именно лента, а не пустой зазор). `justify-content: flex-end` на самой
   ленте (ниже) НЕ ломается: при короткой переписке контент внутри вытянутой
   ленты по-прежнему прижат к низу (к полю ввода), просто у самой ленты
   теперь реальная высота, а не высота в один welcome-абзац. */
.ui-side-rail__chat {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    gap: var(--spacing-sm);
    width: 100%;
    /* min-height: 0 — без этого flex-контейнер не сжимается ниже суммарной
       высоты контента, и .ui-side-rail__chat-messages (flex: 1 1 auto) не
       получает возможности сжаться и породить собственный скролл. */
    min-height: 0;
}

.ui-side-rail__chat-status {
    flex: 0 0 auto;
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

/* flex: 1 1 auto + min-height: 0 — единственный прокручиваемый узел панели
   чата (ревью-требование: скролл ТОЛЬКО у ленты сообщений).
   NTH2 (ревью TASK-551): justify-content: flex-end — при короткой переписке
   (мало сообщений/только welcome-заглушка) содержимое прижато к НИЗУ (ближе к
   полю ввода), а не к верху с пустым хвостом до формы — тот же паттерн, что у
   большинства мессенджеров (лента растёт вверх от поля ввода). Скролл при
   переполнении не страдает: overflow-y: auto на этом же узле работает
   одинаково независимо от justify-content. */
.ui-side-rail__chat-messages {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: var(--spacing-xs);
}

.ui-side-rail__chat-welcome {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

.ui-side-rail__chat-message {
    max-width: 85%;
    padding: var(--spacing-xs) var(--spacing-sm);
    border-radius: var(--border-radius-large);
    font-size: var(--font-size-sm);
    word-break: break-word;
}

.ui-side-rail__chat-message--visitor {
    align-self: flex-end;
    background: var(--color-primary);
    color: var(--color-white);
}

.ui-side-rail__chat-message--operator {
    align-self: flex-start;
    background: var(--color-table-hover);
    color: var(--color-text);
}

.ui-side-rail__chat-message-meta {
    display: block;
    margin-top: 2px;
    font-size: var(--font-size-xxs);
    opacity: 0.75;
}

/* min-width: 0 (живая проверка после ревью) — без этого flex-item'ы этого
   ряда (textarea/email-поле) не сжимаются НИЖЕ своей content-based ширины
   (браузерный дефолт `min-width: auto` для flex-элементов), а `flex-wrap:
   wrap` нужен теперь, когда в ряду 3 элемента (textarea + submit + email-поле
   анонима) — email-поле уходит на отдельную строку (flex-basis 100%, см.
   .ui-side-rail__chat-email-field ниже), а не сжимается вбок вместе с textarea. */
/* Форма — колонка: сверху поле почты (только у анонима), под ним строка
   «поле ввода + кнопка отправки». Раньше здесь был `flex-wrap: wrap` в одну
   строку — после появления третьего элемента (поле почты) кнопку выталкивало
   под поле ввода. */
.ui-side-rail__chat-form {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: var(--spacing-xs);
    min-width: 0;
}

/* Поле ввода чата — раскладка «контрол + append-кнопка» теперь несёт канон-компонент
   ui_input_group (.ui-input-group, см. секцию FORMS выше). Здесь остаётся только
   ОСТАТОЧНАЯ, специфичная именно для textarea-поля чата стилизация, которую компонент
   не может знать (произвольный контрол внутри): запрет ручного ресайза и обнуление
   min-height (в отличие от --width, компонент не трогает ось высоты). */
.ui-side-rail__chat-input {
    resize: none;
    min-height: 0;
}

/* TASK-551 (доп. поставка «почта посетителя чата») — поле почты анонимного
   визитёра, рендерится сервером ВНУТРИ формы сообщения, сразу после textarea/
   кнопки отправки (см. _app_side_rail.html.twig, блок panel_chat; ВНУТРИ, а не
   рядом — иначе HTML5 `required` не действует, браузер валидирует только
   form-associated элементы).

   `flex: 1 1 100%` — ВСЕГДА собственная строка внутри `.ui-side-rail__chat-form`
   (flex-wrap: wrap выше), а не третий элемент в одном ряду с textarea/кнопкой
   (живая проверка поймала регрессию первой версии: 3 flex-item'а без wrap не
   помещались в ширину панели `min(320px, …)`, панель расползалась
   горизонтальным скроллом — заказчик прислал скриншот). `min-width: 0` — тот
   же фикс, что и у `.ui-side-rail__chat-form`/`.ui-side-rail__chat-input`
   выше: без него `flex: 1 1 100%` не спасает — контейнер всё равно не
   сжимается ниже content-based ширины своего `<input>` (там же `min-width: 0`,
   ниже). */
.ui-side-rail__chat-email-field {
    flex: 1 1 100%;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.ui-side-rail__chat-email-input {
    width: 100%;
    min-width: 0;
}

.ui-side-rail__chat-email-hint {
    font-size: var(--font-size-xxs);
    color: var(--color-text-muted);
}

.ui-side-rail__chat-closed-notice {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: var(--spacing-xs);
}

/* Только layout-позиционирование — цвет/шрифт/подчёркивание уже даёт канон-
   вариант ui_button(variant: 'link') (--button-link-* токены dynamic.css), extraClasses
   здесь ТОЛЬКО добавляет модификатор поверх канон-кнопки, не переопределяет её.
   align-self: flex-start (TASK-551 UI-фикс п.1, было center) — ссылка выровнена
   по левому краю панели, вровень с началом текста сообщений/полем ввода, а не
   по центру пустой ширины панели. */
.ui-side-rail__chat-close-conversation {
    flex: 0 0 auto;
    align-self: flex-start;
}

@media (max-width: 767px) {
    .ui-side-rail__panel--chat:not([hidden]) {
        height: min(480px, calc(100dvh - var(--side-rail-bottom, var(--side-rail-rest-bottom)) - var(--spacing-md)));
    }
}

/* ============================================================
   Impersonation bar (TASK-562) — глобальная плашка «работаешь под другим
   пользователем» (templates/_layout/_app_impersonation_bar.html.twig).

   ПОЧЕМУ ЗДЕСЬ, А НЕ В pages/app-base.css: плашка обязана рендериться на ВСЕХ
   страницах, где пользователь может оказаться под подменой — включая лендинг,
   guide, library и legal (они наследуют base.html.twig, а НЕ base_app_v3), —
   поэтому её стили должны грузиться оттуда, откуда грузятся всегда.
   `pages/app-base.css` подключён только из `base_app_v3` (block layout_stylesheets),
   а `components.css` — из `base.html.twig` для любой страницы.

   БАЗОВЫЙ РЕЖИМ — сама плашка НЕ sticky, но вне приложения её пришпиливает ОБЩИЙ
   контейнер шапки `.layout-header-stack` (assets/styles/base.css): плашка и `.top-bar`
   лежат в нём вместе и едут одним блоком. Так снят конфликт «две sticky-полосы на
   одном top: 0» — без магического числа «высота плашки», которое пришлось бы вручную
   держать в синхроне с её содержимым (плашка переносится на две строки при длинном
   имени пользователя). Отказываться от sticky вне приложения нельзя: при скролле
   терялись бы и индикация повышенной привилегии, и ЕДИНСТВЕННАЯ кнопка выхода.

   ЦВЕТ: насыщенный warning-фон выбран намеренно — состояние повышенной привилегии
   нельзя пропустить взглядом. Текст --color-text (#141C30) на #F97316 = 6.1:1
   (WCAG AA 4.5:1 ✓). Прежний --color-white давал 2.84:1 — провал AA.
   ============================================================ */
.app-impersonation-bar {
    background: var(--color-warning);
    color: var(--color-text);
    box-shadow: var(--shadow-card);
}

/* Sticky-режим — ТОЛЬКО в лейауте приложения (base_app_v3 передаёт sticky: true):
   там плашка живёт внутри `.app-main`, конкурирующей sticky-шапки рядом нет.

   Z-INDEX: собственный токен --z-impersonation-bar (150), НЕ --z-sticky. Он выше
   обычного контента (`z-index: auto`) и НИЖЕ всей лестницы сайдбара —
   --z-sidebar (200, hover-раскрытие поверх колонки контента на 768–1199px),
   --z-sidebar-flyout (300, полноэкранное мобильное меню), --z-sidebar-overlay
   (1000, флайаут-подменю свёрнутого сайдбара). Все три намеренно вторгаются в
   колонку контента и обязаны перекрывать плашку. Прежнее значение --z-sticky
   совпадало с --z-sidebar-overlay (оба 1000), а при РАВНОМ z-index выигрывает
   элемент позже в DOM — плашка (после сайдбара) накрывала подменю. */
.app-impersonation-bar--sticky {
    position: sticky;
    top: 0;
    z-index: var(--z-impersonation-bar);
}

/* Горизонтальный padding — из ОБЩЕГО с `.app-content` токена --app-content-padding-x
   (тема, dynamic.css; см. pages/app-base.css): текст плашки встаёт ровно на левую
   границу контента, а не «вылезает» левее, как было с --spacing-lg (24px). Токен, а
   не литерал 60px: раньше значение дублировалось здесь и в pages/app-base.css
   «синхронизировано вручную», и правка .app-content молча уводила плашку в сторону. */
.app-impersonation-bar__inner {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--spacing-md);
    padding: var(--spacing-sm) var(--app-content-padding-x);
}

.app-impersonation-bar__icon {
    display: flex;
    flex-shrink: 0;
}

/* Текст забирает свободное место, чтобы кнопка выхода держалась правого края
   и не подпрыгивала к тексту при коротком имени пользователя. */
.app-impersonation-bar__text {
    flex: 1 1 auto;
    min-width: 0;
}

/* Кнопка не сжимается: при переносе строки она уходит целиком на новую строку.
   border-color: белая secondary-кнопка на оранжевом фоне даёт 2.8:1 по границе —
   ниже WCAG 1.4.11 (3:1 для границ UI-компонентов). Тёмная рамка на токене даёт
   6.1:1 и делает кнопку различимой как отдельный элемент, а не как светлое пятно. */
.app-impersonation-bar__exit {
    flex-shrink: 0;
    border-color: var(--color-text);
}

@media (max-width: 767px) {
    /* На мобильном плашка не липнет даже в приложении: полноэкранное бургер-меню
       (`.app-sidebar`, position: fixed) обязано владеть вьюпортом целиком, а
       вертикальное место на узком экране слишком дорого, чтобы отдавать его полосе. */
    .app-impersonation-bar--sticky {
        position: static;
        z-index: auto;
    }

    /* Узкий экран: тесная посадка, текст и кнопка выхода переносятся в две строки. */
    .app-impersonation-bar__inner {
        gap: var(--spacing-sm);
        padding: var(--spacing-sm) var(--app-content-padding-x-mobile);
    }
}

@media print {
    .app-impersonation-bar {
        display: none;
    }
}

/* ── Картинки внутри пользовательского контента (TASK-584) ───────────────────
   Описания и комментарии CRM-задач/тикетов приходят из WYSIWYG вместе с
   инлайновыми размерами вида style="width: 575.993px" — скриншот на 1900px
   распирал карточку и уезжал за её край. Правил для `img` внутри контента не
   было вообще, поэтому картинка рисовалась в натуральную величину.

   max-width побеждает инлайновый width (он ограничивает уже вычисленную
   ширину), поэтому чинит и `width` в атрибуте, и `width` в style — трогать
   пользовательскую разметку для этого не нужно.

   Классы проставляет App\Service\ContentImageLinkifier, он же оборачивает
   картинку в ссылку на оригинал (target="_blank") — полный размер остаётся
   доступен по клику, отсюда zoom-in курсор. */
.content-image {
    max-width: 100%;
    height: auto;
    border-radius: var(--border-radius-sm);
}

.content-image-link {
    display: inline-block;
    max-width: 100%;
    cursor: zoom-in;
}
