kingkont 0.7.81 → 0.7.82
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/index.html +1 -0
- package/package.json +1 -1
- package/renderer/board.js +3 -2
- package/renderer/generate.js +41 -0
- package/renderer/settings.js +59 -49
- package/renderer/state.js +3 -1
- package/renderer/styles.css +77 -37
package/index.html
CHANGED
|
@@ -227,6 +227,7 @@
|
|
|
227
227
|
<!-- ===== Меню «что добавить» (двойной клик на пустом месте холста) ===== -->
|
|
228
228
|
<div class="add-menu hidden" id="addMenu">
|
|
229
229
|
<button data-act="text">✏️ Написать</button>
|
|
230
|
+
<button data-act="label">🅰 Подпись</button>
|
|
230
231
|
<button data-act="gen-text">📝 Сгенерировать текст</button>
|
|
231
232
|
<button data-act="audio">🎙 Сгенерировать голос</button>
|
|
232
233
|
<button data-act="image">🖼 Сгенерировать картинку</button>
|
package/package.json
CHANGED
package/renderer/board.js
CHANGED
|
@@ -1634,6 +1634,7 @@ async function createNodeEl(node) {
|
|
|
1634
1634
|
const el = document.createElement('div');
|
|
1635
1635
|
el.className = 'node';
|
|
1636
1636
|
if (node.type === 'text') el.classList.add('text-node');
|
|
1637
|
+
if (node.type === 'label') el.classList.add('label-node');
|
|
1637
1638
|
el.dataset.id = node.id;
|
|
1638
1639
|
el.style.left = node.x + 'px';
|
|
1639
1640
|
el.style.top = node.y + 'px';
|
|
@@ -1693,7 +1694,7 @@ async function createNodeEl(node) {
|
|
|
1693
1694
|
attachDrag(el, node);
|
|
1694
1695
|
|
|
1695
1696
|
el.addEventListener('dblclick', e => {
|
|
1696
|
-
if (e.target.closest('textarea, input, video, button, .delete, .anchor, .resize-handle')) return;
|
|
1697
|
+
if (e.target.closest('textarea, input, video, button, .delete, .anchor, .resize-handle, [contenteditable]')) return;
|
|
1697
1698
|
if (node.type === 'audio' && node.file) {
|
|
1698
1699
|
regenerateNode(node);
|
|
1699
1700
|
return;
|
|
@@ -1705,7 +1706,7 @@ async function createNodeEl(node) {
|
|
|
1705
1706
|
if (node.generated) showNodeSettings(node);
|
|
1706
1707
|
});
|
|
1707
1708
|
el.addEventListener('contextmenu', e => {
|
|
1708
|
-
if (e.target.closest('textarea, input, .anchor, .resize-handle')) return;
|
|
1709
|
+
if (e.target.closest('textarea, input, .anchor, .resize-handle, [contenteditable]')) return;
|
|
1709
1710
|
e.preventDefault();
|
|
1710
1711
|
e.stopPropagation();
|
|
1711
1712
|
showNodeContextMenu(node, e.clientX, e.clientY);
|
package/renderer/generate.js
CHANGED
|
@@ -153,6 +153,11 @@ document.querySelectorAll('#addMenu button').forEach(btn => {
|
|
|
153
153
|
state.addMenuPos = null;
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
|
+
if (act === 'label') {
|
|
157
|
+
await addLabelAt(state.addMenuPos);
|
|
158
|
+
state.addMenuPos = null;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
156
161
|
if (act === 'gen-text') {
|
|
157
162
|
// открываем text-gen modal; если есть fromNode — подставляем @ref в промпт
|
|
158
163
|
if (!await ensureApiKey('text')) return;
|
|
@@ -194,6 +199,42 @@ async function addTextAt(pos) {
|
|
|
194
199
|
scheduleSave();
|
|
195
200
|
}
|
|
196
201
|
|
|
202
|
+
// Label-нода: лёгкая надпись поверх холста, без файла. Текст хранится
|
|
203
|
+
// прямо в scene.json (см. saveBoardMetadata: для type='label' n.text НЕ
|
|
204
|
+
// удаляется при сериализации).
|
|
205
|
+
async function addLabelAt(pos) {
|
|
206
|
+
if (!state.currentBoard) return;
|
|
207
|
+
const rect = canvas.getBoundingClientRect();
|
|
208
|
+
const x = pos ? (pos.clientX - rect.left) / state.zoom : canvasWrap.scrollLeft / state.zoom + 80;
|
|
209
|
+
const y = pos ? (pos.clientY - rect.top) / state.zoom : canvasWrap.scrollTop / state.zoom + 80;
|
|
210
|
+
const node = {
|
|
211
|
+
id: crypto.randomUUID(),
|
|
212
|
+
type: 'label',
|
|
213
|
+
text: '',
|
|
214
|
+
textStyle: { fontSize: 32, italic: false, fontFamily: 'handwritten' },
|
|
215
|
+
x, y,
|
|
216
|
+
width: 260, height: 90,
|
|
217
|
+
};
|
|
218
|
+
state.currentBoard.metadata.nodes.push(node);
|
|
219
|
+
const el = await createNodeEl(node);
|
|
220
|
+
canvas.appendChild(el);
|
|
221
|
+
scheduleSave();
|
|
222
|
+
// Авто-фокус в редактируемый текст, чтобы юзер сразу начал печатать.
|
|
223
|
+
setTimeout(() => {
|
|
224
|
+
const ed = el.querySelector('.label-text');
|
|
225
|
+
if (ed) {
|
|
226
|
+
ed.focus();
|
|
227
|
+
// Курсор в конец (если контент пустой — это всё равно начало).
|
|
228
|
+
const sel = window.getSelection();
|
|
229
|
+
const r = document.createRange();
|
|
230
|
+
r.selectNodeContents(ed);
|
|
231
|
+
r.collapse(false);
|
|
232
|
+
sel?.removeAllRanges();
|
|
233
|
+
sel?.addRange(r);
|
|
234
|
+
}
|
|
235
|
+
}, 30);
|
|
236
|
+
}
|
|
237
|
+
|
|
197
238
|
// =================== Generate (фоновая, не блокирующая) ===================
|
|
198
239
|
// === Preferences modal (API-ключи) ===
|
|
199
240
|
async function openSettings(initialFocus) {
|
package/renderer/settings.js
CHANGED
|
@@ -52,76 +52,69 @@ $('settingsRegen').addEventListener('click', () => {
|
|
|
52
52
|
regenerateNode(node); // там редактируются ВСЕ поля (промпт, модель, голос)
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
// ===================
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
// =================== Label-node body (свободный текст-аннотация) ===================
|
|
56
|
+
// Label-нода — это просто текст «поверх» холста: без фона, без рамки,
|
|
57
|
+
// без header/footer. Используется для подписей, заголовков, заметок
|
|
58
|
+
// «карандашом/краской» — в отличие от text-ноды, которая хранит длинный
|
|
59
|
+
// .md-документ с обычным textarea.
|
|
60
|
+
//
|
|
61
|
+
// Шрифты — system-only, без Google Fonts CDN (приложение офлайновое).
|
|
62
|
+
// На macOS все варианты выглядят естественно; на Win/Linux fallback'и
|
|
63
|
+
// (cursive/fantasy/serif/monospace) сохраняют смысл.
|
|
64
|
+
const LABEL_FONTS = [
|
|
65
|
+
{ id: 'default', label: 'Обычный' },
|
|
66
|
+
{ id: 'handwritten', label: 'Карандашом' },
|
|
67
|
+
{ id: 'marker', label: 'Краской' },
|
|
68
|
+
{ id: 'serif', label: 'С засечками' },
|
|
69
|
+
{ id: 'mono', label: 'Моноширинный' },
|
|
65
70
|
];
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
function getTextNodeStyle(node) {
|
|
70
|
-
return { ...TEXT_NODE_DEFAULT_STYLE, ...(node.textStyle || {}) };
|
|
71
|
-
}
|
|
71
|
+
const LABEL_FONT_SIZES = [12, 14, 18, 24, 32, 48, 64, 96];
|
|
72
|
+
const LABEL_DEFAULT_STYLE = { fontSize: 32, italic: false, fontFamily: 'handwritten' };
|
|
72
73
|
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
function applyLabelStyle(ed, style) {
|
|
75
|
+
ed.style.fontSize = (style.fontSize || 32) + 'px';
|
|
76
|
+
ed.style.fontStyle = style.italic ? 'italic' : 'normal';
|
|
77
|
+
ed.dataset.font = style.fontFamily || 'default';
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
if (!node.textStyle) node.textStyle = { ...TEXT_NODE_DEFAULT_STYLE };
|
|
80
|
+
function renderLabelNodeBody(node, body) {
|
|
81
|
+
if (!node.textStyle) node.textStyle = { ...LABEL_DEFAULT_STYLE };
|
|
82
82
|
|
|
83
|
-
// Тулбар
|
|
83
|
+
// Тулбар форматирования (на hover, чтобы не отвлекать от чистого текста).
|
|
84
84
|
const tb = document.createElement('div');
|
|
85
|
-
tb.className = '
|
|
86
|
-
// Не блокируем drag-mousedown по тулбару — но клики по контролам
|
|
87
|
-
// не должны таскать ноду. stopPropagation на mousedown решает это.
|
|
85
|
+
tb.className = 'label-toolbar';
|
|
88
86
|
tb.addEventListener('mousedown', e => e.stopPropagation());
|
|
89
87
|
|
|
90
|
-
// Селект шрифта.
|
|
91
88
|
const fontSel = document.createElement('select');
|
|
92
89
|
fontSel.title = 'Шрифт';
|
|
93
|
-
for (const f of
|
|
90
|
+
for (const f of LABEL_FONTS) {
|
|
94
91
|
const o = document.createElement('option');
|
|
95
|
-
o.value = f.id;
|
|
96
|
-
o.textContent = f.label;
|
|
92
|
+
o.value = f.id; o.textContent = f.label;
|
|
97
93
|
fontSel.appendChild(o);
|
|
98
94
|
}
|
|
99
95
|
fontSel.value = node.textStyle.fontFamily || 'default';
|
|
100
96
|
fontSel.addEventListener('change', () => {
|
|
101
97
|
node.textStyle.fontFamily = fontSel.value;
|
|
102
|
-
|
|
98
|
+
applyLabelStyle(ed, node.textStyle);
|
|
103
99
|
scheduleSave();
|
|
104
100
|
});
|
|
105
101
|
tb.appendChild(fontSel);
|
|
106
102
|
|
|
107
|
-
// Селект размера.
|
|
108
103
|
const sizeSel = document.createElement('select');
|
|
109
104
|
sizeSel.title = 'Размер шрифта';
|
|
110
|
-
for (const s of
|
|
105
|
+
for (const s of LABEL_FONT_SIZES) {
|
|
111
106
|
const o = document.createElement('option');
|
|
112
|
-
o.value = String(s);
|
|
113
|
-
o.textContent = s + 'px';
|
|
107
|
+
o.value = String(s); o.textContent = s + 'px';
|
|
114
108
|
sizeSel.appendChild(o);
|
|
115
109
|
}
|
|
116
|
-
sizeSel.value = String(node.textStyle.fontSize ||
|
|
110
|
+
sizeSel.value = String(node.textStyle.fontSize || 32);
|
|
117
111
|
sizeSel.addEventListener('change', () => {
|
|
118
|
-
node.textStyle.fontSize = parseInt(sizeSel.value, 10) ||
|
|
119
|
-
|
|
112
|
+
node.textStyle.fontSize = parseInt(sizeSel.value, 10) || 32;
|
|
113
|
+
applyLabelStyle(ed, node.textStyle);
|
|
120
114
|
scheduleSave();
|
|
121
115
|
});
|
|
122
116
|
tb.appendChild(sizeSel);
|
|
123
117
|
|
|
124
|
-
// Italic toggle.
|
|
125
118
|
const italicBtn = document.createElement('button');
|
|
126
119
|
italicBtn.className = 'tt-italic';
|
|
127
120
|
italicBtn.textContent = 'I';
|
|
@@ -131,20 +124,30 @@ function renderTextNodeBody(node, body) {
|
|
|
131
124
|
e.stopPropagation();
|
|
132
125
|
node.textStyle.italic = !node.textStyle.italic;
|
|
133
126
|
italicBtn.classList.toggle('active', node.textStyle.italic);
|
|
134
|
-
|
|
127
|
+
applyLabelStyle(ed, node.textStyle);
|
|
135
128
|
scheduleSave();
|
|
136
129
|
});
|
|
137
130
|
tb.appendChild(italicBtn);
|
|
138
131
|
|
|
139
132
|
body.appendChild(tb);
|
|
140
133
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
134
|
+
// Сам текст — contenteditable (без рамок, без скроллбаров, ровно как
|
|
135
|
+
// плавающая надпись на холсте). plaintext-only — режем форматирование
|
|
136
|
+
// из буфера обмена (текст копируется только как текст).
|
|
137
|
+
const ed = document.createElement('div');
|
|
138
|
+
ed.className = 'label-text';
|
|
139
|
+
ed.contentEditable = 'plaintext-only';
|
|
140
|
+
ed.spellcheck = false;
|
|
141
|
+
ed.dataset.placeholder = 'Текст…';
|
|
142
|
+
ed.textContent = node.text || '';
|
|
143
|
+
applyLabelStyle(ed, node.textStyle);
|
|
144
|
+
ed.addEventListener('input', () => { node.text = ed.textContent; scheduleSave(); });
|
|
145
|
+
ed.addEventListener('mousedown', e => e.stopPropagation());
|
|
146
|
+
// Enter — перенос строки. Esc — снять фокус.
|
|
147
|
+
ed.addEventListener('keydown', e => {
|
|
148
|
+
if (e.key === 'Escape') { e.preventDefault(); ed.blur(); }
|
|
149
|
+
});
|
|
150
|
+
body.appendChild(ed);
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
async function renderNodeBody(node, body) {
|
|
@@ -281,7 +284,14 @@ async function renderNodeBody(node, body) {
|
|
|
281
284
|
}
|
|
282
285
|
|
|
283
286
|
if (node.type === 'text') {
|
|
284
|
-
|
|
287
|
+
const ta = document.createElement('textarea');
|
|
288
|
+
ta.value = node.text || '';
|
|
289
|
+
ta.placeholder = 'Текст ноды...';
|
|
290
|
+
ta.addEventListener('input', () => { node.text = ta.value; scheduleSave(); });
|
|
291
|
+
ta.addEventListener('mousedown', e => e.stopPropagation());
|
|
292
|
+
body.appendChild(ta);
|
|
293
|
+
} else if (node.type === 'label') {
|
|
294
|
+
renderLabelNodeBody(node, body);
|
|
285
295
|
} else if (['video','audio','image'].includes(node.type) && node.file) {
|
|
286
296
|
const nodeEl = body.closest('.node');
|
|
287
297
|
nodeEl?.classList.toggle('image-node', node.type === 'image');
|
package/renderer/state.js
CHANGED
|
@@ -354,7 +354,9 @@ async function saveBoardMetadata(boardHandle, meta) {
|
|
|
354
354
|
}
|
|
355
355
|
const nodesForJson = meta.nodes.map(n => {
|
|
356
356
|
const out = { ...n };
|
|
357
|
-
|
|
357
|
+
// Для text-нод текст в отдельном .md, в JSON его не пишем.
|
|
358
|
+
// Для label-нод — текст inline в JSON (короткие подписи, без файла).
|
|
359
|
+
if (n.type === 'text') delete out.text;
|
|
358
360
|
return out;
|
|
359
361
|
});
|
|
360
362
|
const payload = {
|
package/renderer/styles.css
CHANGED
|
@@ -323,56 +323,96 @@
|
|
|
323
323
|
box-shadow: 0 0 0 2px #5aa8ff, 0 8px 24px rgba(90,168,255,0.4);
|
|
324
324
|
}
|
|
325
325
|
.node-body { flex: 1; min-height: 0; }
|
|
326
|
-
.node.text-node .node-body { padding: 0; display: flex;
|
|
326
|
+
.node.text-node .node-body { padding: 0; display: flex; }
|
|
327
327
|
.node.text-node .node-body textarea {
|
|
328
|
-
width: 100%; resize: none; min-height: 80px;
|
|
328
|
+
width: 100%; height: 100%; resize: none; min-height: 80px;
|
|
329
329
|
border: none; border-radius: 0; padding: 12px; flex: 1;
|
|
330
|
-
|
|
331
|
-
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* === Label-node — плавающая текст-аннотация поверх холста ===
|
|
333
|
+
Без фона / рамки / тени — выглядит как «написанный» прямо на сцене текст.
|
|
334
|
+
Header / footer / resize / anchor скрыты по умолчанию, появляются на
|
|
335
|
+
hover чтобы можно было перетаскивать, удалять, ресайзить. */
|
|
336
|
+
.node.label-node {
|
|
337
|
+
background: transparent;
|
|
338
|
+
border: none; box-shadow: none;
|
|
339
|
+
padding: 0;
|
|
340
|
+
}
|
|
341
|
+
.node.label-node.selected {
|
|
342
|
+
outline: 1px dashed rgba(90,168,255,0.8);
|
|
343
|
+
outline-offset: 2px;
|
|
344
|
+
}
|
|
345
|
+
.node.label-node .node-header,
|
|
346
|
+
.node.label-node .node-footer,
|
|
347
|
+
.node.label-node .resize-handle,
|
|
348
|
+
.node.label-node .anchor,
|
|
349
|
+
.node.label-node .label-toolbar {
|
|
350
|
+
opacity: 0; transition: opacity 0.15s;
|
|
351
|
+
}
|
|
352
|
+
.node.label-node:hover .node-header,
|
|
353
|
+
.node.label-node:hover .node-footer,
|
|
354
|
+
.node.label-node:hover .resize-handle,
|
|
355
|
+
.node.label-node:hover .anchor,
|
|
356
|
+
.node.label-node:hover .label-toolbar,
|
|
357
|
+
.node.label-node:focus-within .label-toolbar { opacity: 1; }
|
|
358
|
+
.node.label-node .node-header {
|
|
359
|
+
background: rgba(20,20,20,0.85);
|
|
360
|
+
border-radius: 6px 6px 0 0;
|
|
361
|
+
}
|
|
362
|
+
.node.label-node .node-body {
|
|
363
|
+
padding: 0; background: transparent;
|
|
364
|
+
display: flex; flex-direction: column; min-height: 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* Тулбар форматирования label — поверх текста, на hover. */
|
|
368
|
+
.node.label-node .label-toolbar {
|
|
369
|
+
display: flex; align-items: center; gap: 4px;
|
|
370
|
+
padding: 3px 6px; background: rgba(20,20,20,0.92);
|
|
371
|
+
border-radius: 4px; margin: 4px;
|
|
372
|
+
font-size: 11px; flex-shrink: 0;
|
|
373
|
+
align-self: flex-start;
|
|
374
|
+
pointer-events: auto;
|
|
375
|
+
}
|
|
376
|
+
.node.label-node .label-toolbar select,
|
|
377
|
+
.node.label-node .label-toolbar button {
|
|
378
|
+
background: #1e1e1e; border: 1px solid #383838; color: #ccc;
|
|
379
|
+
border-radius: 3px; padding: 2px 6px; font-size: 11px;
|
|
380
|
+
cursor: pointer; line-height: 1.2;
|
|
381
|
+
}
|
|
382
|
+
.node.label-node .label-toolbar select:hover,
|
|
383
|
+
.node.label-node .label-toolbar button:hover { background: #2c2c2c; color: #fff; }
|
|
384
|
+
.node.label-node .label-toolbar button.active {
|
|
385
|
+
background: #3a5a8a; color: #fff; border-color: #4a6a9a;
|
|
386
|
+
}
|
|
387
|
+
.node.label-node .label-toolbar .tt-italic { font-style: italic; min-width: 22px; }
|
|
388
|
+
|
|
389
|
+
/* Сам текст. flex:1 — занимает оставшееся тело, поэтому drag-area
|
|
390
|
+
совпадает с визуальной областью. */
|
|
391
|
+
.node.label-node .label-text {
|
|
392
|
+
flex: 1; padding: 8px 12px;
|
|
393
|
+
color: #eaeaea; line-height: 1.2;
|
|
394
|
+
word-break: break-word; white-space: pre-wrap;
|
|
395
|
+
outline: none; cursor: text;
|
|
332
396
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
333
|
-
|
|
397
|
+
text-shadow: 0 1px 2px rgba(0,0,0,0.6);
|
|
398
|
+
}
|
|
399
|
+
.node.label-node .label-text:empty::before {
|
|
400
|
+
content: attr(data-placeholder);
|
|
401
|
+
color: rgba(200,200,200,0.35);
|
|
334
402
|
}
|
|
335
|
-
|
|
336
|
-
.node.text-node .node-body textarea[data-font="handwritten"] {
|
|
337
|
-
/* «Карандашом» — рукописные шрифты, мягкие, наклонные. */
|
|
403
|
+
.node.label-node .label-text[data-font="handwritten"] {
|
|
338
404
|
font-family: 'Caveat', 'Marker Felt', 'Bradley Hand', 'Snell Roundhand', cursive;
|
|
339
|
-
line-height: 1.2;
|
|
340
405
|
}
|
|
341
|
-
.node.
|
|
342
|
-
/* «Краской/маркером» — крупные мазки, plakat-стиль. */
|
|
406
|
+
.node.label-node .label-text[data-font="marker"] {
|
|
343
407
|
font-family: 'Permanent Marker', 'Chalkduster', 'Marker Felt', 'Comic Sans MS', fantasy;
|
|
344
408
|
letter-spacing: 0.02em;
|
|
345
409
|
}
|
|
346
|
-
.node.
|
|
410
|
+
.node.label-node .label-text[data-font="serif"] {
|
|
347
411
|
font-family: Georgia, 'Times New Roman', serif;
|
|
348
412
|
}
|
|
349
|
-
.node.
|
|
413
|
+
.node.label-node .label-text[data-font="mono"] {
|
|
350
414
|
font-family: ui-monospace, 'SF Mono', Menlo, 'Courier New', monospace;
|
|
351
415
|
}
|
|
352
|
-
|
|
353
|
-
/* Тулбар форматирования — компактный, появляется на hover, чтобы не
|
|
354
|
-
отвлекать когда юзер просто читает. */
|
|
355
|
-
.node.text-node .text-toolbar {
|
|
356
|
-
display: flex; align-items: center; gap: 4px;
|
|
357
|
-
padding: 4px 6px; background: #181818; border-bottom: 1px solid #2a2a2a;
|
|
358
|
-
font-size: 11px; opacity: 0.35; transition: opacity 0.15s;
|
|
359
|
-
flex-shrink: 0;
|
|
360
|
-
}
|
|
361
|
-
.node.text-node:hover .text-toolbar,
|
|
362
|
-
.node.text-node .text-toolbar:focus-within { opacity: 1; }
|
|
363
|
-
.node.text-node .text-toolbar select,
|
|
364
|
-
.node.text-node .text-toolbar button {
|
|
365
|
-
background: #1e1e1e; border: 1px solid #383838; color: #ccc;
|
|
366
|
-
border-radius: 3px; padding: 2px 6px; font-size: 11px;
|
|
367
|
-
cursor: pointer; line-height: 1.2;
|
|
368
|
-
}
|
|
369
|
-
.node.text-node .text-toolbar select:hover,
|
|
370
|
-
.node.text-node .text-toolbar button:hover { background: #2c2c2c; color: #fff; }
|
|
371
|
-
.node.text-node .text-toolbar button.active {
|
|
372
|
-
background: #3a5a8a; color: #fff; border-color: #4a6a9a;
|
|
373
|
-
}
|
|
374
|
-
.node.text-node .text-toolbar .tt-spacer { flex: 1; }
|
|
375
|
-
.node.text-node .text-toolbar .tt-italic { font-style: italic; min-width: 22px; }
|
|
376
416
|
.node.video-node .node-body { padding: 0; overflow: hidden; border-radius: 0 0 8px 8px; }
|
|
377
417
|
.node.video-node .node-body video { width: 100%; height: auto; max-height: none; background: transparent; border-radius: 0; display: block; }
|
|
378
418
|
.audio-speed { display: flex; gap: 2px; margin-top: 6px; }
|