kingkont 0.7.83 → 0.7.85

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.
@@ -14,6 +14,7 @@ beautiful Cyrillic-aware label nodes without needing a network connection.
14
14
  | Comfortaa | Johan Aakerlund | <https://fonts.google.com/specimen/Comfortaa> |
15
15
  | PT Serif | ParaType | <https://fonts.google.com/specimen/PT+Serif> |
16
16
  | PT Mono | ParaType | <https://fonts.google.com/specimen/PT+Mono> |
17
+ | Neucha | Jovanny Lemonad | <https://fonts.google.com/specimen/Neucha> |
17
18
 
18
19
  Files were downloaded from the @fontsource jsDelivr mirror in the
19
20
  `cyrillic-400-normal` and `latin-400-normal` subsets.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kingkont",
3
- "version": "0.7.83",
3
+ "version": "0.7.85",
4
4
  "description": "KingKont · Chatium — нод-редактор сцен с AI-генерацией (картинки/видео/голос/SFX/музыка/текст)",
5
5
  "main": "main.js",
6
6
  "bin": {
package/renderer/board.js CHANGED
@@ -1638,8 +1638,14 @@ async function createNodeEl(node) {
1638
1638
  el.dataset.id = node.id;
1639
1639
  el.style.left = node.x + 'px';
1640
1640
  el.style.top = node.y + 'px';
1641
- if (node.width) el.style.width = node.width + 'px';
1642
- if (node.height) el.style.height = node.height + 'px';
1641
+ // Label-ноды всегда auto-размер (по тексту) фиксированный width/height
1642
+ // создавал «невидимые боксы» вокруг текста, что ломало hit-testing когда
1643
+ // несколько label оказывались рядом (клик попадал в перекрывающий, а не
1644
+ // в видимый текст). Игнорируем сохранённые width/height для label-нод.
1645
+ if (node.type !== 'label') {
1646
+ if (node.width) el.style.width = node.width + 'px';
1647
+ if (node.height) el.style.height = node.height + 'px';
1648
+ }
1643
1649
 
1644
1650
  const header = document.createElement('div');
1645
1651
  header.className = 'node-header';
@@ -211,9 +211,10 @@ async function addLabelAt(pos) {
211
211
  id: crypto.randomUUID(),
212
212
  type: 'label',
213
213
  text: '',
214
- textStyle: { fontSize: 32, italic: false, fontFamily: 'handwritten' },
214
+ textStyle: { fontSize: 32, italic: false, fontFamily: 'pencil' },
215
215
  x, y,
216
- width: 260, height: 90,
216
+ // width/height не задаём — label всегда auto-размер по тексту
217
+ // (см. createNodeEl: для type='label' inline-style не применяется).
217
218
  };
218
219
  state.currentBoard.metadata.nodes.push(node);
219
220
  const el = await createNodeEl(node);
@@ -66,7 +66,8 @@ $('settingsRegen').addEventListener('click', () => {
66
66
  // сохранённые сцены. Новые красивые шрифты добавляем под новыми id.
67
67
  const LABEL_FONTS = [
68
68
  { id: 'default', label: 'Обычный' },
69
- { id: 'handwritten', label: 'Карандашом' }, // Caveat
69
+ { id: 'pencil', label: 'Грифель' }, // Neucha — шероховатый карандашный
70
+ { id: 'handwritten', label: 'Прописью' }, // Caveat — гладкий рукописный
70
71
  { id: 'brush', label: 'Скорописью' }, // Bad Script
71
72
  { id: 'marker', label: 'Кистью' }, // Pacifico
72
73
  { id: 'display', label: 'Декор' }, // Lobster
@@ -618,6 +619,42 @@ function attachResize(el, node, handle) {
618
619
  const startX = e.clientX, startY = e.clientY;
619
620
  const startW = el.offsetWidth, startH = el.offsetHeight;
620
621
 
622
+ // Label: ресайз-хендл МАСШТАБИРУЕТ шрифт, а не меняет размеры бокса.
623
+ // Бокс auto-sized по тексту, поэтому при увеличении fontSize он
624
+ // автоматически растёт. Используем диагональную дистанцию (max от dx/dy)
625
+ // — даёт интуитивный «оттянуть угол → больше».
626
+ if (node.type === 'label') {
627
+ if (!node.textStyle) node.textStyle = { fontSize: 32, italic: false, fontFamily: 'pencil' };
628
+ const startSize = node.textStyle.fontSize || 32;
629
+ const labelTextEl = el.querySelector('.label-text');
630
+ const sizeSel = el.querySelector('.label-toolbar select[title="Размер шрифта"]');
631
+ const onMoveL = ev => {
632
+ const dx = (ev.clientX - startX) / state.zoom;
633
+ const dy = (ev.clientY - startY) / state.zoom;
634
+ // Линейная зависимость: ~0.6px шрифта на пиксель диагонального drag.
635
+ // Берём «среднее с уклоном на больший» — чтобы юзер мог тянуть как
636
+ // вправо-вниз (увеличивать), так и влево-вверх (уменьшать).
637
+ const delta = (dx + dy) * 0.6;
638
+ const newSize = Math.max(8, Math.min(200, Math.round(startSize + delta)));
639
+ node.textStyle.fontSize = newSize;
640
+ if (labelTextEl) labelTextEl.style.fontSize = newSize + 'px';
641
+ // Синхронизируем dropdown в тулбаре, если есть подходящий option.
642
+ if (sizeSel) {
643
+ const has = Array.from(sizeSel.options).some(o => o.value === String(newSize));
644
+ if (has) sizeSel.value = String(newSize);
645
+ }
646
+ renderConnections();
647
+ };
648
+ const onUpL = () => {
649
+ document.removeEventListener('mousemove', onMoveL);
650
+ document.removeEventListener('mouseup', onUpL);
651
+ scheduleSave();
652
+ };
653
+ document.addEventListener('mousemove', onMoveL);
654
+ document.addEventListener('mouseup', onUpL);
655
+ return;
656
+ }
657
+
621
658
  // Aspect-lock для image/video: соблюдаем ratio оригинала. Берём nat-
622
659
  // ural размеры media-элемента, считаем chrome (header + footer + padding)
623
660
  // как разницу высоты ноды и высоты media. На этапе resize меняем ту ось,
@@ -82,6 +82,16 @@
82
82
  src: url('../assets/fonts/pt-mono-latin-400.woff2') format('woff2');
83
83
  unicode-range: U+0000-00FF;
84
84
  }
85
+ @font-face {
86
+ font-family: 'KK Neucha'; font-style: normal; font-weight: 400; font-display: block;
87
+ src: url('../assets/fonts/neucha-cyrillic-400.woff2') format('woff2');
88
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
89
+ }
90
+ @font-face {
91
+ font-family: 'KK Neucha'; font-style: normal; font-weight: 400; font-display: block;
92
+ src: url('../assets/fonts/neucha-latin-400.woff2') format('woff2');
93
+ unicode-range: U+0000-00FF;
94
+ }
85
95
 
86
96
  * { box-sizing: border-box; margin: 0; padding: 0; }
87
97
  html, body { height: 100%; }
@@ -415,48 +425,86 @@
415
425
  }
416
426
 
417
427
  /* === Label-node — плавающая текст-аннотация поверх холста ===
418
- Без фона / рамки / тени выглядит как «написанный» прямо на сцене текст.
419
- Header / footer / resize / anchor скрыты по умолчанию, появляются на
420
- hover чтобы можно было перетаскивать, удалять, ресайзить. */
428
+ Auto-размер по тексту: .node автоматически сжимается до контента,
429
+ никаких «невидимых боксов» вокруг текста. Header / toolbar /
430
+ resize / anchor вынесены за пределы видимой области (absolute) и
431
+ показываются на hover. */
421
432
  .node.label-node {
422
433
  background: transparent;
423
434
  border: none; box-shadow: none;
424
435
  padding: 0;
436
+ overflow: visible; /* absolute-дети могут выйти за рамки */
437
+ width: auto !important; /* override любого inline-width из старых сцен */
438
+ height: auto !important;
439
+ min-width: 30px;
440
+ max-width: 800px; /* лимит чтобы не получить однострочного монстра */
441
+ display: block; /* отключаем flex-column — нам не нужно */
442
+ contain: none; /* отменяем content-visibility-оптимизацию */
443
+ contain-intrinsic-size: auto;
425
444
  }
426
445
  .node.label-node.selected {
427
446
  outline: 1px dashed rgba(90,168,255,0.8);
428
447
  outline-offset: 2px;
429
448
  }
430
- .node.label-node .node-header,
431
- .node.label-node .node-footer,
432
- .node.label-node .resize-handle,
433
- .node.label-node .anchor,
434
- .node.label-node .label-toolbar {
449
+
450
+ /* Header (drag-handle + имя + ×) — absolute над текстом, hover-only. */
451
+ .node.label-node .node-header {
452
+ position: absolute;
453
+ bottom: 100%; /* над label, без занятия layout */
454
+ left: 0; right: 0;
455
+ background: rgba(20,20,20,0.92);
456
+ border-radius: 4px 4px 0 0;
457
+ margin-bottom: 2px;
435
458
  opacity: 0; transition: opacity 0.15s;
459
+ pointer-events: none; /* hover-only включаем ниже */
460
+ }
461
+ /* Footer для label не используется (нет сгенерированной информации) — скрываем. */
462
+ .node.label-node .node-footer { display: none; }
463
+ /* Anchor для label не нужен — это просто аннотация, не источник для генерации. */
464
+ .node.label-node .anchor { display: none; }
465
+ /* Resize-handle для label НЕ ресайзит ширину/высоту, а МАСШТАБИРУЕТ шрифт.
466
+ Видимый правый-нижний уголок, hover-only, чтобы не отвлекать. */
467
+ .node.label-node .resize-handle {
468
+ position: absolute;
469
+ right: -2px; bottom: -2px;
470
+ width: 14px; height: 14px;
471
+ background: rgba(90,168,255,0.85);
472
+ border: 1px solid rgba(255,255,255,0.4);
473
+ border-radius: 3px;
474
+ cursor: nwse-resize;
475
+ opacity: 0; transition: opacity 0.15s;
476
+ z-index: 11;
436
477
  }
437
- .node.label-node:hover .node-header,
438
- .node.label-node:hover .node-footer,
439
478
  .node.label-node:hover .resize-handle,
440
- .node.label-node:hover .anchor,
441
- .node.label-node:hover .label-toolbar,
442
- .node.label-node:focus-within .label-toolbar { opacity: 1; }
443
- .node.label-node .node-header {
444
- background: rgba(20,20,20,0.85);
445
- border-radius: 6px 6px 0 0;
446
- }
479
+ .node.label-node:focus-within .resize-handle { opacity: 1; }
480
+
481
+ /* Body wrapper без визуального вклада. */
447
482
  .node.label-node .node-body {
448
483
  padding: 0; background: transparent;
449
- display: flex; flex-direction: column; min-height: 0;
484
+ display: block; min-height: 0;
485
+ position: relative;
450
486
  }
451
487
 
452
- /* Тулбар форматирования label поверх текста, на hover. */
488
+ /* Тулбар форматирования — absolute под текстом, hover-only. */
453
489
  .node.label-node .label-toolbar {
490
+ position: absolute;
491
+ top: 100%; /* под label */
492
+ left: 0;
493
+ margin-top: 4px;
454
494
  display: flex; align-items: center; gap: 4px;
455
495
  padding: 3px 6px; background: rgba(20,20,20,0.92);
456
- border-radius: 4px; margin: 4px;
496
+ border-radius: 4px;
457
497
  font-size: 11px; flex-shrink: 0;
458
- align-self: flex-start;
459
- pointer-events: auto;
498
+ opacity: 0; transition: opacity 0.15s;
499
+ pointer-events: none;
500
+ white-space: nowrap;
501
+ z-index: 10;
502
+ }
503
+ .node.label-node:hover .node-header,
504
+ .node.label-node:hover .label-toolbar,
505
+ .node.label-node:focus-within .node-header,
506
+ .node.label-node:focus-within .label-toolbar {
507
+ opacity: 1; pointer-events: auto;
460
508
  }
461
509
  .node.label-node .label-toolbar select,
462
510
  .node.label-node .label-toolbar button {
@@ -471,15 +519,23 @@
471
519
  }
472
520
  .node.label-node .label-toolbar .tt-italic { font-style: italic; min-width: 22px; }
473
521
 
474
- /* Сам текст. flex:1 занимает оставшееся тело, поэтому drag-area
475
- совпадает с визуальной областью. */
522
+ /* Сам текстauto-ширина по контенту, до max-width.
523
+ line-height: 1.5 чтобы descender'ы рукописных шрифтов (Caveat g/p/y,
524
+ Pacifico, Bad Script) не клипались по нижнему краю. padding 8px вверху
525
+ и внизу — на случай если у конкретной семьи descender лезет ещё дальше. */
476
526
  .node.label-node .label-text {
477
- flex: 1; padding: 8px 12px;
478
- color: #eaeaea; line-height: 1.2;
527
+ display: inline-block;
528
+ padding: 8px 10px;
529
+ color: #eaeaea; line-height: 1.5;
479
530
  word-break: break-word; white-space: pre-wrap;
480
531
  outline: none; cursor: text;
481
532
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
482
533
  text-shadow: 0 1px 2px rgba(0,0,0,0.6);
534
+ min-width: 60px;
535
+ max-width: 100%;
536
+ /* min-height в em — гарантирует что пустая нода (только placeholder)
537
+ всё равно имеет высоту хотя бы одной строки выбранного шрифта. */
538
+ min-height: 1.5em;
483
539
  }
484
540
  .node.label-node .label-text:empty::before {
485
541
  content: attr(data-placeholder);
@@ -487,10 +543,13 @@
487
543
  }
488
544
  /* === Label-шрифты ===
489
545
  Все варианты — bundled woff2 (см. assets/fonts/), кириллица поддержана. */
546
+ .node.label-node .label-text[data-font="pencil"] {
547
+ /* Грифель — шероховатый «как написано карандашом» (Neucha). */
548
+ font-family: 'KK Neucha', 'Neucha', 'Marker Felt', 'Comic Sans MS', cursive;
549
+ }
490
550
  .node.label-node .label-text[data-font="handwritten"] {
491
- /* Карандашоммягкий рукописный (Caveat). */
551
+ /* Прописьюгладкий рукописный (Caveat). */
492
552
  font-family: 'KK Caveat', 'Caveat', 'Marker Felt', cursive;
493
- line-height: 1.1;
494
553
  }
495
554
  .node.label-node .label-text[data-font="brush"] {
496
555
  /* Скорописью — каллиграфия в русском стиле (Bad Script). */