
/* Timetable Grid Styles */

/* Оверлей ожидания на сетке. Сетка высокая (около 1450px при рабочем дне
   07:00–23:00), а оверлей накрывает её целиком — подпись «Загружаем
   расписание…» по центру КОНТЕЙНЕРА у прокрученной страницы оказывалась бы
   за экраном, то есть ровно там, где её не видно. Прижимаем к верху и держим
   липкой: тогда она видна на любой прокрутке. `overflow: clip` у обёртки
   scroll-контейнера не создаёт, поэтому sticky считается от окна. */
.timetable-wrapper > .bk-sec__overlay {
    align-items: flex-start;
    /* Выше карточек броней и липких шапок колонок (там доходит до 900):
       общий z-index оверлея — 5, и на сетке он оказывался ПОД записями,
       то есть гасил пустые ячейки и не гасил главное. */
    z-index: 901;
    background: color-mix(in srgb, var(--bp-paper-1, #fff) 62%, transparent);
}
/* Подпись читается поверх плотной сетки только со своей подложкой. */
.timetable-wrapper > .bk-sec__overlay > * {
    position: sticky;
    top: 45vh;
    background: var(--bp-paper-1, #fff);
    border: 1px solid var(--bp-line);
    border-radius: 999px;
    padding: .35rem .7rem;
    box-shadow: 0 2px 10px rgb(0 0 0 / 8%);
}
.timetable-wrapper > .bk-sec__overlay > .spinner-border {
    padding: 0;
    border-radius: 50%;
    box-shadow: none;
    border-width: .2em;
}

.timetable-wrapper {
    /* ❌ Без overflow-x/auto: колонки — minmax(0,1fr), горизонтально сетка не
       переполняется, а overflow-x:auto делал обёртку scroll-контейнером и
       рисовал ВНУТРЕННИЙ вертикальный скроллбар.
       Скроллит страница; sticky-шапки колонок липнут под .app-header.
       clip — НЕ scroll-контейнер (sticky живёт), но возвращает обрезку
       квадратных фонов ячеек по скруглённым углам обёртки. */
    overflow: clip;
    border: 1px solid var(--bp-line);
    border-radius: var(--bp-radius-lg);
    background: var(--bp-sheet);
    box-shadow: var(--bp-shadow-card);
}

.timetable-grid {
    display: grid;
    /* Часы по ОБОИМ краям: на восьми кортах глаз
       у правой колонки терял строку и уезжал за временем через всю сетку. */
    grid-template-columns: 50px repeat(var(--col-count), minmax(0, 1fr)) 50px;
    position: relative;
    min-width: 0;
}

/* Corner cell */
.tt-corner {
    position: sticky;
    left: 0;
    /* Скроллпорт теперь страница: липнем под sticky .app-header, top:0
       прятал бы шапку за навигационным баром */
    top: var(--app-header-h, 0px);
    /* Выше оверлеев сетки (hover-карточка 30, линия времени 50, панель
       мультивыбора 60) — прилипшую шапку они перекрывать не должны;
       ниже .app-header (1030) и модалок */
    z-index: 85;
    background: var(--bp-paper-2);
    border-right: 1px solid var(--bp-line);
    border-bottom: 2px solid var(--bp-line);
}

/* Стрелки листания окна колонок (вид «Тренеры + Корты»): живут в угловых
   ячейках шапки над столбиками времени — всегда на виду (sticky вместе с
   шапкой) и не съедают ширину сетки */
.tt-colnav {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    border: 0;
    background: transparent;
    color: var(--bp-ink);
    font-size: 1rem;
    cursor: pointer;
    border-radius: var(--bp-r-sm, 6px);
}

.tt-colnav:hover {
    background: var(--bp-line);
}

/* Column Headers */
.tt-header {
    position: sticky;
    top: var(--app-header-h, 0px);
    /* 80 — выше оверлеев сетки (см. .tt-corner), ниже .app-header */
    z-index: 80;
    padding: 12px 8px;
    background: var(--bp-paper-2);
    border-bottom: 2px solid var(--bp-line);
    border-right: 1px solid var(--bp-paper-2);
    font-weight: 600;
    font-size: 0.85rem;
    text-align: center;
    white-space: normal;
    overflow-wrap: anywhere;
}
/* Ширина бордера = 8px как у .tt-cell — края колонок шапки и тела совпадают */
.tt-header { border-right-width: 8px; }

/* :last-child-обнулений бордера НЕТ намеренно: у шапки правило было мёртвым
   (за шапками в grid идут template/строки — :last-child не матчится), а
   снятие бордера только у ячеек смещало заголовок последней колонки на
   ~4px относительно карточек. 8px белого бордера у правого
   края на белом фоне не видны — пусть будут у всех колонок одинаково. */

/* Time Row */
.tt-row {
    display: contents;
}

/* Time Labels */
.tt-time {
    position: sticky;
    left: 0;
    z-index: 10;
    height: 40px;
    padding: 2px 8px;
    background: var(--bp-paper-2);
    border-right: 1px solid var(--bp-line);
    border-top: 1px dashed var(--bp-line);
    font-size: 0.75rem;
    color: var(--bp-muted);
    display: flex;
    align-items: flex-start;
}

.tt-time.hour {
    border-top: 1px solid var(--bp-line);
}

/* Правые близнецы левых часов: липнут к правому краю, разделитель — слева */
.tt-time--right,
.tt-corner--right {
    left: auto;
    right: 0;
    border-right: 0;
    border-left: 1px solid var(--bp-line);
}

/* Grid Cells */
.tt-cell {
    position: relative;
    height: 40px;
    /* 8px, не 1px: карточки соседних колонок сливались.
       Белый жёлоб как в YClients: цвет = фон ячейки — бежевый (paper-2)
       читался как «серая заливка»; колонки разделяют разрывы линий сетки.
       Разделитель бордером, а не grid-gap: gap просвечивал бы контент в
       зазорах под sticky-шапкой колонок при скролле */
    border-right: 8px solid var(--bp-sheet);
    /* Visible dashed line at the half-hour (the solid line is the full hour). */
    border-top: 1px dashed var(--bp-line);
    background: var(--bp-sheet);
}

.tt-cell.selectable { cursor: crosshair; user-select: none; }
/* Плашка прицеливания на ховере пустого слота (как «Новый сеанс» в
   YClients — просьба администраторов): время старта берётся из
   data-newlabel; ночные display-only строки атрибут не получают (Alpine
   снимает его при false). Занятые отрезки перекрыты карточками броней,
   там ховер до ячейки не доходит. Во время протяжки выделения плашку
   гасим — она мельтешит под курсором. [data-newlabel] в селекторе — и
   гейт, и +1 к специфичности против правил «можно с тренером». */
.tt-cell.selectable[data-newlabel]:hover::after {
    content: attr(data-newlabel);
    position: absolute;
    /* inset/display/font перебивают «можно с тренером» (.trainer-avail::after
       растягивает inset:0 + flex-центр — плашка выходила разного размера).
       На всю ширину ячейки, текст по центру. */
    inset: -1px 0 auto 0;
    display: block;
    box-sizing: border-box;
    /* Узкая колонка: не режем текст (правило CRM), плашка выступает вправо */
    min-width: max-content;
    z-index: 3;
    padding: 2px 8px;
    border-radius: 3px;
    background: var(--bp-green-dark);
    color: var(--bp-sheet);
    font-family: var(--ff-ui);
    font-size: .74rem;
    font-weight: 600;
    letter-spacing: normal;
    text-align: center;
    white-space: nowrap;
    pointer-events: none;
}
body.tt-selecting .tt-cell.selectable[data-newlabel]:hover::after { content: none; }
.tt-booking { transition: top .25s ease, height .25s ease; }
body.tt-card-dragging .tt-booking { transition: none; }
@media (prefers-reduced-motion: reduce) {
    .tt-booking { transition: none; }
}
/* Временный класс замера elementFromPoint «сквозь карточки» */
body.tt-elfp .tt-booking { pointer-events: none !important; }
.tt-grid, .tt-booking { user-select: none; }

/* Форма создания: колонка, в которой порядок полей задаёт вкладка
   (`order` на блоках) — «Тренер» ставит тренера перед услугой, «Группа»
   наоборот. Без flex-контейнера значения order не работают вовсе. */
.nc-body {
    display: flex;
    flex-direction: column;
}
/* Порядок полей — КЛАССОМ, а не `:style`: Alpine переписывает атрибут style
   целиком при каждом обновлении и стирает `display:none`, который ставит
   x-show. Из-за этого блок тренера показывался на вкладках, где он скрыт. */
.nc-o4 { order: 4; }
.nc-o5 { order: 5; }

/* Режим переноса записи на другую дату */
.tt-resched-banner {
    position: sticky; top: 0; z-index: 1040;
    display: flex; align-items: center; justify-content: space-between;
    gap: .5rem; padding: .5rem .75rem; margin-bottom: .5rem; border-radius: 8px;
    background: var(--bp-brand, #2d6a4f); color: #fff;
}
.tt-resched-was { opacity: .8; font-size: .85em; }
.tt-resched-backdrop {
    position: fixed; inset: 0; z-index: 1060; background: rgba(0,0,0,.4);
    display: flex; align-items: center; justify-content: center; padding: 1rem;
}
.tt-resched-dialog {
    background: var(--bp-paper, #fff); color: var(--bp-ink, inherit);
    padding: 1rem; border-radius: 8px; max-width: 440px; width: 100%;
    box-shadow: 0 8px 32px rgba(0,0,0,.25);
}
/* Роскошный «прожектор»: затемняем весь экран, светлыми оставляем календарь
   слева, выбор даты сверху и сетку расписания — до завершения/отмены переноса */
body.tt-reschedule-active::before {
    content: ''; position: fixed; inset: 0; z-index: 1035;
    background: rgba(10, 18, 14, 0.58);
}
body.tt-reschedule-active #sidebar-calendar,
body.tt-reschedule-active .tt-tb-group,
body.tt-reschedule-active .timetable-grid {
    position: relative; z-index: 1038;
    box-shadow: 0 0 0 3px var(--bp-green), 0 12px 48px rgba(0,0,0,.5);
    border-radius: 12px;
}
/* Тулбар журнала: ЕДИНЫЙ язык контролов:
   одна высота 30px, один шрифт (моно-версалитет утилитарного слоя бренда),
   один контур --bp-line; активное состояние — чернильная заливка.
   Скин перекрывает Bootstrap только внутри .tt-toolbar. */
.tt-toolbar {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    /* Тулбар всегда занимает свою строку. Чипы строки дня (счёт записей,
       оплаты, отмены) приезжают ПОЗЖЕ — их считает сервер, — и без этого
       правила они разгоняли строку дня с 237 до 772px и выталкивали тулбар
       вниз на 35px через полсекунды после открытия, когда рука уже идёт к
       кнопкам вида. Раньше строку держала широкой всегда развёрнутая
       легенда; она свернулась в кнопку, и держать стало нечем. */
    flex-basis: 100%;
}
.tt-toolbar .tt-tb-group { display: flex; gap: 0; align-items: center; }
.tt-toolbar .tt-tb-group > * + * { margin-left: -1px; }
/* Дата по центру поля; «Сегодня» — отдельная кнопка, не слипается с ‹дата› */
.tt-toolbar .tt-tb-group input[type="date"] { text-align: center; }

.tt-toolbar .tt-tb-group > button:last-child { margin-left: 8px; }
.tt-toolbar .tt-tb-sep {
    width: 1px;
    height: 22px;
    background: var(--bp-line);
    flex: none;
}
.tt-toolbar .btn,
.tt-toolbar .form-control {
    height: 32px;
    display: inline-flex;
    align-items: center;
    padding: 0 12px;
    font-family: var(--ff-ui);
    font-size: .78rem;
    font-weight: 600;
    letter-spacing: .01em;
    text-transform: none;
    border: 1px solid var(--bp-line-strong);
    border-radius: var(--bp-radius-sm);
    background: var(--bp-sheet);
    color: var(--bp-ink-soft);
    box-shadow: none;
}

/* Поле даты — запасной выбор дня: то же самое делает месячный календарь
   сайдбара, и держать оба сразу незачем. Показываем поле ровно там, где
   календаря нет: узкий экран (сайдбар заменён выдвижным меню) и свёрнутая
   рельса. Условия зеркалят nav-shell.css — меняешь видимость календаря,
   поменяй и здесь. Правило стоит ПОСЛЕ скина тулбара и повторяет его
   специфичность: иначе `.tt-toolbar .form-control` перебивает display. */
.tt-toolbar .tt-tb-date { width: 140px; }
@media (min-width: 992px) {
    .tt-toolbar .tt-tb-date { display: none; }
    body.sidebar-railed:not(.tt-reschedule-active) .tt-toolbar .tt-tb-date {
        display: inline-flex;
    }
}
.tt-toolbar .btn:hover:not(:disabled) {
    background: var(--bp-blue-tint);
    color: var(--bp-blue-ink);
    border-color: var(--bp-blue);
}
/* Активные состояния — синий тинт (v3: без чёрных заливок) */
.tt-toolbar .btn.btn-primary,
.tt-toolbar .btn.btn-secondary {
    background: var(--bp-blue-tint);
    border-color: var(--bp-blue);
    color: var(--bp-blue);
}
.tt-toolbar .btn.btn-primary:hover,
.tt-toolbar .btn.btn-secondary:hover {
    background: var(--bp-blue-tint);
    color: var(--bp-blue-ink);
}
/* Чип «Оплаты за день» — зелёный: принятые деньги */
.tt-chip-paid {
    background: var(--bp-green-tint) !important;
    border-color: var(--bp-green-dark) !important;
    color: var(--bp-green-dark) !important;
}

.tt-count-chip {
    font-family: var(--ff-ui);
    font-size: .74rem;
    font-weight: 600;
    letter-spacing: .01em;
    text-transform: none;
    padding: 3px 10px;
    border: 1px solid var(--bp-line-strong);
    border-radius: 999px;
    background: var(--bp-paper-2);
    color: var(--bp-ink-soft);
}

/* Сводка «не выгружено в YClients»: янтарная, а не красная — работа клуба не
   пострадала, разобраться нужно не сейчас. Ссылка выглядит чипом. */
a.tt-chip-export {
    text-decoration: none;
    background: var(--bp-warning-tint) !important;
    border-color: var(--bp-warning) !important;
    color: var(--bp-warning-deep) !important;
}
a.tt-chip-export:hover {
    background: var(--bp-warning-bg) !important;
}

/* Кнопка-тумблер легенды: нейтральная, в отличие от красных «Отмен» — она
   ничего не сообщает о дне, а только раскрывает справку по цветам. */
button.tt-chip-legend {
    cursor: pointer;
}
button.tt-chip-legend:hover {
    background: var(--bp-paper);
    color: var(--bp-ink);
}

/* Кнопка-тумблер ленты отмен: как чип «Оплаты за день», но красно-розовая —
   потерянные на отменах деньги (грустная, но показательная метрика) */
button.tt-chip-cancels {
    cursor: pointer;
    background: rgba(220, 53, 69, 0.12) !important;
    border-color: var(--bp-stamp-red, #dc3545) !important;
    color: var(--bp-stamp-red, #dc3545) !important;
}
button.tt-chip-cancels:hover {
    background: rgba(220, 53, 69, 0.22) !important;
}

/* Панель «Тренеры сегодня»: чипы с часами работы; тултип — услуги */
.journal-trainers-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
    margin-bottom: 8px;
    padding: 8px 12px;
    background: var(--bp-sheet);
    border: 1px solid var(--bp-line);
    border-radius: var(--bp-radius);
    box-shadow: var(--bp-shadow-card);
    font-size: .78rem;
}
.journal-trainers-bar .jtb-chip {
    display: inline-flex;
    gap: 6px;
    align-items: center;
    padding: 3px 12px;
    background: var(--bp-sheet);
    border: 1px solid var(--bp-line-strong);
    border-radius: 999px;
    cursor: pointer;
    font-size: inherit;
    color: inherit;
    transition: border-color .12s ease, background .12s ease;
}
.journal-trainers-bar .jtb-chip:hover {
    border-color: var(--bp-blue);
    background: var(--bp-blue-tint);
}
.journal-trainers-bar .jtb-chip .jtb-hours {
    font-family: var(--ff-ui);
    font-variant-numeric: tabular-nums;
    font-size: .74rem;
    color: var(--bp-ink-soft);
}
.journal-trainers-bar .jtb-chip.jtb-active {
    border-color: var(--bp-blue);
    background: var(--bp-blue-tint);
    color: var(--bp-blue);
    font-weight: 600;
}
/* Чип несёт цвет тренера: лёгкий тинт фона + рамка его тона; активный
   (фильтр включён) — кольцо в цвете тренера */
.journal-trainers-bar .jtb-chip {
    background: color-mix(in srgb, var(--jtb-color, transparent) 12%, var(--bp-sheet));
    border-color: color-mix(in srgb, var(--jtb-color, transparent) 45%, var(--bp-line-strong));
}
.journal-trainers-bar .jtb-chip.jtb-active {
    background: color-mix(in srgb, var(--jtb-color, transparent) 22%, var(--bp-sheet));
    border-color: var(--jtb-color, var(--bp-blue));
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--jtb-color, transparent) 35%, transparent);
    color: var(--bp-ink);
}
.journal-trainers-bar .jtb-item {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 7px;
}
.journal-trainers-bar .jtb-color-dot {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    border: 2px solid var(--bp-sheet);
    box-shadow: 0 0 0 1px var(--bp-line-strong);
    cursor: pointer;
    padding: 0;
    flex: none;
}
.journal-trainers-bar .jtb-palette {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    /* выше липких шапок сетки (85), ниже модалок (1040) */
    z-index: 900;
    display: flex;
    gap: 6px;
    align-items: center;
    padding: 8px;
    background: var(--bp-sheet);
    border: 1px solid var(--bp-line-strong);
    border-radius: var(--bp-radius);
    box-shadow: var(--bp-shadow-card);
}
.journal-trainers-bar .jtb-swatch {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 2px solid transparent;
    cursor: pointer;
    padding: 0;
}
.journal-trainers-bar .jtb-swatch-active {
    border-color: var(--bp-ink);
    box-shadow: 0 0 0 2px var(--bp-sheet) inset;
}
.journal-trainers-bar .jtb-swatch-reset {
    border: 1px solid var(--bp-line-strong);
    background: var(--bp-sheet);
    border-radius: 999px;
    font-size: .72rem;
    padding: 2px 8px;
    cursor: pointer;
}
/* Легенда цветов карточек — в тулбаре, прижата вправо (переехала из панели
   тренеров). `margin-left: auto` уводит её к правому краю строки, а на
   узком экране flex-wrap тулбара просто переносит её на свою строку. */
.tt-legend {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 4px 6px;
    align-items: center;
    font-size: .74rem;
    color: var(--bp-ink-soft);
}
/* Ключ легенды — бирка как на одежде: цвет — подложка самой надписи,
   слева остриё ярлычка с дырочкой; цвета = полоскам времени карточек */
.tt-key {
    position: relative;
    padding: 1px 9px 1px 13px;
    clip-path: polygon(8px 0, 100% 0, 100% 100%, 8px 100%, 0 50%);
    cursor: help;
    white-space: nowrap;
    color: #fff;
}
.tt-key::before {
    content: '';
    position: absolute;
    left: 7px; top: 50%;
    width: 4px; height: 4px;
    margin-top: -2px;
    border-radius: 50%;
    background: var(--bp-paper, #f4f2ec);
}
.tt-key.key-play { background: var(--bp-line-strong); color: var(--bp-ink); }
.tt-key.key-training { background: var(--bp-blue); }
.tt-key.key-group { background: var(--bp-purple); }
.tt-key.key-tournament-own { background: var(--bp-warning-fill); color: var(--bp-ink); }
.tt-key.key-tournament-ext { background: var(--bp-stamp-red); }
.tt-key.key-league { background: var(--bp-green); }

/* Аватар-кружочек тренера (чипы + колонка времени) */
.tt-ava {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    overflow: hidden;
    background: var(--bp-green-tint);
    border: 1px solid var(--bp-green-dark);
    flex: none;
    vertical-align: middle;
}
.tt-ava img { width: 100%; height: 100%; object-fit: cover; }
.tt-ava .tt-ava-txt, .tt-ava-txt {
    font-family: var(--ff-ui);
    font-size: .5rem;
    font-weight: 700;
    color: var(--bp-green-dark);
    line-height: 1;
}


/* Свободный слот, где доступен хотя бы один тренер: полупрозрачная
   надпись «тренер» (штриховка читалась как «перечёркнуто») */
.tt-cell.trainer-avail::after {
    content: 'можно с тренером';
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--ff-ui);
    font-size: .6rem;
    letter-spacing: .04em;
    /* Тёмно-зелёный, а не полупрозрачный: бледная надпись терялась, и
       окна тренеров на сетке кортов не читались */
    color: var(--bp-green-dark);
    pointer-events: none;
}
/* при выбранных тренерах сверху надпись уступает место кружочкам */
/* :not(:hover): на ховере это гашение уступает плашке прицеливания —
   иначе при включённой подсветке тренеров рисовалась пустая зелёная
   полоса (специфичности равны, это правило ниже по файлу) */
.tt-cell:has(.tt-cell-avas:not([style*="display: none"])).trainer-avail:not(:hover)::after {
    content: '';
}
.tt-cell .tt-cell-avas {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    /* НИЖЕ карточек: конфликтные записи рисуются со смещением в чужих
       ячейках, и кружок «свободного» тренера ложился поверх */
    z-index: 1;
    pointer-events: none;
}
/* Кольцо цвета тренера (его чип в панели): два выбранных тренера на одном
   слоте различаются и без фото */
.tt-cell .tt-cell-avas .tt-ava {
    width: 22px;
    height: 22px;
    box-shadow: 0 0 0 2px var(--tr-ring, transparent), 0 1px 2px rgba(0,0,0,.2);
}
.tt-ava-tag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}
.tt-ava-name {
    font-size: .72rem;
    color: var(--bp-green-dark);
    font-weight: 600;
    white-space: nowrap;
}

/* Тип записи (легенда в тулбаре) красит ПОЛОСКУ ВРЕМЕНИ, а не кромку слева:
   в колонке шириной 100px пятипиксельная кромка
   съедала место и читалась хуже, чем заливка во всю ширину шапки. */
.tt-booking.type-training .tt-timeband {
    background: var(--bp-blue);
    color: var(--bp-on-accent);
}
.tt-booking.type-group .tt-timeband {
    background: #7c3aed;
    color: var(--bp-on-accent);
}
.tt-booking.type-tournament-own .tt-timeband {
    background: var(--bp-warning-fill);
    color: var(--bp-ink);
}
.tt-booking.type-tournament-ext .tt-timeband {
    background: var(--bp-stamp-red);
    color: var(--bp-on-accent);
}
/* Матч лиги — фирменный зелёный: он не спорит ни с синей тренировкой, ни с
   фиолетовой группой, ни с золотом и красным турниров, и остаётся различимым
   у оператора, который читает сетку боковым зрением. */
.tt-booking.type-league .tt-timeband {
    background: var(--bp-green);
    color: var(--bp-on-accent);
}
/* Значки оплаты и синка держат СВОЙ цвет (зелёный/оранжевый — семантика,
   правила ниже по файлу перебивают наследование); служебные берут цвет
   полоски, иначе фиолетовый и красный тонули в её заливке. */
.tt-timeband .tt-trainer-icon,
.tt-timeband .tt-conflict-icon { color: inherit; }

/* Квитанция мультивыбора живёт в `timetable-selection.css` — отдельным файлом
   вместе со своими кнопками и мобильной раскладкой. */
.tt-resize-handle {
    position: absolute; left: 0; right: 0; height: 7px; cursor: ns-resize; z-index: 3;
}
.tt-resize-handle.top { top: 0; }
.tt-resize-handle.bottom { bottom: 0; }
.tt-resize-handle:hover { background: color-mix(in srgb, var(--bp-ink) 25%, transparent); }
/* во время drag'а карточки прозрачны для elementFromPoint (ищем ячейку под курсором) */
body.tt-card-dragging .tt-booking { pointer-events: none; opacity: 0.85; }
body.tt-card-dragging { user-select: none; }
.tt-booking { cursor: grab; }
.tt-booking:active { cursor: grabbing; }
.tt-cell.selected {
    background: color-mix(in srgb, var(--bp-ink) 10%, transparent);
    box-shadow: inset 0 0 0 2px var(--bp-ink);
}
.tt-cell.hour {
    border-top: 1px solid var(--bp-line);
}

/* Booking Blocks */
/* Пересекающиеся записи колонки стоят рядом, дорожками: место и ширину
   дорожки задают переменные из getOverlapStyle (раскладка — ttLanes).
   Раньше здесь стояло «друг под другом» с !important на left/width, и
   дорожки не применялись вовсе: три двухчасовые записи одного визита
   стопкой выглядели одной записью на шесть часов, а бронь, начатая на
   полчаса позже, ложилась поверх соседки. */
.tt-booking {
    position: absolute;
    left: var(--lane-left, 2px);
    width: var(--lane-width, calc(100% - 4px));
    top: 0;
    z-index: 5;
    /* Отступы держат ВНУТРЕННИЕ блоки (.tt-booking-body / .tt-group-header):
       так полоска времени гасит их отрицательными полями и идёт от края
       до края плитки. */
    padding: 0;
    border-radius: 4px;
    overflow: hidden;
    cursor: pointer;
    font-size: 0.7rem;
    line-height: 1.3;
    transition: transform 0.1s, box-shadow 0.1s;
    box-sizing: border-box;
    border: 1px solid rgba(0,0,0,0.1);
    /* Без тени в покое — граница слота читается по 1px рамке; тень
       появляется только на hover */
}

/* Наведение на ЛЮБУЮ плитку — лёгкое выделение (тень + поверх соседей). */
.tt-booking:hover {
    z-index: 30;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
/* Раскрытие — ТОЛЬКО когда контент НЕ влез (data-clipped мерит x-init:
   scrollHeight>clientHeight). У кого влезло — рамка/рост не появляются. Плитка
   растёт по контенту (min-height:var(--tt-h) держит ≥ слота),
   снимает обрезку и становится непрозрачной (no-show/cancelled иначе просвечивают). */
.tt-booking[data-clipped="true"]:hover {
    height: auto !important;
    overflow: visible;
    opacity: 1 !important;
    /* снять угасание — раскрыто целиком */
    -webkit-mask-image: none;
    mask-image: none;
    /* Узкая карточка дорожки раскрывается на всю ширину колонки: в трети
       колонки текст шёл по слову в строке, и раскрытие вытягивало карточку
       на полсетки, закрывая чужие часы. */
    left: 2px;
    width: calc(100% - 4px);
}
/* Свисающий контент несёт СВОЙ непрозрачный фон (= фон плитки) + рамку/тень:
   голый текст span'ов иначе просвечивает нижний слот, а список участников
   сливается с бронью ниже без границы. Фон/рамку вешаем на
   ОБЁРТКУ и тянем её по контенту (height:auto) — у группы .tt-group-container в
   норме height:100% залочен высотой плитки, из-за чего участники свисали ЗА
   рамку; height:auto растит обёртку под всех участников. */
.tt-booking[data-clipped="true"]:hover .tt-booking-body,
.tt-booking[data-clipped="true"]:hover .tt-group-container {
    height: auto;
    background: inherit;
    /* outline (не border): рисуется ПОВЕРХ, не занимает место в потоке — иначе
       1px border при наведении сдвигал бы текст. */
    outline: 1px solid var(--bp-ink-soft, #55575e);
    border-radius: 5px;
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.22);
}
/* У раскрытой ГРУППЫ гасим фиолет самой плитки целиком: гейтим по
   has-participants — он есть на ЛЮБОЙ группе (group-event И/ИЛИ group-activity),
   а фиолет даёт то border group-event, то border-left:purple group-activity.
   border всех сторон → transparent, оба box-shadow → none; рамку несёт обёртка
   (чёрная, как у обычной брони), иначе вокруг остаётся фиолет. */
.tt-booking.has-participants[data-clipped="true"]:hover {
    border-color: transparent !important;
    box-shadow: none !important;
}
/* Во время перетаскивания/ресайза раскрытие ОТКЛЮЧАЕМ: высота обязана отражать
   длительность (getBookingStyle → --tt-h), иначе live-превью схлопнется в
   height:auto по контенту вместо перетаскиваемого слота. */
body.tt-card-dragging .tt-booking[data-clipped="true"]:hover {
    height: var(--tt-h) !important;
    overflow: hidden;
    left: var(--lane-left, 2px);
    width: var(--lane-width, calc(100% - 4px));
}

/* Payment Status */
.tt-booking.paid {
    background: var(--bp-green-tint);
    border-left: 4px solid var(--bp-green-dark);
    border-color: var(--bp-green-dark) color-mix(in srgb, var(--bp-green-dark) 30%, transparent) color-mix(in srgb, var(--bp-green-dark) 30%, transparent) var(--bp-green-dark);
}

/* Расчёт вёлся в YClients: утверждать нечего — ни зелени «оплачено», ни
   оранжевого «долг». Плитка нейтральна, как блокировка: о деньгах такой брони
   судила прежняя система, а не CRM. */
.tt-booking.outside {
    background: var(--bp-sheet);
    border-left: 4px solid var(--bp-ink-soft);
    border-color: var(--bp-ink-soft) color-mix(in srgb, var(--bp-ink-soft) 30%, transparent) color-mix(in srgb, var(--bp-ink-soft) 30%, transparent) var(--bp-ink-soft);
}

.tt-payment-icon.outside {
    color: var(--bp-ink-soft);
}

/* Не оплачено ≠ отменено: визит в силе — оранжевый «долг»,
   красный зарезервирован за отменами/конфликтами */
.tt-booking.unpaid {
    background: var(--bp-orange-tint);
    border-left: 4px solid var(--bp-orange);
    border-color: var(--bp-orange) color-mix(in srgb, var(--bp-orange) 30%, transparent) color-mix(in srgb, var(--bp-orange) 30%, transparent) var(--bp-orange);
}

/* Раскраска по тренеру: фон карточки — тон тренера, оплату несут рамка и
   значок. Проблемная оплата ОБЯЗАНА кричать на любом фоне: неоплата —
   тёмное кольцо + залитый оранжевый значок ₽, недоплата — янтарное кольцо +
   залитый значок. Кольцо неоплаты тёмное, а не оранжевое: оранжевый теперь
   и цвет тренера (палитра как в YClients), и оранжевая рамка на оранжевом
   тоне пропадала. Группы тонируются тоже — «группу» несёт фиолетовая
   полоса времени (.type-group .tt-timeband), фон отдан тренеру. */
.tt-booking.trainer-tinted,
.tt-booking.trainer-tinted.paid {
    background: color-mix(in srgb, var(--tr-tint, var(--bp-sheet)) 26%, var(--bp-sheet));
    /* Рамку и полосу ОБЯЗАНЫ перекрасить. Правило тренера меняло только фон,
       а рамку карточке давал `.paid` — зелёную. У оплаченных занятий (то
       есть почти у всех) полоса оставалась зелёной поверх бледного тона, и
       восемь разных тренеров читались одним цветом: «их не видно, все
       зелёные» (задача 1238).

       Ширину полосы НЕ трогаем: ниже стоит `border-left-width: 1px
       !important` — 4–5px слева в стакортовой колонке уже признавали
       дороже сигнала. Тренера несёт ЗАЛИВКА, полоса лишь не спорит с ней. */
    border-color: color-mix(in srgb, var(--tr-tint, var(--bp-sheet)) 45%, transparent);
    border-left-color: var(--tr-tint, var(--bp-sheet));
}
.tt-booking.trainer-tinted.unpaid {
    background: color-mix(in srgb, var(--tr-tint, var(--bp-sheet)) 26%, var(--bp-sheet));
    border: 2px solid var(--bp-ink);
    border-left-width: 2px !important;
}
.tt-booking.trainer-tinted.unpaid .tt-payment-icon.unpaid {
    background: var(--bp-orange);
    color: #fff;
    border-radius: 999px;
    padding: 0 5px;
}
.tt-booking.trainer-tinted.partial {
    background: color-mix(in srgb, var(--tr-tint, var(--bp-sheet)) 26%, var(--bp-sheet));
    border: 2px solid var(--bp-warning-fill);
    border-left-width: 2px !important;
}
.tt-booking.trainer-tinted.partial .tt-payment-icon.partial {
    background: var(--bp-warning-fill);
    color: #fff;
    border-radius: 999px;
    padding: 0 5px;
}
/* Фильтр чипами: карточки не выбранных тренеров гаснут, но остаются
   читаемыми и кликабельными — это подсветка, не скрытие. !important —
   иначе no-show (.75) и отмены (.75/.95) перебивают гашение и «чужие»
   плохие исходы становятся ярче отфильтрованных */
.tt-booking.tt-dimmed {
    opacity: .35 !important;
}

/* Клиент не пришёл (attendance = -1): серая карточка — исход визита
   плохой, но это не отмена и не долг */
.tt-booking.no-show {
    background: var(--bp-paper-2);
    border-left: 4px solid var(--bp-muted);
    border-color: var(--bp-muted) color-mix(in srgb, var(--bp-muted) 30%, transparent) color-mix(in srgb, var(--bp-muted) 30%, transparent) var(--bp-muted);
    opacity: 0.75;
}

.tt-booking.no-show .tt-booking-client::after {
    content: ' (не пришёл)';
    color: var(--bp-ink-soft);
    font-weight: 600;
    font-size: 0.7rem;
}

.tt-booking.partial {
    background: var(--bp-warning-tint);
    border-left: 4px solid var(--bp-warning-fill);
    border-color: var(--bp-warning-fill) color-mix(in srgb, var(--bp-warning-fill) 30%, transparent) color-mix(in srgb, var(--bp-warning-fill) 30%, transparent) var(--bp-warning-fill);
}

/* Внешняя занятость корта — серая штриховка, read-only (курсор default). Своя
   заглушка той же штриховки двигается и тянется за кромки, как бронь. */
.tt-booking.busy-block.type-court-block { cursor: grab; }
.tt-booking.busy-block {
    background: repeating-linear-gradient(
        45deg,
        var(--bp-paper-2),
        var(--bp-paper-2) 8px,
        color-mix(in srgb, var(--bp-muted) 14%, var(--bp-paper-2)) 8px,
        color-mix(in srgb, var(--bp-muted) 14%, var(--bp-paper-2)) 16px
    );
    border-left: 4px solid var(--bp-muted);
    border-color: var(--bp-muted);
    color: var(--bp-ink-soft);
    cursor: default;
}

/* Кромки слева больше нет ни у одного статуса: оплату несёт фон плитки, тип —
   полоска времени. 4–5px слева в колонке 100px стоили дороже сигнала. */
.tt-booking.paid,
.tt-booking.unpaid,
.tt-booking.partial,
.tt-booking.no-show,
.tt-booking.busy-block,
.tt-booking.group-event { border-left-width: 1px !important; }

/* Group Events (Tournaments & Group Trainings) — обводка как у обычной
   брони, фон — ТОТ ЖЕ тинт, что у group-activity: у группы один вид,
   фиолет дают полоска времени и тинт */
.tt-booking.group-event {
    background: var(--bp-purple-tint) !important;
}

.tt-booking.group-event.paid {
    background: linear-gradient(135deg, #f3e8ff 0%, #e9d5ff 50%, #d1fae5 100%) !important;
}

.tt-booking.group-event.unpaid {
    background: linear-gradient(135deg, #f3e8ff 0%, #e9d5ff 50%, #fee2e2 100%) !important;
}

.tt-booking.group-event:hover {
    box-shadow: 0 4px 12px rgba(147, 51, 234, 0.25);
}

/* Режим «Тренеры по цветам»: группа берёт тон тренера поверх фиолета.
   !important — фиолет групп сам стоит под !important (правила выше и ниже
   по файлу), и обычное объявление его не перебьёт никакой специфичностью. */
.tt-booking.trainer-tinted.group-event,
.tt-booking.trainer-tinted.group-activity,
.tt-booking.trainer-tinted.group-event.paid,
.tt-booking.trainer-tinted.group-event.unpaid {
    background: color-mix(in srgb, var(--tr-tint, var(--bp-sheet)) 26%, var(--bp-sheet)) !important;
}

/* Conflict Highlighting (Overbooking) */
.tt-booking.conflict {
    border: 2px solid var(--bp-stamp-red) !important;
    border-left-width: 2px !important;
    animation: conflict-pulse 1.5s ease-in-out infinite;
}

@keyframes conflict-pulse {
    0%, 100% { box-shadow: 0 0 6px color-mix(in srgb, var(--bp-stamp-red) 30%, transparent); }
    50% { box-shadow: 0 0 12px color-mix(in srgb, var(--bp-stamp-red) 60%, transparent); }
}


/* Бейдж корта («по тренерам») — пилюля СТРОКОЙ снизу плитки, симметрично чипу
   тренера в режиме «по кортам». Плавающей в правом верхнем углу она больше
   быть не может: там теперь полоска времени, и в колонке ~95px пилюля накрывала
   и сам интервал, и значки оплаты. */
.tt-court-badge {
    display: inline-flex;
    align-items: center;
    margin-top: 2px;
    background: var(--bp-blue-tint);
    color: var(--bp-blue);
    padding: 0 5px;
    border-radius: 999px;
    font-size: 0.55rem;
    font-weight: 700;
    letter-spacing: .01em;
    white-space: nowrap;
    font-family: var(--ff-ui);
    transition: transform 0.15s ease;
}

.tt-booking:hover .tt-court-badge {
    transform: scale(1.05);
}

.tt-booking-client {
    font-weight: 500;
    color: var(--bp-ink);
    word-wrap: break-word;
    overflow-wrap: break-word;
}

.tt-booking-service {
    color: var(--bp-muted);
    font-size: 0.65rem;
    word-wrap: break-word;
    overflow-wrap: break-word;
}

/* Телефон клиента на плитке. Пишем СПЛОШНЫМ `+7…`, а не через formatPhone:
   в узкой колонке (~88px под текст) форматированный номер переносится на
   вторую строку и съедает единственную свободную строку плитки. Полный
   формат — в тултипе и в карточке записи. */
.tt-booking-phone {
    font-size: 0.62rem;
    line-height: 1.25;
    color: var(--bp-ink-soft);
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}
/* Комментарий записи на плитке (как YClients): виден без открытия брони.
   Текст целиком — перенос, без обрезки многоточием (правило проекта). */
.tt-booking-comment {
    display: flex;
    gap: 3px;
    align-items: flex-start;
    margin-top: 2px;
    font-size: 0.65rem;
    line-height: 1.2;
    color: var(--bp-ink-soft);
    white-space: normal;
    overflow-wrap: anywhere;
}
.tt-booking-comment i {
    flex: 0 0 auto;
    margin-top: 1px;
    opacity: 0.65;
}

/* Матч лиги на плитке — те же правила, что у комментария: текст ЦЕЛИКОМ,
   переносом. В строке две пары по два человека, и «Карава…» не отвечает на
   вопрос «чей это корт». */
.tt-booking-league {
    display: flex;
    gap: 3px;
    align-items: flex-start;
    margin-top: 2px;
    font-size: 0.65rem;
    line-height: 1.2;
    color: var(--bp-green-dark);
    white-space: normal;
    overflow-wrap: anywhere;
}
.tt-booking-league i {
    flex: 0 0 auto;
    margin-top: 1px;
    opacity: 0.75;
}
/* Длинный комментарий в норме обрезается плиткой (overflow:hidden), полностью
   раскрывается общим .tt-booking:hover — текст остаётся на фоне выросшей плитки
   (paid/unpaid), поэтому не просвечивает. Отдельного правила не
   нужно — единый механизм. */

/* Current Time Line */
.tt-current-time {
    position: absolute;
    /* Ровно между столбиками часов — линия не режет ни левый, ни правый */
    left: 50px;
    right: 50px;
    height: 2px;
    background: var(--bp-stamp-red);
    z-index: 50;
    pointer-events: none;
}

.tt-current-time::before {
    content: '';
    position: absolute;
    left: -5px;
    top: -4px;
    width: 10px;
    height: 10px;
    background: var(--bp-stamp-red);
    border-radius: 50%;
}

.tt-current-label {
    position: absolute;
    left: -55px;
    top: -8px;
    padding: 2px 6px;
    background: var(--bp-stamp-red);
    color: #fff;
    font-size: 0.7rem;
    font-weight: 600;
    border-radius: var(--bp-radius-sm);
}

/* Animation */
.spin-animation {
    animation: spin 1s linear infinite;
}

@keyframes spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

/* Hide elements until Alpine.js loads */
[x-cloak] { display: none !important; }

/* Фиолетовый акцент групп/турниров и денежные бейджи — в booking-card.css
   (общий компонент карточки записи), сетка использует те же классы. */

/* Полоска времени — шапка плитки (время в общей
   массе текста сливалось с именем). Время слева, значки статуса справа.
   Узкая колонка — ~86px под текст, и редкая плитка с полным набором значков
   (инвентарь + синк + конфликт) в строку не влезает: тогда значки переносятся
   СВОЕЙ строкой (flex-wrap), а не вылезают за край плитки.
   Полоска идёт ОТ КРАЯ ДО КРАЯ: отступы плитки перенесены внутрь (.tt-booking
   padding:0 → .tt-booking-body/.tt-group-header), поэтому отрицательные поля
   ровно гасят отступ хозяина и не вылезают за рамку раскрытой по hover
   плитки — её несёт тот же .tt-booking-body. */
.tt-timeband {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 0 3px;
    /* Поля гасят отступ хозяина (6px), свой padding уже — в 95px колонке
       время и бейдж мест иначе не встают в одну строку */
    margin: -4px -6px 3px;
    padding: 1px 4px;
    background: color-mix(in srgb, var(--bp-ink) 14%, transparent);
    color: var(--bp-ink);
}

/* В событии полоска лежит внутри фиолетовой шапки — её отбивает светлая
   заливка, тёмная на градиенте читалась бы грязью. */
.tt-timeband--group {
    margin-bottom: 2px;
    /* фон не переопределяем: полоска берёт фиолет type-group — как у
       «незакрашенного» варианта группы */
}

/* Имя — блок, а не flex-строка: бейдж «new» идёт ТЕКСТОМ следом за именем и
   переносится вместе с ним, не занимая отдельную строку узкой плитки. */
.tt-booking-client-row {
    display: block;
}

.tt-booking-client-row .tt-new-badge {
    margin-left: 4px;
    vertical-align: 1px;
}

.tt-group-icon {
    font-size: 0.8rem;
    flex-shrink: 0;
    background: linear-gradient(135deg, #9333ea, #7c3aed);
    color: white;
    padding: 1px 4px;
    border-radius: 4px;
    margin-right: 2px;
}

.tt-booking-indicators {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
    margin-left: auto;
}

/* Время — первая строка плитки (формат YClients):
   глаз ищет в сетке сначала «когда», потом «кто». Кегль мельче имени: интервал
   и значки статуса обязаны уместиться в ОДНУ строку узкой колонки (на восьми
   кортах ≈ 86px под текст), иначе значки съезжали бы вниз на каждой плитке. */
.tt-time-compact {
    font-size: 0.56rem;
    /* Цвет — от полоски: на цветной заливке типа он белый, на нейтральной —
       чернила брендбука (контраст) */
    color: inherit;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.tt-payment-icon {
    font-size: 0.7rem;
}

/* «new» — первый визит клиента (как в YClients): сигнал предложить акции */
.tt-new-badge {
    font-size: 0.58rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .04em;
    padding: 1px 5px;
    border-radius: 999px;
    background: var(--bp-blue);
    color: var(--bp-on-accent);
    line-height: 1.4;
    cursor: help;
}

/* Красные значки «оператор, посмотри»: переплата к возврату и время под
   турниры без организатора (корты заняты, а турнирщик его не видит).
   Вид общий намеренно — одинаковое рисуем одинаково. */
.tt-overpaid-badge,
.tt-slot-orphan {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    font-size: 0.58rem;
    font-weight: 700;
    padding: 1px 5px;
    border-radius: 999px;
    background: var(--bp-stamp-red);
    color: #fff;
    line-height: 1.4;
    white-space: nowrap;
    cursor: help;
}

.tt-payment-icon.paid {
    color: var(--bp-green-dark);
}

.tt-payment-icon.unpaid {
    color: var(--bp-orange);
}

.tt-payment-icon.partial {
    color: var(--bp-warning);
}

.tt-payment-icon.no-show {
    color: var(--bp-ink-soft);
}

.tt-trainer-icon {
    font-size: 0.7rem;
    color: #8e44ad;
}

/* Время + тренер одной строкой; если имя длинное — чип переносится ПОД
   время целиком (текст не обрезаем — правило UX). */
.tt-metarow {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
    min-width: 0;
}

/* Чип тренера: фото (или инициалы) + имя. Цвет НЕ фиолетовый — карточка
   группы сама фиолетовая, и текст на ней сливался. Светлая
   полупрозрачная плашка + тёмные чернила читаются на любом фоне карточки
   (зелёный/фиолетовый/оранжевый/серый).
   На карточках короче часа имя скрыто (`duration_min < 60`): высота
   карточки задана длительностью, и чип с именем уезжал ЗА её границы —
   имя было бы не обрезано, а не видно вовсе. Там остаётся фото + тултип,
   полное имя — в карточке записи. */
.tt-trainer {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    min-width: 0;
    max-width: 100%;
    padding: 1px 5px 1px 1px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.82);
    box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.1);
}

/* Чип без имени (короткая карточка) — кружок фото без «хвоста» плашки */
.tt-trainer:not(:has(.tt-trainer-name:not([style*="none"]))) {
    padding: 1px;
}

.tt-trainer-photo,
.tt-trainer-initials {
    flex: 0 0 auto;
    width: 15px;
    height: 15px;
    border-radius: 50%;
}

.tt-trainer-photo {
    object-fit: cover;
    box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.12);
}

.tt-trainer-initials {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bp-ink);
    color: #fff;
    font-size: 0.45rem;
    font-weight: 700;
    line-height: 1;
}

.tt-trainer-name {
    font-size: 0.62rem;
    font-weight: 600;
    color: var(--bp-ink);
    white-space: normal;
    overflow-wrap: anywhere;
}

/* Тот же тренерский чип в карточке записи — крупнее (есть место) */
.tt-equipment-icon {
    font-size: 0.6rem;
    color: var(--bp-warning);
    font-weight: 600;
}

.tt-conflict-icon {
    font-size: 0.7rem;
    color: var(--bp-stamp-red);
}


/* Трейнер-вью (этап 1c): нерабочее время тренера — штрихованная заливка,
   рабочее — лёгкий тон тренера (цвет его чипа). Раньше смену от выходного
   отличала только бледная штриховка, и открытое время тренера на сетке
   не читалось: штриховку сделали заметнее, а смену — цветной. */
.tt-cell.off-shift {
    background: repeating-linear-gradient(
        135deg,
        transparent 0 12px,
        rgba(120, 112, 96, .24) 12px 15px
    );
}
.tt-cell.on-shift:not(.selected) {
    background: color-mix(in srgb, var(--tr-tint, var(--bp-green)) 12%, var(--bp-sheet));
}
.tt-cell.off-shift.selectable { cursor: crosshair; }
.tt-cell .tt-free-courts {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--ff-ui);
    font-variant-numeric: tabular-nums;
    font-size: .62rem;
    letter-spacing: .04em;
    color: color-mix(in srgb, var(--bp-blue) 45%, transparent);
    pointer-events: none;
    overflow: hidden;
}
.tt-header-inactive { opacity: .55; }

.tt-header .th-hours {
    display: block;
    font-family: var(--ff-ui);
    font-variant-numeric: tabular-nums;
    font-size: .64rem;
    font-weight: 400;
    color: var(--bp-ink-soft);
}

/* Статус синка: «доехала ли бронь до YClients» */
/* Метка «не выгружено в YClients» на плитке: янтарная, как и сводка дня. */
.tt-sync-icon {
    font-size: 0.7rem;
}
.tt-sync-icon.sync-pending { color: var(--bp-muted); }
.tt-sync-icon.sync-pushed,
.tt-sync-icon.sync-resource_set { color: var(--bp-blue); }
.tt-sync-icon.sync-webhook_confirmed { color: var(--bp-green-dark); }
.tt-sync-icon.sync-slot_taken_433,
.tt-sync-icon.sync-error { color: var(--bp-stamp-red); }

/* Group Activity (from group_activities table - empty events without bookings) */
.tt-booking.group-activity {
    /* Унификация: у группы ОДИН вид — как у группы с
       участниками; пунктир и градиент пустого события убраны. Левой
       полоски нет — рамка как у обычной брони */
    background: var(--bp-purple-tint) !important;
}

/* Hover НЕ меняет фон группы: визуальный тип несёт border-left (type-*
   система), а наведение — единое для всех карточек (раскрытие по контенту +
   тень у .tt-booking:hover, фиолетовая тень у .group-event:hover). Раньше только у group-activity
   фон «мигал» ярко-фиолетовым — несогласованно с турниром (DRY: hover-визуал
   был на структурном классе, а не на type-*). */

/* Отменённые живут в своей колонке «Отмены» — карточке достаточно тихого
   серого штриха; красный больше не нужен (не путать с неоплатой) */
.tt-booking.cancelled {
    opacity: 0.75;
    background: repeating-linear-gradient(
        -45deg,
        color-mix(in srgb, var(--bp-muted) 14%, transparent),
        color-mix(in srgb, var(--bp-muted) 14%, transparent) 4px,
        transparent 4px,
        transparent 8px
    ) !important;
    border-left: 4px solid var(--bp-muted) !important;
    border-color: var(--bp-muted) color-mix(in srgb, var(--bp-muted) 30%, transparent) color-mix(in srgb, var(--bp-muted) 30%, transparent) var(--bp-muted) !important;
    pointer-events: auto;
}

.tt-booking.cancelled .tt-booking-client {
    text-decoration: line-through;
    text-decoration-color: var(--bp-ink-soft);
}

/* Отмена с НЕВОЗВращёнными деньгами — требует действия (оформить возврат):
   яркая красная лента слева и меньше приглушения, чтобы отделить от «возврат
   оформлен / без денег». Бэк сортирует такие карточки НАВЕРХ колонки отмен,
   отделяя те, по которым возврат ещё предстоит. */
.tt-booking.cancelled.cancelled-unrefunded {
    opacity: 0.95;
    border-left: 4px solid var(--bs-danger, #dc3545) !important;
    border-color: var(--bs-danger, #dc3545)
        color-mix(in srgb, var(--bs-danger, #dc3545) 30%, transparent)
        color-mix(in srgb, var(--bs-danger, #dc3545) 30%, transparent)
        var(--bs-danger, #dc3545) !important;
}

/* Отмены — лента справа от сетки: по времени они наслаивались */
.tt-with-cancels {
    display: flex;
    align-items: flex-start;
    gap: 12px;
}
.tt-with-cancels > .timetable-grid { flex: 1 1 auto; min-width: 0; }

.tt-cancels {
    flex: 0 0 240px;
    max-width: 240px;
    border: 1px solid var(--bp-line-strong);
    border-radius: 10px;
    background: var(--bp-paper-2);
    overflow: hidden;
    display: flex;
    flex-direction: column;
    /* Липнет к верху: сетка длинная, лента отмен должна оставаться на виду.
       Отступ учитывает sticky .app-header — top:12px прятал заголовок ленты
       под синим баром */
    position: sticky;
    top: calc(var(--app-header-h, 0px) + 12px);
    max-height: calc(100vh - 140px);
}
.tt-cancels__head {
    position: sticky;
    top: 0;
    padding: .5rem .6rem;
    font-size: .74rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--bp-ink-soft);
    background: var(--bp-paper-2);
    border-bottom: 1px solid var(--bp-line);
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.tt-cancels__count {
    background: var(--bp-muted, #9a938a);
    color: #fff;
    border-radius: 999px;
    padding: 0 .4rem;
    font-size: .7rem;
}
.tt-cancels__list { overflow-y: auto; padding: .4rem; }

.tt-cancel-card {
    display: block;
    width: 100%;
    text-align: left;
    border: 1px solid var(--bp-line);
    border-left: 4px solid var(--bp-muted, #9a938a);
    border-radius: 8px;
    background: var(--bp-paper-1, #fff);
    padding: .4rem .5rem;
    margin-bottom: .35rem;
    cursor: pointer;
    /* Текст не режем (правило проекта): переносим */
    white-space: normal;
    overflow-wrap: anywhere;
}
.tt-cancel-card:hover { border-color: var(--bp-line-strong); }
/* Требует действия — красная лента слева, как у такой же плитки в сетке */
.tt-cancel-card--unrefunded { border-left-color: var(--bs-danger, #dc3545); }
.tt-cancel-card__time {
    display: block;
    font-size: .72rem;
    color: var(--bp-ink-soft);
    font-variant-numeric: tabular-nums;
}
/* Без line-through: имена/названия групп переносятся на 2–3 строки, и
   перечёркнутый многострочный текст плохо читается. Отменённость несёт
   заголовок ленты и серый левый бордер карточки. */
.tt-cancel-card__client {
    display: block;
    font-weight: 600;
    font-size: .82rem;
    color: var(--bp-ink-1, #2f2a24);
}
.tt-cancel-card__svc { display: block; font-size: .74rem; color: var(--bp-ink-2, #6b6357); }
.tt-cancel-card__reason { display: block; font-size: .72rem; color: var(--bp-ink-soft); font-style: italic; }

@media (max-width: 900px) {
    .tt-with-cancels { flex-wrap: wrap; }
    .tt-cancels { flex: 1 1 100%; max-width: none; position: static; max-height: 340px; }
}

.tt-capacity-row {
    margin-top: 2px;
}

.bg-purple-light {
    background-color: rgba(126, 58, 242, 0.2);
    color: #7c3aed;
    font-size: 0.6rem;
    padding: 1px 4px;
}

/* Group Event with Participants */
.tt-booking.has-participants {
    padding: 0 !important;
    /* overflow наследует hidden от базового .tt-booking: в норме контент группы
       обрезается по событию (в узких колонках длинные заголовки/имена
       раньше свисали за нижнюю границу). Полный список раскрывает
       общий .tt-booking:hover, растя плитку по контенту (min-height:var(--tt-h)
       держит ≥ длительности — группа на 5ч не схлопывается). */
    position: relative;
}

.tt-group-container {
    display: flex;
    flex-direction: column;
    height: 100%; /* заполнить карточку (высота по длительности события) */
}

/* Тело обычной брони несёт отступы плитки (см. .tt-booking padding:0) */
.tt-booking-body {
    padding: 4px 6px;
}

.tt-group-header {
    flex-shrink: 0; /* шапка фикс, список забирает остаток высоты */
}

/* Шапка группы НЕ закрашена (единый вид с пустой группой и обычной
   бронью) — фиолет несут полоска времени и фон-тинт карточки */
.tt-group-header {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    padding: 4px 6px;
    color: var(--bp-ink);
    font-size: 0.7rem;
    font-weight: 500;
}

.tt-group-titlerow {
    display: flex;
    align-items: center;
    gap: 6px;
}

.tt-group-header .tt-group-icon {
    /* подложка убрана: на светлой шапке белая плашка была невидима */
    padding: 2px 0;
    font-size: 0.7rem;
    margin-right: 0;
}

.tt-group-title {
    flex: 1;
    min-width: 0;
    /* Full title — no «…» (правило проекта): the time moved to its own line below. */
    white-space: normal;
    overflow-wrap: anywhere;
}

/* Время события — та же роль, что у времени брони: первая строка плитки */
.tt-group-time {
    font-size: 0.56rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Заполненность группы X/N (native, §15 T1) — как gw-slots недельной сетки.
   Кегль подобран так, чтобы бейдж встал РЯДОМ со временем в полоске узкой
   колонки, а не переносился под него своей строкой. */
/* Собранные взносы турнира — та же плашка: два числа шапки выглядят одинаково. */
.tt-group-seats,
.tt-tournament-fees {
    background: rgba(255,255,255,0.3);
    padding: 0 3px;
    border-radius: 8px;
    font-size: 0.55rem;
    font-weight: 700;
    white-space: nowrap;
}

.tt-participants-list {
    padding: 2px;
    background: rgba(147, 51, 234, 0.05);
    /* В потоке внутри карточки: влезли по высоте — видны все чётко */
    flex: 1 1 auto;
    min-height: 0;
    /* overflow:visible — обрезку/угасание/детект делает ПЛИТКА (единый
       data-clipped на .tt-booking), иначе переполнение списка скрыто внутри
       него и scrollHeight плитки его не видит → рамка у группы не появлялась. */
    overflow: visible;
}

/* Контент не влез в событие (data-clipped мерит x-init на плитке) — угасание
   снизу как сигнал «показано не всё», полностью раскрывается по наведению.
   ЕДИНЫЙ для брони (услуга/коммент) и группы (список участников). */
.tt-booking[data-clipped="true"] {
    -webkit-mask-image: linear-gradient(
        to bottom, #000 calc(100% - 14px), transparent);
    mask-image: linear-gradient(
        to bottom, #000 calc(100% - 14px), transparent);
}
/* Раскрытие списка на hover обеспечивает ЕДИНЫЙ .tt-booking:hover (растит
   плитку по контенту + снимает mask со списка). Отдельного правила для группы
   больше нет — раньше здесь был position:absolute; top:100%, он цеплял список к
   НИЖНЕЙ границе высокого события, оставляя пустую область. */

.tt-participant {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 3px 6px;
    margin-bottom: 1px;
    border-radius: 3px;
    font-size: 0.65rem;
    background: var(--bp-sheet);
    border-left: 2px solid transparent;
}

.tt-participant.paid {
    border-left-color: var(--bp-green);
    background: var(--bp-green-tint);
}

.tt-participant.unpaid {
    border-left-color: var(--bp-orange);
    background: var(--bp-orange-tint);
}

/* Расчёт вне CRM: ни зелени, ни оранжевого — про деньги этого участника
   сказать нечего, и строка не должна притворяться, что сказала. */
.tt-participant.outside {
    border-left-color: var(--bp-ink-soft);
    background: var(--bp-sheet);
}

.tt-participant-name {
    font-weight: 500;
    white-space: normal;
    overflow-wrap: anywhere;
}

.tt-participant-status {
    flex-shrink: 0;
    font-size: 0.7rem;
}

/* ═══════════════════ Тач-устройства (любая ширина) ═══════════════════════
   Перенос и ресайз карточки живут на mousemove — пальцем их не выполнить, а
   кромки ресайза при этом крадут по 7px сверху и снизу у КАЖДОЙ плитки, где
   как раз и целится палец. Гасим их на грубом указателе; сам drag глушится в
   JS (`startCardDrag` → `coarsePointer`), иначе тап оставлял бы висеть
   `cardDrag` до mouseup, который после скролла не приходит. */
@media (pointer: coarse) {
    .tt-resize-handle { display: none !important; }
    .tt-booking, .tt-booking:active { cursor: pointer; }
}

/* ═══════════════════════ Телефон (≤767.98px) ═════════════════════════════
   Прокрутку дня ведёт СТРАНИЦА. Раньше сетка была собственным
   скроллпортом (518px окна на 1434px расписания): на одном экране жили два
   разных жеста, инерция гасилась о край коробки, «докрутить до конца дня»
   одним махом было нельзя, а свайп вниз уводил сетку вбок. Теперь сетка течёт
   со страницей, как на десктопе, а шапка кортов липнет под `.app-header`.

   Плата за это — ширина: горизонтального скролла у сетки больше нет (он и
   делал её скроллпортом, а скроллпорт по X является им и по Y — sticky-шапка
   тогда липнет к коробке, а не к экрану). Корты показываются ОКНОМ по
   `maxVisibleCols()`, окно листается теми же стрелками `.tt-colnav`, что и
   «Тренеры + Корты» на десктопе (`timetable.js`). */
@media (max-width: 767.98px) {
    /* Лента отмен уезжает ПОД сетку: сбоку на телефоне места нет. Её
       собственный max-height снимает класс кита `bp-m-flow` (разметка) —
       прокрутки внутри прокрутки на этом экране больше нет вовсе. */
    .tt-with-cancels { display: block; }
    .tt-cancels {
        position: static;
        flex: none;
        max-width: none;
        margin-top: 12px;
    }

    .timetable-grid {
        /* Узкая колонка часов слева и 30px справа под стрелку листания;
           корты делят остаток поровну (minmax(0, …) — твёрдый минимум колонки
           снова распирал бы сетку за экран). */
        grid-template-columns: 40px repeat(var(--col-count), minmax(0, 1fr)) 30px;
        /* Вертикаль — странице, вбок сетка не ездит вовсе: диагональный свайп
           больше не уводит расписание из-под пальца */
        touch-action: pan-y pinch-zoom;
    }
    /* Правый столбик часов держит только колонку под стрелку: вторая линейка
       времени на 390px не окупается, но без ячейки в КАЖДОЙ строке
       grid-раскладка съезжает на колонку. */
    .tt-time--right {
        padding: 0;
        border-top-color: transparent;
    }
    .tt-time--right span { display: none; }
    .tt-colnav { font-size: 0.85rem; }

    /* Липнем под шапку приложения — как на десктопе: скроллпорт снова страница */
    .tt-header,
    .tt-corner {
        top: var(--app-header-h, 0px);
    }

    /* Линия «сейчас» — абсолютный ребёнок сетки; grid-column растягивает её
       containing block на все колонки, left равняется на узкую мобильную
       колонку часов (40px вместо 50px), right — на колонку стрелки. */
    .tt-current-time {
        grid-column: 1 / -1;
        left: 40px;
        right: 30px;
    }
    .tt-current-label {
        left: 2px;
        top: -8px;
        display: inline-block;
    }
    .tt-header {
        padding: 7px 4px;
        font-size: 0.72rem;
        border-right-width: 6px;
    }
    .tt-header .badge { font-size: 0.6rem; }
    .tt-cell { border-right-width: 6px; }
    .tt-time {
        font-size: 0.64rem;
        padding: 2px 3px;
    }
    /* Подпись «можно с тренером» — мельче, но ЦЕЛИКОМ (текст не режем): это
       единственный сигнал свободного тренера на слоте, точкой его не заменить */
    .tt-cell.trainer-avail::after { font-size: 0.55rem; line-height: 1.1; }

    /* Имена не рвём посреди слова: в колонке ~98px `anywhere` давал
       «Анастаси / я Сергиенк». `break-word` переносит слово целиком и рвёт
       только то, что не влезает в строку в принципе. */
    .tt-trainer-name,
    .tt-participant-name,
    .tt-group-title { overflow-wrap: break-word; }

    /* Шапка дня. Всё, что стоит НАД сеткой, крадёт у неё высоту экрана,
       поэтому здесь важен каждый десяток пикселей: чипы переносятся строками
       (раньше уезжали в горизонтальный скролл без единого признака, что там
       что-то есть, — «Отмены» и «Легенда» лежали за правым краем целиком). */
    .tt-daybar {
        gap: 5px !important;
        flex-wrap: wrap;
        width: 100%;
    }
    .tt-daybar h4 { font-size: 0.95rem; }
    /* Чип с радиусом 999px, получив перенос текста на 3–4 строки, вырастает в
       овал во весь экран. На телефоне чипу нужна прямоугольная форма. */
    .tt-count-chip {
        border-radius: var(--bp-radius-sm);
        padding: 3px 7px;
        font-size: 0.64rem;
    }
    /* «Отмены» и «Легенда» — живые кнопки, им нужен палец, а не курсор */
    button.tt-count-chip {
        min-height: 40px;
        display: inline-flex;
        align-items: center;
    }
    /* Вход в шторку тренеров стоит в той же строке, что «Отмены» и «Легенда»:
       третья строка шапки стоила бы сетке ещё 45px первого экрана */
    .tt-daybar .bp-sheet-trigger {
        padding: 2px 8px;
        font-size: 0.72rem;
    }
    /* Легенда раскрывается прямо в шапке дня — переносом, не лентой: за краем
       экрана ключи не видны вовсе */
    .tt-legend {
        margin-left: 0;
        width: 100%;
        gap: 2px 12px;
        font-size: 0.68rem;
        flex-wrap: wrap;
    }

    /* Панель тренеров уехала в шторку «Тренеры» (кит К1): это фильтр-подсветка,
       нужная раз на смене, а лента из семи чипов стоила сетке 42px высоты.
       Внутри шторки чипы переносятся строками и растут до пальца. */
    .journal-trainers-bar {
        flex-wrap: wrap;
        padding: 0;
        margin-bottom: 0;
        border: 0;
        background: none;
    }
    .journal-trainers-bar .jtb-chip { min-height: 40px; }
    /* Смена цвета тренера с телефона не нужна, а 14px-точка рядом с чипом —
       главный источник промахов: тап открывал палитру вместо подсветки */
    .jtb-color-dot,
    .jtb-palette { display: none; }

    /* Тулбар: снаружи остаётся только дата, остальное — в шторке «Вид».
       nowrap + сжимаемые дети: вход в шторку обязан остаться в ОДНОЙ строке с
       датой, иначе управление снова стоит журналу лишние 40px высоты. */
    .tt-toolbar {
        gap: 4px;
        width: 100%;
        flex-wrap: nowrap;
    }
    .tt-toolbar .tt-tb-sep { display: none; }
    .tt-toolbar .btn,
    .tt-toolbar .form-control { height: 36px; }
    .tt-toolbar .tt-tb-group {
        flex: 1 1 auto;
        min-width: 0;
    }
    .tt-toolbar .tt-tb-group .btn { padding: 0 8px; font-size: 0.78rem; }
    .tt-toolbar .tt-tb-group > button:last-child { margin-left: 4px; }
    .tt-tb-date {
        flex: 1 1 6rem;
        min-width: 0;
        width: auto;
        font-size: 0.78rem;
    }
    /* Внутри шторки сегменты разворачиваются на всю ширину и растут до пальца */
    .tt-tb-view .btn-group {
        display: flex;
        flex-wrap: wrap;
        width: 100%;
    }
    .tt-tb-view .btn-group > .btn { flex: 1 1 40%; min-height: 44px; }
    .tt-tb-view > .btn { width: 100%; min-height: 44px; }

}

/* Шторки существуют только на телефоне: на десктопе их обёртки обязаны быть
   прозрачными для раскладки вокруг, иначе сегменты вида и раскраски съедут в
   отдельную коробку, а панель тренеров получит лишний блок-контейнер. */
@media (min-width: 768px) {
    .tt-toolbar > .tt-tb-view,
    .tt-trainers-sheet { display: contents; }
}
