/* ============================================================================
 * GRAPHITE — дизайн-система (единый источник правды)
 * Извлечена из панелей и канваса граф-редактора PlotRox. Тёмная по определению.
 *
 * 🔴 ЭТО ЕДИНСТВЕННЫЙ ИСТОЧНИК. Потребители ПОДКЛЮЧАЮТ этот файл
 *    (`@import "…/Graphite/graphite.css"` или <link>), НЕ копируют токены.
 *    Правка этого файла автоматически меняет все места, где он используется.
 *
 * Неймспейс изолирован:
 *   · переменные — `--g-*`   (редактор PlotRox использует `--gr-*` — НЕ пересекается)
 *   · классы     — `.g-*` под корнем `.graphite`
 *   · бренд продукта (indigo #6466F1, Inter) — отдельная система, здесь ни при чём.
 *
 * Версия: см. VERSION. Меняешь оттенок — правишь ОДНО место (блок токенов ниже).
 * ==========================================================================*/

/* ── ТОКЕНЫ ─────────────────────────────────────────────────────────────────
 * Объявлены и на :root, и на .graphite: правила Graphite работают и в ПОРТАЛАХ
 * (меню/окна рендерятся в <body>, вне корня-обёртки) — иначе var() не резолвится
 * и правило молча отваливается целиком (фон прозрачный, рамка светлая). */
:root,
.graphite {
  /* Поверхности — глубина ТОНОМ, тень только для отрыва (меню/поповер) */
  --g-canvas:   #1c1c1c; /* @kind color */   /* дальний план, фон канваса */
  --g-panel:    #202020; /* @kind color */   /* доки, сайдбары, меню, поповеры */
  --g-card:     #242424; /* @kind color */   /* карточки, вложенные блоки */
  --g-raised:   #2a2a2a; /* @kind color */   /* поля, строки, ховер, вторичная подложка */

  /* Текст — иерархия яркостью */
  --g-ink:      #e8e8e8; /* @kind color */
  --g-dim:      #9a9a9a; /* @kind color */
  --g-faint:    #6f6f6f; /* @kind color */

  /* Границы — структура сильнее контура поля */
  --g-line:      #333333; /* @kind color */              /* рамки/разделители/оверлей */
  --g-line-soft: #2b2b2b; /* @kind color */              /* контур поля в покое — тише */
  --g-focus:     rgba(37,99,235,.42); /* @kind color */  /* кольцо фокуса — не неон */
  --g-dot:       #3a3a3a; /* @kind color */              /* точки сетки канваса */

  /* Акцент — только на активном */
  --g-accent:      #2563eb; /* @kind color */
  --g-accent-hover:#2f6ef0; /* @kind color */
  --g-accent-ink:  #ffffff; /* @kind color */

  /* Радиусы */
  --g-r-xs:  4px; /* @kind radius */
  --g-r-sm:  6px; /* @kind radius */
  --g-r-md:  8px; /* @kind radius */
  --g-r-lg: 12px; /* @kind radius */

  /* Тени (для отрыва от плоскости) */
  --g-shadow-pop:  0 2px 10px rgba(0,0,0,.35); /* @kind shadow */
  --g-shadow-menu: 0 8px 24px rgba(0,0,0,.45); /* @kind shadow */

  /* Метрика
     🔴 КЕГЕЛЬ ОДИН. `--g-fs-ui` — единственный размер текста в системе (закон 8).
     Токена «крупная метка» больше нет: он был объявлен как 13px, не применялся
     нигде и противоречил реализации, где панель форсит 11px на всё. Иерархию
     держат цвет, вес и размер иконки — не кегль. */
  --g-font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; /* @kind font */
  --g-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace; /* @kind font */
  --g-fs-ui:   11px; /* @kind font */   /* тело UI — единственная ступень */
  --g-fs-micro: 10px; /* @kind font */  /* ТОЛЬКО цифра в пилюле/бейдже, не для текста */
  --g-row-h:   34px; /* @kind spacing */   /* высота строки «метка-значение» */
  --g-label-w: 33.333%; /* @kind spacing *//* ширина колонки метки (две вертикали на панель) */
  --g-gap:     8px; /* @kind spacing */    /* иконка → текст */
  --g-pad:     12px; /* @kind spacing */   /* внутренний отступ панели по горизонтали */

  /* Иконки — три роли, замер редактора (служебная / сущность / крупная) */
  --g-icon-sm: 12px; /* @kind spacing */   /* шеврон, плюс, галочка */
  --g-icon-md: 14px; /* @kind spacing */   /* иконка сущности: тип ноды, пункт рейки */
  --g-icon-lg: 16px; /* @kind spacing */   /* действие в рейке, кнопка сворачивания */

  /* Моторика — замер канона редактора (Tailwind transition-colors) */
  --g-t:    150ms; /* @kind other */
  --g-ease: cubic-bezier(.4, 0, .2, 1); /* @kind other */

  /* Плёнки состояний — ОДНИ на всю систему.
     🔴 Строка свойства, узел дерева, пункт рейки и пункт меню обязаны
     подсвечиваться одной и той же плёнкой: разные ховеры на соседних
     поверхностях читаются как разные компоненты, хотя это один жест. */
  --g-hover-film:  rgba(255,255,255,.035); /* @kind color */
  --g-active-film: rgba(255,255,255,.07); /* @kind color */   /* нажатие / раскрытая группа */
  --g-select-film: rgba(37,99,235,.16); /* @kind color */     /* устойчивое «выбрано» */

  /* Раскладка доков — числа связаны между собой (см. закон 9) */
  --g-rail-w:            48px; /* @kind spacing */  /* рейка действий у края окна */
  --g-rail-w-narrow:     40px; /* @kind spacing */  /* рейка-переключатель свёрнутого дока */
  --g-rail-item:         28px; /* @kind spacing */  /* квадрат пункта рейки */
  --g-rail-item-action:  24px; /* @kind spacing */  /* квадрат действия (плотнее пункта) */
  --g-sidebar-w:        352px; /* @kind spacing */  /* развёрнутый док; рейка ВХОДИТ в эту ширину */
  --g-head-h:            32px; /* @kind spacing */  /* шапка дока */

  /* Дерево / список */
  --g-tree-indent-base:  8px; /* @kind spacing */   /* отступ нулевого уровня */
  --g-tree-indent:      12px; /* @kind spacing */   /* прибавка на уровень */
  --g-tree-row-h:       24px; /* @kind spacing */   /* строка списка — НЕ строка свойства (34px) */

  /* Канвас — сетка. Значения совпадают с реализацией (React Flow gap/size),
     иначе привязка к сетке и фон разъедутся. */
  --g-grid:      20px; /* @kind spacing */
  --g-grid-dot:   1px; /* @kind spacing */

  /* Связи и порты.
     🔴 Линия связи берёт ТУ ЖЕ лестницу яркости, что и текст, а не свою:
     покой = ступень faint, наведение = ступень dim. Замер на фоне полотна:
     #6f6f6f даёт 3.2:1 (порог для нечитаемых элементов — 3:1), #9a9a9a — 5.5:1,
     акцент #2563eb — 3.3:1. Прежний кандидат #4a4a4a давал 1.9:1: линия в 1.5px
     на таком контрасте просто теряется, а именно она несёт структуру графа. */
  --g-edge:          var(--g-faint); /* @kind color */
  --g-edge-hover:    var(--g-dim); /* @kind color */
  --g-edge-selected: var(--g-accent); /* @kind color */  /* выбрано = акцент, как везде */
  --g-edge-w:        1.5px; /* @kind spacing */
  --g-edge-w-active: 2.5px; /* @kind spacing */
  --g-edge-hit:       24px; /* @kind spacing */    /* невидимая полоса клика */
  --g-port:           12px; /* @kind spacing */    /* диаметр порта */
  --g-port-ring:       2px; /* @kind spacing */    /* ободок порта цветом карточки */

  /* ── СМЫСЛОВОЙ СПЕКТР: КАНАЛЫ, АЛЬФЫ, НОТЫ ────────────────────────────────
     🔴 РАЗБОРНАЯ ФОРМА СПЕКТРА — чтобы потребитель мог СОБРАТЬ тинт, а не копировать
     готовые числа. До этого формула жила только внутри классов `.g-tint-*`, и слой
     редактора, которому нужны те же цвета в СВОИХ селекторах, физически не мог их
     переиспользовать — там осело ~200 литералов, и правка спектра до канваса не доходила.
     Теперь: каналы отдельно, альфы отдельно, нота текста отдельно.

       background: rgba(var(--g-hue-amber), var(--g-tint-a));         фон тинта
       background: rgba(var(--g-hue-amber), var(--g-tint-a-strong));  выделенный
       border-color: rgba(var(--g-hue-amber), var(--g-tint-a-line));  рамка
       color: var(--g-ink-amber);                                     текст/значок

     🔴 Каналы БЕЗ alpha и без `rgb()` — только «R,G,B». Иначе их нельзя подставить
     внутрь `rgba()`, а именно это и нужно потребителю. */
  --g-hue-green:     34,197,94; /* @kind color */       /* #22c55e */
  --g-hue-cyan:      6,182,212; /* @kind color */       /* #06b6d4 */
  --g-hue-blue:      59,130,246; /* @kind color */      /* #3b82f6 */
  --g-hue-amber:     245,158,11; /* @kind color */      /* #f59e0b */
  --g-hue-red:       239,68,68; /* @kind color */       /* #ef4444 */
  --g-hue-violet:    139,92,246; /* @kind color */      /* #8b5cf6 */
  --g-hue-indigo:    99,102,241; /* @kind color */      /* #6366f1 */
  --g-hue-fuchsia:   217,70,239; /* @kind color */      /* #d946ef */
  --g-hue-teal:      20,184,166; /* @kind color */      /* #14b8a6 */
  --g-hue-lime:      132,204,22; /* @kind color */      /* #84cc16 */
  --g-hue-emerald:   16,185,129; /* @kind color */      /* #10b981 */
  --g-hue-sky:       14,165,233; /* @kind color */      /* #0ea5e9 */
  --g-hue-rose:      244,63,94; /* @kind color */       /* #f43f5e */
  --g-hue-pink:      236,72,153; /* @kind color */      /* #ec4899 */
  --g-hue-orange:    249,115,22; /* @kind color */      /* #f97316 */
  --g-hue-yellow:    234,179,8; /* @kind color */       /* #eab308 */
  --g-hue-neutral:   148,148,148; /* @kind color */     /* #949494 */

  --g-ink-green:     #86efac; /* @kind color */         /* роль: start */
  --g-ink-cyan:      #67e8f9; /* @kind color */         /* роль: глава */
  --g-ink-blue:      #93c5fd; /* @kind color */         /* роль: панель/сцена */
  --g-ink-amber:     #fcd34d; /* @kind color */         /* роль: выбор */
  --g-ink-red:       #fca5a5; /* @kind color */         /* роль: финал/удаление */
  --g-ink-violet:    #c4b5fd; /* @kind color */         /* роль: условие */
  --g-ink-indigo:    #a5b4fc; /* @kind color */         /* роль: результат */
  --g-ink-fuchsia:   #f0abfc; /* @kind color */         /* роль: интерактив */
  --g-ink-teal:      #5eead4; /* @kind color */         /* роль: переменная */
  --g-ink-lime:      #bef264; /* @kind color */         /* роль: схождение */
  --g-ink-emerald:   #6ee7b7; /* @kind color */         /* роль: — */
  --g-ink-sky:       #7dd3fc; /* @kind color */         /* роль: — */
  --g-ink-rose:      #fda4af; /* @kind color */         /* роль: — */
  --g-ink-pink:      #f9a8d4; /* @kind color */         /* роль: — */
  --g-ink-orange:    #fdba74; /* @kind color */         /* роль: — */
  --g-ink-yellow:    #fde047; /* @kind color */         /* роль: — */
  --g-ink-neutral:   #cfcfcf; /* @kind color */         /* роль: черновик/выкл */

  /* Альфы формулы — ОДНО место на всю систему. Меняешь насыщенность спектра здесь,
     и она едет и в классы `.g-tint-*`, и во все selectors потребителя. */
  --g-tint-a:        .16; /* @kind other */   /* фон тинта */
  --g-tint-a-strong: .26; /* @kind other */   /* выделенный фон */
  --g-tint-a-line:   .45; /* @kind other */   /* рамка */
  /* 🔴 «Нейтраль» звучит ТИШЕ остальных нот намеренно: это «черновик/выключено»,
     он не должен спорить со смысловыми оттенками. Поэтому у него свои альфы,
     а не общие — и они тоже живут здесь, а не литералами в классе. */
  --g-tint-a-mute:      .14; /* @kind other */
  --g-tint-a-line-mute: .40; /* @kind other */

  /* ── ЦВЕТА ТИПОВ НОД ──────────────────────────────────────────────────────
     🔴 ЕДИНСТВЕННЫЙ ХОЗЯИН ЭТИХ ЗНАЧЕНИЙ — ЭТОТ ФАЙЛ (решение владельца 2026-07-29).
     Раньше поверх дефолтов в коде побеждало админ-переопределение `platform.node_colors`
     (Панель администратора → «Цвета нод граф-редактора»), и правка системы до канваса
     не доходила. Теперь наоборот: редактор читает эти переменные, админка на них не влияет.
     Значения — нота `<оттенок>-300` смыслового спектра: это цвет ЗНАЧКА и заливки
     мини-карты на тёмном, а не фон плашки. Роли 1-в-1 из graphite.tokens.json. */
  --g-node-start:       var(--g-ink-green); /* @kind color */  /* green   — начало истории */
  --g-node-scene:       var(--g-ink-blue); /* @kind color */  /* blue    — сцена/панель */
  --g-node-page:        var(--g-ink-blue); /* @kind color */  /* blue    — страница */
  --g-node-content:     var(--g-ink-blue); /* @kind color */  /* blue    — контент */
  --g-node-chapter:     var(--g-ink-cyan); /* @kind color */  /* cyan    — глава */
  --g-node-choice:      var(--g-ink-amber); /* @kind color */  /* amber   — выбор */
  --g-node-condition:   var(--g-ink-violet); /* @kind color */  /* violet  — условие */
  --g-node-interactive: var(--g-ink-fuchsia); /* @kind color */  /* fuchsia — интерактив */
  --g-node-poll:        var(--g-ink-fuchsia); /* @kind color */  /* fuchsia — опрос */
  --g-node-quiz:        var(--g-ink-fuchsia); /* @kind color */  /* fuchsia — квиз */
  --g-node-results:     var(--g-ink-indigo); /* @kind color */  /* indigo  — результаты */
  --g-node-merge:       var(--g-ink-lime); /* @kind color */  /* lime    — схождение */
  --g-node-ending:      var(--g-ink-red); /* @kind color */  /* red     — финал */
  --g-node-fallback:    var(--g-ink-neutral); /* @kind color */  /* neutral — неизвестный тип */

  /* Сирота — НАДТИП: нода ВНЕ ГРАФА. Живёт поверх любого типа (глава-сирота остаётся
     cyan), поэтому это не цвет значка, а цвет ОБВОДКИ карточки.
     🔴 ОПРЕДЕЛЕНИЕ УТОЧНЕНО 2026-08-03 (владелец): сирота — не «нода без входящих и
     исходящих», а всё, до чего НЕ ДОХОДИТ ОБХОД ОТ НАЧАЛА. Разница не тонкая: оторванный
     фрагмент из двух связанных между собой нод под прежнее правило не попадал вовсе —
     в панели он лежал в «Не связаны», а на полотне не помечался ничем. Набор обязан
     совпадать с тем, что панель выносит в «Не связаны», иначе два места говорят разное.
     🔴 Оттенок orange занят только этой ролью: red — финал и удаление, amber — выбор,
     иначе «оторвана» читалась бы как «финал». */
  --g-node-orphan:      var(--g-ink-orange);  /* orange — сирота: нода вне графа. @kind color */

  /* Геометрия обводки сироты. 🔴 ВЫНЕСЕНА В ТОКЕНЫ НЕ РАДИ НАСТРАИВАЕМОСТИ, а потому что
     у надтипа ДВЕ поверхности отрисовки: карточка на полотне (CSS `outline`) и нода на
     мини-карте (SVG `stroke`). Пока числа стояли литералами в каждой из них, они
     совпадали ПО СОВПАДЕНИЮ, а не по устройству: правка в системе не доходила до
     потребителя, и расхождение было НЕМЫМ — обводка просто оказывалась разной толщины
     на одной поверхности из двух, без ошибки и предупреждения. */
  --g-node-orphan-w:        2px; /* @kind spacing */ /* толщина штриха — обе поверхности */
  --g-node-orphan-outline:  var(--g-node-orphan-w) dashed var(--g-node-orphan); /* @kind other */
  --g-node-orphan-offset:   2px; /* @kind spacing */ /* зазор обводки до кромки карточки */
  --g-node-orphan-offset-selected: 3px; /* @kind spacing */ /* у выделенной шире: акцентная рамка обязана остаться видна */
  /* SVG-двойник пунктира: на мини-карте штрих задаётся `stroke-dasharray`, а не ключевым
     словом `dashed` — канал другой, поэтому значение обязано жить рядом со своим. */
  --g-node-orphan-dasharray: 3 2; /* @kind other */

  /* ── ЗНАЧКИ ТИПОВ НОД ─────────────────────────────────────────────────────
     🔴 ПАРА К ЦВЕТАМ ВЫШЕ. Цвет типа ноды жил в системе, а ЗНАЧОК того же типа —
     в коде редактора (карта `start: 'rocket'` в TSX). Одно понятие, два хозяина:
     перекрасить старт можно было здесь, а заменить ему значок — только правкой кода
     и пересборкой фронта. Теперь оба свойства типа живут рядом и правятся тут.

     Значение — ИМЯ СИМВОЛА в спрайте без префикса `g-`. Список доступных имён —
     в `graphite.icons.json` и в карточке витрины «Глифы: таблица соответствий».
     🔴 Имени нет в спрайте — значок молча не нарисуется (отказ спрайта немой):
     сверяйся с таблицей, а не по памяти. */
  --g-node-icon-start:       rocket; /* @kind other */
  --g-node-icon-scene:       film; /* @kind other */
  --g-node-icon-page:        file-text; /* @kind other */
  --g-node-icon-content:     file-text; /* @kind other */
  --g-node-icon-chapter:     book-open; /* @kind other */
  --g-node-icon-choice:      git-branch; /* @kind other */
  --g-node-icon-condition:   diamond; /* @kind other */
  --g-node-icon-interactive: zap; /* @kind other */
  --g-node-icon-poll:        zap; /* @kind other */
  --g-node-icon-quiz:        zap; /* @kind other */
  --g-node-icon-results:     bar-chart-3; /* @kind other */
  --g-node-icon-merge:       git-merge; /* @kind other */
  --g-node-icon-ending:      flag; /* @kind other */
  --g-node-icon-result:      dices; /* @kind other */
  --g-node-icon-fallback:    circle; /* @kind other */
  --g-node-icon-orphan:      alert-triangle; /* @kind other */ /* значок надтипа — в дереве и в проверках */

  /* Значки типов ПЕРЕМЕННЫХ — та же логика */
  --g-var-icon-number:  hash; /* @kind other */
  --g-var-icon-boolean: toggle-left; /* @kind other */
  --g-var-icon-string:  type; /* @kind other */

  /* ── ТИПЫ МЕДИА: нота и значок ───────────────────────────────────────────
     Пара к `--g-node-*` / `--g-node-icon-*`, только для медиатеки. Оба свойства типа
     лежат рядом и правятся в одном месте — иначе цвет живёт в системе, а глиф в коде.
     🔴 Зачем ноте быть в системе, если тип и так подписан словом. Замер 2026-07-31:
     «Недавние» красили значок типа (image=green, video=blue, audio=violet, text=amber),
     а «Все медиа» рисовали ТОТ ЖЕ значок без цвета. Один список, два решения — читатель
     не понимает, значит ли цвет что-нибудь. Система назначает ноту ОДИН раз. */
  --g-media-image: var(--g-ink-green); /* @kind color */
  --g-media-video: var(--g-ink-blue); /* @kind color */
  --g-media-audio: var(--g-ink-violet); /* @kind color */
  --g-media-text:  var(--g-ink-amber); /* @kind color */

  --g-media-icon-image: image; /* @kind other */
  --g-media-icon-video: video; /* @kind other */
  --g-media-icon-audio: music; /* @kind other */
  --g-media-icon-text:  file-text; /* @kind other */

  /* Инструменты над списком. Значки НЕ произвольные: фильтр — «отмеченный список»,
     сортировка — «две стрелки», направление — та же стрелка с приоритетом. */
  --g-media-icon-filter: list-checks; /* @kind other */
  --g-media-icon-sort:   arrow-down-up; /* @kind other */
  --g-media-icon-upload: upload; /* @kind other */
  --g-media-icon-picked: check; /* @kind other */         /* подмена значка у выбранного пункта меню */

  /* ── ТЕХНИЧЕСКИЕ ПОДЛОЖКИ ────────────────────────────────────────────────
     🔴 Светлая подложка превью — НАМЕРЕННО светлая (решение владельца): на ней видны
     прозрачные PNG, белый логотип на прозрачном фоне на тёмном исчез бы. Это не
     недокрашенное место; менять — только осознанно. */
  --g-media-canvas: #e5e5e5; /* @kind color */            /* холст превью в медиа-пуле */
  --g-scrim:        rgba(0,0,0,.5); /* @kind color */     /* затемнение под бейджем/кнопкой поверх картинки */
  --g-scrim-soft:   rgba(0,0,0,.35); /* @kind color */    /* лёгкое затемнение: подпись поверх полотна */

  /* Слои. 🔴 Числа не декоративные: в редакторе на них уже наступали
     (легаси нав-полоса перехватывала клики над правым доком). */
  --g-z-panel:   240; /* @kind other */   /* пришвартованный док */
  --g-z-dock:    250; /* @kind other */   /* док, который должен быть ВЫШЕ соседнего */
  --g-z-menu:   1200; /* @kind other */   /* портальное меню/поповер */
  /* Уровень вложенности строки дерева. Дефолт живёт здесь, а не на .g-tree-row:
     токен под селектором компонента системой не регистрируется. Строка задаёт свой
     уровень инлайном: style="--g-depth:2". */
  --g-depth: 0; /* @kind other */

  /* Заливка глифа. Дефолт `none` живёт здесь (символы объявлены с
     `fill="var(--g-icon-fill, none)"`), атрибут `data-icon-fill` на самом <svg>
     переключает его на currentColor. */
  --g-icon-fill: none; /* @kind color */

  --g-z-modal:  2000; /* @kind other */   /* диалог подтверждения */

  /* HSL-двойники поверхностей — для shadcn-моста (там формат «H S% L%» без hsl()).
     🔴 Держим ЗДЕСЬ, рядом с hex-оригиналами: мост в этом файле и мост потребителя
     (напр. editor-dark.css) читают ОДИН источник, иначе значения расходятся молча. */
  --g-hsl-panel:  0 0% 12.5%; /* @kind color */  /* = #202020 */
  --g-hsl-card:   0 0% 14%; /* @kind color */    /* = #242424 */
  --g-hsl-raised: 0 0% 16.5%; /* @kind color */  /* = #2a2a2a */
  --g-hsl-line:   0 0% 20%; /* @kind color */    /* = #333333 */
  --g-hsl-ink:    0 0% 91%; /* @kind color */    /* = #e8e8e8 */
  --g-hsl-dim:    0 0% 60.4%; /* @kind color */  /* = #9a9a9a. 🔴 НЕ 60%: ровно 60% даёт #999999,
                                  и вторичный текст через мост расходился с --g-dim
                                  на один пункт — молча, во всех shadcn-утилитах. */
  --g-hsl-accent: 221.2 83.2% 53.3%; /* @kind color */  /* = #2563eb, акцент */
  --g-hsl-danger:   0 84.2% 60.2%; /* @kind color */    /* = #ef4444, оттенок «финал/удаление» */
}

/* ── shadcn-СОВМЕСТИМОСТЬ ────────────────────────────────────────────────────
 * Если потребитель на shadcn-утилитах (`bg-popover`, `bg-accent`, `border-border`),
 * эти HSL-тройки уводят их в тёмную палитру Graphite.
 *
 * 🔴 НЕ на глобальном `:root`: эти имена — общие для всего приложения. Объявленные
 * глобально, они перекрасили бы ВЕСЬ продукт, куда бы файл ни импортировали
 * (у PlotRox `graphite.css` попадает в общий бандл через тему → каталог/ЛК/плеер
 * почернели бы целиком). Поэтому — только под маркерами Graphite.
 * ДВА способа подключить мост (оба равноправны, см. «Мост» в reference.md):
 *   1. `.graphite`       — корень области, целиком живущей в системе;
 *   2. `.graphite-scope`  — «только мост, без базы»: когда у области СВОИ фон/шрифт
 *      и нужны лишь тёмные shadcn-токены (чужое легаси, готовый контейнер).
 * 🔴 Вешать НАДО и на корень области, И на ПОРТАЛЫ (меню/окна рендерятся в <body>,
 * вне корня; без маркера они останутся светлыми — это главная грабля системы). */
.graphite,
.graphite-scope {
  --background: var(--g-hsl-panel);   --foreground: var(--g-hsl-ink);
  --card: var(--g-hsl-card);          --card-foreground: var(--g-hsl-ink);
  --popover: var(--g-hsl-panel);      --popover-foreground: var(--g-hsl-ink);
  --muted: var(--g-hsl-raised);       --muted-foreground: var(--g-hsl-dim);
  --accent: var(--g-hsl-raised);      --accent-foreground: var(--g-hsl-ink);
  --secondary: var(--g-hsl-raised);   --secondary-foreground: var(--g-hsl-ink);
  --border: var(--g-hsl-line);        --input: var(--g-hsl-line);
  /* 🔴 БЕЗ ЭТИХ ЧЕТЫРЁХ мост дырявый: `--primary` — цвет ГЛАВНОЙ кнопки
     (`<Button variant="default">` = `bg-primary text-primary-foreground`), а он
     единственный из ходовых токенов оставался светлым дефолтом shadcn. Замер в
     граф-редакторе PlotRox: кнопка приходила `#3b82f6` на `#f8fafc` — ярче акцента
     системы и мимо палитры, притом что все остальные токены уже были тёмными.
     Симптом обманчив: «кнопка же синяя, значит всё хорошо» — а это ДРУГОЙ синий. */
  --primary: var(--g-hsl-accent);
  --primary-foreground: 0 0% 100%; /* @kind color */
  --destructive: var(--g-hsl-danger);
  --destructive-foreground: 0 0% 100%; /* @kind color */
  --ring: var(--g-hsl-accent);
}

/* Тёмная по определению: светлой темы у системы НЕТ. Если корневой data-theme
   переключат на light — держим тёмный вид, а не ломаемся. */
:root[data-theme="light"] .graphite { color-scheme: dark; }

/* ── БАЗА ───────────────────────────────────────────────────────────────── */
.graphite {
  background: var(--g-canvas);
  color: var(--g-ink);
  font-family: var(--g-font);
  font-size: var(--g-fs-ui);
  -webkit-font-smoothing: antialiased;
}
.graphite *,
.graphite *::before,
.graphite *::after { box-sizing: border-box; }

/* ── ПОВЕРХНОСТИ ────────────────────────────────────────────────────────── */
.g-canvas { background: var(--g-canvas); }
.g-panel  { background: var(--g-panel);  border: 1px solid var(--g-line); border-radius: var(--g-r-md); }
.g-card   { background: var(--g-card);   border: 1px solid var(--g-line); border-radius: var(--g-r-md); }
.g-raised { background: var(--g-raised); }

/* ── РЕЙКА: узкая вертикальная полоса иконок ────────────────────────────────
 * Две роли, ОДИН примитив:
 *   · рейка ДЕЙСТВИЙ — видна всегда, ничего не переключает (сохранить, играть,
 *     статус, отменить/вернуть). Живёт внутри дока, её ширина ВХОДИТ в его ширину;
 *   · рейка-ПЕРЕКЛЮЧАТЕЛЬ — это свёрнутый док: каждый пункт разворачивает его
 *     на свою вкладку. Уже, чем рейка действий (--g-rail-w-narrow).
 *
 * 🔴 Пункт рейки — САМОСТОЯТЕЛЬНЫЙ примитив, а не `.g-menu-item`: у пункта меню
 * `width:100%` и текст слева, в колодце 48px это растянет его и собьёт центровку.
 * 🔴 Рейка скроллится. В реализации не скроллилась — при низком вьюпорте нижняя
 * группа просто обрезалась корневым overflow:hidden. Это дефект, не решение. */
.g-rail {
  width: var(--g-rail-w); flex: 0 0 auto;
  display: flex; flex-direction: column; align-items: center;
  gap: 6px; padding: 8px 4px;
  background: var(--g-panel); overflow-y: auto;
}
.g-rail--narrow { width: var(--g-rail-w-narrow); padding-inline: 0; }
.g-rail__group { display: flex; flex-direction: column; align-items: center; gap: 4px; }
/* Нижняя группа. В реализации её НЕТ (всё прижато к верху) — система вводит явно:
   без распорки «помощь» и «настройки» тонут в середине потока. */
.g-rail__group--end { margin-top: auto; }
.g-rail__sep { width: 16px; height: 1px; background: var(--g-line); margin-block: 2px; }
/* Горизонтальный вариант: та же рейка действий умеет лежать в строку (панель инструментов
   над полотном). Разделитель поворачивается вместе с ней — иначе в горизонтали он
   превращается в невидимую точку. */
.g-rail__sep--h { width: 1px; height: 16px; margin-block: 0; margin-inline: 2px; }

.g-rail-item {
  width: var(--g-rail-item); height: var(--g-rail-item);
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; padding: 0; border: 1px solid transparent;
  border-radius: var(--g-r-sm); background: transparent; color: var(--g-dim);
  cursor: pointer; transition: background var(--g-t) var(--g-ease),
                              color var(--g-t) var(--g-ease);
}
.g-rail-item svg { width: var(--g-icon-md); height: var(--g-icon-md); flex: 0 0 auto; }
.g-rail-item:hover { background: var(--g-hover-film); color: var(--g-ink); }
/* 🔴 Активный пункт ОБЯЗАН быть виден. В свёрнутом доке реализации подсветки нет
   вовсе — пользователь не видит, куда док развернётся. Эталон взят у плашки
   инструментов канваса: единственное место, где активность показана честно. */
.g-rail-item.is-active,
.g-rail-item[aria-current="true"] { background: var(--g-accent); color: var(--g-accent-ink); }
.g-rail-item.is-active:hover { background: var(--g-accent-hover); }
/* Обведённый вариант — служебная кнопка (свернуть/развернуть). Обводка отделяет
   «управление доком» от «содержимого дока»: пункты рейки рамки не носят. */
.g-rail-item--framed { border-color: var(--g-line); background: var(--g-card); color: var(--g-ink); }
/* Плотный вариант — группа действий: квадрат меньше, иконка крупнее. */
.g-rail-item--action { width: var(--g-rail-item-action); height: var(--g-rail-item-action);
  border-radius: var(--g-r-xs); }
.g-rail-item--action svg { width: var(--g-icon-lg); height: var(--g-icon-lg); }

/* ── ДОК: пришвартованная панель (рейка + тело) ─────────────────────────────
 * 🔴 Док — НЕ `.g-panel`. Панель-поверхность имеет радиус по всем углам и рамку
 * по кругу; док прижат к краю окна, поэтому у него НЕТ радиуса и рамка ровно
 * одна — со стороны канваса. Смешивать их нельзя, это разные сущности.
 * 🔴 Ширина рейки ВХОДИТ в ширину дока (48 + 304 = 352), а не добавляется к ней:
 * от --g-sidebar-w считаются отступы канваса, зум-контролы и центр-оверлей.
 * Прибавишь рейку сверху — разъедется вся геометрия полотна. */
.g-dock {
  width: var(--g-sidebar-w); flex: 0 0 auto;
  display: flex; flex-direction: row; align-items: stretch;
  background: var(--g-panel); color: var(--g-ink);
  overflow: hidden;                 /* скролл живёт в теле, не здесь */
  border-inline-end: 1px solid var(--g-line);
}
/* Правый док — вертикальный стек (шапка · вкладки · тело), рейки внутри себя он
   не держит: свёрнутый вид САМ становится рейкой. Левый — горизонтальный
   (рейка + тело), потому что его действия видны всегда. */
.g-dock--right { border-inline-end: none; border-inline-start: 1px solid var(--g-line);
  flex-direction: column; }
.g-dock:not(.g-dock--right) > .g-rail { border-inline-end: 1px solid var(--g-line); }
/* Свёрнутый док = голая рейка. Тело РАЗМОНТИРУЕТСЯ, а не прячется display:none —
   иначе его скролл, автосейв-таймеры и портальные меню продолжают жить. */
.g-dock.is-collapsed { width: var(--g-rail-w); }
.g-dock--right.is-collapsed { width: var(--g-rail-w-narrow); }

.g-dock__body {
  flex: 1 1 auto; min-width: 0; min-height: 0;
  display: flex; flex-direction: column; align-items: stretch;
  gap: var(--g-gap); padding: var(--g-gap);
  overflow-y: auto;                 /* ЕДИНСТВЕННЫЙ скролл-контейнер дока */
}
.g-dock__head {
  height: var(--g-head-h); flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--g-gap);
  padding-inline: 10px; border-bottom: 1px solid var(--g-line);
  font-size: var(--g-fs-ui); font-weight: 600; color: var(--g-ink);
}
.g-dock__head .g-spacer { margin-inline-start: auto; }
/* Строка контекста под шапкой: имя вкладки · инлайн-статус · действие справа.
   Статус автосейва живёт ЗДЕСЬ, а не отдельной полосой, и НЕ кодируется цветом:
   «сохраняю» и «сохранено» — один приглушённый тон (закон 4 про честный статус). */
.g-dock__context {
  display: flex; align-items: center; gap: var(--g-gap);
  padding: 4px var(--g-pad); border-bottom: 1px solid var(--g-line);
  font-size: var(--g-fs-ui); color: var(--g-dim);
}
.g-status { color: var(--g-faint); font-weight: 400; }
/* Группа строк свойств: отрицательные поля выносят ховер и разделители на всю
   ширину дока, при этом текст метки остаётся на вертикали --g-pad. */
.g-group { display: flex; flex-direction: column; margin-inline: calc(-1 * var(--g-pad)); }
.g-group + .g-group { border-top: 1px solid var(--g-line); }
/* Пустое состояние поверхности — не «нет данных», а что сделать. */
.g-empty {
  flex: 1 1 auto; display: flex; flex-direction: column;
  align-items: center; justify-content: center; text-align: center;
  gap: var(--g-gap); padding: 24px; color: var(--g-dim);
}
.g-empty svg { width: 24px; height: 24px; opacity: .4; }
.g-empty small { color: var(--g-faint); }

/* ── ПАНЕЛЬ СВОЙСТВ: строка «метка → значение», ДВЕ вертикали ────────────── */
/* 🔴 Разделитель секции берёт --g-line (структура), а не --g-line-soft (контур
   поля). Секция делит панель на части — это структура; тише контура поля она
   быть не может, иначе иерархия границ переворачивается. */
.g-section-h {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--g-gap);
  width: 100%; padding: 8px var(--g-pad); font-size: var(--g-fs-ui);
  color: var(--g-dim); background: transparent; border: none;
  border-top: 1px solid var(--g-line); text-align: left; cursor: pointer;
  transition: background var(--g-t) var(--g-ease);
}
.g-section-h:first-child { border-top: none; }
.g-section-h:hover { background: var(--g-hover-film); color: var(--g-ink); }
.g-section-h .g-chevron { color: var(--g-faint); font-size: var(--g-fs-ui); }
.g-section-h:hover .g-chevron { color: var(--g-dim); }
/* Свёрнутая секция: маркер — ПОДМЕНА иконки, не поворот. Поворот шеврона в
   системе оставлен ровно за одним жестом — ящиком, раскрывающимся ВВЕРХ. */
.g-section-h.is-collapsed { color: var(--g-faint); }
/* Правый слот — сводка значения у свёрнутой секции («3 варианта», «5 мин»):
   держит колонку значений, чтобы свёрнутая секция не теряла содержание. */
.g-section-h__right { color: var(--g-faint); font-weight: 400;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.g-row {
  display: flex; align-items: center; gap: var(--g-gap);
  min-height: var(--g-row-h); padding: 0 var(--g-pad); font-size: var(--g-fs-ui);
  transition: background var(--g-t) var(--g-ease);
}
.g-label { width: var(--g-label-w); flex: 0 0 auto; color: var(--g-dim);
  transition: color var(--g-t) var(--g-ease); }
/* 🔴 ЕДИНАЯ ГЕОМЕТРИЯ БОКСА У ЗНАЧЕНИЯ И ПОЛЯ — не косметика, а выравнивание.
   Значение в строке бывает трёх видов: <input>, <button> (открывает меню) и <span>
   (read-only). У поля есть padding и рамка, у span'а их нет — и ТЕКСТ внутри встаёт
   на РАЗНЫЕ вертикали, хотя сами боксы выровнены идеально. Ловится это только замером
   ТЕКСТА (Range), по `getBoundingClientRect()` элемента всё выглядит ровно — ложная
   зелень. Поэтому padding/рамка/радиус живут ЗДЕСЬ, общими для `.g-val` и `.g-field`,
   а не в классах компонента. Значения — из реализации, где это уже чинилось. */
.g-val, .g-field {
  box-sizing: border-box;
  padding: 4px 6px;
  border: 1px solid transparent;
  border-radius: var(--g-r-xs);
  /* 🔴 Значение ВСЕГДА по левому краю (закон «две вертикали»). Без явного правила
     значение-<button> (строка, открывающая меню) берёт браузерный дефолт `center`
     и уезжает с вертикали — при этом БОКС остаётся на месте, так что замер левых
     кромок показывает «всё ровно». Поймано пилотом только потому, что сканер
     печатает ещё и text-align. */
  text-align: left;
}
.g-val   { flex: 1; color: var(--g-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 🔴 ОДНА КОРОБКА НА СТРОКУ. Значение бывает не только текстом: часто это КОНТЕЙНЕР
   (`.g-val`) с полем внутри — поле плюс суффикс «px», кнопка, иконка. Тогда padding и
   рамку должно нести ЧТО-ТО ОДНО, иначе они складываются и поле уезжает вправо на
   `padding+border` (замер: 130px → 137px), а его текст перестаёт стоять на общей
   вертикали значений — ровно тот дефект выравнивания, ради которого геометрия и
   вынесена в примитив. Коробку отдаём полю: оно интерактивное, ему нужны ховер и фокус.
   Поймано переводом панелей ноды на примитивы (Graphite 1.2.3). */
.g-val:has(.g-field), .g-val:has(input), .g-val:has(textarea) {
  padding: 0; border-color: transparent; border-width: 0;
}
.g-chevron { color: var(--g-faint); font-size: var(--g-fs-ui);
  transition: color var(--g-t) var(--g-ease); }

/* Наведение подсвечивает ВСЮ строку — метку и значение вместе. Единица взаимодействия
   здесь строка-свойство, а не отдельный контрол: пользователь наводится на «Жанр»,
   а не на поле ввода жанра. Подсветка идёт от края до края панели (padding живёт на
   `.g-row`), поэтому строка читается как пункт списка, а не как поле в рамке.
   🔴 Поле внутри в это время получает ТОЛЬКО контур, без своего фона: два разных пятна
   на одной строке снова оторвали бы значение от метки — ровно то, что чинится. */
.g-row:hover { background: var(--g-hover-film); }
.g-row:hover .g-label   { color: var(--g-ink); }
.g-row:hover .g-chevron { color: var(--g-dim); }

/* Поле без рамки в покое: контур на hover (своём или строки), синий+фон на focus */
.g-field {
  flex: 1; background: transparent; color: var(--g-ink); font: inherit;
  outline: none;   /* box-sizing/padding/border/radius — общие с .g-val, см. выше */
  transition: border-color var(--g-t) var(--g-ease), background var(--g-t) var(--g-ease);
}
/* 🔴 `:not(:focus)` обязателен: `.g-row:hover .g-field` — три класса (0,3,0), а `.g-field:focus`
   — два (0,2,0). Без исключения наведение на СФОКУСИРОВАННОЕ поле перебивало бы синюю рамку
   серой. Гасим правило на фокусе, а не поднимаем вес focus-правила. */
.g-row:hover .g-field:not(:focus),
.g-field:hover:not(:focus) { border-color: var(--g-line-soft); }
.g-field:focus  { border-color: var(--g-accent); background: rgba(37,99,235,.08); }
.g-field::placeholder { color: var(--g-faint); }

/* ── СВОРАЧИВАЕМЫЕ СПИСКИ И ДЕРЕВЬЯ ─────────────────────────────────────────
 * 🔴 СЕКЦИЯ И УЗЕЛ ДЕРЕВА — РАЗНЫЕ ПРИМИТИВЫ, и различает их СТОРОНА ШЕВРОНА:
 *   · секция (`.g-section-h`) — шеврон СПРАВА, у границы колонок. Слева он встал
 *     бы в поток и сдвинул заголовок с вертикали меток — это ошибка, уже
 *     допущенная и исправленная в реализации;
 *   · узел дерева (`.g-tree-row`) — шеврон СЛЕВА, первым: здесь он и есть маркер
 *     иерархии, и все имена уровня выравниваются по нему.
 * Высота тоже разная: строка свойства 34px, строка списка 24px. Список плотнее,
 * потому что его читают пачкой, а свойство — по одному.
 *
 * 🔴 ОТСТУП УРОВНЯ — ТОЛЬКО PADDING, НИКОГДА MARGIN. Фон строки обязан идти от
 * края до края панели: с margin подсветка уезжала бы ступеньками и перестала бы
 * читаться как полоса. Глубина передаётся переменной: style="--g-depth:2".
 * 🔴 Узел НИКОГДА не задаёт ширину по контенту (`min-width: max-content` запрещён):
 * в узкой панели строки растянулись бы до самого длинного имени, а счётчики и
 * кнопки уехали бы за правый край — невидимо. Длинное имя усекается, полное — в title. */
.g-tree { display: flex; flex-direction: column; font-size: var(--g-fs-ui); min-width: 0; }

.g-tree-row {
  display: flex; align-items: center; gap: 6px;
  min-height: var(--g-tree-row-h); min-width: 0;
  padding-block: 4px; padding-inline-end: var(--g-gap);
  padding-inline-start: calc(var(--g-tree-indent-base) + var(--g-depth) * var(--g-tree-indent));
  border: none; background: transparent; color: var(--g-ink);
  text-align: left; cursor: pointer; border-radius: var(--g-r-xs);
  transition: background var(--g-t) var(--g-ease), opacity var(--g-t) var(--g-ease);
}
.g-tree-row:hover { background: var(--g-hover-film); }
/* «Выбрано» — устойчивое состояние, оно НЕ должно исчезать под курсором и не
   должно спорить с ховером: акцентная плёнка + ink-текст. */
.g-tree-row.is-selected { background: var(--g-select-film); color: var(--g-ink); }
.g-tree-row.is-selected:hover { background: var(--g-select-film); }
/* Приглушённые варианты. Все трое ОСТАЮТСЯ кликабельными — приглушение говорит
   «не в фокусе», а не «недоступно». Недоступность — это .is-disabled. */
.g-tree-row.is-empty { opacity: .45; }              /* группа без детей */
.g-tree-row.is-muted { opacity: .55; }              /* вне текущего пути */
.g-tree-row.is-ref   { opacity: .5; font-style: italic; }  /* повтор: схождение/цикл */
.g-tree-row.is-muted:hover, .g-tree-row.is-ref:hover { opacity: .9; }
.g-tree-row.is-disabled { opacity: .4; pointer-events: none; }
.g-tree-row.is-dragging { opacity: .5; }
.g-tree-row.is-drop-target { box-shadow: inset 0 0 0 1px var(--g-accent); }

/* Шеврон-раскрытие: своя кнопка со своим ховером — попасть в 12px иконку внутри
   24px строки иначе невозможно. У листа вместо неё распорка той же ширины,
   иначе имена листьев и веток встанут на разные вертикали. */
.g-tree-toggle {
  width: var(--g-icon-lg); height: var(--g-icon-lg); flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; border: none; background: transparent; color: var(--g-faint);
  border-radius: var(--g-r-xs); cursor: pointer;
}
.g-tree-toggle:hover { background: var(--g-active-film); color: var(--g-dim); }
.g-tree-toggle svg { width: var(--g-icon-sm); height: var(--g-icon-sm); }
.g-tree-toggle--leaf { visibility: hidden; pointer-events: none; }

/* Иконка типа — единственный носитель цвета в строке. Цвет берётся из смыслового
   спектра (`color` у .g-tint-*), поэтому пишем currentColor, а не свой оттенок. */
.g-tree-ico { width: var(--g-icon-md); height: var(--g-icon-md); flex: 0 0 auto; color: currentColor; }
.g-tree-row.is-muted .g-tree-ico { width: var(--g-icon-sm); height: var(--g-icon-sm); }
.g-tree-name { flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.g-tree-count { margin-inline-start: auto; color: var(--g-faint); font-variant-numeric: tabular-nums; }
/* Действие на строке — появляется по наведению НА СТРОКУ, а не на себя:
   иначе до него не дотянуться, оно невидимо до самого касания. */
.g-tree-action {
  width: var(--g-icon-lg); height: var(--g-icon-lg); flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; border: none; background: transparent; color: var(--g-dim);
  border-radius: var(--g-r-xs); cursor: pointer;
  opacity: 0; transition: opacity var(--g-t) var(--g-ease);
}
.g-tree-row:hover .g-tree-action,
.g-tree-action:focus-visible { opacity: 1; }
.g-tree-action:hover { background: var(--g-active-film); color: var(--g-ink); }
.g-tree-action svg { width: var(--g-icon-sm); height: var(--g-icon-sm); }
/* Строка-действие «＋ создать» — та же геометрия, акцентный текст. */
.g-tree-add { color: var(--g-accent); }
.g-tree-add svg { width: var(--g-icon-sm); height: var(--g-icon-sm); }
.g-tree-empty { padding: var(--g-pad) var(--g-gap); color: var(--g-faint); }

/* Заголовок сегмента панели (над деревом): метка + счётчик, без рамки.
   Это НЕ секция панели свойств — у сегмента нет колонок и нет разделителя. */
.g-seg-head {
  display: flex; align-items: center; gap: 4px;
  padding: 4px var(--g-gap) 2px;
  font-size: var(--g-fs-ui); font-weight: 600; color: var(--g-dim);
}
.g-seg-head__count { margin-inline-start: auto; color: var(--g-faint);
  font-variant-numeric: tabular-nums; font-weight: 400; }

/* ── ТУЛБАР НАД СПИСКОМ: «где смотрю» слева, «чем управляю» справа ────────
 * Одна строка на две роли. Слева — ОБЛАСТЬ просмотра (что за набор перед глазами),
 * справа — ИНСТРУМЕНТЫ над ним. Разводить их по разным строкам нельзя: в панели 352px
 * каждая лишняя строка съедает ряд списка, а связь «эти кнопки — про этот набор»
 * держится именно соседством.
 *
 * 🔴 ПОРЯДОК ИНСТРУМЕНТОВ ФИКСИРОВАН: [фильтр] → [сортировка] → [действие].
 * Он не про эстетику, а про ход мысли: сначала СУЖАЮ набор, потом УПОРЯДОЧИВАЮ
 * оставшееся, и только потом ДОБАВЛЯЮ новое. Кнопка добавления стоит последней ещё и
 * потому, что она единственная необратимая — её не нажимают мимоходом.
 *
 * 🔴 Тулбар ОДИН на все области. Если у какой-то области своих инструментов нет, они не
 * исчезают, а продолжают работать: пропажа кнопок при переключении читается как поломка. */
.g-toolbar {
  display: flex; align-items: center; gap: 2px;
  padding: 6px var(--g-gap);
  font-size: var(--g-fs-ui);
}
.g-toolbar__end { margin-inline-start: auto; display: flex; align-items: center;
  gap: 2px; flex: 0 0 auto; }

/* Квадратная кнопка-иконка тулбара. Размер тот же, что у пункта рейки (28px): рука
   уже знает эту мишень, заводить второй размер для того же жеста незачем.
   🔴 Подпись живёт в `title`, а не рядом: четыре подписанные кнопки не влезают в 352px,
   и первое, что резалось, — сами подписи. Значок обязан читаться без слова. */
.g-icon-btn {
  width: var(--g-rail-item); height: var(--g-rail-item); flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid transparent; border-radius: var(--g-r-sm);
  background: transparent; color: var(--g-dim); cursor: pointer;
  transition: background var(--g-t) var(--g-ease), color var(--g-t) var(--g-ease);
}
.g-icon-btn svg { width: var(--g-icon-md); height: var(--g-icon-md); flex: 0 0 auto; }
.g-icon-btn:hover { background: var(--g-hover-film); color: var(--g-ink); }
.g-icon-btn.is-active { background: var(--g-accent); color: var(--g-accent-ink); }
.g-icon-btn:disabled { opacity: .4; cursor: default; }
.g-icon-btn:focus-visible { outline: 2px solid var(--g-focus); outline-offset: 2px; }

/* ── МЕДИАТЕКА ───────────────────────────────────────────────────────────
 * Сетка плиток. Колонок ДВЕ: в панели 352px третья даёт превью уже 96px, на котором
 * кадр не опознать. Ширину колонки задаёт панель, а не плитка. */
.g-media-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--g-gap); padding: var(--g-gap);
}
.g-media-tile {
  display: flex; flex-direction: column; gap: 5px; min-width: 0;
  padding: 6px; border: 1px solid var(--g-line);
  border-radius: var(--g-r-md); background: var(--g-card);
  cursor: grab; text-align: left;
  transition: border-color var(--g-t) var(--g-ease), background var(--g-t) var(--g-ease);
}
.g-media-tile:hover { border-color: var(--g-dim); background: var(--g-raised); }
.g-media-tile:active { cursor: grabbing; }
/* 🔴 Холст превью СВЕТЛЫЙ намеренно (`--g-media-canvas`): на тёмном пропадает прозрачный
   PNG — белый логотип на прозрачном фоне сливается с подложкой и выглядит как пустая
   плитка. Это единственное светлое пятно системы, и оно именованное. */
.g-media-tile__canvas {
  position: relative; display: flex; align-items: center; justify-content: center;
  min-height: 84px; overflow: hidden;
  border-radius: var(--g-r-sm); background: var(--g-media-canvas);
  color: var(--g-canvas);
}
.g-media-tile__name { color: var(--g-ink); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.g-media-tile__meta { display: flex; align-items: center; justify-content: space-between;
  gap: var(--g-gap); color: var(--g-faint); font-size: var(--g-fs-micro); }
/* Бейдж «файл уже занят в этом проекте» — поверх холста, слева сверху. Смысл несёт
   ПОЛОЖЕНИЕ (левый верх = свойство объекта), а не только цвет.
   🔴 Бейдж НЕ берёт ноту спектра, хотя везде в системе статус — это тинт. Причина:
   он лежит поверх ПРОИЗВОЛЬНОЙ картинки, а тинт рассчитан на известный тёмный фон —
   на светлом холсте пастель по пастели не читается вовсе. Здесь работает тот же приём,
   что у индикатора перетаскивания: затемняющий подслой и белый текст. */
.g-media-tile__badge {
  position: absolute; inset-block-start: 4px; inset-inline-start: 4px;
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 6px; border-radius: 999px;
  background: var(--g-scrim); color: #fff;
  font-size: var(--g-fs-micro); font-weight: 600;
}
/* Индикатор перетаскивания — проявляется на наведении, поверх затемнения. */
.g-media-tile__drag {
  position: absolute; inset-block-start: 4px; inset-inline-end: 4px;
  display: inline-flex; padding: 2px; border-radius: var(--g-r-xs);
  background: var(--g-scrim); color: #fff;
  opacity: 0; transition: opacity var(--g-t) var(--g-ease);
}
.g-media-tile:hover .g-media-tile__drag { opacity: 1; }

/* Разделители сегментов. 🔴 --soft берёт СВОЙ цвет, а не альфу от --g-line:
   полупрозрачная линия на тёмном фоне гаснет в ноль (в реализации оба «мягких»
   разделителя рендерятся невидимыми — это дефект, не тонкая настройка). */
.g-sep { height: 1px; background: var(--g-line); flex: 0 0 auto; }
.g-sep--soft  { background: var(--g-line-soft); }
.g-sep--bleed { margin-inline: calc(-1 * var(--g-gap)); }

/* ── ИКОНКА ─────────────────────────────────────────────────────────────────
 * Значок берётся из спрайта системы, а не из библиотеки в бандле:
 *   <svg class="g-icon"><use href="/graphite.icons.svg#g-save"/></svg>
 * Так глиф правится в системе и уезжает на стенд без пересборки фронта.
 *
 * 🔴 Потребитель владеет ЦВЕТОМ и РАЗМЕРОМ, система — НАЧЕРТАНИЕМ. Цвет идёт через
 * `currentColor` (наследуется внутрь внешнего спрайта), размер — здесь. А толщина штриха
 * и скругления заданы на самом символе: селекторные правила host-документа внутрь
 * внешнего `<use>` не достают — это замерено, обойти нельзя, и это правильно.
 * 🔴 `--g-icon-fill` — единственная дверь к заливке: символы объявлены с
 * `fill="var(--g-icon-fill, none)"`, поэтому по умолчанию глиф контурный, а
 * `--g-icon-fill: currentColor` делает его залитым. Голый `fill` с хоста НЕ работает. */
.g-icon {
  width: var(--g-icon-md); height: var(--g-icon-md);
  flex: 0 0 auto; display: inline-block; vertical-align: middle;
  color: inherit;
}
.g-icon--sm { width: var(--g-icon-sm); height: var(--g-icon-sm); }
.g-icon--lg { width: var(--g-icon-lg); height: var(--g-icon-lg); }
/* Заливка — через атрибут, а не через класс `.g-icon--filled`: токен, объявленный
   только внутри правила компонента, системой не регистрируется. Разметка:
   <svg class="g-icon" data-icon-fill> */
[data-icon-fill] { --g-icon-fill: currentColor; }

/* ── МЕНЮ (канон): 11px / пункт 25px / иконка 12 / фон панели / радиус 8 ──── */
.g-menu {
  background: var(--g-panel); border: 1px solid var(--g-line);
  border-radius: var(--g-r-md); box-shadow: var(--g-shadow-menu); padding: 5px;
}
.g-menu-item {
  display: flex; align-items: center; gap: var(--g-gap);
  font-size: var(--g-fs-ui); line-height: 1.35; color: var(--g-ink);
  padding: 5px 8px; border-radius: var(--g-r-sm); cursor: pointer;
  background: transparent; border: none; width: 100%; text-align: left;
}
.g-menu-item:hover { background: var(--g-raised); }
.g-menu-item.is-danger { color: #fca5a5; }
.g-menu-item svg { width: 12px; height: 12px; flex: 0 0 auto; }
.g-menu-sep { height: 1px; background: var(--g-line); margin: 5px 2px; }

/* ── КНОПКИ ─────────────────────────────────────────────────────────────── */
.g-btn {
  display: inline-flex; align-items: center; gap: 7px;
  font: inherit; font-size: var(--g-fs-ui); border-radius: var(--g-r-sm);
  padding: 6px 12px; cursor: pointer; border: 1px solid transparent;
  transition: background .15s, border-color .15s;
}
.g-btn svg { width: 13px; height: 13px; flex: 0 0 auto; }
.g-btn--primary { background: var(--g-accent); color: var(--g-accent-ink); }
.g-btn--primary:hover { background: var(--g-accent-hover); }
.g-btn--ghost { background: transparent; border-color: var(--g-line); color: var(--g-ink); }
.g-btn--ghost:hover { background: var(--g-raised); }
.g-btn--danger { background: transparent; color: #fca5a5; }
.g-btn--danger:hover { background: rgba(239,68,68,.12); }
.g-btn:focus-visible { outline: 2px solid var(--g-focus); outline-offset: 2px; }

/* ── СТАТУС-ПИЛЮЛИ ──────────────────────────────────────────────────────── */
/* Рамка по умолчанию — структурная. Без неё `border: 1px solid` без цвета брал
   currentColor, и пилюля без тинта получала рамку цветом собственного текста. */
.g-pill {
  display: inline-flex; align-items: center;
  font-size: var(--g-fs-micro); font-weight: 600;
  padding: 2px 9px; border-radius: 999px;
  border: 1px solid var(--g-line);
}
/* Пилюля-ФИЛЬТР: тот же кирпич, но его можно нажать и он помнит выбор.
   🔴 Отдельного компонента «чип фильтра» в системе нет намеренно: фильтр — это тот же
   статус, только переключаемый пользователем. Разводить два похожих кирпича значит
   заводить два места, где живёт одна форма. */
.g-pill--pick { cursor: pointer; background: transparent; color: var(--g-dim);
  transition: background var(--g-t) var(--g-ease), color var(--g-t) var(--g-ease); }
.g-pill--pick:hover { background: var(--g-hover-film); color: var(--g-ink); }
.g-pill--pick.is-active { background: var(--g-accent); border-color: var(--g-accent);
  color: var(--g-accent-ink); }
.g-pill--pick:focus-visible { outline: 2px solid var(--g-focus); outline-offset: 2px; }

/* ── ЭЛЕМЕНТ СПИСКА В НЕСКОЛЬКО СТРОК ───────────────────────────────────── */
/* Строка свойства (`.g-row`) и строка дерева (`.g-tree-row`) — ОДНОСТРОЧНЫЕ и держат
   фиксированную высоту. Запись справки, релиз, уведомление в одну строку не влезают.
   🔴 Не растягивать `.g-tree-row` инлайном: у неё высота, направление и выравнивание
   заданы примитивом, и переопределение каждого из них по месту — это уже своя вёрстка,
   просто под чужим именем. Отсюда отдельный кирпич с той же ховер-плёнкой. */
.g-item {
  display: flex; flex-direction: column; gap: 6px; width: 100%;
  padding: 7px var(--g-pad); text-align: left; cursor: pointer;
  background: transparent; border: none; border-top: 1px solid var(--g-line-soft);
  color: var(--g-ink); font-size: var(--g-fs-ui);
  transition: background var(--g-t) var(--g-ease);
}
.g-item:first-child { border-top: none; }
.g-item:hover { background: var(--g-hover-film); }
.g-item:focus-visible { outline: 2px solid var(--g-focus); outline-offset: -2px; }
.g-item__head { display: flex; align-items: center; gap: var(--g-gap); width: 100%; min-width: 0; }
.g-item__title { flex: 1; min-width: 0; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.g-item__text { color: var(--g-dim); }
.g-item__meta { display: flex; align-items: center; justify-content: space-between;
  gap: var(--g-gap); width: 100%; color: var(--g-faint); font-size: var(--g-fs-micro); }

/* ── КАРТОЧКА НОДЫ ──────────────────────────────────────────────────────── */
/* 🔴 У карточки НЕТ overflow:hidden. Порты сидят на кромке и выступают наружу —
   обрезка молча съела бы точки подключения. Скругление шапки решается радиусом
   на самой шапке, а не клипом на карточке. */
/* 🔴 РАДИУС 12px, А НЕ 8px. Замер продукта: карточка ноды приходит из `ui/Card`
   с `rounded-xl` — это 12px, то есть `--g-r-lg`. Пока у примитива стоял `--g-r-md`,
   система описывала карточку, которой на канвасе нет; расхождение молчаливое —
   в коде примитив не использовался ни разу, и проверить его было не на чем. */
.g-node { position: relative; border-radius: var(--g-r-lg); border: 1px solid var(--g-line);
  background: var(--g-card); }
.g-node.is-selected { border-color: var(--g-accent);
  box-shadow: 0 0 0 1px var(--g-accent), 0 0 18px rgba(37,99,235,.35); }
/* Сирота — надтип поверх любого типа: обводка, а НЕ рамка.
   Помечается всё, до чего не доходит обход от начала (см. комментарий у токена).
   🔴 Именно `outline`, а не `border-color`. Причина сильнее, чем «где-то стоит инлайн»:
   рамку карточки в продукте держат ТРИ РАЗНЫХ МЕХАНИЗМА, и ни одно правило
   `border-color` не выигрывает у всех сразу.
     · инлайн `style={{ borderColor }}` — Схождение · Результаты · Интерактив
       (цвет там константа в коде, класс из системы её не перебьёт в принципе);
     · класс Tailwind с модификатором прозрачности — Условие · Опрос · Викторина ·
       Результат (`border-violet-400/50` и родня);
     · кольцо вместо рамки — Финал (`ring-2` на выделенной, рамки своего цвета нет).
   Плюс §5 слоя редактора бьёт рамки с `!important`. Пришлось бы биться с каждым
   механизмом отдельно, и первый же новый тип ноды сломал бы правило молча.
   Outline — отдельный канал: он не участвует в этой гонке, работает поверх любого типа,
   ничего не сдвигает и не режет порты на кромке.
   Пунктир держит состояние вторым признаком — на мелком масштабе оттенок не читается. */
.g-node.is-orphan {
  outline: var(--g-node-orphan-outline);
  outline-offset: var(--g-node-orphan-offset);
}
/* Выделение сильнее: акцентная рамка остаётся видна, обводка сироты сохраняется рядом. */
.g-node.is-orphan.is-selected { outline-offset: var(--g-node-orphan-offset-selected); }
.g-node__head { display: flex; align-items: center; gap: 7px; padding: 8px 10px;
  border-radius: calc(var(--g-r-lg) - 1px) calc(var(--g-r-lg) - 1px) 0 0;
  font-size: var(--g-fs-ui); font-weight: 600; }
.g-node__head svg { width: 14px; height: 14px; flex: 0 0 auto; }
.g-node__body { padding: 8px 10px; background: var(--g-card); color: var(--g-dim);
  font-size: var(--g-fs-ui); }

/* ── КАНВАС ─────────────────────────────────────────────────────────────────
 * 🔴 ЗАКОН СЛОЯ: всё, что должно лечь ПОВЕРХ полотна (центр-оверлей, панель
 * инструментов, миникарта, подсказка), обязано быть РЕБЁНКОМ корня канваса, а не
 * его соседом. Корень графа создаёт свой stacking context, и сосед-оверлей не
 * переслоить никаким z-index — он либо под всем полотном, либо над ним целиком.
 *
 * 🔴 ШАГ СЕТКИ ФУНКЦИОНАЛЕН, а не декоративен: к нему привязывается перетаскивание
 * и выравнивание нод. --g-grid должен совпадать с шагом, который движок графа
 * рисует на самом деле, иначе фон и привязка разъезжаются на глазах.
 * Оговорка: `.g-graph` — статичная имитация для макетов и витрин. Живое полотно
 * масштабирует сетку зумом; CSS-градиент этого не умеет и не должен притворяться. */
.g-graph {
  background-color: var(--g-canvas);
  background-image: radial-gradient(var(--g-dot) var(--g-grid-dot), transparent var(--g-grid-dot));
  background-size: var(--g-grid) var(--g-grid);
}
/* Рамка выделения (протяжка по пустому месту). Единственный элемент полотна,
   который в реализации остался светлотемным — здесь он переведён на акцент. */
.g-marquee {
  background: rgba(37,99,235,.10);
  border: 1px dotted var(--g-accent);
  border-radius: var(--g-r-xs);
}
/* Рамка группы нод — пунктир, чтобы не спорить со сплошными кромками карточек. */
.g-group-frame {
  border: 2px dashed rgba(99,102,241,.75); background: rgba(99,102,241,.05);
  border-radius: var(--g-r-lg); pointer-events: none;
}
/* Плашка инструментов полотна: вертикальная по умолчанию, разделители по узкой
   стороне. Единственное место системы, где иконка бывает залита акцентом. */
.g-ctl {
  display: flex; flex-direction: column;
  background: var(--g-panel); border: 1px solid var(--g-line);
  border-radius: var(--g-r-md); box-shadow: var(--g-shadow-pop); overflow: hidden;
}
.g-ctl--horiz { flex-direction: row; }
.g-ctl__btn {
  width: 30px; height: 30px; box-sizing: border-box; padding: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--g-panel); color: var(--g-ink);
  border: none; border-bottom: 1px solid var(--g-line); cursor: pointer;
  transition: background var(--g-t) var(--g-ease);
}
.g-ctl--horiz .g-ctl__btn { border-bottom: none; border-right: 1px solid var(--g-line); }
.g-ctl__btn:last-child { border-bottom: none; border-right: none; }
.g-ctl__btn svg { width: var(--g-icon-md); height: var(--g-icon-md); }
.g-ctl__btn:hover { background: var(--g-raised); }
.g-ctl__btn.is-active { background: var(--g-accent); color: var(--g-accent-ink); }
/* 🔴 Контурные и залитые иконки в одной плашке уживаются только через явное
   исключение: общее правило заливки превращает контурную иконку в кляксу. */
.g-ctl__btn--outline svg { fill: none; stroke: currentColor; stroke-width: 2; }
/* Ручка перетаскивания: полоса во всю узкую сторону, всегда первой (в разметке
   она последняя — движок графа дописывает своих детей после кнопок). */
.g-ctl__handle {
  order: -1; display: flex; align-items: center; justify-content: center;
  background: var(--g-canvas); cursor: move; touch-action: none; user-select: none;
  width: 100%; height: 14px; border-bottom: 1px solid var(--g-line);
}
.g-ctl--horiz .g-ctl__handle { width: 14px; height: auto; align-self: stretch;
  border-bottom: none; border-right: 1px solid var(--g-line); }
.g-ctl__handle:hover { background: var(--g-raised); }
.g-ctl__grip { width: 18px; height: 3px; border-radius: 2px; background: var(--g-dim); }
.g-ctl--horiz .g-ctl__grip { width: 3px; height: 18px; }
/* Миникарта */
.g-minimap { background: var(--g-panel); border: 1px solid var(--g-line);
  border-radius: var(--g-r-md); overflow: hidden; }
/* Центр-оверлей — вид, вставший поверх полотна (линейный редактор, тайтл,
   релизы). Радиус крупный: это ОКНО поверх сцены, а не блок внутри панели. */
.g-overlay-frame {
  background: var(--g-panel); border: 1px solid var(--g-line);
  border-radius: var(--g-r-lg); box-shadow: var(--g-shadow-menu);
  display: flex; flex-direction: column; overflow: hidden;
}
.g-overlay-frame__head {
  display: flex; align-items: center; gap: var(--g-pad);
  padding: 8px 16px; flex: 0 0 auto;
  border-bottom: 1px solid var(--g-line); font-size: var(--g-fs-ui);
}
.g-overlay-frame__body { flex: 1 1 auto; min-height: 0; overflow: auto; }

/* ── СВЯЗИ И ПОРТЫ ──────────────────────────────────────────────────────────
 * 🔴 ЦВЕТ СВЯЗИ НЕ КОДИРУЕТ СМЫСЛ. Связь — это структура, а не сущность: она
 * говорит «отсюда туда», и её оттенок не должен спорить с тонировкой нод.
 * Спектр остаётся за нодами; ребро живёт нейтральным и меняет только ЯРКОСТЬ —
 * кроме выбранного, где включается общий акцент. Наследование цвета от типа
 * исходной ноды в системе сознательно НЕ вводится: на развилке из одной ноды
 * выходят несколько разных путей, и один цвет на всех ничего не сообщил бы.
 * 🔴 ВЫБРАНО = АКЦЕНТ, ВЕЗДЕ ОДИНАКОВО. В реализации выбранное ребро зелёное, а
 * синий у ребра означает наведение — тот же синий, которым подсвечена выбранная
 * НОДА. Один цвет на два разных состояния — это и чинится здесь.
 * 🔴 Состояние держится и ТОЛЩИНОЙ, не только цветом: при мелком масштабе и при
 * дальтонизме оттенок линии в 1.5px не читается вовсе. */
.g-edge {
  fill: none; stroke: var(--g-edge); stroke-width: var(--g-edge-w);
  transition: stroke var(--g-t) var(--g-ease), stroke-width var(--g-t) var(--g-ease);
}
.g-edge:hover, .g-edge.is-hover { stroke: var(--g-edge-hover); stroke-width: var(--g-edge-w-active); }
.g-edge.is-selected { stroke: var(--g-edge-selected); stroke-width: var(--g-edge-w-active); }
/* Полоса попадания: тонкую линию мышью не поймать. Невидима, но кликабельна. */
.g-edge-hit { stroke: transparent; stroke-width: var(--g-edge-hit); fill: none; cursor: pointer; }
/* Линия-призрак при протяжке ОБЯЗАНА выглядеть как будущее ребро — тот же тип
   линии, цвет и толщина. Иначе жест обещает одно, а отпускание даёт другое. */
.g-edge-ghost { fill: none; stroke: var(--g-edge-hover); stroke-width: var(--g-edge-w);
  stroke-dasharray: 4 4; }
/* Подпись на связи: плашка цветом панели. Белая плашка на тёмном полотне —
   самое яркое пятно всей сцены, ярче любой ноды. */
.g-edge-label      { fill: var(--g-ink); font-size: var(--g-fs-micro); }
.g-edge-label-bg   { fill: var(--g-panel); stroke: var(--g-line); rx: var(--g-r-xs); }
/* Марширующий пунктир — ТОЛЬКО осмысленным подмножеством связей (текущий путь,
   активная ветка). Анимировать весь граф разом — значит не сообщить ничего. */
.g-edge.is-animated { stroke-dasharray: 8 8; animation: g-dash .8s linear infinite; }
.g-edge.is-animated:hover { animation-duration: .4s; }
@keyframes g-dash { from { stroke-dashoffset: 16; } to { stroke-dashoffset: 0; } }

/* Порт — ОДИН размер на все типы нод. Два разных диаметра (12px у одних, 6px у
   других) читаются как два разных механизма, хотя жест один и тот же; мелкий
   тёмный кружок на тёмной карточке к тому же почти не виден. */
.g-port {
  width: var(--g-port); height: var(--g-port); border-radius: 50%;
  background: var(--g-dim); border: var(--g-port-ring) solid var(--g-card);
  box-sizing: border-box; cursor: crosshair;
  transition: background var(--g-t) var(--g-ease), transform var(--g-t) var(--g-ease);
}
.g-port:hover, .g-port.is-connecting { background: var(--g-accent); transform: scale(1.15); }
/* Валидность цели — бесплатная обратная связь, которой в реализации нет вовсе:
   движок графа уже помечает порты при протяжке, но никто их не красит. */
.g-port.is-valid   { background: #10b981; }
.g-port.is-invalid { background: #ef4444; }
/* Типизированные выходы: да/нет, верно/неверно. Единственный случай, когда порт
   цветной по смыслу — и тогда подпись рядом обязательна, цвета одного мало. */
.g-port--true  { background: #10b981; }
.g-port--false { background: #ef4444; }
.g-port-caption { font-size: var(--g-fs-ui); color: var(--g-faint);
  pointer-events: none; white-space: nowrap; }

/* Стрелка. 🔴 markerUnits="userSpaceOnUse" ОБЯЗАТЕЛЕН: иначе маркер масштабируется
   от толщины линии, и переход покой→наведение раздувает наконечник вдвое —
   пользователь видит скачок геометрии, а не подсветку. */
.g-arrow      { fill: var(--g-edge); }
.g-arrow--hover    { fill: var(--g-edge-hover); }
.g-arrow--selected { fill: var(--g-edge-selected); }

/* ── СМЫСЛОВОЙ СПЕКТР ────────────────────────────────────────────────────
 * Пастель НЕ глушится в серый — кодирует смысл. Формула на оттенок:
 *   bg  rgba(<hue-500>, .16)  ·  bg-strong .26  ·  border .45  ·  text <hue>-300
 * Применяй классом `.g-tint-<hue>` к пилюле/шапке ноды/бейджу. Фон .16;
 * добавь `.is-strong` для .26. Текст и рамка идут в комплекте. */
.g-tint-green    { background: rgba(var(--g-hue-green), var(--g-tint-a));  border-color: rgba(var(--g-hue-green), var(--g-tint-a-line));  color: var(--g-ink-green); }
.g-tint-cyan     { background: rgba(var(--g-hue-cyan), var(--g-tint-a));  border-color: rgba(var(--g-hue-cyan), var(--g-tint-a-line));  color: var(--g-ink-cyan); }
.g-tint-blue     { background: rgba(var(--g-hue-blue), var(--g-tint-a)); border-color: rgba(var(--g-hue-blue), var(--g-tint-a-line)); color: var(--g-ink-blue); }
.g-tint-amber    { background: rgba(var(--g-hue-amber), var(--g-tint-a)); border-color: rgba(var(--g-hue-amber), var(--g-tint-a-line)); color: var(--g-ink-amber); }
.g-tint-red      { background: rgba(var(--g-hue-red), var(--g-tint-a));  border-color: rgba(var(--g-hue-red), var(--g-tint-a-line));  color: var(--g-ink-red); }
.g-tint-violet   { background: rgba(var(--g-hue-violet), var(--g-tint-a)); border-color: rgba(var(--g-hue-violet), var(--g-tint-a-line)); color: var(--g-ink-violet); }
.g-tint-indigo   { background: rgba(var(--g-hue-indigo), var(--g-tint-a)); border-color: rgba(var(--g-hue-indigo), var(--g-tint-a-line)); color: var(--g-ink-indigo); }
.g-tint-fuchsia  { background: rgba(var(--g-hue-fuchsia), var(--g-tint-a)); border-color: rgba(var(--g-hue-fuchsia), var(--g-tint-a-line)); color: var(--g-ink-fuchsia); }
.g-tint-teal     { background: rgba(var(--g-hue-teal), var(--g-tint-a)); border-color: rgba(var(--g-hue-teal), var(--g-tint-a-line)); color: var(--g-ink-teal); }
.g-tint-lime     { background: rgba(var(--g-hue-lime), var(--g-tint-a)); border-color: rgba(var(--g-hue-lime), var(--g-tint-a-line)); color: var(--g-ink-lime); }
.g-tint-emerald  { background: rgba(var(--g-hue-emerald), var(--g-tint-a)); border-color: rgba(var(--g-hue-emerald), var(--g-tint-a-line)); color: var(--g-ink-emerald); }
.g-tint-sky      { background: rgba(var(--g-hue-sky), var(--g-tint-a)); border-color: rgba(var(--g-hue-sky), var(--g-tint-a-line)); color: var(--g-ink-sky); }
.g-tint-rose     { background: rgba(var(--g-hue-rose), var(--g-tint-a));  border-color: rgba(var(--g-hue-rose), var(--g-tint-a-line));  color: var(--g-ink-rose); }
.g-tint-pink     { background: rgba(var(--g-hue-pink), var(--g-tint-a)); border-color: rgba(var(--g-hue-pink), var(--g-tint-a-line)); color: var(--g-ink-pink); }
.g-tint-orange   { background: rgba(var(--g-hue-orange), var(--g-tint-a)); border-color: rgba(var(--g-hue-orange), var(--g-tint-a-line)); color: var(--g-ink-orange); }
.g-tint-yellow   { background: rgba(var(--g-hue-yellow), var(--g-tint-a));  border-color: rgba(var(--g-hue-yellow), var(--g-tint-a-line));  color: var(--g-ink-yellow); }
.g-tint-neutral  { background: rgba(var(--g-hue-neutral), var(--g-tint-a-mute));
                   border-color: rgba(var(--g-hue-neutral), var(--g-tint-a-line-mute));
                   color: var(--g-ink-neutral); }

/* 🔴 `.is-ink` — тинт ТОЛЬКО цветом, без плашки. Нужен там, где красить надо
   штрих или глиф, а не поверхность: иконка типа в дереве, линия связи, шеврон.
   Без него `.g-tint-*` тащит за собой фон и рамку, и в строке списка появляется
   лишний прямоугольник. Атрибутный селектор (0,2,0) перебивает тинт (0,1,0) —
   одно правило вместо семнадцати. Для SVG задаём и stroke: контурная иконка
   красится штрихом, а не заливкой. */
[class*="g-tint-"].is-ink { background: none; border-color: transparent; }
svg[class*="g-tint-"].is-ink { stroke: currentColor; fill: none; }

.g-tint-green.is-strong   { background: rgba(var(--g-hue-green), var(--g-tint-a-strong)); }
.g-tint-cyan.is-strong    { background: rgba(var(--g-hue-cyan), var(--g-tint-a-strong)); }
.g-tint-blue.is-strong    { background: rgba(var(--g-hue-blue), var(--g-tint-a-strong)); }
.g-tint-amber.is-strong   { background: rgba(var(--g-hue-amber), var(--g-tint-a-strong)); }
.g-tint-red.is-strong     { background: rgba(var(--g-hue-red), var(--g-tint-a-strong)); }
.g-tint-violet.is-strong  { background: rgba(var(--g-hue-violet), var(--g-tint-a-strong)); }
.g-tint-indigo.is-strong  { background: rgba(var(--g-hue-indigo), var(--g-tint-a-strong)); }
.g-tint-fuchsia.is-strong { background: rgba(var(--g-hue-fuchsia), var(--g-tint-a-strong)); }
.g-tint-teal.is-strong    { background: rgba(var(--g-hue-teal), var(--g-tint-a-strong)); }
.g-tint-lime.is-strong    { background: rgba(var(--g-hue-lime), var(--g-tint-a-strong)); }

/* ── ФОКУС И СКРОЛЛ ─────────────────────────────────────────────────────────
 * 🔴 ФОКУС РИСУЕТСЯ ВНУТРЬ. Тело дока — скролл-контейнер (`overflow-y:auto`), а он
 * КЛИПУЕТ внешний `outline` у полноширинных строк: обводка уходит под кромку и
 * пропадает. Поэтому кольцо фокуса у всего, что живёт в панели, — inset box-shadow.
 * У кнопок вне скролла (`.g-btn`) внешний outline остаётся — там он виден. */
.g-rail-item:focus-visible,
.g-tree-row:focus-visible,
.g-tree-toggle:focus-visible,
.g-tree-action:focus-visible,
.g-section-h:focus-visible,
.g-menu-item:focus-visible,
.g-ctl__btn:focus-visible {
  outline: none; box-shadow: inset 0 0 0 2px var(--g-focus);
}
/* Скроллбар. Системный рисуется светлым и пробивает дыру в тёмной панели — в реализации
 * редактора это выглядело ровно так: правил скроллбара не было ни одного.
 *
 * 🔴 ЕДЕТ С МАРКЕРОМ, а не с именами конкретных блоков. Раньше правила были заскоплены на
 * `.g-dock__body`/`.g-tree`, то есть доставались только тем, кто носит примитивы системы;
 * потребитель со своей разметкой (граф-редактор) не получал ничего. Теперь достаточно
 * повесить `.graphite` или `.graphite-scope` на корень области.
 * 🔴 `scrollbar-color` НАСЛЕДУЕТСЯ — поэтому для Firefox хватает объявления на корне.
 * Для WebKit псевдоэлемент наследование не поддерживает, нужен потомковый селектор. */
.graphite, .graphite-scope,
.g-dock__body, .g-tree, .g-overlay-frame__body {
  scrollbar-width: thin; scrollbar-color: var(--g-raised) transparent;
}
.graphite ::-webkit-scrollbar, .graphite-scope ::-webkit-scrollbar,
.g-dock__body::-webkit-scrollbar, .g-tree::-webkit-scrollbar,
.g-overlay-frame__body::-webkit-scrollbar { width: 10px; height: 10px; }
.graphite ::-webkit-scrollbar-track, .graphite-scope ::-webkit-scrollbar-track,
.g-dock__body::-webkit-scrollbar-track, .g-tree::-webkit-scrollbar-track,
.g-overlay-frame__body::-webkit-scrollbar-track { background: transparent; }
.graphite ::-webkit-scrollbar-thumb, .graphite-scope ::-webkit-scrollbar-thumb,
.g-dock__body::-webkit-scrollbar-thumb, .g-tree::-webkit-scrollbar-thumb,
.g-overlay-frame__body::-webkit-scrollbar-thumb {
  background: var(--g-raised); border-radius: 999px;
  border: 3px solid transparent; background-clip: content-box;
}
.graphite ::-webkit-scrollbar-thumb:hover, .graphite-scope ::-webkit-scrollbar-thumb:hover,
.g-dock__body::-webkit-scrollbar-thumb:hover, .g-tree::-webkit-scrollbar-thumb:hover,
.g-overlay-frame__body::-webkit-scrollbar-thumb:hover {
  background: var(--g-line); background-clip: content-box;
}

@media (prefers-reduced-motion: reduce) {
  .graphite *, .g-field, .g-btn { transition: none !important; }
  /* Марширующий пунктир — самая заметная непрерывная анимация системы.
     Связь остаётся видимой: гасим движение, а не пунктир. */
  .g-edge.is-animated, .g-edge.is-animated:hover { animation: none !important; }
}
