@kodaeditor/core 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/docx-import.d.ts +11 -11
  2. package/dist/editor.d.ts +109 -159
  3. package/dist/fallback.d.ts +7 -8
  4. package/dist/i18n.d.ts +15 -26
  5. package/dist/import-formats.d.ts +19 -17
  6. package/dist/index.d.ts +4 -4
  7. package/dist/kodaeditor-core.umd.cjs +1 -1
  8. package/dist/kodaeditor.js +1 -1
  9. package/dist/kodaeditor.umd.cjs +1 -1
  10. package/dist/license.d.ts +46 -50
  11. package/dist/schema.d.ts +7 -7
  12. package/dist/serialize.d.ts +11 -11
  13. package/dist/web-component.d.ts +80 -189
  14. package/package.json +4 -6
  15. package/dist/commands.d.ts +0 -15
  16. package/dist/docx-alignment.d.ts +0 -14
  17. package/dist/docx-breaks.d.ts +0 -31
  18. package/dist/docx-forms.d.ts +0 -28
  19. package/dist/docx-headers.d.ts +0 -22
  20. package/dist/docx-images.d.ts +0 -41
  21. package/dist/docx-math.d.ts +0 -18
  22. package/dist/docx-styles.d.ts +0 -53
  23. package/dist/docx-symbols.d.ts +0 -19
  24. package/dist/docx-text-styles.d.ts +0 -27
  25. package/dist/floatingToolbar.d.ts +0 -7
  26. package/dist/group-icons.d.ts +0 -17
  27. package/dist/icons.d.ts +0 -1
  28. package/dist/image-view.d.ts +0 -27
  29. package/dist/markdown.d.ts +0 -4
  30. package/dist/menubar.d.ts +0 -60
  31. package/dist/offline-license.d.ts +0 -36
  32. package/dist/pagination.d.ts +0 -11
  33. package/dist/paste.d.ts +0 -54
  34. package/dist/pdf-import-cdn.d.ts +0 -2
  35. package/dist/pdf-import.d.ts +0 -8
  36. package/dist/print.d.ts +0 -18
  37. package/dist/rtf-images.d.ts +0 -24
  38. package/dist/search.d.ts +0 -17
  39. package/dist/slashMenu.d.ts +0 -8
  40. package/dist/styles.d.ts +0 -8
  41. package/dist/table-split.d.ts +0 -26
  42. package/dist/table-view.d.ts +0 -34
  43. package/dist/tableColumnTrade.d.ts +0 -15
  44. package/dist/tableToolbar.d.ts +0 -14
  45. package/dist/toc-view.d.ts +0 -23
  46. package/dist/toolbar-editor.d.ts +0 -89
  47. package/dist/validate-signature.d.ts +0 -64
  48. package/dist/xlsx-import-cdn.d.ts +0 -1
  49. package/dist/xlsx-import.d.ts +0 -2
@@ -1,41 +0,0 @@
1
- /**
2
- * Извлечение картинок из .docx и вставка их в нужные ячейки таблиц.
3
- *
4
- * mammoth тащит только простые inline-картинки и теряет VML-фигуры (круглое
5
- * фото в `v:oval`), плавающие рисунки и часть DrawingML. Для резюме/форм это
6
- * критично — пропадает фото и логотипы. Здесь картинки достаются прямо из zip
7
- * (`word/media/*`), сопоставляются с ячейками таблиц по позиции в OOXML и
8
- * вставляются в соответствующие ячейки mammoth-вывода.
9
- */
10
- /** Картинки по таблицам: cells[индекс ячейки row-major] = массив data-URI. */
11
- export interface DocxTableImages {
12
- cells: string[][];
13
- }
14
- /** Читает .docx и возвращает картинки по таблицам/ячейкам. Безопасно к ошибкам. */
15
- export declare function extractDocxImages(arrayBuffer: ArrayBuffer): Promise<DocxTableImages[]>;
16
- /** Вставляет извлечённые картинки в ячейки таблиц mammoth-вывода по позиции. */
17
- export declare function applyDocxImages(root: HTMLElement, tables: DocxTableImages[]): void;
18
- /**
19
- * Размеры ОТОБРАЖЕНИЯ картинок, которые рендерит mammoth, в порядке документа.
20
- * Источники (mammoth теряет размер — рисует в натуральную величину):
21
- * - DrawingML инлайн: `<wp:inline>` → `<wp:extent cx cy>` (EMU);
22
- * - VML-картинка: `<v:shape|rect|oval style="width:..pt;height:..pt">` c
23
- * вложенным `<v:imagedata>` (pt). Чистые векторные фигуры (без imagedata,
24
- * напр. блок-схемы) пропускаются — mammoth их всё равно не рендерит.
25
- * Возвращает по элементу на каждый рисунок-картинку (null, если размера нет).
26
- */
27
- export declare function extractInlineImageSizes(arrayBuffer: ArrayBuffer): Promise<({
28
- w: number;
29
- h: number;
30
- } | null)[]>;
31
- /**
32
- * Проставляет размеры отображения на инлайн-картинки mammoth-вывода.
33
- * Безопасно: применяет ТОЛЬКО когда число `<img>` совпадает с числом инлайн-
34
- * рисунков (тогда соответствие 1:1 по порядку надёжно). Иначе — пропуск, чтобы
35
- * не проставить чужой размер (mammoth мог потерять часть рисунков или это VML).
36
- * Вызывать на сыром mammoth-выводе, ДО инъекции табличных картинок.
37
- */
38
- export declare function applyInlineImageSizes(root: HTMLElement, sizes: ({
39
- w: number;
40
- h: number;
41
- } | null)[]): void;
@@ -1,18 +0,0 @@
1
- /**
2
- * OMML-формулы (Office Math) при импорте .docx.
3
- *
4
- * mammoth ПОЛНОСТЬЮ выкидывает математику (m:oMath) — формула пропадает без
5
- * следа. Полноценный рендер (MathML) схема редактора не пропустит, поэтому
6
- * здесь формула конвертируется в ЧИТАЕМЫЙ Unicode-текст (дроби, степени,
7
- * индексы, корни, интегралы/суммы) и подставляется НА МЕСТО формулы прямо в
8
- * document.xml ДО mammoth — так текст формулы встаёт в правильную позицию.
9
- * Лучше «(a)/(b) + x²», чем пустота.
10
- */
11
- /** oMath -> текст. */
12
- export declare function omathToText(omath: Element): string;
13
- /**
14
- * Мутирует parsed document.xml: заменяет m:oMath/m:oMathPara на текстовые
15
- * w:r-прогоны. Возвращает true, если что-то заменено. Чистая DOM-операция —
16
- * чтобы препроцессинг (формулы + чекбоксы) делал ОДИН repack zip.
17
- */
18
- export declare function replaceMathInDoc(doc: Document): boolean;
@@ -1,53 +0,0 @@
1
- /**
2
- * Дочерпывание табличных стилей из OOXML при импорте .docx.
3
- *
4
- * mammoth даёт чистую семантику, но осознанно игнорирует оформление:
5
- * - ширины колонок (`w:tblGrid`),
6
- * - заливку ячеек (`w:shd` на ячейке),
7
- * - заливку из СТИЛЯ таблицы (`w:tblStyle` + conditional formatting: шапка,
8
- * бандинг строк/колонок) — частый кейс встроенных стилей Word (Light Shading,
9
- * Grid Table и т.п.) и форм ЭДО.
10
- * Всё это читается прямо из zip и накладывается на таблицы mammoth-вывода
11
- * (порядок таблиц и ячеек совпадает с документным).
12
- */
13
- export interface DocxTableStyle {
14
- /** Ширины колонок в px (из w:tblGrid, twips -> px). */
15
- colWidths: number[];
16
- /**
17
- * Заливки ячеек ПО СТРОКАМ (на каждую отрисованную mammoth-строку — список
18
- * заливок слева направо). vMerge-continue ячейки пропущены (mammoth их не
19
- * рендерит). Раскладка по строкам локализует рассинхрон рамками строки.
20
- */
21
- cellFills: (string | null)[][];
22
- }
23
- /** Парсит theme1.xml -> Map слот(dk1/lt1/accent1…) -> hex(6). */
24
- export declare function parseThemeColors(xml: string): Map<string, string>;
25
- interface CondFills {
26
- whole: string | null;
27
- firstRow: string | null;
28
- lastRow: string | null;
29
- firstCol: string | null;
30
- lastCol: string | null;
31
- band1Horz: string | null;
32
- band2Horz: string | null;
33
- band1Vert: string | null;
34
- band2Vert: string | null;
35
- rowBandSize: number;
36
- colBandSize: number;
37
- }
38
- type StyleMap = Map<string, CondFills>;
39
- /** Парсит word/styles.xml -> Map styleId -> CondFills (с разрешением basedOn). */
40
- export declare function parseTableStyleDefs(stylesXml: string, theme: Map<string, string>): StyleMap;
41
- /**
42
- * Разбор таблиц из XML. styles/theme опциональны (тесты могут передать xml без
43
- * стилей; тогда работает только явный w:shd).
44
- */
45
- export declare function parseTablesFromXml(xml: string, styles?: StyleMap, theme?: Map<string, string>): DocxTableStyle[];
46
- /** Читает .docx и возвращает стили таблиц по порядку. Безопасно к ошибкам. */
47
- export declare function extractDocxTableStyles(arrayBuffer: ArrayBuffer): Promise<DocxTableStyle[]>;
48
- /**
49
- * Накладывает стили на таблицы mammoth-вывода: ширины колонок (через
50
- * `<colgroup>` — дальше их подхватит normalizeTableWidths) и заливку ячеек.
51
- */
52
- export declare function applyDocxTableStyles(root: HTMLElement, styles: DocxTableStyle[]): void;
53
- export {};
@@ -1,19 +0,0 @@
1
- /**
2
- * Символьные прогоны Wingdings/Symbol (w:sym) при импорте .docx.
3
- *
4
- * Старые формы ЭДО ставят чекбоксы и маркеры символьным шрифтом: не текст ☐, а
5
- * <w:sym w:font="Wingdings" w:char="F0A8"/> — код глифа В ШРИФТЕ. Без самого
6
- * шрифта Wingdings (а сайт его не грузит) mammoth выдаёт либо пусто, либо
7
- * нестандартный глиф (🞎/🗹), не совпадающий с ☐/☒ из обычных чекбоксов форм.
8
- *
9
- * Здесь известные коды Wingdings/Symbol заменяются НА МЕСТЕ в document.xml на
10
- * корректный Unicode ДО mammoth: пустой чекбокс -> ☐, отмеченный -> ☒/☑,
11
- * телефон -> ☎, греческие буквы Symbol -> α/β/… Неизвестные коды не трогаем —
12
- * пусть их обрабатывает mammoth, чтобы ничего не сломать.
13
- */
14
- /**
15
- * Мутирует parsed document.xml: w:sym с известным шрифтом/кодом -> текстовый
16
- * прогон w:r/w:t с Unicode-символом. Неизвестные оставляет нетронутыми.
17
- * Возвращает true, если что-то заменено. Общий repack с формулами/чекбоксами.
18
- */
19
- export declare function replaceSymbolsInDoc(doc: Document): boolean;
@@ -1,27 +0,0 @@
1
- /**
2
- * Восстановление оформления текста при импорте .docx (цвет и размер).
3
- *
4
- * mammoth маппит «Heading 1/2/3» и «Title» в <h1>/<h2>/<h3>, но выбрасывает
5
- * цвет и кегль, заданные в именованных стилях (word/styles.xml). Из-за этого
6
- * синие заголовки резюме (#548AB7) и крупное имя (48pt) теряются. Здесь цвет и
7
- * размер вычисляются для каждого абзаца (прямой run-стиль > стиль абзаца) и
8
- * накладываются на блоки mammoth по тексту.
9
- */
10
- interface TextStyle {
11
- color?: string;
12
- size?: string;
13
- }
14
- /** Читает .docx и возвращает оформление по тексту абзацев. Безопасно к ошибкам. */
15
- export declare function extractDocxTextStyles(arrayBuffer: ArrayBuffer): Promise<Map<string, TextStyle>>;
16
- /** Накладывает цвет/размер на блоки mammoth-вывода по тексту. */
17
- export declare function applyDocxTextStyles(root: HTMLElement, styles: Map<string, TextStyle>): void;
18
- interface StyledRun {
19
- text: string;
20
- color: string | null;
21
- size: string | null;
22
- font: string | null;
23
- background: string | null;
24
- }
25
- export declare function extractDocxRunColors(arrayBuffer: ArrayBuffer): Promise<Map<string, StyledRun[]>>;
26
- export declare function applyDocxRunColors(root: HTMLElement, map: Map<string, StyledRun[]>): void;
27
- export {};
@@ -1,7 +0,0 @@
1
- /**
2
- * Floating toolbar (Notion-UX): по выделению текста всплывает мини-панель
3
- * форматирования над выделением (жирный/курсив/подчёркнутый/зачёркнутый).
4
- */
5
- import { Plugin } from 'prosemirror-state';
6
- import type { Strings } from './i18n';
7
- export declare function floatingToolbarPlugin(strings: Strings): Plugin;
@@ -1,17 +0,0 @@
1
- /**
2
- * Нейтральные обложки-контейнеры (нарисованы в том же line-стиле, что и пакет
3
- * панели). Их нет среди команд, поэтому держим здесь — для группы это
4
- * естественнее, чем «B»/«ссылка» в роли обложки.
5
- */
6
- export declare const GROUP_EXTRA_ICONS: Record<string, string>;
7
- /** Популярные emoji для обложки группы (iconKind:'emoji', сам символ хранится). */
8
- export declare const GROUP_EMOJI: string[];
9
- /** Тип обложки кнопки-группы. */
10
- export type GroupIconKind = 'builtin' | 'emoji' | 'image';
11
- /**
12
- * Ключи иконок для вкладки «Иконки» лого-пикера: сперва нейтральные обложки,
13
- * затем все иконки панели (тот же пакет). Дедуп на случай пересечений.
14
- */
15
- export declare function groupIconKeys(): string[];
16
- /** Отрисовать обложку группы в HTML (для кнопки/превью). */
17
- export declare function renderGroupIcon(kind: GroupIconKind, icon: string): string;
package/dist/icons.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare const TOOLBAR_ICONS: Record<string, string>;
@@ -1,27 +0,0 @@
1
- /**
2
- * NodeView картинки с ручкой ресайза (правый-нижний угол). Тянем мышью —
3
- * меняется ширина; по отпусканию ширина пишется в атрибут width узла (т.е.
4
- * сохраняется в HTML как style="width:NNpx"). Ручка видна при наведении или
5
- * когда картинка выделена.
6
- */
7
- import type { Node as PMNode } from 'prosemirror-model';
8
- import type { EditorView, NodeView } from 'prosemirror-view';
9
- export declare class ImageNodeView implements NodeView {
10
- dom: HTMLElement;
11
- private img;
12
- private node;
13
- private view;
14
- private getPos;
15
- constructor(node: PMNode, view: EditorView, getPos: () => number | undefined);
16
- /**
17
- * Режим обрезки: накладывает слой поверх картинки, пользователь тянет рамку
18
- * выделения (pointer = мышь/touch), затем «Применить» вырезает область через
19
- * canvas в новый data-URL (client-side, на сервер не уходит).
20
- */
21
- private enterCrop;
22
- /** Вырезает область (в координатах отображения) через canvas -> новый src. */
23
- private applyCrop;
24
- private startResize;
25
- update(node: PMNode): boolean;
26
- ignoreMutation(): boolean;
27
- }
@@ -1,4 +0,0 @@
1
- /** Markdown -> HTML (синхронно). */
2
- export declare function markdownToHtml(md: string): string;
3
- /** HTML (вывод редактора) -> Markdown. */
4
- export declare function htmlToMarkdown(html: string): string;
package/dist/menubar.d.ts DELETED
@@ -1,60 +0,0 @@
1
- /**
2
- * Верхнее меню (menubar) — структурированный доступ к работе с компонентами
3
- * документа: Файл / Правка / Вставка / Формат / Таблица / Вид. Каждое меню —
4
- * выпадающая панель пунктов; пункт может иметь подменю (Таблица → Строка/Столбец/
5
- * Ячейка). Действия переиспользуют те же методы редактора, что и тулбар, —
6
- * поэтому menubar НИЧЕГО не дублирует по логике, только даёт второй способ доступа.
7
- *
8
- * Тот же модуль строит контекстное меню (правый клик) — это просто всплывающая
9
- * панель из тех же MenuItemSpec в точке курсора.
10
- */
11
- import type { Strings } from './i18n';
12
- export interface MenuItemSpec {
13
- label?: string;
14
- icon?: string;
15
- separator?: boolean;
16
- action?: () => void;
17
- submenu?: MenuItemSpec[];
18
- enabled?: () => boolean;
19
- }
20
- export interface MenuSpec {
21
- label: string;
22
- items: MenuItemSpec[];
23
- }
24
- export interface MenubarHost {
25
- shell: HTMLElement;
26
- strings: Strings;
27
- }
28
- export declare class Menubar {
29
- private menus;
30
- private host;
31
- private bar?;
32
- private openPanels;
33
- private activeTop?;
34
- private tops;
35
- private onAway;
36
- private onKey;
37
- constructor(menus: MenuSpec[], host: MenubarHost);
38
- /** Полоса меню (для вставки над тулбаром). */
39
- render(): HTMLElement;
40
- /** Roving-фокус на верхний пункт i (обновляет tabindex). */
41
- private focusTop;
42
- private currentTopIndex;
43
- /** Клавиатура на верхних пунктах menubar. */
44
- private onTopKey;
45
- private toggleTop;
46
- private openTop;
47
- /** Включённые пункты панели (для клавиатурной навигации). */
48
- private itemsOf;
49
- /** Фокус на пункт панели по индексу (отрицательный = с конца). */
50
- private focusPanelItem;
51
- /** Клавиатура внутри панели меню/подменю/контекста (WAI-ARIA menu). */
52
- private onPanelKey;
53
- /** Контекстное меню (правый клик): панель из items в точке (clientX, clientY). */
54
- openContext(items: MenuItemSpec[], clientX: number, clientY: number): void;
55
- private buildPanel;
56
- private openSubmenu;
57
- /** Закрыть все панели глубже указанной (для переключения подменю на одном уровне). */
58
- private closeDeeperThan;
59
- closeAll(): void;
60
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * Офлайн-активация: подписанный ECDSA (P-256) файл лицензии вместо живого
3
- * запроса на license-сервер — для корпоративных клиентов без исходящего
4
- * доступа в интернет (не грейс-кэш, а изначально нулевая сетевая
5
- * зависимость от нашей инфраструктуры).
6
- *
7
- * Приватный ключ подписи НИКОГДА не попадает в этот бандл — генерируется и
8
- * хранится отдельно (см. apps/license-server/src/offline-license-cli.mjs).
9
- * Публичный ключ ниже — не секрет по своей природе (нужен только для
10
- * проверки подписи, не для её создания), безопасно шить в открытый код.
11
- *
12
- * Формат подписи — raw IEEE-P1363 (r||s, 64 байта для P-256), НЕ DER: это
13
- * то, что ждёт браузерный Web Crypto API. Node по умолчанию подписывает в
14
- * DER — CLI обязан использовать { dsaEncoding: 'ieee-p1363' }, иначе
15
- * подпись здесь не пройдёт верификацию (проверено сквозным тестом).
16
- */
17
- export interface OfflineLicensePayload {
18
- v: 1;
19
- key: string;
20
- customer: string;
21
- domains: string[];
22
- plugins: string[];
23
- issuedAt: string;
24
- expiresAt: string | null;
25
- }
26
- export interface OfflineLicenseResult {
27
- valid: boolean;
28
- plugins: string[];
29
- reason?: string;
30
- }
31
- /**
32
- * Проверяет содержимое офлайн-файла лицензии для текущего домена. Никогда
33
- * не бросает исключение — любая проблема (битый файл, неверная подпись,
34
- * домен не совпал, срок истёк) возвращается как `{valid:false, reason}`.
35
- */
36
- export declare function verifyOfflineLicense(fileText: string, domain: string): Promise<OfflineLicenseResult>;
@@ -1,11 +0,0 @@
1
- /**
2
- * Страничный вид (pagination) — безопасный режим: НЕ меняет модель документа и
3
- * не вмешивается в транзакции (исключает риск циклов/поломки редактирования).
4
- * Плагин лишь рисует визуальные границы страниц A4 в отдельном DOM-оверлее
5
- * поверх содержимого, измеряя реальную высоту блоков. Включается переключателем
6
- * (stateRef.on); выключен по умолчанию.
7
- */
8
- import { Plugin } from 'prosemirror-state';
9
- export declare function paginationPlugin(stateRef: {
10
- on: boolean;
11
- }): Plugin;
package/dist/paste.d.ts DELETED
@@ -1,54 +0,0 @@
1
- /**
2
- * Smart-paste: очистка HTML, вставленного из Microsoft Word / Excel / Google Docs.
3
- *
4
- * Главная боль ЭДО - пользователи копируют документы из Word, и обычные
5
- * редакторы тащат за собой `mso-` стили, `<o:p>`, условные комментарии и
6
- * мусорные span-ы. Здесь это вычищается ДО парсинга в модель документа,
7
- * сохраняя реальную структуру (заголовки, списки, форматирование).
8
- *
9
- * Премиум-функция: включается лицензией (plugin "smart-paste").
10
- */
11
- export declare function inertCreate<K extends keyof HTMLElementTagNameMap>(tag: K): HTMLElementTagNameMap[K];
12
- /**
13
- * Убирает дубль-таблицы Word `mc:AlternateContent`. Word кладёт в буфер ОДИН и
14
- * тот же блок дважды - основная вёрстка (Choice) и запасная (Fallback, часто с
15
- * упрощёнными/битыми ширинами). Здесь среди верхнеуровневых таблиц с идентичным
16
- * текстом оставляем ПЕРВУЮ (Choice - она структурнее), последующие дубли убираем.
17
- */
18
- export declare function dedupeAlternateTables(root: HTMLElement): void;
19
- /**
20
- * Удаляет узлы-комментарии из DOM. Безопасно для структуры: комментарий -
21
- * отдельный узел и не может содержать элементы, поэтому вложенные таблицы не
22
- * пострадают (в отличие от строкового regex `<!--…-->`, который мог «съесть»
23
- * таблицу, оказавшуюся между двумя Word-комментариями).
24
- */
25
- export declare function stripComments(root: HTMLElement): void;
26
- export declare function cleanWordHTML(html: string): string;
27
- export declare function normalizeWordWhitespace(root: HTMLElement): void;
28
- export declare function inlineWordStyles(root: HTMLElement): void;
29
- /** Переносит высоту строк (<tr height>) на ячейки - высота полей форм. */
30
- export declare function copyRowHeights(root: HTMLElement): void;
31
- export declare function normalizeTableWidths(root: HTMLElement): void;
32
- export declare function reconstructWordFootnotes(root: HTMLElement): void;
33
- /**
34
- * Восстанавливает списки Word во ВСЁМ поддереве.
35
- *
36
- * Раньше обход шёл только по прямым детям root, и это молча теряло списки в
37
- * двух совершенно обычных случаях:
38
- *
39
- * - абзацы внутри обёртки `<div class=WordSection1>`, которую Word кладёт
40
- * вокруг документа: при вставке из буфера она доходит до нас (там же
41
- * приезжает <style>, ради которого написан inlineWordStyles);
42
- * - абзацы внутри ЯЧЕЕК ТАБЛИЦ - в договорах и актах это встречается
43
- * постоянно.
44
- *
45
- * В обоих случаях пункты оставались абзацами вида «1.Первый пункт» с номером
46
- * внутри текста. Корпусная проверка на 6000 документов этого не поймала,
47
- * потому что шла через docx-import: там разметку порождает конвертер, обёрток
48
- * Word в ней нет, а списки в ячейках приходят уже настоящими <ol>/<ul>.
49
- * То есть 6000 документов проверили путь ИМПОРТА ФАЙЛА, а не путь БУФЕРА.
50
- *
51
- * Контейнеры собираются ДО правок: обработка переставляет узлы, и обход по
52
- * живой коллекции пропускал бы элементы.
53
- */
54
- export declare function reconstructWordLists(root: HTMLElement): void;
@@ -1,2 +0,0 @@
1
- export declare function pdfToHtml(data: ArrayBuffer): Promise<string>;
2
- export declare function pdfToImages(data: ArrayBuffer, scale?: number): Promise<string[]>;
@@ -1,8 +0,0 @@
1
- /**
2
- * Извлекает текст PDF в HTML-абзацы. Строки группируются по Y-координате,
3
- * абзацы разделяются по увеличенному вертикальному промежутку (эвристика —
4
- * качество зависит от PDF; сканы-картинки текста не дают).
5
- */
6
- export declare function pdfToHtml(data: ArrayBuffer): Promise<string>;
7
- /** Рендерит страницы PDF в PNG data-URL (scale: чёткость). */
8
- export declare function pdfToImages(data: ArrayBuffer, scale?: number): Promise<string[]>;
package/dist/print.d.ts DELETED
@@ -1,18 +0,0 @@
1
- /**
2
- * Экспорт документа в PDF средствами браузера (диалог печати → «Сохранить как
3
- * PDF»). Полностью CLIENT-SIDE: содержимое НЕ покидает машину пользователя —
4
- * важно для ЭДО (документы не уходят на сервер). Рендер выполняет движок самого
5
- * браузера, поэтому PDF выглядит как контент в редакторе (WYSIWYG).
6
- *
7
- * Печать идёт в скрытом <iframe>, а не в текущей странице, чтобы напечатать
8
- * ТОЛЬКО документ (без тулбара/UI хоста) и не зависеть от Shadow DOM.
9
- */
10
- /** Собирает самостоятельный HTML-документ для печати из контента редактора. */
11
- export declare function buildPrintHtml(contentHtml: string, lang?: string, title?: string): string;
12
- /**
13
- * Печатает HTML-контент через скрытый iframe. Дожидается загрузки картинок
14
- * (data:/удалённых) перед вызовом print(), но не висит дольше таймаута. iframe
15
- * снимается после закрытия диалога печати (afterprint) либо по страховочному
16
- * таймауту.
17
- */
18
- export declare function printDocument(ownerDoc: Document, contentHtml: string, lang?: string): void;
@@ -1,24 +0,0 @@
1
- /**
2
- * Восстановление картинок при вставке из Word через RTF-флейвор буфера.
3
- *
4
- * Боль: в `text/html` буфера Word кладёт картинки ссылками `file:///C:/...`
5
- * (локальные пути, сайту недоступны) или VML-плейсхолдерами — поэтому при
6
- * обычной вставке картинки теряются. Но тот же буфер содержит `text/rtf`, где
7
- * изображения встроены hex-кодом в группах `{\pict ...}`. Этот же приём
8
- * используют PowerPaste (TinyMCE) и PasteFromOffice (CKEditor).
9
- *
10
- * Здесь RTF разбирается, PNG/JPEG-картинки достаются как data-URI и
11
- * подставляются в плейсхолдеры HTML по порядку. Метафайлы (WMF/EMF) браузер
12
- * не рисует — такие позиции остаются null, чтобы не сбить порядок маппинга.
13
- */
14
- /**
15
- * Извлекает картинки из RTF по порядку. Поддерживаются PNG и JPEG;
16
- * неподдержанные форматы (метафайлы) дают null, сохраняя порядок.
17
- */
18
- export declare function extractRtfImages(rtf: string): (string | null)[];
19
- /**
20
- * Подставляет извлечённые из RTF картинки в плейсхолдеры HTML по порядку.
21
- * Обрабатывает и обычные `<img>`, и VML `<v:imagedata>` (Word рисует ими),
22
- * конвертируя последние в `<img>`, чтобы пережить санитайзинг.
23
- */
24
- export declare function mergeRtfImagesIntoHtml(root: HTMLElement, images: (string | null)[]): void;
package/dist/search.d.ts DELETED
@@ -1,17 +0,0 @@
1
- /**
2
- * Найти/заменить: плагин ProseMirror, который хранит запрос и подсвечивает
3
- * совпадения декорациями (активное — отдельным классом). Поиск по тексту,
4
- * регистронезависимый. Навигация/замена — методами Editor (см. editor.ts).
5
- */
6
- import { Plugin, PluginKey } from 'prosemirror-state';
7
- export interface Match {
8
- from: number;
9
- to: number;
10
- }
11
- export interface SearchState {
12
- query: string;
13
- matches: Match[];
14
- active: number;
15
- }
16
- export declare const searchKey: PluginKey<SearchState>;
17
- export declare function searchPlugin(): Plugin<SearchState>;
@@ -1,8 +0,0 @@
1
- /**
2
- * Slash-меню (Notion-UX): по вводу «/» в начале блока всплывает меню блоков
3
- * (заголовки, списки, цитата, код, таблица) с фильтрацией по тексту, выбором
4
- * стрелками/Enter и кликом. Отличитель от TinyMCE.
5
- */
6
- import { Plugin } from 'prosemirror-state';
7
- import type { Strings } from './i18n';
8
- export declare function slashMenuPlugin(strings: Strings): Plugin;
package/dist/styles.d.ts DELETED
@@ -1,8 +0,0 @@
1
- /**
2
- * Стили KodaEditor как строка (инлайнятся в компонент — без внешних запросов,
3
- * что удобно для строгой CSP и self-hosted поставки).
4
- *
5
- * Дизайн-токены через CSS-переменные, чтобы клиент мог переопределить тему
6
- * одной строкой, не трогая внутренности.
7
- */
8
- export declare const editorStyles = "\n:host {\n display: block;\n}\n.euz {\n --koda-bg: #ffffff;\n --koda-fg: #1a1a1f;\n --koda-muted: #6b7280;\n --koda-border: #e4e4e7;\n --koda-accent: #2f5bff;\n --koda-accent-soft: #eef2ff;\n --koda-toolbar-bg: #fafafa;\n --koda-radius: 10px;\n --koda-font: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n\n display: flex;\n flex-direction: column;\n position: relative;\n border: 1px solid var(--koda-border);\n border-radius: var(--koda-radius);\n background: var(--koda-bg);\n color: var(--koda-fg);\n font-family: var(--koda-font);\n /* \u042F\u0432\u043D\u044B\u0435 font-size/line-height \u043D\u0430 \u043A\u043E\u0440\u043D\u0435: \u044D\u0442\u043E \u041D\u0410\u0421\u041B\u0415\u0414\u0423\u0415\u041C\u042B\u0415 \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u0430, \u043E\u043D\u0438 \u043F\u0440\u043E\u0445\u043E\u0434\u044F\u0442\n \u0441\u043A\u0432\u043E\u0437\u044C \u0433\u0440\u0430\u043D\u0438\u0446\u0443 Shadow DOM \u043E\u0442 \u0445\u043E\u0441\u0442\u0430. \u0411\u0435\u0437 \u043D\u0438\u0445 \u0447\u0443\u0436\u043E\u0439 body{font-size:Npx!important}\n \u0441\u0434\u0432\u0438\u0433\u0430\u043B \u0431\u044B \u0431\u0430\u0437\u043E\u0432\u044B\u0439 \u0440\u0430\u0437\u043C\u0435\u0440 \u0442\u0443\u043B\u0431\u0430\u0440\u0430/\u043C\u0435\u043D\u044E. .koda-content \u043F\u0435\u0440\u0435\u043E\u043F\u0440\u0435\u0434\u0435\u043B\u044F\u0435\u0442 \u043D\u0430 15px. */\n font-size: 14px;\n line-height: 1.45;\n overflow: hidden;\n}\n\n.koda-toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 2px;\n padding: 6px 8px;\n background: var(--koda-toolbar-bg);\n border-bottom: 1px solid var(--koda-border);\n flex-shrink: 0;\n}\n\n.koda-btn {\n appearance: none;\n border: 1px solid transparent;\n background: transparent;\n color: var(--koda-fg);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 34px;\n height: 34px;\n padding: 0 8px;\n border-radius: 7px;\n font-size: 14px;\n font-weight: 600;\n line-height: 1;\n cursor: pointer;\n transition: background 120ms ease, color 120ms ease, border-color 120ms ease;\n}\n.koda-btn svg { width: 18px; height: 18px; display: block; }\n/* \u0421\u0430\u043C\u043E\u043E\u0447\u0435\u0432\u0438\u0434\u043D\u044B\u0435 \u0442\u0435\u043A\u0441\u0442\u043E\u0432\u044B\u0435 \u043A\u043D\u043E\u043F\u043A\u0438: B \u0436\u0438\u0440\u043D\u0430\u044F, I \u043A\u0443\u0440\u0441\u0438\u0432, U \u043F\u043E\u0434\u0447\u0451\u0440\u043A\u043D\u0443\u0442\u0430, S \u0437\u0430\u0447\u0451\u0440\u043A\u043D\u0443\u0442\u0430. */\n.koda-btn[data-tb=\"bold\"] { font-weight: 800; }\n.koda-btn[data-tb=\"italic\"] { font-style: italic; font-family: Georgia, \"Times New Roman\", serif; }\n.koda-btn[data-tb=\"underline\"] { text-decoration: underline; text-underline-offset: 2px; }\n.koda-btn[data-tb=\"strike\"] { text-decoration: line-through; }\n/* \u0412\u044B\u043F\u0430\u0434\u0430\u0448\u043A\u0438 \u043F\u0430\u043D\u0435\u043B\u0438 (\u0441\u0442\u0438\u043B\u044C/\u0448\u0440\u0438\u0444\u0442/\u0440\u0430\u0437\u043C\u0435\u0440) */\n.koda-select {\n appearance: none;\n -webkit-appearance: none;\n border: 1px solid transparent;\n background-color: transparent;\n color: var(--koda-fg);\n height: 34px;\n min-width: 62px;\n max-width: 170px;\n padding: 0 26px 0 9px;\n border-radius: 7px;\n font: 600 13px var(--koda-font);\n cursor: pointer;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23475569' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 7px center;\n background-size: 12px;\n transition: background-color 120ms ease, border-color 120ms ease;\n}\n.koda-select:hover { background-color: #efefef; }\n.koda-select:focus-visible { outline: 2px solid var(--koda-accent); outline-offset: 1px; }\n.koda-btn:hover { background: #efefef; }\n.koda-btn:active { transform: translateY(0.5px); }\n.koda-btn:focus-visible {\n outline: 2px solid var(--koda-accent);\n outline-offset: 1px;\n}\n.koda-btn[aria-pressed=\"true\"] {\n /* \u041A\u043E\u043D\u0442\u0440\u0430\u0441\u0442 \u22654.5:1 (#1a3bbf \u043D\u0430 #e4e9ff \u2248 5.4:1) \u2014 WCAG 1.4.3 */\n background: #e4e9ff;\n color: #1a3bbf;\n border-color: #c4ceff;\n}\n.koda-sep {\n width: 1px;\n height: 18px;\n margin: 0 4px;\n background: var(--koda-border);\n}\n\n.koda-content {\n position: relative;\n font-size: 15px;\n line-height: 1.6;\n /* \u0410\u0434\u0430\u043F\u0442\u0438\u0432\u043D\u0430\u044F \u0432\u044B\u0441\u043E\u0442\u0430 (\u0434\u0435\u0444\u043E\u043B\u0442): \u0440\u0430\u0441\u0442\u0451\u0442 \u0441 \u0442\u0435\u043A\u0441\u0442\u043E\u043C \u0434\u043E \u0434\u043E\u043B\u0438 \u044D\u043A\u0440\u0430\u043D\u0430, \u0434\u0430\u043B\u044C\u0448\u0435 \u2014\n \u0432\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u0438\u0439 \u0441\u043A\u0440\u043E\u043B\u043B. min(A4, 80vh): \u043D\u0430 \u0432\u044B\u0441\u043E\u043A\u043E\u043C \u044D\u043A\u0440\u0430\u043D\u0435 \u0443\u043F\u0438\u0440\u0430\u0435\u0442\u0441\u044F \u0432 \u043B\u0438\u0441\u0442 A4\n (1123px), \u043D\u0430 \u043D\u0438\u0437\u043A\u043E\u043C \u2014 \u0432 80% \u0432\u044B\u0441\u043E\u0442\u044B \u043E\u043A\u043D\u0430, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u0440\u0435\u0434\u0430\u043A\u0442\u043E\u0440 \u0432\u043B\u0435\u0437\u0430\u0435\u0442 \u043D\u0430 \u043B\u044E\u0431\u043E\u043C\n \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0435 \u0438 \u0442\u0443\u043B\u0431\u0430\u0440 \u0432\u0441\u0435\u0433\u0434\u0430 \u0432\u0438\u0434\u0435\u043D. \u041F\u0435\u0440\u0435\u043E\u043F\u0440\u0435\u0434\u0435\u043B\u044F\u0435\u0442\u0441\u044F \u0447\u0435\u0440\u0435\u0437 --koda-max-height\n (\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 none = autogrow; \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u0430\u044F \u0432\u044B\u0441\u043E\u0442\u0430 = \u0444\u0438\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C). */\n overflow-y: auto;\n max-height: var(--koda-max-height, min(1123px, 80vh));\n /* \u00AB\u0421\u0442\u043E\u043B\u00BB \u043F\u043E\u0434 \u043B\u0438\u0441\u0442\u043E\u043C */\n background: #eceef1;\n padding: 18px;\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n/* \u041B\u0438\u0441\u0442 A4: \u0448\u0438\u0440\u0438\u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B, \u043F\u043E\u043B\u044F, \u0442\u0435\u043D\u044C; \u0441\u0442\u0430\u0440\u0442\u0443\u0435\u0442 \u043D\u0435\u0431\u043E\u043B\u044C\u0448\u0438\u043C, \u0440\u0430\u0441\u0442\u0451\u0442 \u043F\u043E \u0442\u0435\u043A\u0441\u0442\u0443. */\n.koda-content .ProseMirror {\n width: min(794px, 100%);\n box-sizing: border-box;\n /* \u0425\u043E\u043B\u0441\u0442-\u00AB\u043B\u0438\u0441\u0442\u00BB \u0437\u0430\u043C\u0435\u0442\u043D\u043E \u043A\u0440\u0443\u043F\u043D\u0435\u0435 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0438 \u0430\u0434\u0430\u043F\u0442\u0438\u0432\u0435\u043D: \u0440\u0430\u0441\u0442\u0451\u0442 \u0441 \u0432\u044B\u0441\u043E\u0442\u043E\u0439\n \u043E\u043A\u043D\u0430 (52vh), \u043D\u043E \u0432 \u0440\u0430\u0437\u0443\u043C\u043D\u044B\u0445 \u0433\u0440\u0430\u043D\u0438\u0446\u0430\u0445 320\u2013640px. \u0411\u043E\u043B\u044C\u0448\u0435 \u043C\u0435\u0441\u0442\u0430 \u0434\u043B\u044F \u0442\u0435\u043A\u0441\u0442\u0430 \u0438\n \u0447\u0442\u043E\u0431\u044B \u0432\u044B\u043F\u0430\u0434\u0430\u0448\u043A\u0438/\u0434\u0438\u0430\u043B\u043E\u0433\u0438 \u043E\u0449\u0443\u0449\u0430\u043B\u0438\u0441\u044C \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043D\u043D\u043E. */\n min-height: clamp(320px, 52vh, 640px);\n /* \u0412 flex-\u043A\u043E\u043B\u043E\u043D\u043A\u0435 \u043B\u0438\u0441\u0442 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E (flex-shrink:1) \u0443\u0436\u0438\u043C\u0430\u043B\u0441\u044F \u043F\u043E\u0434 \u0432\u044B\u0441\u043E\u0442\u0443\n \u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440\u0430, \u0438 \u043A\u043E\u043D\u0442\u0435\u043D\u0442 \u0432\u044B\u043B\u0435\u0437\u0430\u043B \u0437\u0430 \u0431\u0435\u043B\u043E\u0435 \u043F\u043E\u043B\u0435. flex-shrink:0 \u2014 \u043B\u0438\u0441\u0442 \u0440\u0430\u0441\u0442\u0451\u0442\n \u043F\u043E \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u043C\u0443, \u0430 \u0432\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u0438\u0439 \u0441\u043A\u0440\u043E\u043B\u043B \u0431\u0435\u0440\u0451\u0442 \u043D\u0430 \u0441\u0435\u0431\u044F \u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440 (.koda-content). */\n flex-shrink: 0;\n background: #fff;\n padding: 48px 56px;\n box-shadow: 0 1px 10px rgba(0, 0, 0, 0.12);\n border-radius: 2px;\n}\n/* \u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0438 \u043D\u0435 \u0432\u044B\u043B\u0435\u0437\u0430\u044E\u0442 \u0437\u0430 \u043B\u0438\u0441\u0442 */\n.koda-content .ProseMirror img {\n max-width: 100%;\n height: auto;\n}\n\n/* \u0413\u0430\u0441\u0438\u043C \u0434\u0435\u0444\u043E\u043B\u0442\u043D\u044B\u0439 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043D\u044B\u0439 outline \u043D\u0430 \u0441\u0430\u043C\u043E\u043C ProseMirror, \u043D\u043E \u0440\u0438\u0441\u0443\u0435\u043C\n \u0432\u0438\u0434\u0438\u043C\u043E\u0435 \u0444\u043E\u043A\u0443\u0441-\u043A\u043E\u043B\u044C\u0446\u043E \u043D\u0430 \u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440\u0435 \u2014 WCAG 2.4.7 / 2.4.11 */\n.koda-content .ProseMirror { outline: none; }\n.koda-content .ProseMirror:focus { outline: none; }\n.koda-content:focus-within {\n outline: 2px solid var(--koda-accent);\n outline-offset: -2px;\n border-radius: 4px;\n}\n\n/* \u0412\u0438\u0437\u0443\u0430\u043B\u044C\u043D\u043E \u0441\u043A\u0440\u044B\u0442\u044B\u0439 \u0442\u0435\u043A\u0441\u0442 \u0434\u043B\u044F \u0441\u043A\u0440\u0438\u043D\u0440\u0438\u0434\u0435\u0440\u043E\u0432 */\n.koda-sr-only {\n position: absolute;\n width: 1px; height: 1px;\n padding: 0; margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n.koda-content .ProseMirror p { margin: 0 0 0.7em; }\n.koda-content .ProseMirror h1 { font-size: 1.8em; line-height: 1.2; margin: 0.4em 0 0.3em; }\n.koda-content .ProseMirror h2 { font-size: 1.45em; line-height: 1.25; margin: 0.4em 0 0.3em; }\n.koda-content .ProseMirror h3 { font-size: 1.2em; margin: 0.4em 0 0.3em; }\n.koda-content .ProseMirror blockquote {\n margin: 0.6em 0;\n padding: 0.2em 0 0.2em 1em;\n border-left: 3px solid var(--koda-accent);\n color: var(--koda-muted);\n}\n.koda-content .ProseMirror pre {\n background: #0f172a;\n color: #e2e8f0;\n padding: 12px 14px;\n border-radius: 8px;\n overflow-x: auto;\n font-size: 13px;\n}\n.koda-content .ProseMirror code {\n background: #f1f5f9;\n padding: 1px 5px;\n border-radius: 4px;\n font-size: 0.9em;\n}\n.koda-content .ProseMirror pre code { background: transparent; padding: 0; }\n.koda-content .ProseMirror ul,\n.koda-content .ProseMirror ol { padding-left: 1.4em; margin: 0.4em 0; }\n.koda-content .ProseMirror a { color: var(--koda-accent); }\n.koda-content .ProseMirror img { max-width: 100%; height: auto; border-radius: 4px; }\n.koda-content .ProseMirror img.ProseMirror-selectednode { outline: 2px solid var(--koda-accent); }\n\n/* \u0422\u0430\u0431\u043B\u0438\u0446\u044B */\n.koda-content .tableWrapper { overflow-x: auto; margin: 0.6em 0; }\n.koda-content .ProseMirror table {\n border-collapse: collapse;\n table-layout: fixed;\n /* \u0428\u0438\u0440\u0438\u043D\u0443 \u0434\u0435\u0440\u0436\u0438\u0442 ResponsiveTableView (width:100% + colgroup \u0432 %) \u2014 \u0442\u0430\u0431\u043B\u0438\u0446\u0430\n \u0440\u0435\u0437\u0438\u043D\u043E\u0432\u0430\u044F \u043D\u0430 \u0448\u0438\u0440\u0438\u043D\u0443 \u043B\u0438\u0441\u0442\u0430, \u043F\u0440\u043E\u043F\u043E\u0440\u0446\u0438\u0438 \u043A\u043E\u043B\u043E\u043D\u043E\u043A \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u044B. */\n width: 100%;\n max-width: 100%;\n overflow: hidden;\n}\n.koda-content .ProseMirror td,\n.koda-content .ProseMirror th {\n border: 1px solid var(--koda-border);\n padding: 6px 9px;\n vertical-align: top;\n position: relative;\n min-width: 2em;\n}\n.koda-content .ProseMirror th {\n background: #f4f4f5;\n font-weight: 600;\n text-align: left;\n}\n.koda-content .ProseMirror td > *,\n.koda-content .ProseMirror th > * { margin-bottom: 0; }\n.koda-content .ProseMirror .selectedCell::after {\n content: \"\";\n position: absolute;\n inset: 0;\n background: rgba(47, 91, 255, 0.12);\n pointer-events: none;\n}\n.koda-content .ProseMirror .column-resize-handle {\n position: absolute;\n right: -3px;\n top: 0;\n bottom: 0;\n width: 6px;\n background: var(--koda-accent);\n opacity: 0.7;\n border-radius: 3px;\n pointer-events: none;\n z-index: 15;\n}\n.koda-content .ProseMirror.resize-cursor { cursor: col-resize; }\n/* \u041F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0430: \u043F\u0440\u0438 \u043D\u0430\u0432\u0435\u0434\u0435\u043D\u0438\u0438 \u043D\u0430 \u0442\u0430\u0431\u043B\u0438\u0446\u0443 \u043F\u0440\u0430\u0432\u044B\u0435 \u0433\u0440\u0430\u043D\u0438\u0446\u044B \u044F\u0447\u0435\u0435\u043A \u0447\u0443\u0442\u044C \u043F\u043E\u0434\u0441\u0432\u0435\u0447\u0438\u0432\u0430\u044E\u0442\u0441\u044F */\n.koda-content .ProseMirror table:hover td,\n.koda-content .ProseMirror table:hover th { transition: border-color 120ms ease; }\n\n/* \u041F\u043B\u0435\u0439\u0441\u0445\u043E\u043B\u0434\u0435\u0440: \u043A\u043B\u0430\u0441\u0441 is-empty + data-placeholder \u0432\u0435\u0448\u0430\u0435\u0442 \u043F\u043B\u0430\u0433\u0438\u043D \u0434\u0435\u043A\u043E\u0440\u0430\u0446\u0438\u0439\n \u043D\u0430 \u043F\u0443\u0441\u0442\u043E\u0439 \u043F\u0435\u0440\u0432\u044B\u0439 \u0431\u043B\u043E\u043A (\u0441\u043C. placeholderPlugin \u0432 editor.ts) */\n.koda-content .ProseMirror .is-empty::before {\n content: attr(data-placeholder);\n color: var(--koda-muted);\n pointer-events: none;\n height: 0;\n float: left;\n}\n\n/* \u0412\u043E\u0434\u044F\u043D\u043E\u0439 \u0437\u043D\u0430\u043A \u0434\u0435\u043C\u043E/\u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u043E\u0439 \u043B\u0438\u0446\u0435\u043D\u0437\u0438\u0438.\n \u0410\u0433\u0440\u0435\u0441\u0441\u0438\u0432\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C: \u0434\u0438\u0430\u0433\u043E\u043D\u0430\u043B\u044C\u043D\u043E\u0435 \u0437\u0430\u043C\u043E\u0449\u0435\u043D\u0438\u0435 \u043F\u043E \u0432\u0441\u0435\u0439 \u043A\u0430\u043D\u0432\u0435 (\u0444\u043E\u043D \u0440\u0430\u0441\u0442\u0451\u0442 \u0441\n \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043C \u0438 \u043F\u043E\u043A\u0440\u044B\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u043F\u0440\u0438 \u0441\u043A\u0440\u043E\u043B\u043B\u0435) + sticky-\u043F\u0438\u043B\u044E\u043B\u044F \u0441\u0432\u0435\u0440\u0445\u0443 \u0434\u043B\u044F\n \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E\u0441\u0442\u0438 (role=status). pointer-events:none \u2014 \u043F\u0435\u0447\u0430\u0442\u0430\u0442\u044C \u043C\u043E\u0436\u043D\u043E. */\n.koda-content.koda-demo .ProseMirror {\n background-image: var(--koda-wm);\n background-repeat: repeat;\n /* \u0427\u0442\u043E\u0431\u044B \u0437\u0430\u043C\u043E\u0449\u0435\u043D\u0438\u0435 \u043F\u0435\u0447\u0430\u0442\u0430\u043B\u043E\u0441\u044C (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u044B \u0433\u0430\u0441\u044F\u0442 \u0444\u043E\u043D \u043F\u0440\u0438 \u043F\u0435\u0447\u0430\u0442\u0438). */\n -webkit-print-color-adjust: exact;\n print-color-adjust: exact;\n}\n.koda-watermark {\n position: sticky;\n top: 8px;\n z-index: 30;\n width: fit-content;\n max-width: 92%;\n margin: 0 auto 6px;\n padding: 5px 14px;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.03em;\n color: #b91c1c;\n background: #fff1f2;\n border: 1px solid #fecaca;\n border-radius: 999px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n text-align: center;\n pointer-events: none;\n}\n\n/* Slash-\u043C\u0435\u043D\u044E (Notion-UX) */\n.koda-slash {\n position: absolute;\n z-index: 20;\n min-width: 210px;\n max-height: 280px;\n overflow-y: auto;\n background: #fff;\n border: 1px solid var(--koda-border);\n border-radius: 10px;\n box-shadow: 0 8px 28px rgba(0, 0, 0, 0.12);\n padding: 4px;\n}\n.koda-slash-item {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 7px 9px;\n border: none;\n background: transparent;\n border-radius: 7px;\n font-size: 14px;\n text-align: left;\n cursor: pointer;\n color: var(--koda-fg);\n}\n.koda-slash-item:hover,\n.koda-slash-item.is-active { background: var(--koda-accent-soft); }\n.koda-slash-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border: 1px solid var(--koda-border);\n border-radius: 6px;\n font-size: 12px;\n font-weight: 600;\n color: var(--koda-muted);\n flex: none;\n}\n\n/* Floating toolbar (Notion-UX) */\n.koda-floating {\n position: absolute;\n z-index: 20;\n display: flex;\n gap: 2px;\n background: #1a1a1f;\n border-radius: 8px;\n padding: 4px;\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);\n}\n.koda-floating .koda-btn { color: #fff; height: 28px; min-width: 28px; }\n.koda-floating .koda-btn:hover { background: rgba(255, 255, 255, 0.14); }\n.koda-floating .koda-btn[aria-pressed=\"true\"] {\n background: var(--koda-accent);\n color: #fff;\n border-color: var(--koda-accent);\n}\n\n/* \u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u043D\u0430\u044F \u043F\u0430\u043D\u0435\u043B\u044C \u0442\u0430\u0431\u043B\u0438\u0446\u044B \u2014 \u0432\u0441\u043F\u043B\u044B\u0432\u0430\u0435\u0442 \u043D\u0430\u0434 \u0442\u0430\u0431\u043B\u0438\u0446\u0435\u0439, \u043A\u043E\u0433\u0434\u0430 \u043A\u0443\u0440\u0441\u043E\u0440 \u0432\u043D\u0443\u0442\u0440\u0438 */\n.koda-table-toolbar {\n position: absolute;\n z-index: 19;\n display: flex;\n gap: 2px;\n background: #1a1a1f;\n border-radius: 8px;\n padding: 4px;\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);\n}\n.koda-table-toolbar .koda-btn { color: #fff; height: 28px; min-width: 28px; }\n.koda-table-toolbar .koda-btn:hover { background: rgba(255, 255, 255, 0.14); }\n\n/* \u0414\u0438\u0430\u043B\u043E\u0433 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043F\u0430\u043D\u0435\u043B\u0438 (\u00AB\u043A\u0430\u0440\u0430\u043D\u0434\u0430\u0448\u00BB) */\n.koda-customizer-list {\n max-height: 320px; overflow-y: auto;\n display: flex; flex-direction: column; gap: 4px; margin: 10px 0;\n}\n.koda-customizer-row {\n display: flex; align-items: center; gap: 6px;\n padding: 4px 6px; border: 1px solid #e2e2e6; border-radius: 6px; background: #fff;\n}\n.koda-customizer-ctl {\n width: 26px; height: 26px; flex: 0 0 auto;\n border: 1px solid #e2e2e6; border-radius: 4px; background: #fff; cursor: pointer;\n font-size: 12px; line-height: 1;\n}\n.koda-customizer-ctl:hover:not(:disabled) { background: #f2f2f5; }\n.koda-customizer-ctl:disabled { opacity: 0.3; cursor: default; }\n.koda-customizer-label { margin-left: 6px; }\n.koda-customizer-add { min-width: 160px; height: 34px; }\n\n/* \u0414\u0438\u0430\u043B\u043E\u0433 (\u0432\u0441\u0442\u0430\u0432\u043A\u0430 \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0438) */\n.koda-loading {\n position: absolute; inset: 0; z-index: 40;\n display: flex; align-items: center; justify-content: center;\n background: rgba(255,255,255,0.6);\n}\n.koda-loading-box {\n display: flex; align-items: center; gap: 10px;\n background: #111827; color: #fff; padding: 12px 18px;\n border-radius: 10px; font-size: 14px; box-shadow: 0 8px 30px rgba(0,0,0,0.25);\n}\n.koda-spinner {\n width: 16px; height: 16px; border-radius: 50%;\n border: 2px solid rgba(255,255,255,0.35); border-top-color: #fff;\n animation: koda-spin 0.7s linear infinite;\n}\n@keyframes koda-spin { to { transform: rotate(360deg); } }\n/* \u0422\u0440\u0430\u043D\u0437\u0438\u0435\u043D\u0442\u043D\u0430\u044F \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0430 (toast) \u2014 \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 \u00AB\u0432\u044B\u0434\u0435\u043B\u0438\u0442\u0435 2+ \u044F\u0447\u0435\u0439\u043A\u0438\u00BB. */\n.koda-toast {\n position: absolute;\n left: 50%;\n bottom: 16px;\n transform: translateX(-50%);\n z-index: 80;\n max-width: 90%;\n padding: 9px 14px;\n background: #1f2937;\n color: #fff;\n font: 500 13px var(--koda-font);\n border-radius: 8px;\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);\n opacity: 0;\n animation: koda-toast-in 180ms ease-out forwards;\n pointer-events: none;\n}\n@keyframes koda-toast-in {\n from { opacity: 0; transform: translate(-50%, 8px); }\n to { opacity: 1; transform: translate(-50%, 0); }\n}\n.koda-dialog-backdrop {\n /* fixed (\u043D\u0435 absolute): \u0434\u0438\u0430\u043B\u043E\u0433 \u0446\u0435\u043D\u0442\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044F \u0432\u043E \u0412\u042C\u042E\u041F\u041E\u0420\u0422\u0415 \u0438 \u043D\u0435 \u043E\u0431\u0440\u0435\u0437\u0430\u0435\u0442\u0441\u044F\n \u0440\u043E\u0434\u0438\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u043C .euz{overflow:hidden} \u043F\u0440\u0438 \u043A\u043E\u0440\u043E\u0442\u043A\u043E\u043C \u0445\u043E\u043B\u0441\u0442\u0435. z-index \u0432\u044B\u0448\u0435 \u0432\u0441\u0435\u0445\n \u0432\u043D\u0443\u0442\u0440\u0435\u043D\u043D\u0438\u0445 \u043E\u0432\u0435\u0440\u043B\u0435\u0435\u0432 (\u043C\u0435\u043D\u044E/\u0442\u0443\u043B\u0431\u0430\u0440), \u0447\u0442\u043E\u0431\u044B \u0431\u044B\u0442\u044C \u043F\u043E\u0432\u0435\u0440\u0445. */\n position: fixed;\n inset: 0;\n z-index: 2147483001;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.18);\n}\n.koda-dialog {\n width: min(460px, 92%);\n background: #fff;\n border: 1px solid var(--koda-border);\n border-radius: 14px;\n box-shadow: 0 20px 60px rgba(15, 23, 42, 0.22);\n padding: 22px;\n font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;\n}\n.koda-dialog-title {\n font-weight: 700;\n font-size: 16px;\n line-height: 1.3;\n color: #0f172a;\n letter-spacing: -0.01em;\n margin: 0 0 16px;\n}\n.koda-dialog-input {\n width: 100%;\n box-sizing: border-box;\n padding: 10px 12px;\n border: 1px solid var(--koda-border);\n border-radius: 9px;\n font-size: 14px;\n color: #0f172a;\n outline: none;\n transition: border-color 0.15s, box-shadow 0.15s;\n}\n.koda-dialog-input::placeholder { color: #94a3b8; }\n.koda-dialog-input:focus {\n border-color: var(--koda-accent);\n box-shadow: 0 0 0 3px rgba(47, 107, 255, 0.18);\n}\n.koda-dialog-row {\n display: flex;\n flex-wrap: wrap; /* \u043C\u043D\u043E\u0433\u043E \u043A\u043D\u043E\u043F\u043E\u043A (\u044D\u043A\u0441\u043F\u043E\u0440\u0442: 7) \u043F\u0435\u0440\u0435\u043D\u043E\u0441\u044F\u0442\u0441\u044F, \u0430 \u043D\u0435 \u0432\u044B\u043B\u0435\u0437\u0430\u044E\u0442 \u0437\u0430 \u043F\u043E\u043F\u0430\u043F */\n gap: 8px;\n justify-content: center;\n margin-top: 18px;\n}\n.koda-dialog-btn {\n min-width: 96px;\n padding: 10px 16px;\n border: 1px solid var(--koda-border);\n background: #fff;\n color: #1f2937;\n border-radius: 10px;\n font-size: 13px;\n font-weight: 600;\n white-space: nowrap;\n text-align: center;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s, transform 0.08s, box-shadow 0.15s;\n}\n.koda-dialog-btn:hover { background: #f8fafc; border-color: #cbd5e1; }\n.koda-dialog-btn:active { transform: translateY(1px); }\n.koda-dialog-btn:focus-visible {\n outline: 2px solid var(--koda-accent);\n outline-offset: 2px;\n}\n.koda-dialog-btn.primary {\n background: var(--koda-accent);\n color: #fff;\n border-color: var(--koda-accent);\n box-shadow: 0 2px 10px rgba(47, 107, 255, 0.3);\n}\n.koda-dialog-btn.primary:hover { background: var(--koda-accent); filter: brightness(1.06); }\n\n/* \u0420\u0435\u0441\u0430\u0439\u0437 \u043A\u0430\u0440\u0442\u0438\u043D\u043E\u043A: \u043E\u0431\u0451\u0440\u0442\u043A\u0430 + \u0440\u0443\u0447\u043A\u0430 \u0432 \u043F\u0440\u0430\u0432\u043E\u043C-\u043D\u0438\u0436\u043D\u0435\u043C \u0443\u0433\u043B\u0443 */\n.koda-content .ProseMirror .koda-img-wrap { display: inline-block; position: relative; line-height: 0; max-width: 100%; }\n.koda-content .ProseMirror .koda-img-wrap img { display: inline-block; max-width: 100%; height: auto; vertical-align: bottom; }\n.koda-content .ProseMirror .koda-img-resize {\n position: absolute; right: 2px; bottom: 2px; width: 12px; height: 12px;\n background: var(--koda-accent); border: 2px solid #fff; border-radius: 2px;\n box-sizing: border-box; cursor: nwse-resize; display: none; z-index: 2;\n}\n.koda-content .ProseMirror .koda-img-wrap:hover .koda-img-resize,\n.koda-content .ProseMirror .koda-img-wrap.ProseMirror-selectednode .koda-img-resize { display: block; }\n.koda-content .ProseMirror .koda-img-crop {\n position: absolute; left: 2px; top: 2px; width: 20px; height: 20px;\n background: rgba(0,0,0,0.55); color: #fff; border-radius: 3px;\n font-size: 13px; line-height: 20px; text-align: center; cursor: pointer;\n display: none; z-index: 2; user-select: none;\n}\n.koda-content .ProseMirror .koda-img-wrap:hover .koda-img-crop,\n.koda-content .ProseMirror .koda-img-wrap.ProseMirror-selectednode .koda-img-crop { display: block; }\n.koda-content .ProseMirror .koda-crop-overlay {\n position: absolute; inset: 0; z-index: 5; cursor: crosshair;\n background: rgba(0,0,0,0.30);\n}\n.koda-content .ProseMirror .koda-crop-sel {\n position: absolute; display: none; border: 1px dashed #fff;\n box-shadow: 0 0 0 9999px rgba(0,0,0,0.45); pointer-events: none;\n}\n.koda-content .ProseMirror .koda-crop-bar {\n position: absolute; left: 50%; bottom: 6px; transform: translateX(-50%);\n display: flex; gap: 6px; z-index: 6;\n}\n\n/* \u041D\u0430\u0439\u0442\u0438/\u0437\u0430\u043C\u0435\u043D\u0438\u0442\u044C */\n.koda-search { display: flex; flex-direction: column; gap: 6px; padding: 8px; background: var(--koda-toolbar-bg); border-bottom: 1px solid var(--koda-border); flex-shrink: 0; }\n.koda-search[hidden] { display: none; }\n.koda-search-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.koda-search-input { flex: 1 1 160px; min-width: 110px; height: 30px; box-sizing: border-box; padding: 0 9px; border: 1px solid var(--koda-border); border-radius: 7px; font: 400 14px var(--koda-font); color: var(--koda-fg); background: #fff; }\n.koda-search-input:focus-visible { outline: 2px solid var(--koda-accent); outline-offset: 1px; }\n.koda-search-count { font: 600 12px var(--koda-font); color: var(--koda-muted); min-width: 42px; text-align: center; }\n.koda-search-btn { height: 30px; min-width: 30px; box-sizing: border-box; padding: 0 10px; border: 1px solid var(--koda-border); background: #fff; border-radius: 7px; font: 600 13px var(--koda-font); color: var(--koda-fg); cursor: pointer; transition: background 120ms ease; }\n.koda-search-btn:hover { background: #efefef; }\n.koda-search-btn:focus-visible { outline: 2px solid var(--koda-accent); outline-offset: 1px; }\n.koda-content .ProseMirror .koda-search-match { background: rgba(255, 213, 0, 0.45); border-radius: 2px; }\n.koda-content .ProseMirror .koda-search-active { background: #ff9632; border-radius: 2px; }\n\n/* \u041F\u043E\u043B\u043D\u043E\u044D\u043A\u0440\u0430\u043D\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C (maximize \u0431\u0435\u0437 Fullscreen API \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u043C) */\n:host([data-fullscreen]) { position: fixed; inset: 0; z-index: 2147483000; background: var(--koda-bg); }\n:host([data-fullscreen]) .euz { height: 100%; border-radius: 0; border: none; }\n:host([data-fullscreen]) .koda-content { max-height: none; flex: 1 1 auto; }\n\n/* === \u041A\u043D\u043E\u043F\u043A\u0430-\u0433\u0440\u0443\u043F\u043F\u0430 \u043D\u0430 \u043F\u0430\u043D\u0435\u043B\u0438 (\u0440\u0430\u0441\u043A\u0440\u044B\u0432\u0430\u0435\u0442 \u043C\u0435\u043D\u044E) === */\n.koda-grp-btn { display: inline-flex; align-items: center; gap: 5px; width: auto; padding: 0 8px; }\n.koda-grp-btn .koda-grp-label { font: 600 13px var(--koda-font); white-space: nowrap; }\n.koda-grp-btn .koda-grp-caret { font-size: 10px; opacity: 0.7; }\n.koda-grp-logo { width: 18px; height: 18px; object-fit: contain; display: block; }\n.koda-grp-emoji { font-size: 16px; line-height: 1; }\n.koda-grp-menu { position: fixed; z-index: 2147483000; min-width: 180px; max-height: 92vh; overflow-y: auto; padding: 5px; background: var(--koda-bg); border: 1px solid var(--koda-border); border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.14); display: flex; flex-direction: column; gap: 2px; }\n.koda-grp-menu-item { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border-radius: 7px; cursor: pointer; }\n.koda-grp-menu-item:hover { background: var(--koda-accent-soft); }\n.koda-grp-menu-item .koda-btn { pointer-events: none; }\n.koda-grp-menu-label { font: 500 13px var(--koda-font); color: var(--koda-fg); }\n\n/* === \u0420\u0435\u0436\u0438\u043C \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u043F\u0430\u043D\u0435\u043B\u0438 (\u043A\u0430\u0440\u0430\u043D\u0434\u0430\u0448) === */\n.koda-edit { display: flex; flex-direction: column; gap: 8px; padding: 10px; background: var(--koda-toolbar-bg); border-bottom: 1px solid var(--koda-border); }\n.koda-edit-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }\n.koda-edit-title { font: 700 14px var(--koda-font); color: var(--koda-fg); }\n.koda-edit-spacer { flex: 1 1 auto; }\n.koda-edit-action { height: 32px; padding: 0 12px; border: 1px solid var(--koda-border); background: #fff; border-radius: 8px; font: 600 13px var(--koda-font); color: var(--koda-fg); cursor: pointer; transition: background 120ms ease, border-color 120ms ease; }\n.koda-edit-action:hover { background: #f1f5ff; border-color: var(--koda-accent); }\n.koda-edit-action--primary { background: var(--koda-accent); border-color: var(--koda-accent); color: #fff; }\n.koda-edit-action--primary:hover { background: #2347d8; }\n.koda-edit-action--ghost { color: var(--koda-muted); }\n.koda-edit-hint { font: 400 12px var(--koda-font); color: var(--koda-muted); }\n.koda-edit-row, .koda-palette { display: flex; align-items: flex-start; flex-wrap: wrap; gap: 6px; padding: 8px; border: 1px dashed var(--koda-border); border-radius: 10px; background: var(--koda-bg); min-height: 46px; }\n.koda-palette { background: #f6f7fb; border-style: solid; }\n.koda-palette-title { font: 600 12px var(--koda-font); color: var(--koda-muted); margin-top: 2px; }\n\n/* \u0424\u0438\u0448\u043A\u0430 \u043A\u043D\u043E\u043F\u043A\u0438 \u0432 \u0440\u0435\u0434\u0430\u043A\u0442\u043E\u0440\u0435 */\n.koda-chip { position: relative; display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 10px; border: 1px solid var(--koda-border); border-radius: 8px; background: #fff; font: 500 13px var(--koda-font); color: var(--koda-fg); cursor: grab; user-select: none; touch-action: none; transition: box-shadow 120ms ease, border-color 120ms ease; }\n.koda-chip:hover { border-color: var(--koda-accent); box-shadow: 0 1px 4px rgba(47,91,255,0.12); }\n.koda-chip:active { cursor: grabbing; }\n.koda-chip .koda-chip-ic { display: inline-flex; width: 18px; height: 18px; }\n.koda-chip .koda-chip-ic svg { width: 18px; height: 18px; }\n.koda-chip-lb { white-space: nowrap; }\n.koda-chip--sep { padding: 0 12px; }\n.koda-chip-bar { display: inline-block; width: 2px; height: 18px; background: var(--koda-border); border-radius: 1px; }\n.koda-chip-x { position: absolute; top: -7px; right: -7px; width: 18px; height: 18px; padding: 0; border: none; border-radius: 50%; background: #ef4444; color: #fff; font: 700 13px/16px var(--koda-font); cursor: pointer; display: none; align-items: center; justify-content: center; box-shadow: 0 1px 3px rgba(0,0,0,0.25); }\n.koda-chip:hover > .koda-chip-x { display: inline-flex; }\n.koda-chip--pal { background: #fff; cursor: grab; }\n.koda-chip--ghost { position: fixed; z-index: 9999; pointer-events: none; opacity: 0.92; box-shadow: 0 8px 24px rgba(0,0,0,0.22); border-color: var(--koda-accent); margin: 0; }\n.koda-chip--placeholder { opacity: 0.35; border-style: dashed; }\n\n/* \u041A\u0430\u043F\u0441\u0443\u043B\u0430 \u0433\u0440\u0443\u043F\u043F\u044B \u0432 \u0440\u0435\u0434\u0430\u043A\u0442\u043E\u0440\u0435 */\n.koda-grp-chip { flex-direction: column; align-items: stretch; gap: 6px; padding: 8px; height: auto; background: var(--koda-accent-soft); border-color: #c7d2fe; cursor: default; }\n.koda-grp-head { display: flex; align-items: center; gap: 6px; cursor: grab; }\n.koda-grp-head .koda-grp-ic { width: 18px; height: 18px; }\n.koda-grp-head .koda-grp-ic svg, .koda-grp-head .koda-grp-ic img { width: 18px; height: 18px; }\n.koda-grp-lb { font: 600 13px var(--koda-font); flex: 1 1 auto; white-space: nowrap; }\n.koda-grp-ctl { width: 22px; height: 22px; padding: 0; border: 1px solid transparent; border-radius: 6px; background: rgba(255,255,255,0.6); color: var(--koda-fg); cursor: pointer; font-size: 13px; line-height: 1; }\n.koda-grp-ctl:hover { border-color: var(--koda-accent); background: #fff; }\n.koda-grp-items { display: flex; align-items: center; flex-wrap: wrap; gap: 5px; min-width: 60px; min-height: 40px; padding: 5px; border: 1px dashed #c7d2fe; border-radius: 7px; background: rgba(255,255,255,0.55); }\n\n/* \u041B\u043E\u0433\u043E-\u043F\u0438\u043A\u0435\u0440 \u0433\u0440\u0443\u043F\u043F\u044B */\n.koda-iconpick { max-width: 460px; }\n.koda-iconpick-top { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }\n.koda-iconpick-preview { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; border: 1px solid var(--koda-border); border-radius: 9px; background: var(--koda-toolbar-bg); flex-shrink: 0; }\n.koda-iconpick-preview svg, .koda-iconpick-preview img { width: 24px; height: 24px; }\n.koda-iconpick-preview .koda-grp-emoji { font-size: 22px; }\n.koda-iconpick-top .koda-dialog-input { flex: 1 1 auto; }\n.koda-iconpick-tabs { display: flex; gap: 4px; margin-bottom: 8px; }\n.koda-iconpick-tab { height: 30px; padding: 0 12px; border: 1px solid var(--koda-border); background: #fff; border-radius: 7px; font: 600 12px var(--koda-font); color: var(--koda-muted); cursor: pointer; }\n.koda-iconpick-tab.is-active { background: var(--koda-accent); border-color: var(--koda-accent); color: #fff; }\n.koda-iconpick-grid { display: flex; flex-wrap: wrap; gap: 6px; max-height: 220px; overflow-y: auto; padding: 4px; }\n.koda-iconpick-cell { width: 40px; height: 40px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--koda-border); border-radius: 8px; background: #fff; cursor: pointer; }\n.koda-iconpick-cell:hover { border-color: var(--koda-accent); background: var(--koda-accent-soft); }\n.koda-iconpick-cell.is-sel { border-color: var(--koda-accent); box-shadow: 0 0 0 2px var(--koda-accent-soft); }\n.koda-iconpick-cell svg { width: 22px; height: 22px; }\n.koda-iconpick-cell .koda-grp-emoji { font-size: 20px; }\n.koda-iconpick-file { font: 400 13px var(--koda-font); }\n\n/* === \u0412\u0435\u0440\u0445\u043D\u0435\u0435 \u043C\u0435\u043D\u044E (menubar) === */\n.koda-menubar { display: flex; align-items: stretch; gap: 1px; padding: 2px 6px; background: var(--koda-bg); border-bottom: 1px solid var(--koda-border); flex-wrap: wrap; }\n.koda-menu-top { height: 30px; padding: 0 11px; border: none; background: transparent; border-radius: 6px; font: 500 13.5px var(--koda-font); color: var(--koda-fg); cursor: pointer; }\n.koda-menu-top:hover { background: var(--koda-toolbar-bg); }\n.koda-menu-top.is-active { background: var(--koda-accent-soft); color: var(--koda-accent); }\n.koda-menu-top:focus-visible { outline: 2px solid var(--koda-accent); outline-offset: -2px; }\n\n/* \u0412\u044B\u043F\u0430\u0434\u0430\u044E\u0449\u0430\u044F \u043F\u0430\u043D\u0435\u043B\u044C \u043C\u0435\u043D\u044E (\u0438 \u043F\u043E\u0434\u043C\u0435\u043D\u044E, \u0438 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u043D\u043E\u0435 \u043C\u0435\u043D\u044E) */\n/* fixed: \u043C\u0435\u043D\u044E \u043F\u043E\u0437\u0438\u0446\u0438\u043E\u043D\u0438\u0440\u0443\u0435\u0442\u0441\u044F \u043F\u043E \u0432\u044C\u044E\u043F\u043E\u0440\u0442\u0443 (menubar.ts) \u0438 \u043D\u0435 \u0440\u0435\u0436\u0435\u0442\u0441\u044F \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u043C\n \u0445\u043E\u043B\u0441\u0442\u043E\u043C. max-height/overflow \u2014 \u0435\u0441\u043B\u0438 \u043C\u0435\u043D\u044E \u0432\u044B\u0448\u0435 \u043E\u043A\u043D\u0430, \u043E\u043D\u043E \u0441\u043A\u0440\u043E\u043B\u043B\u0438\u0442\u0441\u044F. */\n.koda-menu { position: fixed; z-index: 2147483000; min-width: 190px; max-width: 320px; max-height: 92vh; overflow-y: auto; padding: 5px; background: var(--koda-bg); border: 1px solid var(--koda-border); border-radius: 10px; box-shadow: 0 12px 34px rgba(0,0,0,0.16); display: flex; flex-direction: column; gap: 1px; }\n.koda-menu-item { display: flex; align-items: center; gap: 9px; width: 100%; height: 32px; padding: 0 9px; border: none; background: transparent; border-radius: 7px; font: 500 13px var(--koda-font); color: var(--koda-fg); cursor: pointer; text-align: left; }\n.koda-menu-item:hover:not(:disabled) { background: var(--koda-accent-soft); }\n.koda-menu-item:disabled { opacity: 0.4; cursor: default; }\n.koda-menu-item:focus-visible { outline: 2px solid var(--koda-accent); outline-offset: -2px; }\n.koda-menu-ic { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; flex-shrink: 0; color: var(--koda-muted); }\n.koda-menu-ic svg { width: 17px; height: 17px; }\n.koda-menu-lb { flex: 1 1 auto; white-space: nowrap; }\n.koda-menu-arrow { margin-left: auto; font-size: 11px; color: var(--koda-muted); }\n.koda-menu-sep { height: 1px; margin: 4px 6px; background: var(--koda-border); }\n\n/* === \u0412\u0441\u0442\u0430\u0432\u043A\u0430 \u0442\u0430\u0431\u043B\u0438\u0446\u044B: grid-picker (\u043A\u0430\u043A \u0432 Word) === */\n/* Picker \u041D\u0415 \u043D\u0430\u0441\u043B\u0435\u0434\u0443\u0435\u0442 \u0448\u0438\u0440\u0438\u043D\u0443 \u043E\u0431\u044B\u0447\u043D\u043E\u0433\u043E \u0434\u0438\u0430\u043B\u043E\u0433\u0430 (460px) \u2014 \u0443\u0436\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u043F\u043E\u0434 \u0433\u0440\u0438\u0434. */\n.koda-tablepicker { display: inline-block; width: auto; max-width: 92vw; }\n/* \u0411\u0415\u0417 overflow/max-width: \u0438\u043D\u0430\u0447\u0435 \u0441\u0435\u0442\u043A\u0430-\u0432\u044B\u0431\u043E\u0440 \u0440\u0435\u0437\u0430\u043B\u0430\u0441\u044C \u0441\u043F\u0440\u0430\u0432\u0430 (scroll-\u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440 \u043D\u0430\n 2px \u0443\u0436\u0435 \u043A\u043E\u043D\u0442\u0435\u043D\u0442\u0430). \u0414\u0438\u0430\u043B\u043E\u0433 .koda-tablepicker \u0438\u043C\u0435\u0435\u0442 width:auto \u0438 \u0441\u0430\u043C \u0440\u0430\u0441\u0442\u0451\u0442 \u043F\u043E\u0434\n \u0441\u0435\u0442\u043A\u0443 \u2014 \u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440 \u0430\u0434\u0430\u043F\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044F, \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043E\u0431\u0440\u0435\u0437\u0430\u0435\u0442\u0441\u044F. */\n.koda-tp-grid { display: grid; gap: 2px; margin: 6px 0 4px; user-select: none; justify-content: start; }\n/* box-sizing:border-box \u2014 \u0431\u043E\u0440\u0434\u044E\u0440 \u0432\u0445\u043E\u0434\u0438\u0442 \u0432 18px, \u0438\u043D\u0430\u0447\u0435 \u044F\u0447\u0435\u0439\u043A\u0430 \u0440\u0435\u043D\u0434\u0435\u0440\u0438\u0442\u0441\u044F 20px,\n \u043D\u0435 \u0432\u043B\u0435\u0437\u0430\u0435\u0442 \u0432 18px-\u0442\u0440\u0435\u043A \u0433\u0440\u0438\u0434\u0430, \u0438 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u0441\u0442\u043E\u043B\u0431\u0435\u0446 \u0440\u0435\u0436\u0435\u0442\u0441\u044F \u0441\u043F\u0440\u0430\u0432\u0430 \u043D\u0430 2px. */\n.koda-tp-cell { box-sizing: border-box; width: 18px; height: 18px; border: 1px solid var(--koda-border); border-radius: 2px; background: #fff; cursor: pointer; transition: background 60ms; }\n.koda-tp-cell.on { background: var(--koda-accent-soft); border-color: var(--koda-accent); }\n.koda-tp-label { text-align: center; font: 600 13px var(--koda-font); color: var(--koda-fg); margin-bottom: 6px; min-height: 18px; }\n.koda-tp-manual { display: flex; align-items: flex-end; gap: 10px; margin-top: 6px; padding-top: 10px; border-top: 1px solid var(--koda-border); }\n.koda-tp-field { display: flex; flex-direction: column; gap: 3px; font: 500 12px var(--koda-font); color: var(--koda-muted); }\n.koda-tp-field input { width: 64px; height: 30px; box-sizing: border-box; padding: 0 8px; border: 1px solid var(--koda-border); border-radius: 7px; font: 400 13px var(--koda-font); color: var(--koda-fg); background: #fff; }\n.koda-tp-manual .koda-dialog-btn { margin-left: auto; }\n\n/* === \u0414\u0438\u0430\u043B\u043E\u0433\u0438 \u0441\u0432\u043E\u0439\u0441\u0442\u0432 \u0442\u0430\u0431\u043B\u0438\u0446\u044B / \u044F\u0447\u0435\u0439\u043A\u0438 (A3) === */\n.koda-props { min-width: 320px; max-width: 380px; }\n.koda-props-row { display: flex; align-items: center; gap: 12px; margin: 8px 0; }\n.koda-props-label { flex: 0 0 130px; font: 500 13px var(--koda-font); color: var(--koda-fg); }\n.koda-props-field { flex: 1 1 auto; height: 32px; box-sizing: border-box; padding: 0 8px; border: 1px solid var(--koda-border); border-radius: 7px; font: 400 13px var(--koda-font); color: var(--koda-fg); background: #fff; }\ninput.koda-props-field[type=color] { padding: 2px; height: 32px; cursor: pointer; }\ninput.koda-props-field[type=number] { max-width: 90px; }\n.koda-props-bg { display: flex; align-items: center; gap: 10px; border: none; padding: 0; height: auto; }\n.koda-props-bg input[type=color] { flex: 0 0 48px; }\n.koda-props-bgoff { display: inline-flex; align-items: center; gap: 4px; font: 400 12.5px var(--koda-font); color: var(--koda-muted); white-space: nowrap; }\n.koda-props-check { width: 15px; height: 15px; }\n\n/* === \u041F\u043E\u043B\u044F \u0448\u0430\u0431\u043B\u043E\u043D\u0430 (merge fields) \u0434\u043B\u044F \u042D\u0414\u041E (B1) === */\n.koda-content .ProseMirror .koda-merge-field {\n display: inline-block; padding: 1px 8px; margin: 0 1px; border-radius: 5px;\n background: var(--koda-accent-soft); color: var(--koda-accent);\n border: 1px solid color-mix(in oklab, var(--koda-accent) 35%, transparent);\n font: 600 0.92em var(--koda-font); white-space: nowrap; cursor: default;\n user-select: all; vertical-align: baseline;\n}\n.koda-content .ProseMirror .koda-merge-field::before { content: '\u2B21 '; opacity: 0.6; font-size: 0.85em; }\n.koda-merge-picker { min-width: 300px; max-width: 420px; }\n.koda-merge-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin: 6px 0 12px; }\n.koda-merge-pick {\n display: flex; flex-direction: column; align-items: flex-start; gap: 2px;\n padding: 8px 10px; border: 1px solid var(--koda-border); border-radius: 8px;\n background: #fff; cursor: pointer; text-align: left; transition: border-color 120ms ease, background 120ms ease;\n}\n.koda-merge-pick:hover { border-color: var(--koda-accent); background: var(--koda-accent-soft); }\n.koda-merge-pick-lb { font: 600 13px var(--koda-font); color: var(--koda-fg); }\n.koda-merge-pick-key { font: 400 11px ui-monospace, monospace; color: var(--koda-muted); }\n\n/* === \u0421\u043D\u043E\u0441\u043A\u0438 (B2): \u0430\u0432\u0442\u043E-\u043D\u0443\u043C\u0435\u0440\u0430\u0446\u0438\u044F CSS-\u0441\u0447\u0451\u0442\u0447\u0438\u043A\u043E\u043C === */\n.koda-content .ProseMirror { counter-reset: koda-fn; }\n.koda-content .ProseMirror .koda-footnote {\n counter-increment: koda-fn; cursor: help; color: var(--koda-accent);\n font-weight: 700; vertical-align: super; font-size: 0.72em; padding: 0 1px; user-select: all;\n}\n.koda-content .ProseMirror .koda-footnote::after { content: counter(koda-fn); }\n\n/* === \u041E\u0433\u043B\u0430\u0432\u043B\u0435\u043D\u0438\u0435 (B2) === */\n.koda-content .ProseMirror .koda-toc {\n border: 1px solid var(--koda-border); border-left: 3px solid var(--koda-accent);\n border-radius: 8px; padding: 12px 16px; margin: 14px 0; background: var(--koda-toolbar-bg);\n user-select: none;\n}\n.koda-toc-title { font: 700 14px var(--koda-font); color: var(--koda-fg); margin-bottom: 8px; }\n.koda-toc-item { font: 400 13px var(--koda-font); padding: 2px 0; cursor: pointer; color: var(--koda-fg); line-height: 1.5; }\n.koda-toc-item:hover { color: var(--koda-accent); }\n.koda-toc-num { color: var(--koda-muted); font-weight: 600; }\n.koda-toc-empty { color: var(--koda-muted); font-size: 13px; }\n\n/* === \u0413\u0430\u043B\u0435\u0440\u0435\u044F \u0441\u0442\u0438\u043B\u0435\u0439 (\u0441\u0438\u0441\u0442\u0435\u043C\u0430 \u0441\u0442\u0438\u043B\u0435\u0439) === */\n.koda-style-gallery {\n position: absolute; z-index: 60; min-width: 220px; max-height: 380px; overflow-y: auto;\n padding: 5px; background: var(--koda-bg); border: 1px solid var(--koda-border);\n border-radius: 10px; box-shadow: 0 12px 34px rgba(0,0,0,0.16);\n display: flex; flex-direction: column; gap: 2px;\n}\n.koda-style-item { display: block; width: 100%; padding: 7px 11px; border: none; background: transparent; border-radius: 7px; cursor: pointer; text-align: left; }\n.koda-style-item:hover { background: var(--koda-accent-soft); }\n.koda-style-item:focus-visible { outline: 2px solid var(--koda-accent); outline-offset: -2px; }\n.koda-style-prev { display: block; color: var(--koda-fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n\n/* === \u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 \u0440\u0435\u0446\u0435\u043D\u0437\u0435\u043D\u0442\u0430 (C1) === */\n.koda-content .ProseMirror .koda-comment {\n background: rgba(255, 200, 0, 0.28); border-bottom: 2px solid rgba(230, 160, 0, 0.65);\n cursor: pointer; border-radius: 2px;\n}\n.koda-content .ProseMirror .koda-comment:hover { background: rgba(255, 200, 0, 0.45); }\n.koda-comment-popup {\n position: absolute; z-index: 65; max-width: 280px; min-width: 160px; padding: 10px 12px;\n background: var(--koda-bg); border: 1px solid var(--koda-border); border-radius: 10px;\n box-shadow: 0 10px 30px rgba(0,0,0,0.16);\n}\n.koda-comment-text { font: 400 13px var(--koda-font); color: var(--koda-fg); margin-bottom: 8px; white-space: pre-wrap; }\n.koda-comment-resolve {\n height: 28px; padding: 0 12px; border: 1px solid var(--koda-border); background: var(--koda-accent-soft);\n color: var(--koda-accent); border-radius: 7px; font: 600 12px var(--koda-font); cursor: pointer;\n}\n.koda-comment-resolve:hover { background: var(--koda-accent); color: #fff; }\n.koda-dialog-hint { font: 400 12px var(--koda-font); color: #ef4444; margin: 4px 0 0; min-height: 14px; }\n\n/* === Track changes (C2): \u043F\u0440\u0435\u0434\u043B\u0430\u0433\u0430\u0435\u043C\u044B\u0435 \u043F\u0440\u0430\u0432\u043A\u0438 === */\n.koda-content .ProseMirror .koda-ins {\n color: #15803d; text-decoration: underline; text-decoration-color: rgba(21,128,61,0.5);\n background: rgba(21,128,61,0.08);\n}\n.koda-content .ProseMirror .koda-del {\n color: #b91c1c; text-decoration: line-through; text-decoration-color: rgba(185,28,28,0.75);\n background: rgba(185,28,28,0.08);\n}\n/* \u0418\u043D\u0434\u0438\u043A\u0430\u0442\u043E\u0440 \u0432\u043A\u043B\u044E\u0447\u0451\u043D\u043D\u043E\u0433\u043E \u0440\u0435\u0436\u0438\u043C\u0430 \u0440\u0435\u0446\u0435\u043D\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u2014 \u0442\u043E\u043D\u043A\u0430\u044F \u043F\u043E\u043B\u043E\u0441\u0430 \u0441\u0432\u0435\u0440\u0445\u0443 \u043A\u043E\u043D\u0442\u0435\u043D\u0442\u0430 */\n.koda-content .ProseMirror[data-track=\"1\"] { box-shadow: inset 0 3px 0 -1px var(--koda-accent); }\n\n/* \u041A\u0438\u0441\u0442\u044C \u0444\u043E\u0440\u043C\u0430\u0442\u0430 \u0430\u043A\u0442\u0438\u0432\u043D\u0430 (D1): \u044F\u0432\u043D\u0430\u044F \u0441\u043F\u043B\u043E\u0448\u043D\u0430\u044F \u0437\u0430\u043B\u0438\u0432\u043A\u0430 + \u0431\u0435\u043B\u0430\u044F \u0438\u043A\u043E\u043D\u043A\u0430 + \u043C\u044F\u0433\u043A\u043E\u0435\n \u043A\u043E\u043B\u044C\u0446\u043E \u2014 \u043D\u0435\u0434\u0432\u0443\u0441\u043C\u044B\u0441\u043B\u0435\u043D\u043D\u044B\u0439 \u00AB\u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442 \u0432\u043A\u043B\u044E\u0447\u0451\u043D\u00BB, \u043F\u043E\u043A\u0430 \u043A\u043E\u043F\u0438\u0440\u0443\u0435\u0442\u0441\u044F \u0444\u043E\u0440\u043C\u0430\u0442.\n \u0426\u0432\u0435\u0442 \u0444\u043E\u043D\u0430/\u043A\u043E\u043B\u044C\u0446\u0430 \u043D\u0430 var(--koda-accent) (\u043D\u0430\u0434\u0451\u0436\u043D\u043E \u0440\u0435\u0437\u043E\u043B\u0432\u0438\u0442\u0441\u044F). \u041F\u0443\u043B\u044C\u0441\u0430\u0446\u0438\u044F \u2014\n \u043D\u0435\u043D\u0430\u0432\u044F\u0437\u0447\u0438\u0432\u044B\u0439 \u0441\u0438\u0433\u043D\u0430\u043B, \u0447\u0442\u043E \u043A\u0438\u0441\u0442\u044C \u00AB\u0436\u0434\u0451\u0442\u00BB \u043A\u043B\u0438\u043A; \u0433\u0430\u0441\u0438\u0442\u0441\u044F prefers-reduced-motion. */\n.koda-btn.is-armed,\n.koda-btn.is-armed:hover {\n background: var(--koda-accent);\n color: #fff;\n border-color: var(--koda-accent);\n box-shadow: 0 0 0 2px var(--koda-accent-soft), 0 0 0 3px var(--koda-accent);\n animation: koda-armed-pulse 1.4s ease-in-out infinite;\n}\n.koda-btn.is-armed svg { color: #fff; stroke: #fff; }\n@keyframes koda-armed-pulse {\n 0%, 100% { box-shadow: 0 0 0 2px var(--koda-accent-soft), 0 0 0 3px var(--koda-accent); }\n 50% { box-shadow: 0 0 0 4px var(--koda-accent-soft), 0 0 0 3px var(--koda-accent); }\n}\n@media (prefers-reduced-motion: reduce) {\n .koda-btn.is-armed { animation: none; }\n}\n\n/* === \u0421\u0442\u0440\u0430\u043D\u0438\u0447\u043D\u044B\u0439 \u0432\u0438\u0434 (D5) === */\n.koda-content.koda-paged { position: relative; background: #ececed; overflow: visible; max-height: none; padding: 16px 0; }\n.koda-content.koda-paged .ProseMirror {\n max-width: 794px; min-height: 1000px; margin: 0 auto; background: #fff;\n padding: 56px 64px; box-shadow: 0 2px 16px rgba(0,0,0,0.12); box-sizing: border-box;\n}\n.koda-page-overlay { position: absolute; inset: 16px 0; pointer-events: none; z-index: 3; max-width: 794px; margin: 0 auto; left: 0; right: 0; }\n.koda-page-divider { position: absolute; left: 0; right: 0; border-top: 2px dashed var(--koda-accent); opacity: 0.55; }\n.koda-page-num {\n position: absolute; right: 6px; top: 4px; font: 600 11px var(--koda-font); color: var(--koda-accent);\n background: var(--koda-bg); padding: 1px 7px; border-radius: 6px; border: 1px solid var(--koda-border);\n}\n\n/* === \u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E\u0441\u0442\u0438 (D2) === */\n.koda-a11y { min-width: 320px; max-width: 420px; }\n.koda-a11y-list { margin: 8px 0; display: flex; flex-direction: column; gap: 6px; }\n.koda-a11y-ok { font: 600 14px var(--koda-font); color: #15803d; padding: 12px; text-align: center; }\n.koda-a11y-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: 1px solid var(--koda-border); border-radius: 8px; font: 400 13px var(--koda-font); color: var(--koda-fg); }\n.koda-a11y-count { flex-shrink: 0; min-width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; background: #fef3c7; color: #b45309; border-radius: 6px; font-weight: 700; font-size: 12px; }\n\n/* === \u0418\u0441\u0442\u043E\u0440\u0438\u044F \u0432\u0435\u0440\u0441\u0438\u0439 (C3) === */\n.koda-versions { min-width: 360px; max-width: 440px; }\n.koda-versions-list { max-height: 280px; overflow-y: auto; margin: 8px 0; }\n.koda-versions-empty { color: var(--koda-muted); font: 400 13px var(--koda-font); padding: 18px; text-align: center; }\n.koda-version-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: 1px solid var(--koda-border); border-radius: 8px; margin-bottom: 6px; }\n.koda-version-meta { flex: 1 1 auto; min-width: 0; }\n.koda-version-name { font: 600 13px var(--koda-font); color: var(--koda-fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.koda-version-date { font: 400 11px var(--koda-font); color: var(--koda-muted); }\n.koda-version-del { width: 26px; height: 26px; border: none; background: transparent; color: var(--koda-muted); border-radius: 6px; cursor: pointer; font-size: 13px; flex-shrink: 0; }\n.koda-version-del:hover { background: rgba(239,68,68,0.12); color: #ef4444; }\n";