/* ===== Floating islands layout ===== */
.left-col {
  position: absolute;
  top: 8px;
  left: 8px;
  bottom: calc(8px + var(--dock-offset, 0px));
  width: 260px;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: calc(100vh - 16px - var(--dock-offset, 0px));
  pointer-events: none;
  /* Сжатие под давлением dock'ов графа/таймлайна — синхронно с
     transition: transform 200ms у самих dock'ов (см. .sm-dock / .anim-dock). */
  transition: bottom 200ms ease-out, max-height 200ms ease-out;
}
.left-col > * {
  pointer-events: auto;
}
.left-col .left_panel {
  width: 100%;
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
/* layers panel fills left_panel, #layerList — единственный скролл.
   max-height:260 из базового .list мешает: при сжатом left_panel появлялся
   двойной скролл (list с 260 + panel с остатком). */
.left-col .left_panel #layersPanel {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.left-col .left_panel #layerList {
  flex: 1 1 0;
  min-height: 0;
  max-height: none;
}
.left-col .left_panel .info-toggle-panel {
  flex-shrink: 0;
}
.nav-island {
  background: var(--island-bg);
  border: 1px solid var(--island-border);
  border-radius: var(--r-lg);
  padding: 8px;
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}
.nav-island .chip { white-space: nowrap; text-align: center; }
#homeBtn { width: 45%; }
#startNewBtn { width: 55%; }

/* ===== Meta island (title + description) ===== */
.meta-island {
  background: var(--island-bg);
  border: 1px solid var(--island-border);
  border-radius: var(--r-lg);
  padding: 12px 14px;
  flex-shrink: 0;
}

/* ===== Space+drag pan cursor ===== */
.scroll-inner.is-panning {
  cursor: grab !important;
}
.scroll-inner.is-panning:active {
  cursor: grabbing !important;
}
.scroll-inner.is-panning * {
  cursor: inherit !important;
}

/* ===== Top toolbar (центрированная панель сверху) — flex-обёртка
 * для двух островов: режимы (граф/анимация) + zoom/preview. */
.top-toolbar {
  position: fixed;
  top: 14px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 8px;
  z-index: 5;
}

/* Mode island — segmented control: «Редактор / Граф / Анимация».
 * Подтверждение выбора — sliding active-indicator (фиксированная плашка
 * под активной кнопкой). Плашка двигается только при click — позиция
 * подъезжает после смены .is-open у chip'ов (см. js/editor/modeIsland.js).
 * Hover на неактивной = только цвет текста ярче. */
.mode-island {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 5px;
  /* Concentric corners: outer = inner(chip r-md = 8) + padding (5) = 13. */
  border-radius: calc(var(--r-md) + 5px);
  background: var(--island-bg);
  border: 1px solid var(--island-border);
  position: relative;
}
/* Active-indicator — solid, ездит за активной кнопкой через transform/width. */
.mode-island__indicator {
  position: absolute;
  top: 5px;
  bottom: 5px;
  left: 0;
  width: 0;
  border-radius: var(--r-md);
  background: rgba(139, 92, 246, 0.22);   /* V2 soft heavier */
  transition: transform 220ms cubic-bezier(0.4, 0, 0.2, 1),
              width 220ms cubic-bezier(0.4, 0, 0.2, 1);
  pointer-events: none;
  z-index: 0;
}
/* Кнопки — поверх индикатора. Гасим всё что браузер может «нарисовать»
 * (default border, focus-ring, :active background/transform, tap-highlight). */
.mode-island .chip,
.mode-island .chip:hover,
.mode-island .chip:focus,
.mode-island .chip:focus-visible,
.mode-island .chip:active {
  appearance: none;
  -webkit-appearance: none;
  background: transparent !important;
  border: 0 !important;
  outline: 0 !important;
  box-shadow: none !important;
  transform: none !important;
  -webkit-tap-highlight-color: transparent;
}
.mode-island .chip {
  color: var(--gray-cc);
  position: relative;
  z-index: 1;
  padding: 4px 10px;
  gap: 4px;
  transition: color 0.18s;
}
.mode-island .chip:hover:not(.is-open) {
  color: var(--white);
}
.mode-island .chip.is-open {
  color: var(--accent-hover);   /* V2: текст ярче */
  cursor: default;
}
/* Modifier --compact — для таб-варианта (компактнее, для размещения внутри
 * панели). Уменьшенный padding, шрифт 11px, индикатор тоньше. */
.mode-island--compact {
  padding: 3px;
  border-radius: calc(var(--r-sm) + 4px);
  gap: 2px;
}
.mode-island--compact .mode-island__indicator {
  top: 3px;
  bottom: 3px;
  border-radius: var(--r-sm);
}
.mode-island--compact .chip {
  height: 24px;
  font-size: 11px;
  line-height: 18px;
  padding: 3px 10px;
}
/* Modifier --full — остров и chip'ы растягиваются на всю ширину контейнера
 * (для tab'ов внутри панели слоёв). flex:1 + width:100% — растяжение
 * работает и в auto-width, и в flex-родителе с justify-content: space-between. */
.mode-island--full {
  display: flex;
  width: 100%;
  flex: 1;
}
.mode-island--full .chip {
  flex: 1;
  justify-content: center;
}

/* ===== Zoom island (внутри top-toolbar) ===== */
.zoom-island {
  display: inline-flex;
  gap: 4px;
  padding: 5px;
  border-radius: calc(var(--r-md) + 5px);
  background: var(--island-bg);
  border: 1px solid var(--island-border);
}
.zoom-island .chip span {
  font-variant-numeric: tabular-nums;
  min-width: 32px;
  text-align: center;
}
/* Вариант zoom-island внутри dock header — без обводки/подложки острова,
   просто группа кнопок с gap'ом. */
.sm-zoom-island {
  position: static;
  transform: none;
  padding: 0;
  background: transparent;
  border: none;
  gap: 4px;
}
/* Кнопки zoom +/-/fit — квадратные 26×26 */
.chip.sm-zoom-btn {
  width: 26px;
  height: 26px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* Label «100%» — чуть шире, чтобы «125%»/«67%» влезали */
.chip.sm-zoom-label-btn {
  height: 26px;
  padding: 0 6px;
  min-width: 46px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* «+ Добавить ноду» — стиль идентичен #playToggleBtn (Preview), V2 soft heavier. */
.chip.sm-add-node {
  margin-left: 10px;
  padding: 0 10px;
  background: rgba(139, 92, 246, 0.22);
  border-color: transparent;
  color: var(--accent-hover);
}
.chip.sm-add-node:hover { background: rgba(139, 92, 246, 0.30); }
.zoom-island-sep {
  width: 1px;
  align-self: stretch;
  margin: 0;
  background: var(--gray-33);
}
#playToggleBtn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 10px;
  color: var(--accent-hover);   /* V2: текст ярче */
  background: rgba(139, 92, 246, 0.22);   /* V2 soft heavier */
  border: 1px solid transparent;
}
#playToggleBtn:hover {
  background: rgba(139, 92, 246, 0.30);
}
#playToggleBtn.is-playing {
  color: #fff;
  background: var(--danger-bg);
  border-color: var(--danger-bg);
}
#playToggleBtn.is-playing:hover {
  background: var(--danger-hover);
  border-color: var(--danger-hover);
}
#playToggleBtn .chip,
#playToggleBtn span {
  min-width: 0;
  text-align: left;
}

/* Правила для play-mode */
/* Иконки в #playToggleBtn: переключение play/stop по классу .is-playing.
   В idle показан треугольник (.ic-play), в play-mode — квадрат (.ic-stop). */
#playToggleBtn .ic-stop { display: none; }
#playToggleBtn.is-playing .ic-play { display: none; }
#playToggleBtn.is-playing .ic-stop { display: inline-block; }
/* Active-state Stop в play-mode — красный стиль, явно показывает «выход
   из режима». В anim-mode playBtn остаётся обычным accent Preview — клик
   закрывает animDock и стартует Preview (см. playController.startPlay). */
#playToggleBtn.is-playing {
  background: var(--danger-bg);
  border-color: var(--danger-bg);
  color: #fff;
}
#playToggleBtn.is-playing:hover:not(.disabled) {
  background: var(--danger-hover);
  border-color: var(--danger-hover);
  color: #fff;
}
/* Кнопки видимые ТОЛЬКО в play-mode (.is-play-only) — например, restart. */
.is-play-only { display: none !important; }
body.play-mode .is-play-only { display: inline-flex !important; }

body.play-mode .layer-node { outline: none !important; }
body.play-mode .marquee-rect { display: none !important; }
body.play-mode .hint-handles { display: none !important; }
body.play-mode .text-handles { display: none !important; }
/* Hint в play-mode — невидимый (как в share-превью): фон/рамка
   убираются, но клик-зона остаётся (pointer-events: auto). */
body.play-mode .hint-area {
  background: transparent !important;
  border-color: transparent !important;
}
body.play-mode .layer-node[data-id] { cursor: default; }
body.play-mode .stage { cursor: default; }
/* Hint в play-mode — рука как в share-превью.
   .hint-area — клик-зона hint-слоя (см. js/editor/hints.js). */
body.play-mode .hint-area,
body.play-mode .layer-node:has(> .hint-area) { cursor: pointer !important; }

/* ===== State machine dock (floating, bottom) ===== */
.sm-dock {
  position: fixed;
  left: 8px;
  right: 8px;
  bottom: 8px;
  height: 44vh;
  max-height: calc(100vh - 80px);
  min-height: 200px;
  background: var(--island-bg);
  border: 1px solid var(--island-border);
  border-radius: var(--r-lg);
  display: grid;
  grid-template-rows: auto 1fr;
  overflow: hidden;
  box-shadow: var(--shadow-island);
  z-index: 120;
  transform: translateY(calc(100% + 20px));
  pointer-events: none;
  transition: transform 200ms ease-out;
}
.sm-dock.is-open {
  transform: translateY(0);
  pointer-events: auto;
}
/* Верхний край dock — drag-ручка для ресайза по высоте. */
.sm-dock-resize {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 6px;
  cursor: ns-resize;
  z-index: 10;
  background: transparent;
  transition: background 0.15s ease;
}
.sm-dock-resize:hover,
.sm-dock-resize.is-dragging {
  background: var(--accent);
  opacity: 0.55;
}

.sm-dock-header {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 8px 14px;
  border-bottom: 1px solid var(--island-border);
  background: var(--dock-header-bg);
}
.sm-dock-header > .sm-dock-close { justify-self: end; }
/* Layout-обёртка для title-блока. Типографика — через .dock-title в HTML. */
.sm-dock-title {
  display: flex;
  align-items: center;
  gap: 8px;
}
.sm-dock-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 10px var(--accent);
}
/* Доп. отступ слева — типографика через .dock-meta в HTML. */
.sm-dock-meta {
  margin-left: 4px;
}
/* Кнопка закрытия dock — квадратная 24×24 */
.sm-dock-close {
  width: 24px;
  height: 24px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.sm-dock-body {
  display: grid;
  grid-template-columns: 1fr 260px;
  overflow: hidden;
}

/* ============================================================
   Anim dock — floating редактор Action (timeline + ключи).
   Параллелен .sm-dock: тот же визуальный паттерн (resize-ручка top,
   header + body grid), но своя логика (имя Action / длительность /
   конверсия simple→nla). Общий z-index с .sm-dock; одновременно
   открыт может быть только один (см. animDock.js).
   ============================================================ */
.anim-dock {
  position: fixed;
  left: 8px;
  right: 8px;
  bottom: 8px;
  height: 44vh;
  max-height: calc(100vh - 80px);
  min-height: 200px;
  background: var(--island-bg);
  border: 1px solid var(--island-border);
  border-radius: var(--r-lg);
  display: grid;
  grid-template-rows: auto 1fr;
  overflow: hidden;
  box-shadow: var(--shadow-island);
  z-index: 120;
  transform: translateY(calc(100% + 20px));
  pointer-events: none;
  transition: transform 200ms ease-out;
}
.anim-dock.is-open {
  transform: translateY(0);
  pointer-events: auto;
}
.anim-dock-resize {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 6px;
  cursor: ns-resize;
  z-index: 10;
  background: transparent;
  transition: background 0.15s ease;
}
.anim-dock-resize:hover,
.anim-dock-resize.is-dragging {
  background: var(--accent);
  opacity: 0.55;
}

.anim-dock-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 14px;
  border-bottom: 1px solid var(--island-border);
  background: var(--dock-header-bg);
  gap: 12px;
}
/* Layout для title-блока. Типографика — через .dock-title / .dock-meta в HTML. */
.anim-dock-title {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;        /* чтобы name мог уменьшаться */
}
.anim-dock-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 10px var(--accent);
  flex-shrink: 0;
}
/* «Анимация» — типографика через .dock-title; здесь только layout. */
.anim-dock-mode {
  flex-shrink: 0;
}
/* Имя Action — типографика через .dock-meta; здесь только поведение редактирования. */
.anim-dock-name {
  outline: none;
  border-radius: var(--r-sm);
  padding: 2px 6px;
  margin: -2px -2px;
  transition: background 0.15s;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 280px;
  cursor: text;
}
.anim-dock-name:hover {
  background: var(--hover-bg-sm);
}
.anim-dock-name[contenteditable="true"] {
  background: var(--hover-bg-lg);
  cursor: text;
}
.anim-dock-name:empty::before {
  content: attr(data-placeholder);
  color: var(--gray-66);
}

.anim-dock-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
/* Layout для блоков FPS / Длительность / Кадр. Типографика — через .dock-meta. */
.anim-dock-duration,
.anim-dock-frame,
.anim-dock-fps {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
/* .lbl внутри блоков — типографика наследуется от .dock-meta родителя. */
.anim-dock-duration .ui-input,
.anim-dock-fps .ui-input,
.anim-dock-frame .ui-input {
  width: 56px;
  height: 24px;
  font-size: 12px;
  padding: 2px 6px;
  text-align: center;
}
.anim-dock-fps .ui-input,
.anim-dock-frame .ui-input { width: 44px; }
/* .suf внутри блоков — типографика наследуется от .dock-meta родителя. */
.anim-dock-frame {
  font-variant-numeric: tabular-nums;
}
/* Toggle-кнопка «Кадр / Сек» — выглядит как label, но кликабельна. */
.anim-dock-frame-toggle {
  background: transparent;
  border: none;
  padding: 1px 4px;
  margin: -1px -4px;
  font: inherit;
  color: var(--gray-66);
  cursor: pointer;
  border-radius: var(--r-sm);
  transition: background 0.15s, color 0.15s;
}
.anim-dock-frame-toggle:hover {
  background: var(--hover-bg);
  color: var(--white);
}
.anim-dock-close,
.anim-dock-play,
.anim-dock-loop,
.anim-dock-fit {
  width: 24px;
  height: 24px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.anim-dock-play.is-playing {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--white);
}
.anim-dock-play.is-playing .ic-play  { display: none; }
.anim-dock-play.is-playing .ic-pause { display: inline-block !important; }
/* Loop (default) — accent border-color активного toggle. Once — серый. */
.anim-dock-loop {
  color: var(--accent);
  border-color: var(--accent);
}
.anim-dock-loop.is-once {
  color: var(--gray-cc);
  border-color: var(--island-border);
}
.anim-dock-loop.is-once .ic-loop { display: none; }
.anim-dock-loop.is-once .ic-once { display: inline-block !important; }

.anim-dock-body {
  position: relative;
  overflow: hidden;
  background: var(--dock-canvas-bg);
}

/* ============================================================
   Anim timeline (T4) — внутри #animDockBody.
   Grid: 140px header | 1fr body. Левая колонка — track headers,
   правая — ruler + ключи + cursor.
   ============================================================ */
.anim-tl {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: 140px 1fr 220px;
  overflow: hidden;
  font-size: 11px;
  color: var(--gray-cc);
}

/* Левая колонка — корнер сверху + список заголовков треков под ним.
   Ширина фиксирована (140px), внутренняя сетка — 26px ruler-corner + 1fr. */
.anim-tl-side-col {
  display: grid;
  grid-template-rows: 26px 1fr;
  border-right: 1px solid var(--island-border);
  overflow: hidden;
}
.anim-tl-side-corner {
  background: rgba(255,255,255,0.02);
  border-bottom: 1px solid var(--island-border);
}

/* Скролл-обёртка таймлайна: ruler внутри (sticky top), area под ним.
   Горизонтальный pan через scrollLeft (wheel или 2 пальца).
   Вертикальный — natural overflow если треков много. */
.anim-tl-scroll {
  position: relative;
  overflow: auto;
  background: transparent;
  /* trackpad pan чувствительнее браузерного wheel — ОК */
}

.anim-tl-ruler {
  position: sticky;
  top: 0;
  z-index: 5;
  height: 26px;
  background: rgba(24,24,32,0.92);
  border-bottom: 1px solid var(--island-border);
  cursor: pointer;
  user-select: none;
}

/* Range bar (A4) — диапазон проигрывания, как в After Effects.
   Находится под ruler'ом, sticky тоже остаётся видимым при scroll. */
.anim-tl-range-bar {
  position: sticky;
  top: 26px;
  z-index: 4;
  height: 8px;
  background: rgba(255,255,255,0.02);
  border-bottom: 1px solid var(--island-border);
  user-select: none;
}
.anim-tl-range-bar-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--accent-soft);
  border-left: 2px solid var(--accent);
  border-right: 2px solid var(--accent);
  cursor: grab;
  box-sizing: border-box;
  /* Минимальная видимая ширина — handles не схлопываются в точку и
     остаются хваеtemy. Реальный data-range может быть меньше; для drag
     это не важно (вычисление в пикселях по pointermove). */
  min-width: 8px;
}
.anim-tl-range-bar-fill:active { cursor: grabbing; }
.anim-tl-range-bar-handle {
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 8px;
  background: var(--accent);
  cursor: ew-resize;
  border-radius: 1px;
  box-shadow: 0 0 6px var(--accent);
}
.anim-tl-range-bar-handle.is-left  { left:  -4px; }
.anim-tl-range-bar-handle.is-right { right: -4px; }
.anim-tl-tick {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--gray-33);
}
/* Frame-major (нумерованные кадры) — приглушённые, вторичные. */
.anim-tl-tick.is-major {
  background: var(--gray-44);
  width: 1px;
}
/* Секундные тики — основные деления, ярче и толще. */
.anim-tl-tick.is-second {
  background: var(--gray-cc);
  width: 1px;
}
/* Frame-ticks (legacy class, не используется в frame-based ruler). */
.anim-tl-tick.is-frame {
  top: auto;
  bottom: 0;
  height: 5px;
  background: var(--gray-44);
  opacity: 0.7;
}
.anim-tl-tick-label {
  position: absolute;
  top: 4px;
  left: 4px;
  font-size: 10px;
  color: var(--gray-66);
  white-space: nowrap;
  pointer-events: none;
}
/* Лейбл секунды — белый, жирнее: явно отделить от frame labels. */
.anim-tl-tick.is-second .anim-tl-tick-label {
  color: var(--white);
  font-weight: 600;
}

.anim-tl-side {
  overflow-y: auto;
  overflow-x: hidden;
}
.anim-tl-track-header,
.anim-tl-layer-header,
.anim-tl-channel-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 8px;
  height: 28px;
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
}
.anim-tl-track-header.is-broken,
.anim-tl-layer-header.is-broken {
  color: var(--gray-66);
}

/* Layer header (свёрнутый/раскрытый) */
.anim-tl-layer-header {
  cursor: pointer;
}
.anim-tl-layer-header:hover {
  background: var(--hover-bg-sm);
}
.anim-tl-layer-header.is-layer-selected {
  background: var(--accent-soft);
}
.anim-tl-layer-header.is-layer-selected .anim-tl-track-name {
  color: var(--accent);
}
.anim-tl-chevron {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  color: var(--gray-66);
  transition: transform 0.15s;
  cursor: pointer;
}
.anim-tl-layer-header.is-expanded .anim-tl-chevron {
  transform: rotate(90deg);
}
.anim-tl-chevron:hover { color: var(--white); }
.anim-tl-layer-badges {
  display: inline-flex;
  gap: 3px;
  margin-left: auto;
  flex-shrink: 0;
}
/* Кнопка удаления дорожки слоя — видна на hover layer-header. */
.anim-tl-layer-del {
  width: 18px;
  height: 18px;
  padding: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--gray-66);
  cursor: pointer;
  border-radius: var(--r-sm);
  flex-shrink: 0;
}
.anim-tl-layer-header:hover .anim-tl-layer-del,
.anim-tl-layer-header.is-layer-selected .anim-tl-layer-del {
  display: inline-flex;
}
.anim-tl-layer-del:hover {
  background: rgba(239,68,68,0.12);
  color: #ef4444;
}
.anim-tl-ch-badge {
  display: inline-flex;
  width: 14px;
  height: 14px;
  align-items: center;
  justify-content: center;
  font-size: 9px;
  font-weight: 600;
  color: var(--gray-66);
  background: rgba(255,255,255,0.05);
  border-radius: var(--r-sm);
}
.anim-tl-layer-header.is-layer-selected .anim-tl-ch-badge {
  color: var(--accent);
  background: rgba(139, 92, 246, 0.18);
}

/* Channel header (раскрытый layer → отдельные строки каналов) */
.anim-tl-channel-header {
  background: transparent;
}
.anim-tl-channel-indent {
  display: inline-block;
  width: 14px;            /* отступ под chevron — channel визуально внутри layer */
  flex-shrink: 0;
}
.anim-tl-channel-header .anim-tl-track-channel {
  color: var(--gray-cc);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Backward-compat: старый .anim-tl-track-row.is-layer-selected — оставлен
   для row'ов которые имеют data-layer-id (раскрытые channel rows). */
.anim-tl-track-row.is-layer-selected::before {
  background: var(--accent);
  opacity: 0.5;
}

/* Summary key (показывается на layer-row когда свёрнут): чуть меньше
   и светлее, чтобы визуально отличался от ключа в channel-row. */
.anim-tl-key--summary {
  width: 8px;
  height: 8px;
  /* margin reset не нужен — transform translate(-50%, -50%) выше центрирует. */
}

/* Marquee (rect-select) — прозрачная рамка с accent-обводкой. */
.anim-tl-marquee {
  position: absolute;
  background: var(--accent-soft);
  border: 1px dashed var(--accent);
  pointer-events: none;
  z-index: 4;
}
.anim-tl-track-name {
  color: var(--white);
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
}
.anim-tl-track-header.is-broken .anim-tl-track-name { color: var(--gray-66); font-style: italic; }
.anim-tl-track-channel {
  color: var(--gray-66);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  flex-shrink: 0;
}

.anim-tl-area {
  position: relative;
  /* width устанавливается из JS = duration * pxPerSec + gutter */
  background: transparent;
  /* Ключи и струны за пределами area.width клипятся — иначе ключ
     с time > duration растягивает scrollWidth и Fit не помогает. */
  overflow: hidden;
}
/* Один трек — одна струна посередине. Высота строки 28px, линия в
   геометрическом центре (top: 14px). Ключи позиционируются top:50% =
   ровно на струне. */
.anim-tl-track-row {
  position: relative;
  height: 28px;
}
/* Базовая струна больше не через ::before — рисуется явно через
   .anim-tl-track-string (sermовый сегмент + dashed по краям если есть
   ключи). Это позволяет показать пунктир ДО первого ключа и ПОСЛЕ
   последнего без перерисовки ::before. */
.anim-tl-track-string {
  position: absolute;
  top: 50%;
  height: 1px;
  background: var(--gray-33);
  transform: translateY(-0.5px);
  pointer-events: none;
  z-index: 0;
}
.anim-tl-track-string--dashed {
  background: transparent;
  border-top: 1px dashed var(--gray-44);
  height: 0;
}
/* Out-of-range overlays — приглушают треки за пределами Action.range. */
.anim-tl-area-dim-left,
.anim-tl-area-dim-right {
  position: absolute;
  top: 0;
  bottom: 0;
  background: rgba(0,0,0,0.35);
  pointer-events: none;
  z-index: 1;
}

.anim-tl-key {
  position: absolute;
  top: 50%;
  width: 10px;
  height: 10px;
  background: var(--accent);
  border: 1px solid var(--white);
  /* Центрируем точно по `left` через transform — учитывает border (10px+2px). */
  transform: translate(-50%, -50%) rotate(45deg);
  border-radius: 1px;
  cursor: ew-resize;
  transition: box-shadow 0.15s;
  z-index: 2;
  user-select: none;
  touch-action: none;
}
.anim-tl-key:hover {
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.anim-tl-key.is-selected {
  background: var(--white);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
  z-index: 3;
}
.anim-tl-key.is-dragging {
  box-shadow: 0 0 0 4px var(--accent-soft);
  z-index: 4;
}

.anim-tl-cursor {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 1px;
  background: var(--accent);
  box-shadow: 0 0 6px var(--accent);
  pointer-events: none;
  z-index: 6;     /* поверх ruler/range-bar/area */
}
.anim-tl-cursor::before {
  content: '';
  position: absolute;
  top: 4px;
  left: -4px;
  width: 9px;
  height: 9px;
  background: var(--accent);
  transform: rotate(45deg);
  border-radius: 1px;
}

.anim-tl-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  color: var(--gray-66);
  pointer-events: none;
  z-index: 3;
  text-align: center;
  padding: 0 24px;
  letter-spacing: 0.02em;
}
.anim-tl-empty kbd {
  display: inline-block;
  padding: 1px 6px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 600;
  background: var(--hover-bg);
  border: 1px solid var(--island-border);
  border-radius: var(--r-sm);
  margin: 0 2px;
  color: var(--white);
}

/* Properties panel: третья колонка animDock body. */
.anim-tl-props {
  border-left: 1px solid var(--island-border);
  background: rgba(255,255,255,0.02);
  padding: 12px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.anim-tl-props-empty {
  font-size: 11px;
  color: var(--gray-66);
  text-align: center;
  padding: 24px 0;
  line-height: 1.5;
}
.anim-tl-props-fields {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.anim-tl-props-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 11px;
  color: var(--gray-cc);
}
.anim-tl-props-row > span {
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 10px;
  color: var(--gray-66);
}
.anim-tl-props-row input,
.anim-tl-props-row select {
  height: 26px;
  padding: 0 8px;
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--island-border);
  border-radius: var(--r-md);
  color: var(--white);
  font-size: 12px;
  font-family: inherit;
  outline: none;
  transition: border-color 0.15s;
}
.anim-tl-props-row input:focus,
.anim-tl-props-row select:focus {
  border-color: var(--accent);
}
/* Убираем native spinner у number-input (ввод через клавиши + стрелки
   в keydown handler). */
.anim-tl-props-row input[type="number"]::-webkit-inner-spin-button,
.anim-tl-props-row input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.anim-tl-props-row input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}
.anim-tl-props-del {
  margin-top: 8px;
  justify-content: center;
  color: #ef4444;
  border-color: rgba(239,68,68,0.3);
}
.anim-tl-props-del:hover {
  background: rgba(239,68,68,0.1);
  border-color: #ef4444;
  color: #ef4444;
}

/* Insert-key popup (T6a) — открывается при K, рядом с курсором. */
.anim-tl-insert-popup {
  position: fixed;
  z-index: 200;
  min-width: 200px;
  background: var(--island-bg);
  border: 1px solid var(--island-border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-popup);
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.anim-tl-insert-popup-title {
  padding: 6px 10px 4px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--gray-66);
}
.anim-tl-insert-popup-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.anim-tl-insert-popup-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 10px;
  background: transparent;
  border: none;
  border-radius: var(--r-sm);
  color: var(--gray-cc);
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
  text-align: left;
}
.anim-tl-insert-popup-item:hover:not(:disabled) {
  background: var(--hover-bg);
  color: var(--white);
}
.anim-tl-insert-popup-item:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
.anim-tl-insert-popup-item.is-all {
  color: var(--accent);
  border-top: 1px solid var(--island-border);
  margin-top: 4px;
  padding-top: 8px;
}
.anim-tl-insert-popup-item.is-all:hover:not(:disabled) {
  background: var(--accent-soft);
  color: var(--white);
}
.anim-tl-insert-popup-key {
  font-family: monospace;
  font-size: 11px;
  color: var(--gray-66);
  padding: 1px 6px;
  background: var(--hover-bg-sm);
  border-radius: var(--r-sm);
  border: 1px solid var(--island-border);
}
.anim-tl-insert-popup-item:hover .anim-tl-insert-popup-key {
  color: var(--white);
}

.sm-graph {
  position: relative;
  overflow: hidden;
  /* Холст темнее dock'а — визуальная разница между «полем для нод»
   * и панелью свойств справа. Сетка 24px поверх через linear-gradients. */
  background:
    linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px) 0 0 / 24px 24px,
    linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px) 0 0 / 24px 24px,
    var(--gray-15);
}
.sm-graph.is-panning { cursor: grabbing; }
.sm-graph.is-space { cursor: grab; }
.sm-graph-content {
  position: absolute;
  inset: 0;
  transform-origin: 0 0;
  will-change: transform;
}
.sm-graph-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  font-size: 12px;
  color: var(--gray-66);
  text-align: center;
  line-height: 1.5;
}

.sm-edges {
  position: absolute;
  inset: 0;
  pointer-events: none;
  color: var(--gray-cc);
  overflow: visible;
}
.sm-edge {
  stroke: var(--gray-cc);
  stroke-width: 2;
  fill: none;
  transition: stroke 200ms, stroke-width 200ms;
}
.sm-edge.is-selected { stroke: var(--accent); stroke-width: 2.5; }
.sm-edge.is-fired {
  stroke: var(--accent);
  stroke-width: 2.5;
  animation: smEdgeFire 700ms ease-out;
}
@keyframes smEdgeFire {
  0%   { stroke: #fff; stroke-width: 3.5; filter: drop-shadow(0 0 6px var(--accent)); }
  100% { stroke: var(--accent); stroke-width: 2.5; }
}
.sm-edge-label {
  font-size: 10px;
  fill: var(--gray-cc);
  pointer-events: none;
}
.sm-edge-hit {
  stroke: transparent;
  stroke-width: 16;
  fill: none;
  pointer-events: stroke;
  cursor: pointer;
}
/* Marquee в графе — отдельный класс, внутри #smGraphContent (т.е. тоже
 * трансформируется вместе с нодами). Совпадает стилем с .marquee-rect. */
.sm-marquee-rect {
  position: absolute;
  border: 1px solid var(--accent);
  background: rgba(139, 92, 246, 0.12);
  pointer-events: none;
  z-index: 10;
}

.sm-node {
  position: absolute;
  min-width: 170px;
  border-radius: var(--r-md);
  background: linear-gradient(180deg, #2b2b38 0%, #1f1f29 100%);
  border: 1px solid #35353f;
  /* overflow НЕ clipping — кружки сокетов торчат на 7px наружу и должны
   * оставаться видимыми. Header получает top-radius явно (см. ниже). */
  box-shadow:
    0 10px 24px rgba(0, 0, 0, 0.55),
    0 1px 0 rgba(255, 255, 255, 0.05) inset,
    0 0 0 1px rgba(0, 0, 0, 0.3);
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  /* z-index создаёт собственный stacking context — чтобы дочерние сокеты
   * (z:3) не «всплывали» в родительский контекст и не торчали над другими
   * нодами при наложении (например, во время drag). */
  z-index: 1;
  transition: border-color 150ms, box-shadow 150ms, transform 200ms;
}
.sm-node,
.sm-node * { user-select: none; -webkit-user-select: none; }
.sm-node:hover { border-color: #4a4a58; }
.sm-node.is-dragging {
  cursor: grabbing;
  z-index: 10;
  box-shadow:
    0 14px 32px rgba(0, 0, 0, 0.65),
    0 1px 0 rgba(255, 255, 255, 0.05) inset;
}
.sm-node.is-selected {
  border-color: var(--accent);
  box-shadow:
    0 0 0 2px var(--accent-soft),
    0 10px 24px rgba(0, 0, 0, 0.55),
    0 1px 0 rgba(255, 255, 255, 0.05) inset;
}

/* Fade-in для только что созданной ноды (двойной клик по ребру → Delay).
 * Класс ставится кодом и очищается сам после animationend. */
.sm-node--appearing {
  animation: smNodeAppear 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
@keyframes smNodeAppear {
  from { opacity: 0; transform: scale(0.88); }
  to   { opacity: 1; transform: scale(1); }
}

/* Header — цветной по типу, с иконкой, label типа и опциональным badge.
 * Top-radius у ноды НЕ клипает background header'а (у ноды overflow не
 * hidden — чтобы сокеты торчали). Поэтому header получает top-radius сам. */
.sm-node-hdr {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  border-top-left-radius: var(--r-md);
  border-top-right-radius: var(--r-md);
}
.sm-node-icon { width: 12px; height: 12px; flex-shrink: 0; }
.sm-node-type { flex: 0 0 auto; }
.sm-node--compact .sm-node-hdr { border-bottom: 0; border-radius: var(--r-md); }

/* Body — title и опциональный subtitle на нейтральном фоне. */
.sm-node-body { padding: 8px 12px 10px; display: flex; flex-direction: column; }

/* Labels у output-сокетов (для нод с несколькими outputs — например,
 * Animation с play+done). Sockets абсолютные, их top синхронизируется с
 * y-центром соответствующего label через syncSocketsToLabels() в nodes.js.
 * Text-align: right ставит labels у правого края ноды — сокет торчит дальше. */
/* `.sm-node-lbls` — две колонки (left/right) подписей у сокетов.
 * Колонки выравниваются по низу (align-items: flex-end) — даже если в
 * левой 2 лейбла, а в правой 1, нижний лейбл правой колонки совпадает
 * по y с нижним левой. Высота блока определяется содержимым — нода
 * остаётся компактной. */
.sm-node-lbls {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 24px;
  margin-top: 8px;
}
.sm-node-lbls-col {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.sm-node-lbls-col--left  { align-items: flex-start; }
.sm-node-lbls-col--right { align-items: flex-end; }
.sm-node-lbl {
  font-size: 10px;
  line-height: 14px;
  color: var(--gray-cc);
  letter-spacing: 0;
  text-transform: none;
  font-weight: 400;
}
/* Лейбл-сокет на левой стороне — text-align:left для согласованности
 * с положением точки сокета слева. */
.sm-node-lbl--side-left { text-align: left; }

/* Типы нод — цвет применяется к header, border ноды — полупрозрачный по типу. */
.sm-node--event     { border-color: rgba(56, 132, 255, 0.3); }
.sm-node--event     .sm-node-hdr { background: rgba(56, 132, 255, 0.18); color: #6490ff; border-color: rgba(56, 132, 255, 0.4); }
.sm-node--event:hover     { border-color: rgba(100, 144, 255, 0.6); }

.sm-node--animation { border-color: rgba(139, 92, 246, 0.3); }
.sm-node--animation .sm-node-hdr { background: rgba(139, 92, 246, 0.22); color: var(--accent-hover); border-color: rgba(139, 92, 246, 0.4); }
.sm-node--animation .sm-node-hdr .sm-node-badge { background: rgba(139, 92, 246, 0.28); color: var(--accent-hover); }
.sm-node--animation:hover { border-color: rgba(167, 139, 250, 0.55); }

.sm-node--layer     { border-color: rgba(74, 222, 128, 0.3); }
.sm-node--layer     .sm-node-hdr { background: rgba(74, 222, 128, 0.16); color: #4ade80; border-color: rgba(74, 222, 128, 0.35); }
.sm-node--layer:hover     { border-color: rgba(74, 222, 128, 0.55); }

.sm-node--delay     .sm-node-hdr { background: rgba(168, 168, 178, 0.12); color: var(--gray-cc); }

/* Для Animation высота body теперь определяется списком labels
 * (.sm-node-lbls), а sockets sync'атся с их y — padding-хак больше не нужен. */

/* Сокеты (usiki) */
.sm-socket {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--gray-33);
  border: 2px solid var(--gray-44);
  cursor: crosshair;
  z-index: 3;
  transition: background 120ms, border-color 120ms, transform 120ms, box-shadow 120ms;
}
/* Магнит-зона: невидимый ::before 32×32 вокруг точки 12×12 — в ~7× больше
 * по площади. Hit-area работает автоматически через pointer-events на
 * ::before; при hover появляется halo в цвет сокета. */
.sm-socket::before {
  content: '';
  position: absolute;
  inset: -10px;
  border-radius: 50%;
  background: transparent;
  box-shadow: 0 0 0 0 transparent inset;
  cursor: crosshair;
  transition: background 180ms ease-out, box-shadow 180ms ease-out;
}
.sm-socket--output { right: -7px; background: #6490ff; border-color: #6490ff; }
.sm-socket--input  { left: -7px;  background: var(--accent); border-color: var(--accent); }
/* side override — переставляет кружок на другую сторону независимо от dir.
 * Используется для косметических случаев (Animation.play / Layer.play),
 * когда визуально логичнее разместить сокет на «нелогичной» стороне. dir
 * (а значит совместимость и drag-to-connect) остаётся прежним. */
.sm-socket--side-left  { left:  -7px; right: auto; }
.sm-socket--side-right { right: -7px; left:  auto; }
/* tone override — перекрашивает сокет в нужный цвет (background + border +
 * halo на hover). Применяется поверх дефолтных цветов output/input. */
.sm-socket--tone-green { background: var(--success); border-color: var(--success); }
.sm-socket--tone-green:hover::before,
.sm-socket--tone-green.sm-socket--hover::before {
  background: rgba(74, 222, 128, 0.18);
  box-shadow: 0 0 0 1px rgba(74, 222, 128, 0.5) inset;
}
/* Сокет «Конец» (Animation/Action/Delay/Tween.done) — ромб + розовый.
 * Семантически одинаков для всех нод; визуально отличается от обычных
 * trigger-сокетов чтобы было сразу понятно «здесь срабатывает по
 * завершению, не по клику». */
.sm-socket--done {
  background: #ec4899;
  border-color: #ec4899;
  border-radius: 2px;
  transform: translateY(-50%) rotate(45deg);
}
.sm-socket--done::before { border-radius: 2px; }
.sm-socket--done:hover::before,
.sm-socket--done.sm-socket--hover::before {
  background: rgba(236, 72, 153, 0.18);
  box-shadow: 0 0 0 1px rgba(236, 72, 153, 0.5) inset;
}
/* Halo — rgba напрямую (не через opacity), чтобы при уводе курсора не было
 * вспышки: opacity прыгает мгновенно, а background плавает 180мс.
 * `.sm-socket--hover` — класс от edges.js во время drag-to-connect. */
.sm-socket--output:hover::before,
.sm-socket--output.sm-socket--hover::before {
  background: rgba(56, 132, 255, 0.18);
  box-shadow: 0 0 0 1px rgba(56, 132, 255, 0.5) inset;
}
.sm-socket--input:hover::before,
.sm-socket--input.sm-socket--hover::before {
  background: rgba(139, 92, 246, 0.18);
  box-shadow: 0 0 0 1px rgba(139, 92, 246, 0.5) inset;
}
.sm-socket:hover,
.sm-socket--hover {
  transform: translateY(-50%) scale(1.3);
  box-shadow: 0 0 10px currentColor;
}
/* Done-сокет ромб — hover должен сохранить rotate(45deg) поверх scale. */
.sm-socket--done:hover,
.sm-socket--done.sm-socket--hover {
  transform: translateY(-50%) rotate(45deg) scale(1.3);
}

/* Старый трюк с labels через ::after сокета (tooltip-like overlay) — убран.
 * Labels теперь как `.sm-node-lbl` в body, sockets синхронизируются с ними
 * через syncSocketsToLabels() (см. nodes.js). */

.sm-edge--pending {
  stroke: #fff;
  stroke-dasharray: 4 4;
  opacity: 0.8;
  pointer-events: none;
}

/* Popup «+ Ноду» */
.sm-popup {
  position: absolute;
  top: 40px;
  right: 12px;
  min-width: 220px;
  max-width: 320px;
  max-height: 60vh;
  overflow-y: auto;
  background: var(--gray-22);
  border: 1px solid var(--gray-33);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-popup);
  padding: 8px;
  z-index: 200;
}
.sm-popup-title {
  font-size: 11px;
  color: var(--gray-66);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 4px 8px 8px;
  border-bottom: 1px solid var(--island-border);
  margin-bottom: 6px;
}
.sm-popup-section-title {
  font-size: 10px;
  color: var(--gray-66);
  padding: 4px 8px 2px;
}
.sm-popup-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.sm-popup-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: var(--r-sm);
  background: transparent;
  border: 0;
  color: var(--white);
  text-align: left;
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
}
.sm-popup-item:hover {
  background: var(--gray-33);
}
.sm-popup-item-type {
  font-size: 10px;
  color: var(--gray-66);
  margin-left: auto;
}
.sm-popup-empty {
  padding: 8px 10px;
  font-size: 11px;
  color: var(--gray-66);
}
.sm-node.is-active {
  border-color: var(--accent);
  box-shadow:
    0 0 0 2px var(--accent),
    0 0 20px rgba(139, 92, 246, 0.5),
    0 10px 24px rgba(0, 0, 0, 0.55);
  transform: scale(1.04);
}
.sm-node-title {
  font-size: 13px;
  color: var(--white);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sm-node-sub {
  font-size: 10px;
  color: var(--gray-66);
  margin-top: 3px;
}
/* Badge живёт в header справа от label типа. */
.sm-node-badge {
  margin-left: auto;
  display: inline-block;
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.2px;
  padding: 1px 6px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.9);
  text-transform: none;
}

.sm-props {
  border-left: 1px solid var(--island-border);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.sm-props h4 {
  margin: 0;
  padding: 12px 14px 6px;
  font-size: 11px;
  color: var(--gray-66);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-weight: 500;
}
.sm-props-body {
  padding: 0 0 12px;
  overflow-y: auto;
  flex: 1;
  font-size: 12px;
  color: var(--gray-cc);
  line-height: 1.5;
}
/* Blender-style props panel — плоский, без коробок-в-коробке.
 * Props panel уже сама внутри dock-сайдбара (.sm-props), так что
 * собственный border не нужен. Только subtle separators между секциями. */
.sm-props-body {
  padding: 0 !important;
  gap: 0;
}
/* Title-bar — текст с цветной точкой типа, разделитель снизу. Без фона/рамки. */
.sm-titlebar {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 14px 10px;
}
.sm-titlebar-dot {
  width: 10px; height: 10px; border-radius: 2px;
  flex-shrink: 0;
}
.sm-titlebar-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--gray-cc);
  font-weight: 600;
}
/* Категории — плоские секции, разделены только border-bottom. */
.sm-cat {
  border-top: 1px solid var(--island-border);
}
.sm-cat-hdr {
  display: flex; align-items: center;
  padding: 8px 14px;
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.6px;
  color: var(--gray-66); font-weight: 500;
  cursor: pointer; user-select: none;
  transition: color 120ms;
}
.sm-cat-hdr:hover { color: var(--gray-cc); }
.sm-cat-hdr::before {
  content: '▾';
  margin-right: 6px; font-size: 9px;
  transition: transform 120ms;
}
.sm-cat.is-collapsed .sm-cat-hdr::before { transform: rotate(-90deg); }
.sm-cat-body { padding: 0 14px 10px; display: flex; flex-direction: column; gap: 6px; }
.sm-cat.is-collapsed .sm-cat-body { display: none; }
/* Inline-row для полей внутри категории (Blender-style: label слева, control справа). */
.sm-cat .sm-field {
  display: grid;
  grid-template-columns: 80px 1fr;
  align-items: center;
  gap: 8px;
  padding: 0;
  min-height: 24px;
}
.sm-cat .sm-field label {
  font-size: 11px;
  color: var(--gray-66);
  margin: 0;
}
.sm-cat .sm-field .ui-select,
.sm-cat .sm-field .ui-input {
  height: 24px;
  font-size: 11px;
  background-color: transparent;
  border: 1px solid var(--gray-33);
  border-radius: 4px;
  color: var(--white);
  width: 100%;
  font-family: inherit;
}
.sm-cat .sm-field .ui-input { padding: 2px 8px; }
/* Right padding под SVG-chevron из .ui-select (background-image: chevron). */
.sm-cat .sm-field .ui-select { padding: 2px 22px 2px 8px; }
.sm-cat .sm-field .ui-select:hover,
.sm-cat .sm-field .ui-input:hover { border-color: var(--gray-44); }
.sm-cat .sm-field .ui-select:focus,
.sm-cat .sm-field .ui-input:focus {
  outline: none; border-color: var(--accent); background-color: var(--gray-22);
}
/* Inline-row для readonly «От / К» в edge-режиме */
.sm-cat .sm-cat-body .row {
  display: grid;
  grid-template-columns: 80px 1fr;
  align-items: center;
  gap: 8px;
  padding: 2px 0;
  border: none;
  font-size: 11px;
}
.sm-cat .sm-cat-body .row .k { color: var(--gray-66); }
.sm-cat .sm-cat-body .row .v { color: var(--gray-cc); text-align: left; }
/* Hint — после всех категорий с верхним отступом */
.sm-props-body > .sm-props-hint {
  margin: 4px 14px 12px;
  color: var(--gray-44);
  font-size: 11px;
  line-height: 1.5;
}
.sm-props-body .row {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  padding: 4px 0;
  border-bottom: 1px solid var(--island-border);
}
.sm-props-body .row:last-child { border-bottom: 0; }
.sm-props-body .row .k { color: var(--gray-66); }
.sm-props-body .row .v { color: var(--white); text-align: right; }
.sm-props-hint {
  margin: 0;
  color: var(--gray-66);
  font-size: 11px;
  line-height: 1.5;
}
.sm-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px 0;
}
.sm-field label {
  font-size: 11px;
  color: var(--gray-cc);
}
/* sm-field теперь только внутри .sm-cat — стили задаются в правилах
 * `.sm-cat .sm-field .ui-select` ниже (Blender-style props panel). */

.sm-debug-bar {
  display: flex;
  gap: 12px;
  padding: 8px 14px;
  border-top: 1px solid var(--island-border);
  background: rgba(139, 92, 246, 0.08);
  font-size: 11px;
  color: var(--accent);
}
.sm-debug-bar b { color: var(--white); font-weight: 500; }

/* Активная кнопка mode-island'а — фон даёт sliding `.mode-island__indicator`
 * (см. блок выше). Логика what is .is-open — main.js syncEditorModeBtn. */

/* ===== Tool island (bottom center toolbar) ===== */
.tool-island {
  position: fixed;
  /* Поднимается над dock'ом (graph / animation) — общая CSS-переменная
     --dock-offset устанавливается обоими dock'ами при open/resize. */
  bottom: calc(14px + var(--dock-offset, 0px));
  left: 50%;
  transform: translateX(-50%);
  display: inline-flex;
  gap: 4px;
  padding: 6px;
  border-radius: var(--r-lg);
  background: var(--island-bg);
  border: 1px solid var(--island-border);
  z-index: 5;
  transition: bottom 0.2s ease;
}

/* ===== Share island auth row ===== */
.share-island-row {
  display: flex;
  align-items: center;
  gap: 6px;
}
.share-island-row #authBtn { flex: 1; text-align: center; }
.share-island-email {
  font-size: 12px;
  color: var(--gray-cc);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.share-island-icon-btn {
  border: 1px solid var(--island-border);
  background: rgba(255,255,255,0.03);
  color: var(--gray-cc);
  cursor: pointer;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-md);
  flex-shrink: 0;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.share-island-icon-btn:hover { background: rgba(255,255,255,0.08); border-color: var(--gray-33); color: var(--white); }

/* Share button */
/* Info toggle */
.info-toggle-btn {
  width: 100%;
  height: 28px;
  border: 1px solid var(--island-border);
  background: rgba(255,255,255,0.03);
  color: var(--gray-cc);
  font-size: 12px;
  cursor: pointer;
  border-radius: var(--r-md);
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.info-toggle-btn:hover { background: rgba(255,255,255,0.06); color: var(--white); border-color: var(--gray-33); }
.info-toggle-btn.active { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.info-collapse { margin-top: 8px; }
.info-toggle-panel { border-bottom: none; }

.right-col {
  position: absolute;
  top: 8px;
  right: 8px;
  bottom: calc(8px + var(--dock-offset, 0px));
  width: 260px;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: calc(100vh - 16px - var(--dock-offset, 0px));
  pointer-events: none;
  /* Сжатие синхронно с .left-col и dock'ами (см. выше). */
  transition: bottom 200ms ease-out, max-height 200ms ease-out;
}
.right-col > * {
  pointer-events: auto;
}
.share-island {
  background: var(--island-bg);
  border: 1px solid var(--island-border);
  border-radius: var(--r-lg);
  padding: 10px;
  flex-shrink: 0;
}
.right-col .right_panel {
  width: 100%;
  flex: 1 1 0;
  min-height: 0;
  position: relative;
  overflow-y: auto;
  overflow-x: hidden;
}
/* .scroll-inner внутри .right_panel — legacy-обёртка; её overflow:hidden
   ломал вертикальный скролл самого right_panel. Делаем её прозрачной
   для layout и overflow, чтобы скролл работал на right_panel. */
.right-col .right_panel .scroll-inner {
  overflow: visible;
  max-height: none;
  flex: 0 0 auto;
}
.share-btn {
  width: 100%;
  height: 32px;
  margin-top: 8px;
  padding: 0 18px;
  font-size: 12px;
  font-weight: 600;
  border: none;
  border-radius: var(--r-md);
  background: var(--accent);
  color: #fff;
  cursor: pointer;
  transition: filter 0.15s, background 0.15s;
}
.share-btn:hover { filter: brightness(1.1); }
.share-btn:disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; }

.share-link-row {
  display: flex;
  gap: 4px;
  margin-top: 8px;
  animation: share-link-row-pop 360ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Pop-in: появление с лёгким ростом + мерцание акцентного outline,
   чтобы при клике «Поделиться» юзер сразу видел где появилась ссылка
   (раньше row молча подменял кнопку, фидбек был только в toast сверху). */
@keyframes share-link-row-pop {
  0%   { transform: scale(0.92); opacity: 0; }
  60%  { transform: scale(1.02); opacity: 1; }
  100% { transform: scale(1);    opacity: 1; }
}
.share-link-row::before {
  content: '';
  position: absolute;
  inset: -3px;
  border-radius: var(--r-md);
  border: 1px solid var(--accent);
  pointer-events: none;
  animation: share-link-row-glow 1200ms ease-out forwards;
  opacity: 0;
}
.share-link-row { position: relative; }
@keyframes share-link-row-glow {
  0%   { opacity: 0.85; }
  100% { opacity: 0; }
}
.share-link-input {
  flex: 1;
  min-width: 0;
  height: 28px;
  padding: 0 10px;
  border-radius: var(--r-md);
  border: 1px solid var(--island-border);
  background: rgba(0,0,0,0.25);
  color: var(--gray-cc);
  font-size: 11px;
  outline: none;
}
.share-link-copy {
  width: 28px;
  height: 28px;
  border: 1px solid var(--island-border);
  background: rgba(255,255,255,0.03);
  color: var(--gray-cc);
  border-radius: var(--r-md);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.share-link-copy:hover { background: rgba(255,255,255,0.08); color: var(--white); border-color: var(--gray-33); }

/* Save toast */
.save-toast {
  position: fixed;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  background: var(--island-bg);
  border: 1px solid var(--island-border);
  border-radius: var(--r-lg);
  color: var(--white);
  font-size: 13px;
  z-index: 200;
  animation: save-toast-in 0.9s ease;
}
.save-toast[hidden] { display: none !important; }
.save-toast-btn {
  height: 28px;
  padding: 0 14px;
  font-size: 12px;
  font-weight: 600;
  border: none;
  border-radius: var(--r-md);
  background: var(--accent);
  color: #fff;
  cursor: pointer;
  transition: filter 0.15s;
}
.save-toast-btn:hover { filter: brightness(1.1); }
.save-toast-dismiss {
  border: none;
  background: transparent;
  color: var(--gray-66);
  cursor: pointer;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-sm);
  transition: background 0.15s, color 0.15s;
  margin-left: -4px;
}
.save-toast-dismiss:hover { background: rgba(255,255,255,0.1); color: var(--white); }
.save-toast.shake {
  animation: save-toast-shake 0.4s ease;
}
@keyframes save-toast-shake {
  0%, 100% { transform: translateX(-50%); }
  20% { transform: translateX(calc(-50% - 6px)); }
  40% { transform: translateX(calc(-50% + 6px)); }
  60% { transform: translateX(calc(-50% - 4px)); }
  80% { transform: translateX(calc(-50% + 4px)); }
}
@keyframes save-toast-in {
  0% { opacity: 0; transform: translateX(-50%) translateY(-10px); }
  100% { opacity: 1; transform: translateX(-50%) translateY(0); }
}

.ctrl-group {
  margin-bottom: 16px;
}

/* button group — pill shape */
.btn-group {
  display: flex;
  gap: 1.5px;
  border: 1px solid transparent;
  border-radius: var(--r-md);
}

/* Lab tabs «Слои │ Actions» — segmented control по паттерну mode-island
 * (сами стили в блоке .mode-island выше). `.lab-tabs` остался только как
 * data-marker для setActiveTab/main.js query selector'ов — визуально
 * отвечает .mode-island.mode-island--compact.mode-island--full. */

/* Action list — список анимаций под табом «Анимации».
   Паттерн рендера copy-from layer list. */

/* Toolbar над списком: широкая primary-кнопка «+ Создать анимацию».
   Лежит в теле вкладки (не в header'е) — иначе tabs «прыгают» по
   ширине при переключении между «Слои» и «Анимации». */
.actions-toolbar {
  display: flex;
  margin-top: 8px;
}
.actions-toolbar .chip {
  flex: 1;
  justify-content: center;
  gap: 6px;
  padding: 6px 10px;
}

/* gap между toolbar и списком — такой же 8px как между header и
   #layerList. Empty-state не схлопывается (нужна заглушка). */
#actionList { margin-top: 8px; }
#actionList:empty::after {
  content: "Пока пусто.";
  display: block;
  padding: 12px 8px;
  font-size: 11px;
  color: var(--gray-66);
  text-align: center;
  line-height: 1.5;
}
/* Action list = layer list 1-в-1.
   Базовые стили строки/имени/actions берутся из .list li (base.css) —
   это даёт идентичный layout, hover, active, fade-gradient у actions
   и т.д. Здесь оставлен только override cursor (без grab — нет drag-
   reorder) и accent-цвет mode-badge для NLA Action'ов. */
.action-item {
  cursor: pointer;
}
/* Inline-rename Action'а — same look что у layer-name.editing. */
.action-item .layer-name[contenteditable="true"] {
  background: rgba(255,255,255,0.06);
  border-radius: var(--r-sm);
  padding: 1px 4px;
  margin: -1px -4px;
  outline: none;
}
.btn-group.active {
  border-color: var(--accent);
}
.btn-group .chip.square,
.btn-group .chip {
  border-radius: 0;
}
.btn-group .chip:first-child,
.btn-group .chip.square:first-child {
  border-radius: var(--r-md) 0 0 var(--r-md);
}
.btn-group .chip:last-child,
.btn-group .chip.square:last-child,
.btn-group > :last-child {
  border-radius: 0 var(--r-md) var(--r-md) 0;
}
.btn-group .ui-input {
  border-color: transparent;
}
.btn-group.active .chip {
  border-color: transparent;
}

.row2 {
  flex-wrap: nowrap;
}

.row2 img {
  display: block;
}

@media (max-width: 540px) {
  .row2 {
    flex-wrap: wrap;
  }
}

/* Stage-host оборачивает stage и overlay-sibling. Position: relative —
   absolute overlay'и (включая stage-overlays) позиционируются от
   границ фрейма. */
.stage-host {
  position: relative;
  display: inline-block;
}

.stage {
  width: max-content;
  /* width/height inline из canvasRenderer.setCanvasSize. */
  position: relative;
  /* Stage overflow: visible — слои могут вылезать за пределы фрейма.
     Затемнение «снаружи» рисует sibling .stage-bleed-fade поверх (z=9998),
     селекшн-overlay (.stage-overlays z=99999) поверх него. В play-mode
     выставляется --bleed-alpha:1 → outside полностью закрашен, как было
     при overflow:hidden. */
  overflow: visible;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.04), 0 20px 60px rgba(0,0,0,0.25);
}

/* Затемнитель зоны bleed (за фреймом).
   inset:0 — занимает rect stage внутри inline-block .stage-host.
   box-shadow в 99999px красит ВСЁ за пределами этого rect полупрозрачным
   фоном-фреймом. Layers, вылезающие за стейдж, попадают под этот shadow
   и визуально приглушаются. Стоимость: один композитный слой, paint
   только при resize холста. Управление через --bleed-alpha (0..1) на
   .stage-host — изменение var не трогает render() и не пересчитывает path. */
.stage-bleed-fade {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 9998;
  box-shadow: 0 0 0 99999px rgba(13, 13, 18, var(--bleed-alpha, 0.7));
  /* Скруглений у фрейма нет (см. cb3bd85 в canvasRenderer) — острый rect. */
}

/* В play-mode оставляем ту же структуру что в edit-mode (bleed-alpha=0.7,
   те же rgba) — фон вокруг канваса визуально не меняется. Слои за рамкой
   скрываем не через alpha=1 на bleed, а через overflow:hidden на самом
   stage — это обрезает выходящий контент чисто, без дополнительного
   затемнения. */
body.play-mode .stage { overflow: hidden !important; }

/* Overlay-слой поверх stage'а. Накладывается ровно по границам stage
   (внутри .stage-host). Хранит .layer-overlay для каждого выделенного
   слоя — рамку трансформации + (на следующих этапах) handles. Pointer-
   events: none на контейнере — пропускаем клики на canvas. На самих
   handle-элементах ставится pointer-events: auto. */
.stage-overlays {
  position: absolute;
  top: 0;
  left: 0;
  overflow: visible;
  pointer-events: none;
  /* Z-index должен быть выше любого layer-node. Layer-nodes имеют
     inline z-index = 1 + idx (растёт с количеством слоёв), поэтому
     ставим заведомо большое число — иначе overlay frontmost'а слоя
     оказывался ПОД самим слоем (idx > 2 → z-index слоя > 2). */
  z-index: 99999;
}

.layer-overlay {
  position: absolute;
  top: 0;
  left: 0;
  /* outline компенсируем под --editor-zoom (выставляется в
     applyViewTransform на .center-content), чтобы рамка визуально
     оставалась 2px screen при любом масштабе. */
  outline: calc(2px / var(--editor-zoom, 1)) solid var(--accent);
  /* pointer-events: auto — чтобы по рамке за фреймом можно было схватить
     слой и тащить (когда сам .layer-node клипается .stage overflow:hidden
     и не получает клика). Handles внутри overlay делают stopPropagation
     в их handlers — это не мешает их работе. */
  pointer-events: auto;
}
/* В Preview overlay скрываем — рамок выделения там не должно быть. */
body.play-mode .stage-overlays { display: none; }

.size-inputs {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.chip-row {
  display: flex;
  gap: 6px;
}

.panel {
  border-bottom: 1px solid var(--island-border);
  padding: 10px 14px 12px;
  align-self: start;
  width: 100%;
}

.panel.is-disabled {
  opacity: 0.55;
}

#spriteSettings.is-disabled {
  opacity: 0.6;
  pointer-events: none;
}

/* Клип и opacity слоя — на самой layer-node (не на content-детях).
   • Polygon в SVG <clipPath> рассчитан в pre-transform local coords
     ровно как layer-node DOM (width = visualSize). Для sprite-PNG это
     frameW × scaleX; sprite wrapper внутри имеет width=frameW БЕЗ
     scale → polygon coords не совпадали бы с wrapper space. Apply
     clip к layer-node (visualSize space) — coords совпадают.
   • Handles и outline теперь живут в .stage-overlays (sibling stage),
     не внутри layer-node → им clip-path / opacity не affect'ит. */
.layer-node {
  clip-path: var(--layer-clip, none);
  opacity: var(--layer-opacity, 1);
}

.panel .ttl {
  font-size: 15px;
  font-weight: 600;
  color: var(--white);
  margin-bottom: 2px;
  line-height: 1.2;
}

.panel .sub {
  font-size: 11px;
  color: var(--gray-66);
}

.placeholder_my {
  display: flex;
  justify-content: center;
  /* по горизонтали */
  align-items: center;
  /* по вертикали */
  height: 100%;

  font-family: Inter, Arial, sans-serif;
  /* шрифт */
  font-size: 14px;
  /* размер */
  font-weight: 400;
  /* толщина */
  line-height: 1.4;
  /* межстрочный */
  color: var(--gray-66);
  /* цвет */
}

/* remove focus outline on contenteditable */
.panel .ttl:focus,
.panel .ttl:focus-visible,
.panel .sub:focus,
.panel .sub:focus-visible,
[contenteditable="true"]:focus,
[contenteditable="true"]:focus-visible {
  outline: none;
  box-shadow: none;
}

/* показываем плейсхолдер, даже когда поле в фокусе */
[contenteditable][data-placeholder]:empty::before {
  content: attr(data-placeholder);
  color: var(--gray-66);
  opacity: 0.9;
  pointer-events: none;
}

/* опционально: сделать его чуть бледнее в фокусе */
[contenteditable][data-placeholder]:focus:empty::before {
  opacity: 0.55;
}

.label {
  color: #ccc;
  font-size: 12px;
  margin-bottom: 6px;
}

.layer-list {
  list-style: none;
  margin: 0;
  padding: 4px;
  max-height: 260px;
  overflow: auto;
  background: rgba(0,0,0,0.18);
  border: 1px solid var(--island-border);
  border-radius: var(--r-md);
}

.layer-list li {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 5px 7px;
  border-radius: var(--r-md);
  cursor: grab;
}

.layer-list li:last-child {
  border-bottom: none;
}

.layer-list li.active {
  background: var(--accent-soft);
  color: var(--white);
}

.layer-list li.drag-over {
  outline: none;
  background: var(--accent);
  color: #fff;
}

.layer-list .name {
  flex: 1;
  min-width: 0;
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
}

.row.mini {
  margin-top: 6px;
}

.share-row {
  margin-top: 12px;
}



.note {
  font-size: 11px;
  color: var(--gray-66);
}

/* Режим анимации (anim-mode) — body-класс ставится при openAnimDock.
   Скрываем только то, что не имеет смысла во время редактирования Action:
   tool-island (создавать слои в anim-mode нельзя — только анимировать
   существующие) и canvas settings. Острова навигации/мета/share остаются
   видимыми — пользователь может переключаться между проектами и шарить. */
body.anim-mode #toolIsland,
body.anim-mode .right_panel .panel[data-description="Настройка холста"] {
  display: none !important;
}

/* Режим Preview (play-mode) — пользователь тестирует триггеры. Прячем
   панели слева/справа и mode-island (граф/анимация), оставляем
   zoom-island (с превратившимся в Stop). smDock остаётся доступным
   через hotkey M — граф событий можно править прямо в Preview, изменения
   подхватываются авто-restart по event-graph-changed. */
body.play-mode .left-col,
body.play-mode .right-col,
body.play-mode #toolIsland,
body.play-mode #modeIsland {
  display: none !important;
}
/* В anim-mode Preview остаётся доступен — типичный flow: правишь анимацию
   и сразу проверяешь результат. Click на playBtn → playController.startPlay
   автоматически закрывает animDock и стартует Preview. Выход из anim-mode —
   также через mode-island («Редактор» / «Граф» / «Анимация»), Escape или T. */

/* Transform-блок (B1): объединённый x/y/rotation/opacity + размер (W/H,
   anchor, grow/shrink, lock). При открытом #animDock блок дисейблится —
   idle-значения мутирует animPreview интерполяцией, прямое редактирование
   сейчас не имеет смысла. */
#transformBlock .input-prefix__tag svg {
  display: block;
  margin: 0 auto;
  color: var(--gray-66);
}
/* В anim-mode .is-disabled — informational (note + opacity), но
   pointer-events НЕ блокируем — Copy/Paste должны быть кликабельны
   даже когда основные inputs заблокированы. Disabled inputs/reset
   ставятся через JS в transformControls.syncTransform. */
#transformBlock.is-disabled .input-prefix {
  opacity: 0.5;
}
/* Per-input dimming когда поле disabled (например для Solid+fill
   X/Y/Rotation остаются недоступными, а Opacity — активной). Без
   этого пользователь видит активный визуально input, но клик не
   срабатывает. */
#transformBlock .input-prefix:has(input:disabled) {
  opacity: 0.4;
  cursor: not-allowed;
}
#transformBlock .input-prefix:has(input:disabled) .ui-input {
  cursor: not-allowed;
}
/* Sub-section с размером — разделительная полоска сверху чтобы визуально
   отделить от X/Y/rotation/opacity. */
.transform-size {
  margin-top: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--gray-22);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.transform-locked-note {
  font-style: italic;
  display: block;
  margin-top: 6px;
}

/* Blender-style transform mode (G/R/S через keypress без зажатия мыши).
   Cursor + body-class на время mode; снимаются на commit/cancel. */
body.transform-op-active { cursor: move; }
body.transform-op-r { cursor: crosshair; }
body.transform-op-s { cursor: nwse-resize; }
body.transform-op-active * { cursor: inherit !important; }

/* UI switch (toggle) — Apple-like, checkbox с visual track + thumb.
   Используется в блоке «Воспроизведение». */
.ui-switch {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  user-select: none;
}
.ui-switch input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.ui-switch__slider {
  position: relative;
  width: 32px;
  height: 18px;
  background: var(--gray-33);
  border-radius: 999px;
  transition: background 0.15s ease;
  flex-shrink: 0;
}
.ui-switch__slider::before {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  background: var(--gray-cc);
  border-radius: 50%;
  transition: left 0.15s ease, background 0.15s ease;
}
.ui-switch input:checked + .ui-switch__slider {
  background: var(--accent);
}
.ui-switch input:checked + .ui-switch__slider::before {
  left: 16px;
  background: #fff;
}
.ui-switch__label {
  color: var(--gray-cc);
  font-size: 12px;
}

/* Hierarchy block — inline label слева + select справа. Label
   фиксированной ширины чтобы оба ряда (Родитель/Маска) выравнивались
   по колонке. */
.hierarchy-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}
.hierarchy-row:last-child { margin-bottom: 0; }
.hierarchy-row__label {
  flex: 0 0 60px;
  color: var(--gray-66);
  font-size: 11px;
  font-weight: 500;
}
.hierarchy-row__select {
  flex: 1;
  min-width: 0;
}

.layer-node img,
.layer-node svg,
.layer-node video {
  user-select: none;
  display: block;
  /* ← убирает базовую линию и 3px-геп */
}

/* Видео — клик на сам элемент идёт к layer-node parent (как Lottie SVG).
   object-fit: fill — браузерный default для <video> = `contain` (держит aspect
   ratio). Симметрично <img> (default = fill) ставим явно, чтобы non-uniform
   resize при снятом замочке реально растягивал кадр под L.w/L.h. */
.layer-node > video {
  pointer-events: none;
  object-fit: fill;
}

/* Lottie SVG имеет pointer-events: visiblePainted по умолчанию —
   на прозрачных пикселях клик «проходит насквозь» к слою позади
   (нельзя выделить Lottie если за ним PNG). Снимаем pointer-events
   с контейнера полностью: все клики ловит layer-node parent. */
.layer-node .lottie-container,
.layer-node .lottie-container svg {
  pointer-events: none;
}

/* Hints — цвет управляется акцентом темы.
   V5 striped: диагональные полосы accent поверх soft-fill — менее
   прозрачный чем V0, но не перекрывает контент целиком. */
.hint-area {
  pointer-events: auto;
  background:
    repeating-linear-gradient(45deg,
      rgba(139, 92, 246, 0.35) 0 6px,
      transparent 6px 14px),
    rgba(139, 92, 246, 0.18);
  border: 1.5px solid var(--accent);
  box-sizing: border-box;
  border-radius: var(--r-sm);
}

.hint-handles {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.hint-handle {
  position: absolute;
  width: 10px;
  height: 10px;
  background: var(--accent);
  border: 1px solid rgba(255, 255, 255, 0.9);
  border-radius: 3px;
  box-sizing: border-box;
  pointer-events: auto;
  /* Inverse-scale: ручка «не растёт» с zoom холста. JS позиционирует
     handle как `calc(X% - 5px)` так, что centroid handle совпадает с
     edge/corner точкой родителя; transform-origin: center сохраняет
     этот centroid при scale(1/z). visual size handle = 10px screen. */
  transform: scale(calc(1 / var(--editor-zoom, 1)));
  transform-origin: center center;
}

.hint-handle.nw,
.hint-handle.se {
  cursor: nwse-resize;
}

.hint-handle.ne,
.hint-handle.sw {
  cursor: nesw-resize;
}

.hint-handle.n,
.hint-handle.s {
  cursor: ns-resize;
}

.hint-handle.w,
.hint-handle.e {
  cursor: ew-resize;
}

/* Figma-style edge bands — вся длина ребра рамки grab-зона (вместо
   точечного 10×10 .hint-handle.n/s/e/w). Невидимые, hit-зона ~12px
   screen, inverse-scaled через --editor-zoom (постоянная толщина при
   zoom). corner-зоны вырезаются из bands через inset (margin), чтобы
   не перекрывать .hint-handle и rotate-elbows. */
.tx-edge {
  position: absolute;
  pointer-events: auto;
  background: transparent;
  /* Толщина 12px screen / zoom — но inverse-scale тут не подходит
     (band должен оставаться full-length). Используем calc для height/
     width напрямую. */
}
.tx-edge-n {
  top: calc(-6px / var(--editor-zoom, 1));
  left: 8px; right: 8px;
  height: calc(12px / var(--editor-zoom, 1));
  cursor: ns-resize;
}
.tx-edge-s {
  bottom: calc(-6px / var(--editor-zoom, 1));
  left: 8px; right: 8px;
  height: calc(12px / var(--editor-zoom, 1));
  cursor: ns-resize;
}
.tx-edge-w {
  left: calc(-6px / var(--editor-zoom, 1));
  top: 8px; bottom: 8px;
  width: calc(12px / var(--editor-zoom, 1));
  cursor: ew-resize;
}
.tx-edge-e {
  right: calc(-6px / var(--editor-zoom, 1));
  top: 8px; bottom: 8px;
  width: calc(12px / var(--editor-zoom, 1));
  cursor: ew-resize;
}

/* Rotate-elbow zones — невидимые квадраты сразу СНАРУЖИ каждого угла,
   диагональный offset. Размер ~22px screen, в каждом углу. Не
   перекрывают видимый corner-handle (тот в самом углу).
   Reuse CSS-var --editor-zoom для зум-инверсии.

   Курсор — кастомный SVG «rotate» (закруглённые стрелки, Figma-style).
   Black + white outline для видимости на любом фоне. Hotspot=12,12
   (центр 24×24 cursor'а). Fallback на grab если data: URL не поддерживается. */
.tx-rotate-elbow {
  position: absolute;
  width: calc(22px / var(--editor-zoom, 1));
  height: calc(22px / var(--editor-zoom, 1));
  pointer-events: auto;
  background: transparent;
  cursor: grab; /* fallback */
}
/* 4 версии курсора по углам (вращение базовой формы на 0/90/180/270°).
   Базовая форма (для elbow-se): кривая из upper-left в lower-right с
   arrow ← наверху и arrow ↓ внизу. Для других углов — поворот вокруг
   центра курсора. 16×16 cursor (~половина edge cursor'ов).
   Hotspot 8 8 — центр.
   Префикс elbow-* в classes — во избежание коллизии с .sw (color-swatch)
   в base.css. */
/* Базовая форма (rotate 0°) соответствует NE углу: curve concave-side
   смотрит lower-left (= к bbox NE corner относительно cursor position).
   Для остальных углов поворот на 90/180/270° CW. */
.tx-rotate-elbow.elbow-ne {
  right: calc(-22px / var(--editor-zoom, 1));
  top:   calc(-22px / var(--editor-zoom, 1));
  cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><g transform='rotate(0 8 8)'><g fill='white' stroke='white' stroke-width='3' stroke-linejoin='round'><path d='M3 3 Q13 3 13 13' fill='none'/><polygon points='0,3 6,0 6,6'/><polygon points='10,10 13,16 16,10'/></g><g fill='black' stroke='black' stroke-width='1.2' stroke-linejoin='round'><path d='M3 3 Q13 3 13 13' fill='none'/><polygon points='0,3 6,0 6,6'/><polygon points='10,10 13,16 16,10'/></g></g></svg>") 8 8, grab;
}
.tx-rotate-elbow.elbow-se {
  right: calc(-22px / var(--editor-zoom, 1));
  bottom: calc(-22px / var(--editor-zoom, 1));
  cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><g transform='rotate(90 8 8)'><g fill='white' stroke='white' stroke-width='3' stroke-linejoin='round'><path d='M3 3 Q13 3 13 13' fill='none'/><polygon points='0,3 6,0 6,6'/><polygon points='10,10 13,16 16,10'/></g><g fill='black' stroke='black' stroke-width='1.2' stroke-linejoin='round'><path d='M3 3 Q13 3 13 13' fill='none'/><polygon points='0,3 6,0 6,6'/><polygon points='10,10 13,16 16,10'/></g></g></svg>") 8 8, grab;
}
.tx-rotate-elbow.elbow-sw {
  left: calc(-22px / var(--editor-zoom, 1));
  bottom: calc(-22px / var(--editor-zoom, 1));
  cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><g transform='rotate(180 8 8)'><g fill='white' stroke='white' stroke-width='3' stroke-linejoin='round'><path d='M3 3 Q13 3 13 13' fill='none'/><polygon points='0,3 6,0 6,6'/><polygon points='10,10 13,16 16,10'/></g><g fill='black' stroke='black' stroke-width='1.2' stroke-linejoin='round'><path d='M3 3 Q13 3 13 13' fill='none'/><polygon points='0,3 6,0 6,6'/><polygon points='10,10 13,16 16,10'/></g></g></svg>") 8 8, grab;
}
.tx-rotate-elbow.elbow-nw {
  left: calc(-22px / var(--editor-zoom, 1));
  top:  calc(-22px / var(--editor-zoom, 1));
  cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><g transform='rotate(270 8 8)'><g fill='white' stroke='white' stroke-width='3' stroke-linejoin='round'><path d='M3 3 Q13 3 13 13' fill='none'/><polygon points='0,3 6,0 6,6'/><polygon points='10,10 13,16 16,10'/></g><g fill='black' stroke='black' stroke-width='1.2' stroke-linejoin='round'><path d='M3 3 Q13 3 13 13' fill='none'/><polygon points='0,3 6,0 6,6'/><polygon points='10,10 13,16 16,10'/></g></g></svg>") 8 8, grab;
}
body.play-mode .tx-edge,
body.play-mode .tx-rotate-elbow { display: none !important; }

/* Rotate handle (B3) — кружок над верхним краем рамки. Тонкий «стебелёк»
   соединяет с n-ручкой, чтобы было ясно куда приделана метка поворота.
   Inverse-scale через --editor-zoom: handle и offset до n-edge остаются
   константными в screen-pixels (14×14, 28px от рамки). transform: scale
   с origin top-left + translate(-50%, 0) = handle центрируется по
   горизонтали на 50% родителя; top: -28/z layout × parent-zoom = -28
   screen.  ::before connector живёт в local-space handle, поэтому
   автоматически inverse-scaled. */
.media-rotate-handle {
  position: absolute;
  left: 50%;
  top: calc(-28px / var(--editor-zoom, 1));
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  border: 1px solid rgba(255, 255, 255, 0.9);
  box-sizing: border-box;
  pointer-events: auto;
  cursor: grab;
  transform-origin: 0 0;
  transform: translate(-50%, 0) scale(calc(1 / var(--editor-zoom, 1)));
}
.media-rotate-handle:active { cursor: grabbing; }
.media-rotate-handle::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 100%;
  width: 1px;
  height: 14px;
  margin-left: -0.5px;
  background: var(--accent);
}
body.play-mode .media-rotate-handle { display: none !important; }

/* Бейдж хинта теперь только в списке слоёв — см. .layer-type-badge--hint.
   На канвасе хинт — невидимая зона для клика, бейдж не рисуется. */

/* Text layer */
.text-area {
  box-sizing: border-box;
  overflow: hidden;
  user-select: none;
  -webkit-user-select: none;
  display: flex;
}
.text-inner {
  font-family: ui-sans-serif, system-ui, sans-serif;
  box-sizing: border-box;
}

.text-inner[contenteditable="true"] {
  cursor: text;
  user-select: text;
  -webkit-user-select: text;
  outline: 2px solid var(--accent);
}


/* Color swatch — квадратик заполненный цветом */
.color-swatch {
  width: 28px;
  height: 28px;
  border-radius: var(--r-md);
  border: 1px solid var(--island-border);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  flex-shrink: 0;
}

.color-swatch input[type="color"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
  border: none;
  padding: 0;
}

.text-handles {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.text-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 18px;
  padding: 0;
  border-radius: var(--r-sm);
  background: rgba(255,255,255,0.04);
  color: var(--gray-cc);
  font: 600 12px/18px 'Inter', ui-sans-serif, system-ui;
  text-align: center;
  flex-shrink: 0;
  user-select: none;
}

.list .text-badge {
  margin-right: 6px;
}

/* ===== Layer type badge (превьюшка типа слоя в списке) ===== */
.layer-type-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 6px;
  flex-shrink: 0;
  user-select: none;
}
.layer-type-badge svg { display: block; }

/* PNG / картинка */
.layer-type-badge--png {
  background: rgba(100, 120, 180, 0.12);
  color: #8596b8;
}

/* Lottie — розовый акцент */
.layer-type-badge--lottie {
  background: rgba(244, 114, 182, 0.1);
  color: #f0abfc;
}

/* Text — нейтральный */
.layer-type-badge--text {
  background: rgba(255, 255, 255, 0.04);
  color: var(--gray-cc);
  font: 700 13px/1 'Inter', ui-sans-serif, system-ui;
}

/* Hint — акцент темы */
.layer-type-badge--hint {
  background: var(--accent-soft);
  color: var(--accent);
  font: 700 12px/1 'Inter', ui-sans-serif, system-ui;
}

/* Video / GIF — изумрудно-зелёный акцент. */
.layer-type-badge--video {
  background: rgba(52, 211, 153, 0.12);
  color: #6ee7b7;
}

/* ===== Main layout ===== */

.wrap {
  position: relative;
  height: 100vh;
  width: 100%;
  padding: 0;
  background: transparent;
  box-sizing: border-box;
  overflow: hidden;
}

/* Внутри левой скроллим, а не тянем колонку */
.scroll-inner {
  flex: 1 1 auto;
  max-width: 100%;
  max-height: 100%;

  justify-content: flex-start;
  /* по горизонтали влево */
  align-items: flex-start;
  /* по вертикали вверх (на всякий) */

  overflow: hidden;
  /* pan через Space+drag, нативный скролл не нужен */
  box-sizing: border-box;
  /* padding: 16px 20px; */

  display: flex;

  /* ВАЖНО: делаем flex */
}

.center_view {
  position: absolute;
  inset: 0;
  display: flex;
  overflow: hidden;
  z-index: 0;
}

.right_panel,
.left_panel {
  flex: 0 0 auto;
  width: 260px;
  background: var(--island-bg);
  border: 1px solid var(--island-border);
  border-radius: var(--r-lg);
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--gray-33) transparent;
}
.left_panel {
  display: flex;
  flex-direction: column;
  height: auto;
  max-height: 100%;
  align-self: flex-start;
}
.right_panel::-webkit-scrollbar,
.left_panel::-webkit-scrollbar { width: 6px; }
.right_panel::-webkit-scrollbar-track,
.left_panel::-webkit-scrollbar-track { background: transparent; }
.right_panel::-webkit-scrollbar-thumb,
.left_panel::-webkit-scrollbar-thumb { background: var(--gray-33); border-radius: 3px; }

.row_island {
  display: flex;
  flex-direction: column;
  /* в столбик */
  /* gap: 12px; */
  /* расстояние между двумя */
  width: 320px;
}

/* .left-panel-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 140px;
    color: var(--gray-66);
    text-align: center;
} */

/* .left-panel-empty__label {
    text-transform: lowercase;
    letter-spacing: 0.4px;
} */

.center-content {
  width: max-content;
  /* может быть шире окна */
  margin: 0;
  /* margin:0 (не auto), панорамирование через Space+drag / wheel.
     Left padding 300px — clearance от левой панели (260+8). */
  padding: 120px 120px 120px 300px;
  /* bleed-зона: место для отображения слоёв за границей канваса */
}

/* Share button spinner (если нужен именно тут) */
#shareBtn {
  position: relative;
}

#shareBtn .btn__spinner {
  display: none;
  animation: __spin 0.9s linear infinite;
  pointer-events: none;
}

#shareBtn.loading {
  pointer-events: none;
  position: relative;
  overflow: hidden;
  background: var(--accent-hover);
}

#shareBtn.loading::after {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.35) 50%, transparent 100%);
  animation: share-shimmer 0.6s linear infinite;
}

@keyframes share-shimmer {
  0% { left: -100%; }
  100% { left: 100%; }
}

@keyframes __spin {
  from {
    transform: translate(-50%, -50%) rotate(0);
  }

  to {
    transform: translate(-50%, -50%) rotate(360deg);
  }
}

/* Share status (обычный span рядом с кнопкой) */
/* ── Share/status toast ── */
#shareStatus {
  position: fixed;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5300;
  max-width: min(720px, calc(100vw - 24px));
  padding: 10px 16px;
  border-radius: var(--r-lg);
  background: var(--island-bg);
  border: 1px solid var(--island-border);
  color: var(--white);
  font-size: 13px;
  line-height: 1.25;
  opacity: 0;
  pointer-events: none;
  /* пропадание для всех */
  transition: opacity 800ms ease;
}
#shareStatus.show { opacity: 1; }

/* OK: появление медленное */
#shareStatus.ok.show { transition: opacity 960ms ease; }
#shareStatus.ok .toast-icon { color: var(--success); }

/* Error: появление быстрое + shake, бордовый фон */
#shareStatus.error { background: rgba(127, 29, 29, 0.92); border-color: rgba(248, 113, 113, 0.3); color: #fff; }
#shareStatus.error .toast-icon { color: var(--danger); }
#shareStatus.error.show {
  transition: opacity 200ms ease;
  animation: toast-shake 0.4s ease;
}
@keyframes toast-shake {
  0%, 100% { margin-left: 0; }
  20% { margin-left: -6px; }
  40% { margin-left: 6px; }
  60% { margin-left: -4px; }
  80% { margin-left: 4px; }
}

.ui-input{
  background: rgba(0,0,0,0.25);
  border: 1px solid var(--island-border);
  border-radius: var(--r-md);
  color: var(--white);
  outline: none;
  padding: 6px 10px;
  font-size: 12px;
  line-height: 1;
  transition: border-color 0.15s, background 0.15s;
}
.ui-input:focus {
  border-color: var(--accent);
  background: rgba(0,0,0,0.35);
}

.ui-select {
  background: rgba(0,0,0,0.25);
  border: 1px solid var(--island-border);
  border-radius: var(--r-md);
  color: var(--white);
  outline: none;
  padding: 5px 22px 5px 8px;
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  height: 28px;
  min-width: 56px;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath d='M1.5 3.5L5 7l3.5-3.5' stroke='%235a5a64' stroke-width='1.3' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 7px center;
  transition: border-color 0.15s, background-color 0.15s;
}
.ui-select:focus {
  border-color: var(--accent);
}
.ui-select:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.ui-select option {
  background: var(--panel-deep);
  color: var(--white);
}

/* ───── ui-input: number without spinners ───── */

/* Chrome / Edge / Safari */
input.ui-input[type="number"]::-webkit-outer-spin-button,
input.ui-input[type="number"]::-webkit-inner-spin-button{
  -webkit-appearance: none;
  margin: 0;
}

/* Hide number spinners */
input.ui-input[type="number"]{
  -webkit-appearance: textfield;
  appearance: textfield;
}

/* ===== Responsive: scale islands on narrow desktops ===== */
@media (max-width: 960px) {
  .left-col {
    transform: scale(0.85);
    transform-origin: top left;
  }
  .right-col {
    transform: scale(0.85);
    transform-origin: top right;
  }
  .tool-island {
    transform: translateX(-50%) scale(0.85);
    transform-origin: bottom center;
  }
  .zoom-island {
    transform: translateX(-50%) scale(0.85);
    transform-origin: top center;
  }
}

@media (max-width: 780px) {
  .left-col {
    transform: scale(0.72);
    transform-origin: top left;
  }
  .right-col {
    transform: scale(0.72);
    transform-origin: top right;
  }
  .tool-island {
    transform: translateX(-50%) scale(0.72);
    transform-origin: bottom center;
  }
  .zoom-island {
    transform: translateX(-50%) scale(0.72);
    transform-origin: top center;
  }
}

