/*
 * Хром сетчатого дашборда (эпик GL-139; Фаза 1 = GL-140, UX-доработка = GL-161). СВОЙ ассет
 * (public/css), НЕ theme.css. Все цвета — роли --gl-* (Brand::rootTokens), тема свет/тёмная
 * переключается САМА (.dark-проекция ролей) — своих .dark-правил здесь нет (анти-island GL-053/094).
 * Каркас позиций даёт gridstack.min.css (public/css/vendor); здесь — только визуал.
 *
 * Режимы (GL-161): просмотр — виджет ЧИСТЫЙ (никакого edit-хрома); режим настройки включает класс
 * .gl-dash--editing на корне (Alpine) — от него каскадом: пунктир + приглушение + грип/✕-оверлеи
 * (без баровой высоты — зазоры между виджетами не растут), видимая крупная сетка ячеек (::before
 * по --gl-dash-columns × --gl-dash-cell, переменные ставит glDashboardGrid из конфига gridstack).
 */

[x-cloak] { display: none !important; }

.gl-dash {
    display: block;
    width: 100%;
}

/* ── Строка заголовка (h1 страницы + контролы справа; .fi-header даёт flex-раскладку) ── */
/* ⚠ position + z-index:20 ОБЯЗАТЕЛЬНЫ ОБА (GL-229). Корень бага — стекинг-контекст, НЕ числа
   z-index сами по себе. .gl-dash-card__grip/__remove (углы карточек грида) несут z-index:5/6, но
   их предки .grid-stack-item/.gl-dash-card контекст НЕ создают (замерено computed style на живом
   дашборде: position:absolute, z-index:auto, transform:none) — значит грип/✕ красятся ПРЯМО в
   КОРНЕВОМ стекинг-контексте страницы. .gl-dash__head (без своего position/z-index) — тоже НЕ
   создаёт контекст ⇒ вложенный .gl-dash__palette-menu (z-index:30) сравнивается с грип/✕ на РАВНЫХ
   правах в том же корневом контексте — обычно 30 бьёт 6, но стоит любому предку между ними
   (шапка панели/скролл-контейнер) обзавестись СВОИМ контекстом без явного z-index — весь заголовок
   как блок падает на уровень «positioned, z-index:auto», который проигрывает явным
   ПОЛОЖИТЕЛЬНЫМ z-index грид-карточек. Явный position+z-index:20 у ЗАГОЛОВКА (не только у меню)
   фиксирует его контекст на гарантированном уровне «20» независимо от таких предков — числа
   надёжны только когда сравниваются В ОДНОМ контексте, а именно это и обеспечивается. Живой замер
   (computed style + elementFromPoint) — сессия GL-229.
   ⚠ GL-229-хвост: намеренно `relative`, НЕ `sticky` — прилипание при скролле здесь не требуется
   (нужен только гарантированный стекинг-контекст уровня 20); `sticky` без `top` ничем не отличался
   бы от `relative` по факту, но выглядел бы как «прилипающая шапка» и провоцировал следующего
   добавить `top` вслепую. Не заменять на sticky и не добавлять top.
*/
.gl-dash__head {
    position: relative;
    z-index: 20;
    margin-bottom: 1rem;
}

.gl-dash__head-main {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}

/* Ряд «заголовок + индикатор загрузки» внутри колоночного .gl-dash__head-main (GL-682).
   min-width:0 — чтобы обрезка длинного заголовка не ломалась. Скрытый индикатор (x-show=false →
   display:none) выпадает из flex-потока целиком, поэтому паразитного gap после него не остаётся. */
.gl-dash__head-title {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    min-width: 0;
}

.gl-dash__hint {
    font-size: 0.75rem;
    color: var(--gl-muted);
}

/* ── GL-627/GL-682: индикатор загрузки виджетов (СПРАВА от заголовка, в одну линию с ним;
   размещение по решению заказчика 01.08.2026, GL-682 — до этого стоял строкой под заголовком) ──
   Логомарк «Искра» (<x-gl-loader>) + текст — по сквозному §5.27 длительная обработка несёт
   фирменный лоадер. Блок отрисован СЕРВЕРОМ и виден с первого кадра; Alpine его только гасит
   (`x-show="loadingWidgets"`), поэтому здесь НЕТ ни `display:none` по умолчанию, ни правила под
   [x-cloak] — иначе индикатор появлялся бы позже виджетов, ради которых показывается
   (остров под гвардом `x-gl-lazy`, GL-505).
   Цвет — ролями (--gl-muted), не литералом: смена темы должна доезжать сама (UIUX §2.1). */
.gl-dash__loading {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.75rem;
    color: var(--gl-muted);
}

.gl-dash__controls {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex: 0 0 auto;
}

/* ── Кнопки тулбара ─────────────────────────────────────────────────────── */
.gl-dash__btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.45rem 0.8rem;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--gl-text);
    background: var(--gl-surface);
    border: 1px solid var(--gl-hairline);
    border-radius: var(--gl-radius);
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}

.gl-dash__btn:hover { background: var(--gl-surface-hover); }

.gl-dash__btn.is-open { border-color: var(--gl-primary); }

.gl-dash__btn--icon { padding: 0.45rem; }

.gl-dash__btn--primary {
    color: var(--gl-on-primary);
    background: var(--gl-primary);
    border-color: var(--gl-primary);
}

.gl-dash__btn--primary:hover { background: var(--gl-primary-strong); }

.gl-dash__btn-icon { width: 1.05rem; height: 1.05rem; flex: 0 0 auto; }
.gl-dash__btn-caret { width: 0.85rem; height: 0.85rem; flex: 0 0 auto; color: var(--gl-muted); }
.gl-dash__btn--primary .gl-dash__btn-caret { color: var(--gl-on-primary); }

/* ── Палитра-дропдаун (площадь страницы — под виджеты, GL-161 п.5) ───────── */
.gl-dash__palette { position: relative; }

.gl-dash__palette-menu {
    position: absolute;
    top: calc(100% + 0.4rem);
    right: 0;
    z-index: 30;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 17rem;
    max-width: min(22rem, 92vw);
    max-height: min(24rem, 70vh);
    overflow: auto;
    padding: 0.35rem;
    background: var(--gl-surface);
    border: 1px solid var(--gl-hairline);
    border-radius: var(--gl-radius);
    box-shadow: var(--gl-overlay-shadow);
}

.gl-dash__palette-item {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    align-items: flex-start;
    width: 100%;
    padding: 0.45rem 0.6rem;
    text-align: left;
    background: transparent;
    border: none;
    border-radius: calc(var(--gl-radius) - 2px);
    cursor: pointer;
    transition: background 0.12s;
}

.gl-dash__palette-item:hover { background: var(--gl-surface-hover); }
.gl-dash__palette-item:disabled { opacity: 0.55; cursor: progress; }

.gl-dash__palette-name {
    display: inline-flex;
    align-items: center;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--gl-text);
}

.gl-dash__palette-desc {
    text-align: justify;
    font-size: 0.72rem;
    color: var(--gl-muted);
    line-height: 1.25;
}

.gl-dash__palette-empty {
    padding: 0.55rem 0.6rem;
    font-size: 0.8125rem;
    color: var(--gl-muted);
}

/* ── Пустой дашборд ─────────────────────────────────────────────────────── */
.gl-dash__empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.3rem;
    padding: 2.5rem 1rem;
    text-align: center;
    color: var(--gl-muted);
    border: 1px dashed var(--gl-hairline);
    border-radius: var(--gl-radius);
}

.gl-dash__empty-icon { width: 2rem; height: 2rem; color: var(--gl-muted); }
.gl-dash__empty-text { font-size: 0.9rem; font-weight: 600; color: var(--gl-text); }
.gl-dash__empty-sub { font-size: 0.8rem; }

/* ── Карточка виджета (обёртка ячейки грида) ────────────────────────────────
   Прозрачна: видимый «фрейм» несёт сам виджет (его <x-filament::section> и т.п.), рамку не дублируем
   (вид виджета в просмотре не меняется — регресс-безопасно). Обёртка зажимает виджет в ячейку
   (скроллит его ТЕЛО, GL-180 п.3 ниже) и несёт АБСОЛЮТНЫЕ edit-оверлеи (грип, ✕) — без баровой
   высоты (GL-161 п.1). */
/* ⚠ Без position:relative и БЕЗ height:100% — элемент И ЕСТЬ .grid-stack-item-content: gridstack
   держит его position:absolute с inset=margin (зазоры между виджетами живут именно там; absolute
   заодно якорит наши оверлеи грип/✕). relative ломал отступы, а height:100% поверх top:8px
   сталкивал карточку на 8px НИЖЕ ячейки (наезд на соседа — «лишнее пространство» GL-140). */
.gl-dash-card {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border-radius: var(--gl-radius);
}

/* ⚠⚠ GL-538 — ЗАГОЛОВОК ПЛИТКИ НЕ ПРОКРУЧИВАЕТСЯ НИКОГДА, поэтому overflow КАРТОЧКИ прибит здесь,
   а не только в правиле выше. Объявление `.gl-dash-card{overflow:hidden}` (специфичность 0,1,0)
   ПРОИГРЫВАЛО vendor-правилу gridstack `.grid-stack > .grid-stack-item > .grid-stack-item-content
   { overflow: hidden auto }` (0,3,0) — а карточка И ЕСТЬ `.grid-stack-item-content` (см. выше).
   Живой замер GL-538 (перебор document.styleSheets + computed): у карточки было `overflow-y: auto`
   при объявленном `hidden` ⇒ она ВСЁ ВРЕМЯ была скролл-контейнером и, в отличие от `.fi-section-
   content-ctn`, СОДЕРЖИТ шапку секции. Пока `.fi-section-content-ctn` клипал переполнение (GL-273),
   скроллить было нечего; как только GL-523 снимал клип на время наведения, 24px переполнения
   (корень — в applyStretchHeight, см. gl-chart.blade.php) доставались карточке → полоса прокрутки
   на всей плитке вместе с заголовком. Тем же дефектом специфичности `--tip` НЕ освобождал карточку:
   `.gl-dash-card--tip{overflow:visible}` (0,1,0) тоже проигрывал `auto`, т.е. четвёртый (внешний)
   клип тултипа GL-523 фактически не снимался.
   Скролл контента остаётся там, где он и задуман — на `.fi-section-content-ctn` / не-секционном
   корне (правила ниже); списочные виджеты не затронуты. */
.grid-stack > .grid-stack-item > .gl-dash-card {
    overflow: hidden;
}

/* Скроллом владеет ТЕЛО виджета, не обёртка (GL-180 п.3): у секционных виджетов заголовок
   Filament-секции остаётся статичным, прокручивается только .fi-section-content-ctn (вложенных
   скроллов виджеты не несут — см. activity-feed). container-type — якорь @container-брейкпоинтов
   гибких колонок внутри виджетов (ширина ВИДЖЕТА ≠ ширина вьюпорта, @media непригодны). */
.gl-dash-card__body {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    container-type: inline-size;
}

.gl-dash-card__body > * {
    flex: 1 1 auto;
    min-height: 0;
}

/* Не-секционный корень (KPI-плитки и будущие кастомные) — скроллится сам, как раньше. */
.gl-dash-card__body > :not(.fi-section) { overflow: auto; }

/* ── GL-627: СКЕЛЕТ ЛЕНИВОГО ВИДЖЕТА ────────────────────────────────────────
   Пока тело виджета не доехало, Livewire рисует его placeholder — у Filament это
   `.fi-loading-section` (\Filament\Support\Concerns\CanBeLazy::placeholder()).
   ⚠ Высоту Filament печатает ИНЛАЙНОМ (`style="height: 8rem"` в placeholder-вьюхе пакета
   filament/support), а инлайн бьёт таблицу стилей — растянуть скелет на карточку можно ТОЛЬКО
   через !important. Без этого скелет был бы фиксированным восьмирэмовым прямоугольником внутри
   карточки любой высоты.
   Высоту самой карточки при этом задаёт gridstack (gs-h, ограниченный data-gl-min-h/max-h из
   разметки) — то есть скелет ровно того размера, что и приедущий виджет, и сетка НЕ «прыгает»
   по мере догрузки: позиции у gridstack абсолютные и от содержимого не зависят.
   Класс `.fi-loading-section` несёт и `.fi-section`, поэтому правила секции выше применяются к
   нему тоже; здесь только собственный вид скелета. */
.gl-dash-card__body > .fi-loading-section {
    height: 100% !important;
    min-height: 0;
    border: 1px solid var(--gl-hairline);
    border-radius: 0.75rem;
    background: var(--gl-surface);
    position: relative;
    overflow: hidden;
}

/* Бегущий блик — «идёт загрузка», а не «пустая карточка». Собственный, не Tailwind-`animate-pulse`
   темы: ассет дашборда (этот файл) собирается отдельно от theme.css и утилит Tailwind не имеет. */
.gl-dash-card__body > .fi-loading-section::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(
        90deg,
        transparent 0%,
        color-mix(in srgb, var(--gl-muted) 12%, transparent) 50%,
        transparent 100%
    );
    transform: translateX(-100%);
    animation: gl-dash-skeleton 1.4s ease-in-out infinite;
}

@keyframes gl-dash-skeleton {
    to { transform: translateX(100%); }
}

/* Доступность: пользователю с prefers-reduced-motion блик не бежит — остаётся статичная плашка. */
@media (prefers-reduced-motion: reduce) {
    .gl-dash-card__body > .fi-loading-section::after { animation: none; }
}

.gl-dash-card__body > .fi-section {
    display: flex;
    flex-direction: column;
    overflow: hidden; /* скролл-контейнер внутри не выпирает за скругление секции */
}

.gl-dash-card__body > .fi-section > .fi-section-header { flex: 0 0 auto; }

.gl-dash-card__body > .fi-section > .fi-section-content-ctn {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}

/* GL-273: у ГРАФИЧЕСКИХ виджетов (авто-подгоняемый `<x-gl-chart>` — маркер `.gl-chart--stretch`,
   ставится в init(), см. gl-chart.blade.php) остаточный скролл — паразитный: applyStretchHeight()
   уже ужимает график ПОД ячейку (Math.floor → «влезает»), но ApexCharts-SVG/паддинги обёрток дают
   sub-pixel overshoot в 1–2px, и `overflow:auto` рождает вертикальный скроллбар при полностью
   помещающемся графике (репорт заказчика 21.07). Клипаем ровно этот overshoot ТОЛЬКО там, где
   стоит авто-подгоняемый график — реального контента к обрезке нет. Списочные виджеты
   (топы/напоминания/лента) и графики с явной фикс-высотой (`:height=`, без `--stretch`) НЕ
   затронуты — их `overflow:auto` (скролл легитимен) сохраняется. */
.gl-dash-card__body > .fi-section > .fi-section-content-ctn:has(.gl-chart--stretch) {
    overflow: hidden;
}

/* GL-523: ТУЛТИП ГРАФИКА ВЫХОДИТ ИЗ-ПОД КЛИПА НА ВРЕМЯ НАВЕДЕНИЯ.
   Репорт заказчика 26.07: тултип над баром «Заполненности по менеджерам» уходит ПОД шапку виджета
   и обрезается. Живой замер (elementFromPoint по верхней кромке тултипа + мутация с откатом):
     A. база                                 → верх тултипа отдаёт `h2.fi-section-header-heading`;
     B. карточке z-index:999, клипы на месте  → ТО ЖЕ САМОЕ ⇒ спор слоёв тут НИ ПРИ ЧЁМ;
     C. клипы сняты, z-index не трогали       → отдаёт `.apexcharts-tooltip-title` ⇒ корень найден;
     D. клипы возвращены                      → снова обрезан (мутация обратима, вывод причинный).
   Числа того же прогона: тултип top=589 при верхе контент-бокса 612 — обрезаны верхние 23px.
   Обрезающих предков ЧЕТЫРЕ (все объявлены выше в этом файле): .fi-section-content-ctn,
   .fi-section, .gl-dash-card__body и сама .gl-dash-card. Тултип ApexCharts рисуется ВНУТРИ
   .apexcharts-canvas, и перенести его в body нельзя — библиотека переспрашивает свой узел через
   `baseEl.querySelector('.apexcharts-tooltip')` (сверено по коду vendor-сборки), т.е. вынос из
   поддерева её ломает. Поэтому снимаем клип, но ТОЧЕЧНО: только у карточки и только пока курсор
   внутри графика (класс ставит/снимает <x-gl-chart> на pointerenter/pointerleave).
   Сами клипы этим не отменяются — паразитный скролл GL-273/GL-265 вне наведения живёт как прежде.
   z-index — ВТОРАЯ, независимая половина: без него освободившийся тултип всё равно уйдёт ПОД
   соседние плитки (у всех z:auto ⇒ при равенстве побеждает более поздняя в DOM). Уровень 20 —
   потолок хрома страницы по карте слоёв (UIUX §5.36); выше топбара он не встанет ни при каком
   значении, т.к. вся сетка живёт внутри стек-контекста .gl-dash__grid-shell (position:relative,
   z-index:1) — ограничение СТРУКТУРНОЕ, а не «повезло с числом».
   ⚠ GL-538: селектор КАРТОЧКИ здесь поднят до `.grid-stack > .grid-stack-item > .gl-dash-card--tip`
   по той же причине, что и базовый клип выше — иначе vendor-правило gridstack (0,3,0) оставляет ей
   `overflow-y: auto`, и внешний, четвёртый клип не снимается (тултип по-прежнему обрезан карточкой,
   а переполнение вместо выхода наружу даёт полосу прокрутки на всей плитке вместе с заголовком). */
.grid-stack > .grid-stack-item > .gl-dash-card--tip,
.gl-dash-card--tip .gl-dash-card__body,
.gl-dash-card--tip .gl-dash-card__body > .fi-section,
.gl-dash-card--tip .gl-dash-card__body > .fi-section > .fi-section-content-ctn {
    overflow: visible;
}

.gl-dash-card--tip {
    z-index: 20;
}

/* Edit-оверлеи существуют только в режиме настройки. */
.gl-dash-card__grip,
.gl-dash-card__remove { display: none; }

/* ── Режим настройки (.gl-dash--editing): пунктир + приглушение + оверлеи ── */
.gl-dash--editing .gl-dash-card {
    outline: 1.5px dashed color-mix(in srgb, var(--gl-primary) 55%, transparent);
    outline-offset: -1.5px;
    cursor: grab;
}

.gl-dash--editing .grid-stack-item.ui-draggable-dragging .gl-dash-card { cursor: grabbing; }

/* Контент приглушён и инертен: клики не «проваливаются» в ссылки виджета при перетаскивании. */
.gl-dash--editing .gl-dash-card__body {
    opacity: 0.55;
    pointer-events: none;
    user-select: none;
}

.gl-dash--editing .gl-dash-card__grip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 0.4rem;
    left: 0.4rem;
    z-index: 5;
    width: 1.5rem;
    height: 1.5rem;
    color: var(--gl-muted);
    background: var(--gl-surface);
    border: 1px solid var(--gl-hairline);
    border-radius: 0.45rem;
    pointer-events: none; /* декор-аффорданс: тащится вся карточка */
}

.gl-dash-card__grip-icon { width: 0.9rem; height: 0.9rem; }

.gl-dash--editing .gl-dash-card__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 0.4rem;
    right: 0.4rem;
    z-index: 6;
    width: 1.5rem;
    height: 1.5rem;
    color: var(--gl-muted);
    background: var(--gl-surface);
    border: 1px solid var(--gl-hairline);
    border-radius: 0.45rem;
    cursor: pointer;
    transition: color 0.12s, border-color 0.12s;
}

.gl-dash--editing .gl-dash-card__remove:hover {
    color: var(--gl-tone-negative);
    border-color: var(--gl-tone-negative);
}

.gl-dash-card__remove-icon { width: 0.9rem; height: 0.9rem; }

/* ── Видимая сетка ячеек в режиме настройки (GL-161 п.7; GL-180 — полу-клетки) ─────────────
   Основа магнитизма: линии лежат по границам ячеек грида (шаг 100%/columns × cellHeight),
   переменные --gl-dash-columns/--gl-dash-cell ставит glDashboardGrid из конфига gridstack —
   линии совпадают с точками привязки. Клетка GL-180 мельче (24×32px — точность ×2), поэтому
   сетка ДВУХУРОВНЕВАЯ: тонкие minor-линии на каждой полу-клетке + major-линии прежнего шага
   (каждая 2-я = старая клетка 12×64) — плотность выросла, а визуальный ритм прежний.
   В просмотре сетки нет; на мобильной 1-колонке скрыта. */
.gl-dash--editing .grid-stack::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    --gl-dash-line: color-mix(in srgb, var(--gl-primary) 16%, transparent);
    --gl-dash-line-minor: color-mix(in srgb, var(--gl-primary) 7%, transparent);
    background-image:
        linear-gradient(to right, var(--gl-dash-line-minor) 1px, transparent 1px),
        linear-gradient(to bottom, var(--gl-dash-line-minor) 1px, transparent 1px),
        linear-gradient(to right, var(--gl-dash-line) 1px, transparent 1px),
        linear-gradient(to bottom, var(--gl-dash-line) 1px, transparent 1px);
    background-size:
        calc(100% / var(--gl-dash-columns, 24)) var(--gl-dash-cell, 32px),
        calc(100% / var(--gl-dash-columns, 24)) var(--gl-dash-cell, 32px),
        calc(200% / var(--gl-dash-columns, 24)) calc(var(--gl-dash-cell, 32px) * 2),
        calc(200% / var(--gl-dash-columns, 24)) calc(var(--gl-dash-cell, 32px) * 2);
}

@media (max-width: 767.98px) {
    .gl-dash--editing .grid-stack::before { display: none; }
}

/* ── Внешние отступы сетки убраны, меж-виджетные целы (GL-199) ──────────────
   gridstack вставляет отступ ВНУТРИ каждой ячейки (inset .grid-stack-item-content
   на margin со всех 4 сторон) — он и держит меж-виджетный зазор, и висит по
   ВНЕШНЕМУ краю первой/последней колонки/строки (заказчик 20.07: «небольшой
   зазор от края всего дашборда»). Меж-виджетный зазор трогать нельзя (магнит/
   раскладка GL-180 на нём завязаны), поэтому убираем именно ВНЕШНИЙ инсет:
   контейнер `.grid-stack` растягиваем на margin по left/right (и расширяем
   width на 2×margin, иначе съедет вендорная `--gs-column-width` — она вычисляется
   от ширины `.grid-stack` (GL-555: v12+ вместо наших классов `.gs-24`))
   + сдвигаем вверх на margin (top, БЕЗ height — низ по ТЗ не трогаем). Контент
   виджетов после такого сдвига физически занимает ровно то место, что раньше
   было пустым внешним отступом — сам инсет (и то, что рисует на нём) не
   меняется, поэтому меж-виджетные зазоры и позиции ресайз-хендлов (которые
   gridstack ставит от угла .grid-stack-item, не от .grid-stack) не задеты.
   `--gl-dash-margin` — тот же margin, что несёт gridstack (dashboard-grid.js
   setupGrid): единый источник, не второе магическое число. Обёртка
   `.gl-dash__grid-shell` глушит горизонтальный оверфлоу (контейнер физически
   на margin шире родителя по каждому краю) — узко вокруг грида, не задевает
   палитру-дропдаун (сосед в шапке, вне этой обёртки). */
/* ⚠ GL-264 (хвост GL-263): position+z-index ОБА обязательны, и это про СТЕКИНГ-КОНТЕКСТ,
   не про числа. Ресайз-хендлы (`.ui-resizable-*`, зелёные L-уголки) gridstack ставит внутрь
   `.grid-stack-item`, а тот контекст НЕ создаёт (position:absolute, z-index:auto) — уголки красятся
   в КОРНЕВОМ контексте страницы со своим вендорным z-index и бьют меню «Добавить виджет» (оно
   живёт z-index:30 ВНУТРИ контекста шапки уровня 20 — по факту «20» в корне, ниже уголков). Даём
   обёртке грида СВОЙ контекст уровня 1 (ниже шапки-20): все хендлы-потомки теперь заперты в нём и
   не могут всплыть выше меню — уголки видны в норме, но уходят ПОД раскрытую палитру. Числа надёжны
   только когда сравниваются в ОДНОМ контексте — здесь это и обеспечивается (родня заметки GL-229). */
.gl-dash__grid-shell {
    position: relative;
    z-index: 1;
    overflow-x: hidden;
}

.grid-stack {
    margin-top: calc(-1 * var(--gl-dash-margin, 8px));
    margin-left: calc(-1 * var(--gl-dash-margin, 8px));
    margin-right: calc(-1 * var(--gl-dash-margin, 8px));
    width: calc(100% + 2 * var(--gl-dash-margin, 8px));
}

/* ── gridstack: перекрашиваем хром под роли (плейсхолдер дропа, ручка ресайза) ──
   ⚠ GL-199: `.grid-stack-placeholder` НЕ вложен в `.grid-stack-item` — gridstack
   ставит ОБА класса на ОДИН элемент (`<div class="grid-stack-placeholder
   grid-stack-item">`). Прежний селектор `.grid-stack-item > .grid-stack-placeholder`
   требовал предок→потомок между классами одного узла — НИКОГДА не матчился (живой
   замер: borderWidth вычислялся 0px, background — вендорный дефолт rgba(0,0,0,.1)),
   отсюда и репорт заказчика («в тёмной почти не видно» — в СВЕТЛОЙ совпало случайно,
   т.к. вендорный чёрный 10%-оверлей на белом тоже читается серым). Правильный
   селектор — как у самого вендора (`gridstack.min.css`): класс+класс на одном узле. */
.grid-stack > .grid-stack-item.grid-stack-placeholder > .placeholder-content {
    background: var(--gl-dash-drop-bg);
    border: 2px dashed var(--gl-dash-drop-border);
    border-radius: var(--gl-radius);
}

/* Ручка ресайза видна только в режиме настройки (когда грид не static). */
.grid-stack.grid-stack-static .ui-resizable-handle { display: none !important; }

/* Индикаторы ресайза (GL-161 п.2; GL-180 — все стороны/углы). Функционально резайзят все 8
   хендлов (JS: resizable.handles), визуально же рисуем ТОЛЬКО 4 угловых L-уголка ролевого цвета
   (единый язык с прежним se; 8 глифов разом — шум): рёберные n/e/s/w остаются невидимыми
   полосами-зонами со штатным курсором. У всех глушим штатную стрелку gridstack (background)
   и её поворот (transform превращал уголок во вторую «>»-иконку). Позиции хендлов (отступы по
   margin грида) задаёт сам gridstack — углы совпадают с углами карточки. */
.grid-stack-item > .ui-resizable-handle {
    background: none !important;
    transform: none !important;
    opacity: 0.65;
    transition: opacity 0.12s;
}

.grid-stack-item > .ui-resizable-handle:hover { opacity: 1; }

.grid-stack-item > .ui-resizable-se,
.grid-stack-item > .ui-resizable-sw,
.grid-stack-item > .ui-resizable-ne,
.grid-stack-item > .ui-resizable-nw {
    width: 16px;
    height: 16px;
}

.grid-stack-item > .ui-resizable-se {
    border-right: 2px solid var(--gl-primary);
    border-bottom: 2px solid var(--gl-primary);
    border-bottom-right-radius: 4px;
}

.grid-stack-item > .ui-resizable-sw {
    border-left: 2px solid var(--gl-primary);
    border-bottom: 2px solid var(--gl-primary);
    border-bottom-left-radius: 4px;
}

.grid-stack-item > .ui-resizable-ne {
    border-right: 2px solid var(--gl-primary);
    border-top: 2px solid var(--gl-primary);
    border-top-right-radius: 4px;
}

.grid-stack-item > .ui-resizable-nw {
    border-left: 2px solid var(--gl-primary);
    border-top: 2px solid var(--gl-primary);
    border-top-left-radius: 4px;
}

/* ── 24-колоночная геометрия (GL-180) — БЫЛА здесь как ручные проценты .gs-24, УДАЛЕНА GL-555 ──
   Вендорный gridstack.min.css нёс left/width-правила только для .gs-12/.gs-1, поэтому наши 24
   колонки требовали собственных процентных правил (тот же формат, что у вендора). GridStack v12+
   мигрировал на CSS-переменную `--gs-column-width` (ставится на `.grid-stack` как `100/column%`)
   + инлайновый `style.left/width: calc(N * var(--gs-column-width))` на каждом айтеме — это уже
   работает для ЛЮБОГО числа колонок без выделенных классов (и инлайн-стиль всё равно бы победил
   наш класс-селектор без `!important`). См. `docs/ARCHITECTURE.md` §9.4 (запись «Сетчатый дашборд»). */
