kingkont 0.7.84 → 0.7.86
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.
- package/assets/fonts/NOTICE.md +1 -0
- package/assets/fonts/neucha-cyrillic-400.woff2 +0 -0
- package/assets/fonts/neucha-latin-400.woff2 +0 -0
- package/package.json +1 -1
- package/renderer/board.js +32 -0
- package/renderer/generate.js +1 -1
- package/renderer/settings.js +92 -61
- package/renderer/styles.css +58 -40
package/assets/fonts/NOTICE.md
CHANGED
|
@@ -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.
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/renderer/board.js
CHANGED
|
@@ -1773,6 +1773,38 @@ function showNodeContextMenu(node, clientX, clientY) {
|
|
|
1773
1773
|
menu.appendChild(b);
|
|
1774
1774
|
};
|
|
1775
1775
|
add(node.name ? `✏ Переименовать (${node.name})` : '✏ Переименовать', () => renameNode(node));
|
|
1776
|
+
// Label: пункты для смены стиля (шрифт / размер / курсив).
|
|
1777
|
+
// Сделано через ПКМ а не постоянный hover-тулбар, чтобы UI label-ноды
|
|
1778
|
+
// оставался «чистым текстом» без фурнитуры.
|
|
1779
|
+
if (node.type === 'label') {
|
|
1780
|
+
if (!node.textStyle) node.textStyle = { fontSize: 32, italic: false, fontFamily: 'pencil' };
|
|
1781
|
+
const ts = node.textStyle;
|
|
1782
|
+
const curFont = LABEL_FONTS.find(f => f.id === ts.fontFamily) || LABEL_FONTS[0];
|
|
1783
|
+
add(`🔤 Шрифт: ${curFont.label}`, async () => {
|
|
1784
|
+
const choice = await askChoice('Шрифт label-ноды', LABEL_FONTS.map(f => f.label), curFont.label);
|
|
1785
|
+
if (!choice) return;
|
|
1786
|
+
const picked = LABEL_FONTS.find(f => f.label === choice);
|
|
1787
|
+
if (!picked) return;
|
|
1788
|
+
ts.fontFamily = picked.id;
|
|
1789
|
+
scheduleSave();
|
|
1790
|
+
await refreshNodeDOM(node.id);
|
|
1791
|
+
});
|
|
1792
|
+
add(`📏 Размер: ${ts.fontSize || 32}px`, async () => {
|
|
1793
|
+
const choice = await askChoice('Размер шрифта', LABEL_FONT_SIZES.map(s => s + 'px'), (ts.fontSize || 32) + 'px');
|
|
1794
|
+
if (!choice) return;
|
|
1795
|
+
const n = parseInt(choice, 10);
|
|
1796
|
+
if (Number.isFinite(n)) {
|
|
1797
|
+
ts.fontSize = n;
|
|
1798
|
+
scheduleSave();
|
|
1799
|
+
await refreshNodeDOM(node.id);
|
|
1800
|
+
}
|
|
1801
|
+
});
|
|
1802
|
+
add(ts.italic ? '𝐼 Курсив: вкл' : '𝐼 Курсив: выкл', async () => {
|
|
1803
|
+
ts.italic = !ts.italic;
|
|
1804
|
+
scheduleSave();
|
|
1805
|
+
await refreshNodeDOM(node.id);
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1776
1808
|
add('📋 Логи', () => showNodeLogs(node));
|
|
1777
1809
|
if (node.generated) add('⚙ Параметры', () => showNodeSettings(node));
|
|
1778
1810
|
if (node.status === 'generating') {
|
package/renderer/generate.js
CHANGED
|
@@ -211,7 +211,7 @@ async function addLabelAt(pos) {
|
|
|
211
211
|
id: crypto.randomUUID(),
|
|
212
212
|
type: 'label',
|
|
213
213
|
text: '',
|
|
214
|
-
textStyle: { fontSize: 32, italic: false, fontFamily: '
|
|
214
|
+
textStyle: { fontSize: 32, italic: false, fontFamily: 'pencil' },
|
|
215
215
|
x, y,
|
|
216
216
|
// width/height не задаём — label всегда auto-размер по тексту
|
|
217
217
|
// (см. createNodeEl: для type='label' inline-style не применяется).
|
package/renderer/settings.js
CHANGED
|
@@ -66,7 +66,8 @@ $('settingsRegen').addEventListener('click', () => {
|
|
|
66
66
|
// сохранённые сцены. Новые красивые шрифты добавляем под новыми id.
|
|
67
67
|
const LABEL_FONTS = [
|
|
68
68
|
{ id: 'default', label: 'Обычный' },
|
|
69
|
-
{ id: '
|
|
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
|
|
@@ -87,73 +88,56 @@ function applyLabelStyle(ed, style) {
|
|
|
87
88
|
function renderLabelNodeBody(node, body) {
|
|
88
89
|
if (!node.textStyle) node.textStyle = { ...LABEL_DEFAULT_STYLE };
|
|
89
90
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
for (const f of LABEL_FONTS) {
|
|
98
|
-
const o = document.createElement('option');
|
|
99
|
-
o.value = f.id; o.textContent = f.label;
|
|
100
|
-
fontSel.appendChild(o);
|
|
101
|
-
}
|
|
102
|
-
fontSel.value = node.textStyle.fontFamily || 'default';
|
|
103
|
-
fontSel.addEventListener('change', () => {
|
|
104
|
-
node.textStyle.fontFamily = fontSel.value;
|
|
105
|
-
applyLabelStyle(ed, node.textStyle);
|
|
106
|
-
scheduleSave();
|
|
107
|
-
});
|
|
108
|
-
tb.appendChild(fontSel);
|
|
109
|
-
|
|
110
|
-
const sizeSel = document.createElement('select');
|
|
111
|
-
sizeSel.title = 'Размер шрифта';
|
|
112
|
-
for (const s of LABEL_FONT_SIZES) {
|
|
113
|
-
const o = document.createElement('option');
|
|
114
|
-
o.value = String(s); o.textContent = s + 'px';
|
|
115
|
-
sizeSel.appendChild(o);
|
|
116
|
-
}
|
|
117
|
-
sizeSel.value = String(node.textStyle.fontSize || 32);
|
|
118
|
-
sizeSel.addEventListener('change', () => {
|
|
119
|
-
node.textStyle.fontSize = parseInt(sizeSel.value, 10) || 32;
|
|
120
|
-
applyLabelStyle(ed, node.textStyle);
|
|
121
|
-
scheduleSave();
|
|
122
|
-
});
|
|
123
|
-
tb.appendChild(sizeSel);
|
|
124
|
-
|
|
125
|
-
const italicBtn = document.createElement('button');
|
|
126
|
-
italicBtn.className = 'tt-italic';
|
|
127
|
-
italicBtn.textContent = 'I';
|
|
128
|
-
italicBtn.title = 'Курсив';
|
|
129
|
-
if (node.textStyle.italic) italicBtn.classList.add('active');
|
|
130
|
-
italicBtn.addEventListener('click', e => {
|
|
131
|
-
e.stopPropagation();
|
|
132
|
-
node.textStyle.italic = !node.textStyle.italic;
|
|
133
|
-
italicBtn.classList.toggle('active', node.textStyle.italic);
|
|
134
|
-
applyLabelStyle(ed, node.textStyle);
|
|
135
|
-
scheduleSave();
|
|
136
|
-
});
|
|
137
|
-
tb.appendChild(italicBtn);
|
|
138
|
-
|
|
139
|
-
body.appendChild(tb);
|
|
140
|
-
|
|
141
|
-
// Сам текст — contenteditable (без рамок, без скроллбаров, ровно как
|
|
142
|
-
// плавающая надпись на холсте). plaintext-only — режем форматирование
|
|
143
|
-
// из буфера обмена (текст копируется только как текст).
|
|
91
|
+
// Сам текст. По умолчанию НЕ contenteditable — обычный <div>. Это значит:
|
|
92
|
+
// • single-click = выделение ноды (drag handler из attachDrag)
|
|
93
|
+
// • ПКМ = node-context-menu (ниже добавлены пункты «Шрифт/Размер/Курсив»)
|
|
94
|
+
// • dblclick = вход в режим редактирования
|
|
95
|
+
// • blur = выход из редактирования
|
|
96
|
+
// Шрифт/размер/курсив меняются через ПКМ (а не через постоянный hover-тулбар),
|
|
97
|
+
// чтобы UI label-ноды был «чистый текст без фурнитуры».
|
|
144
98
|
const ed = document.createElement('div');
|
|
145
99
|
ed.className = 'label-text';
|
|
146
|
-
ed.contentEditable = 'plaintext-only';
|
|
147
100
|
ed.spellcheck = false;
|
|
148
|
-
ed.dataset.placeholder = '
|
|
101
|
+
ed.dataset.placeholder = 'Двойной клик — редактировать';
|
|
149
102
|
ed.textContent = node.text || '';
|
|
150
103
|
applyLabelStyle(ed, node.textStyle);
|
|
104
|
+
|
|
105
|
+
const enterEditMode = () => {
|
|
106
|
+
if (ed.isContentEditable) return;
|
|
107
|
+
ed.contentEditable = 'plaintext-only';
|
|
108
|
+
ed.focus();
|
|
109
|
+
// Курсор в конец (если контент пустой — это и есть начало).
|
|
110
|
+
const sel = window.getSelection();
|
|
111
|
+
const r = document.createRange();
|
|
112
|
+
r.selectNodeContents(ed);
|
|
113
|
+
r.collapse(false);
|
|
114
|
+
sel?.removeAllRanges();
|
|
115
|
+
sel?.addRange(r);
|
|
116
|
+
};
|
|
117
|
+
const exitEditMode = () => {
|
|
118
|
+
if (!ed.isContentEditable) return;
|
|
119
|
+
// removeAttribute полностью убирает contenteditable, чтобы
|
|
120
|
+
// closest('[contenteditable]') в обработчиках вне (dblclick/contextmenu)
|
|
121
|
+
// не матчился — и ПКМ снова открывал наш node-menu.
|
|
122
|
+
ed.removeAttribute('contenteditable');
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
ed.addEventListener('dblclick', e => {
|
|
126
|
+
e.stopPropagation();
|
|
127
|
+
enterEditMode();
|
|
128
|
+
});
|
|
129
|
+
ed.addEventListener('blur', exitEditMode);
|
|
151
130
|
ed.addEventListener('input', () => { node.text = ed.textContent; scheduleSave(); });
|
|
152
|
-
ed.addEventListener('mousedown', e => e.stopPropagation());
|
|
153
|
-
// Enter — перенос строки. Esc — снять фокус.
|
|
154
131
|
ed.addEventListener('keydown', e => {
|
|
155
132
|
if (e.key === 'Escape') { e.preventDefault(); ed.blur(); }
|
|
156
133
|
});
|
|
134
|
+
ed.addEventListener('mousedown', e => {
|
|
135
|
+
// В edit-режиме: не пускаем drag-обработчик (нужно поставить курсор/выделить текст).
|
|
136
|
+
if (ed.isContentEditable) e.stopPropagation();
|
|
137
|
+
// В read-режиме: пузырится — attachDrag-handler (повешен и на label-text)
|
|
138
|
+
// обработает single-click как «выбрать ноду» и подхватит drag.
|
|
139
|
+
});
|
|
140
|
+
|
|
157
141
|
body.appendChild(ed);
|
|
158
142
|
}
|
|
159
143
|
|
|
@@ -618,6 +602,42 @@ function attachResize(el, node, handle) {
|
|
|
618
602
|
const startX = e.clientX, startY = e.clientY;
|
|
619
603
|
const startW = el.offsetWidth, startH = el.offsetHeight;
|
|
620
604
|
|
|
605
|
+
// Label: ресайз-хендл МАСШТАБИРУЕТ шрифт, а не меняет размеры бокса.
|
|
606
|
+
// Бокс auto-sized по тексту, поэтому при увеличении fontSize он
|
|
607
|
+
// автоматически растёт. Используем диагональную дистанцию (max от dx/dy)
|
|
608
|
+
// — даёт интуитивный «оттянуть угол → больше».
|
|
609
|
+
if (node.type === 'label') {
|
|
610
|
+
if (!node.textStyle) node.textStyle = { fontSize: 32, italic: false, fontFamily: 'pencil' };
|
|
611
|
+
const startSize = node.textStyle.fontSize || 32;
|
|
612
|
+
const labelTextEl = el.querySelector('.label-text');
|
|
613
|
+
const sizeSel = el.querySelector('.label-toolbar select[title="Размер шрифта"]');
|
|
614
|
+
const onMoveL = ev => {
|
|
615
|
+
const dx = (ev.clientX - startX) / state.zoom;
|
|
616
|
+
const dy = (ev.clientY - startY) / state.zoom;
|
|
617
|
+
// Линейная зависимость: ~0.6px шрифта на пиксель диагонального drag.
|
|
618
|
+
// Берём «среднее с уклоном на больший» — чтобы юзер мог тянуть как
|
|
619
|
+
// вправо-вниз (увеличивать), так и влево-вверх (уменьшать).
|
|
620
|
+
const delta = (dx + dy) * 0.6;
|
|
621
|
+
const newSize = Math.max(8, Math.min(200, Math.round(startSize + delta)));
|
|
622
|
+
node.textStyle.fontSize = newSize;
|
|
623
|
+
if (labelTextEl) labelTextEl.style.fontSize = newSize + 'px';
|
|
624
|
+
// Синхронизируем dropdown в тулбаре, если есть подходящий option.
|
|
625
|
+
if (sizeSel) {
|
|
626
|
+
const has = Array.from(sizeSel.options).some(o => o.value === String(newSize));
|
|
627
|
+
if (has) sizeSel.value = String(newSize);
|
|
628
|
+
}
|
|
629
|
+
renderConnections();
|
|
630
|
+
};
|
|
631
|
+
const onUpL = () => {
|
|
632
|
+
document.removeEventListener('mousemove', onMoveL);
|
|
633
|
+
document.removeEventListener('mouseup', onUpL);
|
|
634
|
+
scheduleSave();
|
|
635
|
+
};
|
|
636
|
+
document.addEventListener('mousemove', onMoveL);
|
|
637
|
+
document.addEventListener('mouseup', onUpL);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
|
|
621
641
|
// Aspect-lock для image/video: соблюдаем ratio оригинала. Берём nat-
|
|
622
642
|
// ural размеры media-элемента, считаем chrome (header + footer + padding)
|
|
623
643
|
// как разницу высоты ноды и высоты media. На этапе resize меняем ту ось,
|
|
@@ -674,7 +694,17 @@ function attachResize(el, node, handle) {
|
|
|
674
694
|
|
|
675
695
|
function attachDrag(el, node) {
|
|
676
696
|
const header = el.querySelector('.node-header');
|
|
677
|
-
header
|
|
697
|
+
// Для label-нод вешаем drag и на сам текст: header скрыт до hover, а
|
|
698
|
+
// юзер хочет цеплять ноду с любого места видимого текста. В handler'е
|
|
699
|
+
// пропускаем срабатывание, если label в edit-режиме (см. условие ниже).
|
|
700
|
+
const sources = [header];
|
|
701
|
+
if (node.type === 'label') {
|
|
702
|
+
const lt = el.querySelector('.label-text');
|
|
703
|
+
if (lt) sources.push(lt);
|
|
704
|
+
}
|
|
705
|
+
const handler = e => {
|
|
706
|
+
// Edit-режим label — не таскаем (контекст редактирования текста).
|
|
707
|
+
if (e.currentTarget?.classList?.contains('label-text') && e.currentTarget.isContentEditable) return;
|
|
678
708
|
if (e.target.closest('.delete')) return;
|
|
679
709
|
// Multi-select c модификаторами — без drag
|
|
680
710
|
if (e.metaKey || e.ctrlKey || e.shiftKey) {
|
|
@@ -811,7 +841,8 @@ function attachDrag(el, node) {
|
|
|
811
841
|
};
|
|
812
842
|
document.addEventListener('mousemove', onMove);
|
|
813
843
|
document.addEventListener('mouseup', onUp);
|
|
814
|
-
}
|
|
844
|
+
};
|
|
845
|
+
for (const src of sources) if (src) src.addEventListener('mousedown', handler);
|
|
815
846
|
}
|
|
816
847
|
|
|
817
848
|
// Добавить ноду как клип в указанную дорожку с ripple-вставкой по времени
|
package/renderer/styles.css
CHANGED
|
@@ -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%; }
|
|
@@ -450,9 +460,23 @@
|
|
|
450
460
|
}
|
|
451
461
|
/* Footer для label не используется (нет сгенерированной информации) — скрываем. */
|
|
452
462
|
.node.label-node .node-footer { display: none; }
|
|
453
|
-
/*
|
|
454
|
-
.node.label-node .resize-handle,
|
|
463
|
+
/* Anchor для label не нужен — это просто аннотация, не источник для генерации. */
|
|
455
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;
|
|
477
|
+
}
|
|
478
|
+
.node.label-node:hover .resize-handle,
|
|
479
|
+
.node.label-node:focus-within .resize-handle { opacity: 1; }
|
|
456
480
|
|
|
457
481
|
/* Body — wrapper без визуального вклада. */
|
|
458
482
|
.node.label-node .node-body {
|
|
@@ -461,62 +485,56 @@
|
|
|
461
485
|
position: relative;
|
|
462
486
|
}
|
|
463
487
|
|
|
464
|
-
/* Тулбар
|
|
465
|
-
|
|
466
|
-
position: absolute;
|
|
467
|
-
top: 100%; /* под label */
|
|
468
|
-
left: 0;
|
|
469
|
-
margin-top: 4px;
|
|
470
|
-
display: flex; align-items: center; gap: 4px;
|
|
471
|
-
padding: 3px 6px; background: rgba(20,20,20,0.92);
|
|
472
|
-
border-radius: 4px;
|
|
473
|
-
font-size: 11px; flex-shrink: 0;
|
|
474
|
-
opacity: 0; transition: opacity 0.15s;
|
|
475
|
-
pointer-events: none;
|
|
476
|
-
white-space: nowrap;
|
|
477
|
-
z-index: 10;
|
|
478
|
-
}
|
|
488
|
+
/* Тулбар (.label-toolbar) больше не используется — стиль/шрифт меняются
|
|
489
|
+
через ПКМ. Видимый header показываем только на hover. */
|
|
479
490
|
.node.label-node:hover .node-header,
|
|
480
|
-
.node.label-node:
|
|
481
|
-
.node.label-node:focus-within .node-header,
|
|
482
|
-
.node.label-node:focus-within .label-toolbar {
|
|
491
|
+
.node.label-node:focus-within .node-header {
|
|
483
492
|
opacity: 1; pointer-events: auto;
|
|
484
493
|
}
|
|
485
|
-
.node.label-node .label-toolbar select,
|
|
486
|
-
.node.label-node .label-toolbar button {
|
|
487
|
-
background: #1e1e1e; border: 1px solid #383838; color: #ccc;
|
|
488
|
-
border-radius: 3px; padding: 2px 6px; font-size: 11px;
|
|
489
|
-
cursor: pointer; line-height: 1.2;
|
|
490
|
-
}
|
|
491
|
-
.node.label-node .label-toolbar select:hover,
|
|
492
|
-
.node.label-node .label-toolbar button:hover { background: #2c2c2c; color: #fff; }
|
|
493
|
-
.node.label-node .label-toolbar button.active {
|
|
494
|
-
background: #3a5a8a; color: #fff; border-color: #4a6a9a;
|
|
495
|
-
}
|
|
496
|
-
.node.label-node .label-toolbar .tt-italic { font-style: italic; min-width: 22px; }
|
|
497
494
|
|
|
498
|
-
/* Сам текст — auto-ширина по контенту, до max-width.
|
|
495
|
+
/* Сам текст — auto-ширина по контенту, до max-width.
|
|
496
|
+
line-height: 1.5 — чтобы descender'ы рукописных шрифтов (Caveat g/p/y,
|
|
497
|
+
Pacifico, Bad Script) не клипались по нижнему краю. padding 8px вверху
|
|
498
|
+
и внизу — на случай если у конкретной семьи descender лезет ещё дальше. */
|
|
499
499
|
.node.label-node .label-text {
|
|
500
500
|
display: inline-block;
|
|
501
|
-
padding:
|
|
502
|
-
color: #eaeaea; line-height: 1.
|
|
501
|
+
padding: 8px 10px;
|
|
502
|
+
color: #eaeaea; line-height: 1.5;
|
|
503
503
|
word-break: break-word; white-space: pre-wrap;
|
|
504
504
|
outline: none; cursor: text;
|
|
505
505
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
506
506
|
text-shadow: 0 1px 2px rgba(0,0,0,0.6);
|
|
507
|
-
min-width:
|
|
507
|
+
min-width: 60px;
|
|
508
508
|
max-width: 100%;
|
|
509
|
+
/* min-height в em — гарантирует что пустая нода (только placeholder)
|
|
510
|
+
всё равно имеет высоту хотя бы одной строки выбранного шрифта. */
|
|
511
|
+
min-height: 1.5em;
|
|
509
512
|
}
|
|
510
513
|
.node.label-node .label-text:empty::before {
|
|
511
514
|
content: attr(data-placeholder);
|
|
512
|
-
color: rgba(200,200,200,0.
|
|
513
|
-
}
|
|
515
|
+
color: rgba(200,200,200,0.55);
|
|
516
|
+
}
|
|
517
|
+
/* Когда label пустая — рисуем подсказывающий dashed-контур, чтобы юзер
|
|
518
|
+
видел что нода существует (текст-плейсхолдер мог быть не замечен).
|
|
519
|
+
При наличии текста — никакой рамки, чисто подпись на холсте. */
|
|
520
|
+
.node.label-node:has(.label-text:empty) {
|
|
521
|
+
outline: 1px dashed rgba(200,200,200,0.25);
|
|
522
|
+
outline-offset: 1px;
|
|
523
|
+
border-radius: 3px;
|
|
524
|
+
}
|
|
525
|
+
/* Курсор «I-beam» над текстом — на hover показываем «текстуальную
|
|
526
|
+
природу» ноды, в read-mode сигнал что dblclick → редактировать. */
|
|
527
|
+
.node.label-node .label-text { cursor: text; }
|
|
528
|
+
.node.label-node:hover .label-text:not([contenteditable]) { cursor: pointer; }
|
|
514
529
|
/* === Label-шрифты ===
|
|
515
530
|
Все варианты — bundled woff2 (см. assets/fonts/), кириллица поддержана. */
|
|
531
|
+
.node.label-node .label-text[data-font="pencil"] {
|
|
532
|
+
/* Грифель — шероховатый «как написано карандашом» (Neucha). */
|
|
533
|
+
font-family: 'KK Neucha', 'Neucha', 'Marker Felt', 'Comic Sans MS', cursive;
|
|
534
|
+
}
|
|
516
535
|
.node.label-node .label-text[data-font="handwritten"] {
|
|
517
|
-
/*
|
|
536
|
+
/* Прописью — гладкий рукописный (Caveat). */
|
|
518
537
|
font-family: 'KK Caveat', 'Caveat', 'Marker Felt', cursive;
|
|
519
|
-
line-height: 1.1;
|
|
520
538
|
}
|
|
521
539
|
.node.label-node .label-text[data-font="brush"] {
|
|
522
540
|
/* Скорописью — каллиграфия в русском стиле (Bad Script). */
|