@sequent-org/moodboard 1.4.51 → 1.4.53

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.
@@ -42,19 +42,19 @@ function hexToHsv(hex) {
42
42
  const FRAME_COLORS = [
43
43
  { name: 'Белый', hex: '#FFFFFF' },
44
44
  { name: 'Светло-серый', hex: '#EBEBEB' },
45
- { name: 'Серый', hex: '#ABABAB' },
46
- { name: 'Светло-розовый', hex: '#FFDDE0' },
47
- { name: 'Розовый', hex: '#FFB3BB' },
48
- { name: 'Персиковый', hex: '#FFE5CC' },
49
- { name: 'Оранжевый', hex: '#FFB066' },
50
- { name: 'Светло-жёлтый', hex: '#FFFACC' },
51
- { name: 'Жёлтый', hex: '#FFE566' },
52
- { name: 'Светло-зелёный', hex: '#D4F5D4' },
53
- { name: 'Зелёный', hex: '#88D888' },
54
- { name: 'Небесный', hex: '#D4EEFF' },
55
- { name: 'Голубой', hex: '#80BFFF' },
56
- { name: 'Лавандовый', hex: '#E8D4FF' },
57
- { name: 'Фиолетовый', hex: '#C499FF' },
45
+ { name: 'Серый', hex: '#E4E4E4' },
46
+ { name: 'Светло-розовый', hex: '#FFEFEF' },
47
+ { name: 'Розовый', hex: '#FDD8D8' },
48
+ { name: 'Персиковый', hex: '#FFEDD5' },
49
+ { name: 'Оранжевый', hex: '#FFD3A4' },
50
+ { name: 'Светло-жёлтый', hex: '#FFFBE0' },
51
+ { name: 'Жёлтый', hex: '#FCF3AF' },
52
+ { name: 'Светло-зелёный', hex: '#E9F9EE' },
53
+ { name: 'Зелёный', hex: '#CCEBD7' },
54
+ { name: 'Небесный', hex: '#EDF6FF' },
55
+ { name: 'Голубой', hex: '#CEE7FE' },
56
+ { name: 'Лавандовый', hex: '#F5F2FF' },
57
+ { name: 'Фиолетовый', hex: '#E4DEFC' },
58
58
  ];
59
59
 
60
60
  const ICONS = {
@@ -75,6 +75,7 @@ const ICONS = {
75
75
  bgBordered: '<svg width="22" height="22" viewBox="0 0 22 22" fill="none"><rect x="3" y="3" width="16" height="16" rx="2.5" fill="#D1D5DB"/><rect x="3" y="3" width="16" height="16" rx="2.5" stroke="#374151" stroke-width="1.5"/></svg>',
76
76
  bgOutline: '<svg width="22" height="22" viewBox="0 0 22 22" fill="none"><rect x="3" y="3" width="16" height="16" rx="2.5" stroke="#374151" stroke-width="1.5"/></svg>',
77
77
  eyedropper: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 20.5l4-4"/><path d="M6.5 17.5L17 7a2.5 2.5 0 0 0 0-3.5l-2-2a2.5 2.5 0 0 0-3.5 0L3 10l4 4z"/></svg>',
78
+ check: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>',
78
79
  };
79
80
 
80
81
  const RATIO_LABELS = {
@@ -241,8 +242,7 @@ export class FramePropertiesPanel {
241
242
 
242
243
  const divider = this._makeDivider();
243
244
 
244
- this._btn_lock = this._makeButton(ICONS.unlock, 'Заблокировать', 'lock');
245
- this._btn_lock.addEventListener('click', (e) => { e.stopPropagation(); this._toggleLocked(); });
245
+ const lockControl = this._makeLockControl();
246
246
 
247
247
  const more = this._makeMoreButton();
248
248
 
@@ -251,7 +251,7 @@ export class FramePropertiesPanel {
251
251
  panel.appendChild(rename);
252
252
  panel.appendChild(eye);
253
253
  panel.appendChild(divider);
254
- panel.appendChild(this._btn_lock);
254
+ panel.appendChild(lockControl);
255
255
  panel.appendChild(more);
256
256
 
257
257
  this._lockableEls = [ratio, color, rename, eye, divider];
@@ -271,11 +271,81 @@ export class FramePropertiesPanel {
271
271
  const btn = document.createElement('button');
272
272
  btn.className = 'ipp-btn';
273
273
  btn.title = title;
274
- if (key) { btn.dataset.id = 'fpp-btn-' + key; }
274
+ if (key) {
275
+ btn.id = 'fpp-btn-' + key;
276
+ btn.dataset.id = 'fpp-btn-' + key;
277
+ }
275
278
  btn.innerHTML = iconHtml;
276
279
  return btn;
277
280
  }
278
281
 
282
+ _makeLockControl() {
283
+ const wrapper = document.createElement('div');
284
+ wrapper.className = 'ipp-btn-split-wrapper fpp-lock-wrap';
285
+
286
+ const mainBtn = document.createElement('button');
287
+ mainBtn.className = 'ipp-btn-split-main';
288
+ mainBtn.id = 'fpp-btn-lock';
289
+ mainBtn.dataset.id = 'fpp-btn-lock';
290
+ mainBtn.title = 'Заблокировать';
291
+ mainBtn.innerHTML = ICONS.unlock;
292
+ mainBtn.addEventListener('click', (e) => { e.stopPropagation(); this._toggleLocked(); });
293
+ this._lockSplitMain = mainBtn;
294
+
295
+ const expandBtn = document.createElement('button');
296
+ expandBtn.className = 'ipp-btn-split-expand fpp-lock-chevron';
297
+ expandBtn.title = 'Режим блокировки';
298
+ expandBtn.innerHTML = ICONS.expand;
299
+ expandBtn.addEventListener('click', (e) => {
300
+ e.stopPropagation();
301
+ const isOpen = this._lockDropdown.classList.contains('is-open');
302
+ this._closeMenus();
303
+ if (!isOpen) {
304
+ this._lockDropdown.classList.add('is-open');
305
+ expandBtn.classList.add('is-expanded');
306
+ this._attachOutside();
307
+ }
308
+ });
309
+ this._lockExpandBtn = expandBtn;
310
+
311
+ const dropdown = document.createElement('div');
312
+ dropdown.className = 'fpp-lock-dropdown';
313
+ this._lockDropdown = dropdown;
314
+
315
+ const lockModeItems = [
316
+ { id: 'frame', label: 'Только фрейм' },
317
+ { id: 'frame-and-content', label: 'Фрейм и содержимое' },
318
+ ];
319
+
320
+ lockModeItems.forEach((item) => {
321
+ const btn = document.createElement('button');
322
+ btn.className = 'ipp-dropdown-item';
323
+ btn.id = 'fpp-lock-mode-' + item.id;
324
+ btn.dataset.lockMode = item.id;
325
+
326
+ const checkSpan = document.createElement('span');
327
+ checkSpan.className = 'fpp-lock-mode-check';
328
+ checkSpan.innerHTML = ICONS.check;
329
+ btn.appendChild(checkSpan);
330
+
331
+ const labelSpan = document.createElement('span');
332
+ labelSpan.textContent = item.label;
333
+ btn.appendChild(labelSpan);
334
+
335
+ btn.addEventListener('click', (e) => {
336
+ e.stopPropagation();
337
+ this._closeMenus();
338
+ this._applyLockMode(item.id);
339
+ });
340
+ dropdown.appendChild(btn);
341
+ });
342
+
343
+ wrapper.appendChild(mainBtn);
344
+ wrapper.appendChild(expandBtn);
345
+ wrapper.appendChild(dropdown);
346
+ return wrapper;
347
+ }
348
+
279
349
  _makeDivider() {
280
350
  const div = document.createElement('div');
281
351
  div.className = 'ipp-divider';
@@ -288,6 +358,7 @@ export class FramePropertiesPanel {
288
358
 
289
359
  const btn = document.createElement('button');
290
360
  btn.className = 'fpp-ratio-btn';
361
+ btn.id = 'fpp-ratio-btn';
291
362
  btn.dataset.id = 'fpp-ratio-btn';
292
363
 
293
364
  const textBox = document.createElement('span');
@@ -324,6 +395,7 @@ export class FramePropertiesPanel {
324
395
  items.forEach((item) => {
325
396
  const el = document.createElement('button');
326
397
  el.className = 'ipp-dropdown-item';
398
+ el.id = 'fpp-ratio-item-' + item.id;
327
399
  const ic = document.createElement('span');
328
400
  ic.className = 'ipp-dropdown-icon';
329
401
  ic.innerHTML = item.icon;
@@ -362,6 +434,7 @@ export class FramePropertiesPanel {
362
434
 
363
435
  const colorButton = document.createElement('button');
364
436
  colorButton.className = 'fpp-color-button';
437
+ colorButton.id = 'fpp-color-btn';
365
438
  colorButton.addEventListener('click', (e) => {
366
439
  e.stopPropagation();
367
440
  this._toggleColorPopup(colorButton);
@@ -394,6 +467,7 @@ export class FramePropertiesPanel {
394
467
  BG_TYPES.forEach(({ id, title, icon }) => {
395
468
  const btn = document.createElement('button');
396
469
  btn.className = 'fpp-bg-type-btn';
470
+ btn.id = 'fpp-bg-type-btn-' + id;
397
471
  btn.title = title;
398
472
  btn.dataset.bgMode = id;
399
473
  btn.innerHTML = ICONS[icon];
@@ -425,6 +499,7 @@ export class FramePropertiesPanel {
425
499
  // "None" swatch — transparent
426
500
  const noneSwatch = document.createElement('button');
427
501
  noneSwatch.className = 'fpp-color-swatch fpp-color-swatch--none';
502
+ noneSwatch.id = 'fpp-swatch-none';
428
503
  noneSwatch.title = 'Прозрачный';
429
504
  noneSwatch.dataset.colorHex = 'none';
430
505
  noneSwatch.innerHTML = '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><line x1="3" y1="17" x2="17" y2="3" stroke="#EF4444" stroke-width="1.5" stroke-linecap="round"/></svg>';
@@ -440,6 +515,7 @@ export class FramePropertiesPanel {
440
515
  FRAME_COLORS.forEach((color) => {
441
516
  const btn = document.createElement('button');
442
517
  btn.className = 'fpp-color-swatch';
518
+ btn.id = 'fpp-swatch-' + color.hex.replace('#', '').toLowerCase();
443
519
  btn.title = color.name;
444
520
  btn.dataset.colorHex = color.hex.toUpperCase();
445
521
  btn.style.setProperty('--swatch-color', color.hex);
@@ -470,6 +546,7 @@ export class FramePropertiesPanel {
470
546
 
471
547
  const addBtn = document.createElement('button');
472
548
  addBtn.className = 'fpp-add-color-btn';
549
+ addBtn.id = 'fpp-add-color-btn';
473
550
  addBtn.title = 'Добавить цвет';
474
551
  addBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><line x1="7" y1="1" x2="7" y2="13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><line x1="1" y1="7" x2="13" y2="7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>';
475
552
  addBtn.addEventListener('click', (e) => {
@@ -562,9 +639,18 @@ export class FramePropertiesPanel {
562
639
  this.colorButton.classList.remove('fpp-color-button--none');
563
640
  }
564
641
 
642
+ const targetBgMode = this._getCurrentBgMode();
643
+
644
+ this._bgTypeBtns.forEach((btn) => {
645
+ btn.classList.toggle('is-active', btn.dataset.bgMode === targetBgMode);
646
+ });
647
+
565
648
  this.eventBus.emit(Events.Object.StateChanged, {
566
649
  objectId: this.currentId,
567
- updates: { backgroundColor: pixi },
650
+ updates: {
651
+ backgroundColor: pixi,
652
+ properties: { bgMode: targetBgMode }
653
+ },
568
654
  });
569
655
  }
570
656
 
@@ -574,9 +660,10 @@ export class FramePropertiesPanel {
574
660
  }
575
661
 
576
662
  _setColorSectionDisabled(disabled) {
577
- if (this._colorSection) {
578
- this._colorSection.classList.toggle('is-disabled', disabled);
579
- }
663
+ // Disabled logic removed as per requirements (all colour buttons remain active)
664
+ // if (this._colorSection) {
665
+ // this._colorSection.classList.toggle('is-disabled', disabled);
666
+ // }
580
667
  }
581
668
 
582
669
  _markNoneSelected() {
@@ -633,6 +720,7 @@ export class FramePropertiesPanel {
633
720
  canvas.width = 212;
634
721
  canvas.height = 148;
635
722
  canvas.className = 'fpp-color-picker__canvas';
723
+ canvas.id = 'fpp-hsv-canvas';
636
724
  this._hsvCanvas = canvas;
637
725
 
638
726
  const cursor = document.createElement('div');
@@ -661,6 +749,7 @@ export class FramePropertiesPanel {
661
749
  hueSlider.max = '360';
662
750
  hueSlider.value = '0';
663
751
  hueSlider.className = 'fpp-color-picker__hue-slider';
752
+ hueSlider.id = 'fpp-hue-slider';
664
753
  this._hueSlider = hueSlider;
665
754
 
666
755
  hueSlider.addEventListener('input', (e) => {
@@ -679,6 +768,7 @@ export class FramePropertiesPanel {
679
768
 
680
769
  const eyedropper = document.createElement('button');
681
770
  eyedropper.className = 'fpp-color-picker__eyedropper';
771
+ eyedropper.id = 'fpp-eyedropper-btn';
682
772
  eyedropper.title = 'Пипетка';
683
773
  eyedropper.innerHTML = ICONS.eyedropper;
684
774
  eyedropper.addEventListener('click', (e) => {
@@ -694,6 +784,7 @@ export class FramePropertiesPanel {
694
784
  const hexInput = document.createElement('input');
695
785
  hexInput.type = 'text';
696
786
  hexInput.className = 'fpp-color-picker__hex-input';
787
+ hexInput.id = 'fpp-hex-input';
697
788
  hexInput.value = '#ffffff';
698
789
  hexInput.maxLength = 7;
699
790
  hexInput.spellcheck = false;
@@ -724,6 +815,7 @@ export class FramePropertiesPanel {
724
815
  // Apply button
725
816
  const applyBtn = document.createElement('button');
726
817
  applyBtn.className = 'fpp-color-picker__apply';
818
+ applyBtn.id = 'fpp-picker-apply-btn';
727
819
  applyBtn.textContent = 'Добавить';
728
820
  applyBtn.addEventListener('click', (e) => {
729
821
  e.stopPropagation();
@@ -867,6 +959,7 @@ export class FramePropertiesPanel {
867
959
  const mainBtn = document.createElement('button');
868
960
  mainBtn.className = 'ipp-btn';
869
961
  mainBtn.title = 'Ещё';
962
+ mainBtn.id = 'fpp-btn-more';
870
963
  mainBtn.dataset.id = 'fpp-btn-more';
871
964
  mainBtn.innerHTML = ICONS.more;
872
965
 
@@ -911,6 +1004,7 @@ export class FramePropertiesPanel {
911
1004
 
912
1005
  const btn = document.createElement('button');
913
1006
  btn.className = 'ipp-dropdown-item';
1007
+ btn.id = 'fpp-more-' + item.id;
914
1008
  btn.dataset.id = 'fpp-more-' + item.id;
915
1009
 
916
1010
  if (item.icon) {
@@ -1052,6 +1146,8 @@ export class FramePropertiesPanel {
1052
1146
  if (this._moreDropdown) { this._moreDropdown.classList.remove('is-open'); }
1053
1147
  if (this._moreMainBtn) { this._moreMainBtn.classList.remove('is-active'); }
1054
1148
  if (this._infoPopover) { this._infoPopover.classList.remove('is-open'); }
1149
+ if (this._lockDropdown) { this._lockDropdown.classList.remove('is-open'); }
1150
+ if (this._lockExpandBtn) { this._lockExpandBtn.classList.remove('is-expanded'); }
1055
1151
  this._detachOutside();
1056
1152
  }
1057
1153
 
@@ -1098,23 +1194,52 @@ export class FramePropertiesPanel {
1098
1194
  _toggleLocked() {
1099
1195
  if (!this.currentId) { return; }
1100
1196
  const newLocked = !this._isLocked();
1197
+ const updates = { locked: newLocked };
1198
+ if (newLocked) {
1199
+ const data = this._getObjectData();
1200
+ updates.lockMode = (data && data.properties && data.properties.lockMode) || 'frame';
1201
+ }
1101
1202
  this.eventBus.emit(Events.Object.StateChanged, {
1102
1203
  objectId: this.currentId,
1103
- updates: { properties: { locked: newLocked } },
1204
+ updates: { properties: updates },
1205
+ });
1206
+ this._updateLockUI();
1207
+ this.reposition();
1208
+ }
1209
+
1210
+ _applyLockMode(mode) {
1211
+ if (!this.currentId) { return; }
1212
+ const locked = this._isLocked();
1213
+ const updates = { lockMode: mode };
1214
+ if (!locked) { updates.locked = true; }
1215
+ this.eventBus.emit(Events.Object.StateChanged, {
1216
+ objectId: this.currentId,
1217
+ updates: { properties: updates },
1104
1218
  });
1105
1219
  this._updateLockUI();
1106
1220
  this.reposition();
1107
1221
  }
1108
1222
 
1109
1223
  _updateLockUI() {
1110
- if (!this._btn_lock) { return; }
1224
+ if (!this._lockSplitMain) { return; }
1111
1225
  const locked = this._isLocked();
1112
1226
 
1113
1227
  const data = this._getObjectData();
1114
1228
  const hidden = !!(data && data.properties && data.properties.hidden);
1229
+ const lockMode = (data && data.properties && data.properties.lockMode) || 'frame';
1230
+
1231
+ this._lockSplitMain.innerHTML = locked ? ICONS.lock : ICONS.unlock;
1232
+ this._lockSplitMain.title = locked ? 'Разблокировать' : 'Заблокировать';
1115
1233
 
1116
- this._btn_lock.innerHTML = locked ? ICONS.lock : ICONS.unlock;
1117
- this._btn_lock.title = locked ? 'Разблокировать' : 'Заблокировать';
1234
+ if (this._lockExpandBtn) {
1235
+ this._lockExpandBtn.style.display = locked ? 'none' : '';
1236
+ }
1237
+ if (this._lockDropdown) {
1238
+ if (locked) { this._lockDropdown.classList.remove('is-open'); }
1239
+ this._lockDropdown.querySelectorAll('[data-lock-mode]').forEach((btn) => {
1240
+ btn.classList.toggle('is-active', btn.dataset.lockMode === lockMode);
1241
+ });
1242
+ }
1118
1243
 
1119
1244
  if (this._eyeBtn) {
1120
1245
  this._eyeBtn.dataset.hidden = hidden ? '1' : '0';
@@ -1050,6 +1050,14 @@ export class ImagePropertiesPanel {
1050
1050
 
1051
1051
  if (item.id === 'copy') {
1052
1052
  this.eventBus.emit(Events.Keyboard.Copy);
1053
+ } else if (item.id === 'bring-front') {
1054
+ if (this.currentId) this.eventBus.emit(Events.UI.LayerBringToFront, { objectId: this.currentId });
1055
+ } else if (item.id === 'bring-forward') {
1056
+ if (this.currentId) this.eventBus.emit(Events.UI.LayerBringForward, { objectId: this.currentId });
1057
+ } else if (item.id === 'send-backward') {
1058
+ if (this.currentId) this.eventBus.emit(Events.UI.LayerSendBackward, { objectId: this.currentId });
1059
+ } else if (item.id === 'send-back') {
1060
+ if (this.currentId) this.eventBus.emit(Events.UI.LayerSendToBack, { objectId: this.currentId });
1053
1061
  } else if (item.id === 'duplicate') {
1054
1062
  this._duplicateImage();
1055
1063
  } else if (item.id === 'lock') {
package/src/ui/Topbar.js CHANGED
@@ -176,10 +176,18 @@ export class Topbar {
176
176
  _getCurrentBoardBackgroundHex() {
177
177
  try {
178
178
  const app = window?.moodboard?.coreMoodboard?.pixi?.app || window?.moodboard?.core?.pixi?.app;
179
- const colorInt = app?.renderer?.background?.color ?? app?.renderer?.backgroundColor;
179
+ const renderer = app?.renderer;
180
+ if (!renderer) return null;
181
+ const bg = renderer.background;
182
+ // PIXI v7+: bg.color — экземпляр Color с методом toHex()
183
+ if (bg?.color && typeof bg.color.toHex === 'function') {
184
+ return String(bg.color.toHex()).toLowerCase();
185
+ }
186
+ const colorInt = (typeof bg?.color === 'number')
187
+ ? bg.color
188
+ : (typeof renderer.backgroundColor === 'number' ? renderer.backgroundColor : null);
180
189
  if (typeof colorInt !== 'number') return null;
181
- const hex = `#${colorInt.toString(16).padStart(6, '0')}`;
182
- return hex.toLowerCase();
190
+ return `#${colorInt.toString(16).padStart(6, '0')}`.toLowerCase();
183
191
  } catch (_) { return null; }
184
192
  }
185
193
 
@@ -288,14 +296,20 @@ export class Topbar {
288
296
  // Выделяем активный цвет галочкой по фактическому boardHex
289
297
  try {
290
298
  const ap = window?.moodboard?.coreMoodboard?.settingsApplier;
291
- const boardHex = (
292
- (ap && ap.get && ap.get().backgroundColor) ||
293
- this._currentBoardHex ||
294
- this._getCurrentBoardBackgroundHex() ||
295
- ''
296
- ).toLowerCase();
299
+ const raw = (ap && ap.get && ap.get().backgroundColor)
300
+ || this._currentBoardHex
301
+ || this._getCurrentBoardBackgroundHex()
302
+ || '';
303
+ let boardHex = '';
304
+ if (typeof raw === 'number') {
305
+ boardHex = `#${raw.toString(16).padStart(6, '0')}`;
306
+ } else if (typeof raw === 'string') {
307
+ boardHex = raw;
308
+ }
309
+ boardHex = boardHex.toLowerCase();
297
310
  const match = boardHex ? grid.querySelector(`[data-board="${boardHex}"]`) : null;
298
- if (match) match.classList.add('is-active');
311
+ const toActivate = match || grid.querySelector('[data-board="#ffffff"]');
312
+ if (toActivate) toActivate.classList.add('is-active');
299
313
  } catch (_) {}
300
314
 
301
315
  // позиционируем поповер
@@ -119,6 +119,45 @@ export function getConnectorMidpointScreen(core, connectorId) {
119
119
  };
120
120
  }
121
121
 
122
+ /**
123
+ * Возвращает наивысшую экранную точку коннектора (минимальный y) и горизонтальный центр.
124
+ * Координаты — целые числа (screen-space integer contract).
125
+ * @returns {{ x: number, topY: number } | null}
126
+ */
127
+ export function getConnectorTopScreen(core, connectorId) {
128
+ const segments = core?.connectorLayer?._lastSegments;
129
+ if (!segments) return null;
130
+ const seg = segments.find(s => s.id === connectorId);
131
+ if (!seg) return null;
132
+
133
+ const worldLayer = core?.pixi?.worldLayer;
134
+ const scale = worldLayer?.scale?.x ?? 1;
135
+ const worldX = worldLayer?.x ?? 0;
136
+ const worldY = worldLayer?.y ?? 0;
137
+
138
+ let pts;
139
+ if (seg.points && seg.points.length >= 2) {
140
+ pts = seg.points;
141
+ } else if (seg.start && seg.end) {
142
+ pts = [seg.start, seg.end];
143
+ } else {
144
+ return null;
145
+ }
146
+
147
+ let minWorldY = Infinity;
148
+ let sumWorldX = 0;
149
+ for (const p of pts) {
150
+ if (p.y < minWorldY) minWorldY = p.y;
151
+ sumWorldX += p.x;
152
+ }
153
+ const avgWorldX = sumWorldX / pts.length;
154
+
155
+ return {
156
+ x: Math.round(avgWorldX * scale + worldX),
157
+ topY: Math.round(minWorldY * scale + worldY),
158
+ };
159
+ }
160
+
122
161
  /**
123
162
  * Формирует updates.properties.style для StateChanged-эмита.
124
163
  */
@@ -429,12 +429,12 @@
429
429
  }
430
430
  .fpp-color-swatch--none svg { pointer-events: none; }
431
431
 
432
- /* Disabled colour section when transparent is selected */
433
- .fpp-section--color.is-disabled .fpp-color-swatch:not(.fpp-color-swatch--none) {
432
+ /* Disabled colour section when transparent is selected (removed) */
433
+ /* .fpp-section--color.is-disabled .fpp-color-swatch:not(.fpp-color-swatch--none) {
434
434
  opacity: 0.32;
435
435
  pointer-events: none;
436
436
  cursor: default;
437
- }
437
+ } */
438
438
 
439
439
  /* Custom colours row */
440
440
  .fpp-custom-row {
@@ -1326,3 +1326,43 @@
1326
1326
  color: #6B7280;
1327
1327
  }
1328
1328
 
1329
+ /* Lock Mode Dropdown */
1330
+ .fpp-lock-wrap {
1331
+ position: relative;
1332
+ }
1333
+
1334
+ .fpp-lock-dropdown {
1335
+ position: absolute;
1336
+ top: calc(100% + 6px);
1337
+ left: 50%;
1338
+ transform: translateX(-50%);
1339
+ background: #ffffff;
1340
+ border: 1px solid #E5E7EB;
1341
+ border-radius: 8px;
1342
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1), 0 4px 10px rgba(0, 0, 0, 0.05);
1343
+ padding: 6px;
1344
+ display: none;
1345
+ flex-direction: column;
1346
+ min-width: 190px;
1347
+ z-index: 1001;
1348
+ }
1349
+
1350
+ .fpp-lock-dropdown.is-open {
1351
+ display: flex;
1352
+ }
1353
+
1354
+ .fpp-lock-mode-check {
1355
+ width: 14px;
1356
+ height: 14px;
1357
+ display: inline-flex;
1358
+ align-items: center;
1359
+ justify-content: center;
1360
+ color: #374151;
1361
+ flex-shrink: 0;
1362
+ opacity: 0;
1363
+ }
1364
+
1365
+ .ipp-dropdown-item.is-active .fpp-lock-mode-check {
1366
+ opacity: 1;
1367
+ }
1368
+
@@ -731,7 +731,7 @@
731
731
 
732
732
  /* Light Theme */
733
733
  .moodboard-workspace--light {
734
- background: #f8f9fa;
734
+ background: #ffffff;
735
735
  color: #333;
736
736
  }
737
737
 
@@ -929,7 +929,7 @@
929
929
  .moodboard-root, :root {
930
930
  /* CSS variables for frame style */
931
931
  --frame-border-color: #C7CDD6;
932
- --frame-border-width: 5; /* px */
932
+ --frame-border-width: 1; /* px */
933
933
  --frame-corner-radius: 6; /* px */
934
934
  }
935
935
  .frame-popup {