@sequent-org/moodboard 1.4.49 → 1.4.52

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.
@@ -206,49 +206,119 @@
206
206
  height: 36px;
207
207
  }
208
208
 
209
- /* Frame properties panel — mirror text panel look */
209
+ /* Frame properties toolbar — mirror image toolbar look */
210
210
  .frame-properties-panel {
211
211
  position: absolute;
212
212
  pointer-events: auto;
213
- display: inline-flex;
213
+ display: flex;
214
+ flex-direction: row;
214
215
  align-items: center;
215
- gap: 8px;
216
- padding: 12px 32px;
217
- background-color: #ffffff;
218
- border: 1px solid #e0e0e0;
219
- border-radius: 9999px;
220
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
216
+ gap: 2px;
217
+ padding: 4px 8px;
218
+ background: #ffffff;
219
+ border: 1px solid #E5E7EB;
220
+ border-radius: 8px;
221
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.06);
222
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
221
223
  font-size: 13px;
222
- font-family: 'Roboto', Arial, sans-serif;
223
- min-width: 320px;
224
+ z-index: 3000;
225
+ user-select: none;
226
+ white-space: nowrap;
227
+ }
228
+
229
+ /* Ratio split-dropdown (двухстрочная кнопка «Ratio / значение») */
230
+ .fpp-ratio {
231
+ position: relative;
232
+ display: inline-flex;
233
+ flex-shrink: 0;
234
+ }
235
+
236
+ .fpp-ratio-btn {
237
+ display: inline-flex;
238
+ align-items: center;
239
+ gap: 6px;
224
240
  height: 36px;
241
+ padding: 0 8px;
242
+ border: none;
243
+ background: none;
244
+ border-radius: 6px;
245
+ cursor: pointer;
246
+ color: #374151;
247
+ font-family: inherit;
248
+ transition: background 0.15s;
225
249
  }
226
250
 
227
- .frame-properties-panel .fpp-label {
228
- font-family: 'Roboto', Arial, sans-serif;
229
- font-size: 12px;
230
- color: #666;
231
- font-weight: 500;
251
+ .fpp-ratio-btn:hover {
252
+ background: #F3F4F6;
232
253
  }
233
254
 
234
- .frame-properties-panel .fpp-select,
235
- .frame-properties-panel .fpp-input {
236
- border: 1px solid #ddd;
237
- border-radius: 4px;
238
- padding: 4px 8px;
255
+ .fpp-ratio-text {
256
+ display: flex;
257
+ flex-direction: column;
258
+ align-items: flex-start;
259
+ line-height: 1.15;
260
+ }
261
+
262
+ .fpp-ratio-caption {
263
+ font-size: 10px;
264
+ color: #9CA3AF;
265
+ }
266
+
267
+ .fpp-ratio-value {
239
268
  font-size: 13px;
240
- background-color: #fff;
269
+ color: #374151;
270
+ font-weight: 500;
241
271
  }
242
272
 
243
- .frame-properties-panel .fpp-select {
244
- height: 28px;
245
- line-height: 28px;
273
+ .fpp-ratio-btn svg {
274
+ color: #6B7280;
275
+ transition: transform 0.2s ease;
276
+ }
277
+
278
+ .fpp-ratio-btn.is-active svg {
279
+ transform: rotate(180deg);
280
+ }
281
+
282
+ .fpp-ratio-dropdown {
283
+ position: absolute;
284
+ top: calc(100% + 6px);
285
+ left: 0;
286
+ background: #ffffff;
287
+ border: 1px solid #E5E7EB;
288
+ border-radius: 8px;
289
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1), 0 4px 10px rgba(0, 0, 0, 0.05);
290
+ padding: 6px;
291
+ display: none;
292
+ flex-direction: column;
293
+ min-width: 180px;
294
+ z-index: 1001;
295
+ }
296
+
297
+ .fpp-ratio-dropdown.is-open {
298
+ display: flex;
246
299
  }
247
300
 
248
- .frame-properties-panel .fpp-color-button {
301
+ /* Кружок цвета фона */
302
+ .fpp-color-wrap {
303
+ display: inline-flex;
304
+ align-items: center;
305
+ justify-content: center;
249
306
  width: 28px;
250
307
  height: 28px;
251
- border: 1px solid #ddd;
308
+ border-radius: 6px;
309
+ cursor: pointer;
310
+ flex-shrink: 0;
311
+ transition: background 0.15s;
312
+ }
313
+
314
+ .fpp-color-wrap:hover {
315
+ background: #F3F4F6;
316
+ }
317
+
318
+ .fpp-color-button {
319
+ width: 20px;
320
+ height: 20px;
321
+ border: 1px solid #D1D5DB;
252
322
  border-radius: 50%;
253
323
  background-color: #ffffff;
254
324
  cursor: pointer;
@@ -256,9 +326,274 @@
256
326
  padding: 0;
257
327
  display: block;
258
328
  box-sizing: border-box;
329
+ position: relative;
330
+ }
331
+
332
+ /* transparent indicator — diagonal slash via pseudo */
333
+ .fpp-color-button--none {
334
+ background-color: #ffffff !important;
335
+ border-color: #D1D5DB;
336
+ }
337
+ .fpp-color-button--none::after {
338
+ content: '';
339
+ position: absolute;
340
+ inset: 0;
341
+ background: linear-gradient(to top-right,
342
+ transparent calc(50% - 0.75px),
343
+ #EF4444 calc(50% - 0.75px),
344
+ #EF4444 calc(50% + 0.75px),
345
+ transparent calc(50% + 0.75px)
346
+ );
347
+ border-radius: 50%;
348
+ }
349
+
350
+ /* ═══════════════════════════════════════════════════════════════
351
+ Colour popup dropdown
352
+ ═══════════════════════════════════════════════════════════════ */
353
+
354
+ .fpp-color-popup {
355
+ position: absolute;
356
+ background: #ffffff;
357
+ border: 1px solid #E5E7EB;
358
+ border-radius: 12px;
359
+ box-shadow: 0 10px 25px rgba(0,0,0,0.10), 0 4px 10px rgba(0,0,0,0.05);
360
+ padding: 12px;
361
+ display: none;
362
+ flex-direction: column;
363
+ gap: 14px;
364
+ min-width: 196px;
365
+ z-index: 1001;
366
+ box-sizing: border-box;
367
+ }
368
+ .fpp-color-popup.is-open { display: flex; }
369
+
370
+ .fpp-section { display: flex; flex-direction: column; gap: 8px; }
371
+
372
+ .fpp-section-label {
373
+ font-family: 'Roboto', Arial, sans-serif;
374
+ font-size: 11px;
375
+ font-weight: 500;
376
+ color: #9CA3AF;
377
+ letter-spacing: 0.02em;
378
+ user-select: none;
379
+ }
380
+
381
+ /* Background type row */
382
+ .fpp-bg-type-row { display: flex; gap: 4px; }
383
+
384
+ .fpp-bg-type-btn {
385
+ display: inline-flex;
386
+ align-items: center;
387
+ justify-content: center;
388
+ width: 36px;
389
+ height: 32px;
390
+ border: 1.5px solid transparent;
391
+ border-radius: 6px;
392
+ background: #F9FAFB;
393
+ cursor: pointer;
394
+ color: #374151;
395
+ padding: 0;
396
+ transition: background 0.12s, border-color 0.12s;
397
+ }
398
+ .fpp-bg-type-btn:hover { background: #F3F4F6; border-color: #E5E7EB; }
399
+ .fpp-bg-type-btn.is-active { border-color: #374151; background: #F3F4F6; }
400
+
401
+ /* Color swatch grid */
402
+ .fpp-color-grid {
403
+ display: grid;
404
+ grid-template-columns: repeat(6, 28px);
405
+ gap: 6px;
406
+ }
407
+
408
+ .fpp-color-swatch {
409
+ width: 28px;
410
+ height: 28px;
411
+ border-radius: 50%;
412
+ border: 1.5px solid rgba(0,0,0,0.12);
413
+ background-color: var(--swatch-color, #ffffff);
414
+ cursor: pointer;
415
+ padding: 0;
416
+ margin: 0;
417
+ display: flex;
418
+ align-items: center;
419
+ justify-content: center;
420
+ transition: transform 0.10s, box-shadow 0.10s;
421
+ flex-shrink: 0;
422
+ }
423
+ .fpp-color-swatch:hover { transform: scale(1.12); box-shadow: 0 2px 8px rgba(0,0,0,0.18); }
424
+ .fpp-color-swatch.is-selected { box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #374151; }
425
+
426
+ .fpp-color-swatch--none {
427
+ background-color: #ffffff;
428
+ border-color: #D1D5DB;
429
+ }
430
+ .fpp-color-swatch--none svg { pointer-events: none; }
431
+
432
+ /* Disabled colour section when transparent is selected (removed) */
433
+ /* .fpp-section--color.is-disabled .fpp-color-swatch:not(.fpp-color-swatch--none) {
434
+ opacity: 0.32;
435
+ pointer-events: none;
436
+ cursor: default;
437
+ } */
438
+
439
+ /* Custom colours row */
440
+ .fpp-custom-row {
441
+ display: flex;
442
+ flex-wrap: wrap;
443
+ gap: 6px;
444
+ align-items: center;
445
+ }
446
+
447
+ .fpp-add-color-btn {
448
+ width: 28px;
449
+ height: 28px;
450
+ border-radius: 50%;
451
+ border: 1.5px dashed #D1D5DB;
452
+ background: transparent;
453
+ cursor: pointer;
454
+ display: flex;
455
+ align-items: center;
456
+ justify-content: center;
457
+ color: #9CA3AF;
458
+ padding: 0;
459
+ flex-shrink: 0;
460
+ transition: border-color 0.12s, color 0.12s;
461
+ }
462
+ .fpp-add-color-btn:hover { border-color: #9CA3AF; color: #374151; }
463
+
464
+ /* ═══════════════════════════════════════════════════════════════
465
+ HSV colour picker
466
+ ═══════════════════════════════════════════════════════════════ */
467
+
468
+ .fpp-color-picker {
469
+ position: absolute;
470
+ background: #ffffff;
471
+ border: 1px solid #E5E7EB;
472
+ border-radius: 12px;
473
+ box-shadow: 0 10px 25px rgba(0,0,0,0.12);
474
+ padding: 12px;
475
+ display: none;
476
+ flex-direction: column;
477
+ gap: 10px;
478
+ z-index: 1002;
479
+ width: 236px;
480
+ box-sizing: border-box;
481
+ }
482
+ .fpp-color-picker.is-open { display: flex; }
483
+
484
+ .fpp-color-picker__canvas-wrap {
485
+ position: relative;
486
+ width: 212px;
487
+ height: 148px;
488
+ border-radius: 6px;
489
+ overflow: hidden;
490
+ cursor: crosshair;
491
+ flex-shrink: 0;
492
+ }
493
+ .fpp-color-picker__canvas {
494
+ display: block;
495
+ width: 212px;
496
+ height: 148px;
497
+ }
498
+ .fpp-color-picker__cursor {
499
+ position: absolute;
500
+ width: 14px;
501
+ height: 14px;
502
+ border-radius: 50%;
503
+ border: 2px solid #ffffff;
504
+ box-shadow: 0 0 0 1px rgba(0,0,0,0.30);
505
+ transform: translate(-50%, -50%);
506
+ pointer-events: none;
507
+ top: 0;
508
+ left: 0;
509
+ }
510
+
511
+ .fpp-color-picker__hue-wrap { width: 100%; }
512
+
513
+ .fpp-color-picker__hue-slider {
514
+ -webkit-appearance: none;
515
+ appearance: none;
516
+ width: 100%;
517
+ height: 10px;
518
+ border-radius: 5px;
519
+ background: linear-gradient(to right,
520
+ hsl(0,100%,50%), hsl(30,100%,50%), hsl(60,100%,50%), hsl(90,100%,50%),
521
+ hsl(120,100%,50%), hsl(150,100%,50%), hsl(180,100%,50%), hsl(210,100%,50%),
522
+ hsl(240,100%,50%), hsl(270,100%,50%), hsl(300,100%,50%), hsl(330,100%,50%),
523
+ hsl(360,100%,50%));
524
+ outline: none;
525
+ cursor: pointer;
526
+ border: none;
527
+ }
528
+ .fpp-color-picker__hue-slider::-webkit-slider-thumb {
529
+ -webkit-appearance: none;
530
+ width: 16px;
531
+ height: 16px;
532
+ border-radius: 50%;
533
+ background: #ffffff;
534
+ border: 2px solid rgba(0,0,0,0.28);
535
+ box-shadow: 0 1px 3px rgba(0,0,0,0.20);
536
+ cursor: pointer;
537
+ }
538
+ .fpp-color-picker__hue-slider::-moz-range-thumb {
539
+ width: 16px;
540
+ height: 16px;
541
+ border-radius: 50%;
542
+ background: #ffffff;
543
+ border: 2px solid rgba(0,0,0,0.28);
544
+ cursor: pointer;
545
+ }
546
+
547
+ .fpp-color-picker__bottom {
548
+ display: flex;
549
+ align-items: center;
550
+ gap: 8px;
551
+ }
552
+ .fpp-color-picker__eyedropper {
553
+ width: 30px;
554
+ height: 30px;
555
+ border: 1px solid #E5E7EB;
556
+ border-radius: 6px;
557
+ background: transparent;
558
+ display: flex;
559
+ align-items: center;
560
+ justify-content: center;
561
+ cursor: pointer;
562
+ color: #6B7280;
563
+ padding: 0;
564
+ flex-shrink: 0;
565
+ transition: background 0.12s, color 0.12s;
566
+ }
567
+ .fpp-color-picker__eyedropper:hover { background: #F3F4F6; color: #374151; }
568
+
569
+ .fpp-color-picker__hex-input {
570
+ flex: 1;
571
+ height: 30px;
572
+ border: 1px solid #E5E7EB;
573
+ border-radius: 6px;
574
+ padding: 0 8px;
575
+ font-size: 13px;
576
+ font-family: 'Roboto Mono', 'Courier New', monospace;
577
+ color: #374151;
578
+ outline: none;
579
+ background: #ffffff;
259
580
  }
581
+ .fpp-color-picker__hex-input:focus { border-color: #9CA3AF; }
260
582
 
261
- .frame-properties-panel .fpp-section { display: inline-flex; align-items: center; gap: 6px; }
583
+ .fpp-color-picker__apply {
584
+ width: 100%;
585
+ height: 30px;
586
+ border: none;
587
+ border-radius: 6px;
588
+ background: #374151;
589
+ color: #ffffff;
590
+ font-size: 13px;
591
+ font-family: 'Roboto', Arial, sans-serif;
592
+ font-weight: 500;
593
+ cursor: pointer;
594
+ transition: background 0.12s;
595
+ }
596
+ .fpp-color-picker__apply:hover { background: #1F2937; }
262
597
 
263
598
  .text-properties-panel .tpp-label {
264
599
  font-family: 'Roboto', Arial, sans-serif;
@@ -991,3 +1326,43 @@
991
1326
  color: #6B7280;
992
1327
  }
993
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
+
@@ -1622,3 +1622,23 @@
1622
1622
  }
1623
1623
  }
1624
1624
 
1625
+ /* ── Crop Overlay ─────────────────────────────────────────────────────────── */
1626
+
1627
+ .mb-crop-overlay {
1628
+ position: absolute;
1629
+ inset: 0;
1630
+ z-index: 2000;
1631
+ user-select: none;
1632
+ }
1633
+
1634
+ .mb-crop-img-area {
1635
+ position: absolute;
1636
+ }
1637
+
1638
+ /* Стили ручек задаются инлайн в CropOverlay.js (уголки и засечки) */
1639
+ .mb-crop-handle {
1640
+ position: absolute;
1641
+ box-sizing: border-box;
1642
+ z-index: 1;
1643
+ }
1644
+
@@ -0,0 +1,92 @@
1
+ import * as PIXI from 'pixi.js';
2
+ import { applyRoundedMask } from './applyRoundedMask.js';
3
+
4
+ /**
5
+ * Применяет или снимает кроп на PIXI-спрайте изображения.
6
+ *
7
+ * @param {PIXI.Sprite} sprite
8
+ * @param {object|null} cropRect - { x, y, w, h } нормализованные 0..1 от оригинальной текстуры, или null
9
+ * @param {string|null} cropShape - 'circle' | 'rect' | null
10
+ * @param {{ width: number, height: number }} displaySize - размер отображения в мировых пикселях
11
+ * @param {{ x: number, y: number }} position - top-left позиция в мировых координатах
12
+ * @param {number} [borderRadius=0] - радиус скругления (переприменяем после снятия кропа)
13
+ */
14
+ export function applyCropToSprite(sprite, cropRect, cropShape, displaySize, position, borderRadius = 0) {
15
+ if (!sprite || !sprite.texture?.baseTexture) return;
16
+
17
+ const baseTex = sprite.texture.baseTexture;
18
+ // Оригинальные размеры текстуры (до любого кропа)
19
+ const baseW = sprite._mb?.properties?.baseW || baseTex.realWidth || baseTex.width || 1;
20
+ const baseH = sprite._mb?.properties?.baseH || baseTex.realHeight || baseTex.height || 1;
21
+ const src = sprite._mb?.properties?.src;
22
+
23
+ // Снять circle-маску кропа
24
+ if (sprite._cropCircleMask) {
25
+ if (sprite.mask === sprite._cropCircleMask) sprite.mask = null;
26
+ try { sprite._cropCircleMask.destroy({ children: true }); } catch (_) {}
27
+ sprite._cropCircleMask = null;
28
+ }
29
+
30
+ const { width: dW, height: dH } = displaySize;
31
+
32
+ if (cropRect) {
33
+ const { x: nx, y: ny, w: nw, h: nh } = cropRect;
34
+ const frameX = Math.max(0, Math.round(nx * baseW));
35
+ const frameY = Math.max(0, Math.round(ny * baseH));
36
+ const frameW = Math.max(1, Math.min(Math.round(nw * baseW), baseW - frameX));
37
+ const frameH = Math.max(1, Math.min(Math.round(nh * baseH), baseH - frameY));
38
+
39
+ // Новая текстура с кадрированием
40
+ const croppedTex = new PIXI.Texture(baseTex, new PIXI.Rectangle(frameX, frameY, frameW, frameH));
41
+ sprite.texture = croppedTex;
42
+ sprite.scale.set(dW / frameW, dH / frameH);
43
+ sprite.x = Math.round(position.x + dW / 2);
44
+ sprite.y = Math.round(position.y + dH / 2);
45
+
46
+ // Сбросить borderRadius-маску — circle или radius переустановит её
47
+ if (sprite._borderRadiusMask) {
48
+ if (sprite.mask === sprite._borderRadiusMask) sprite.mask = null;
49
+ try { sprite._borderRadiusMask.destroy({ children: true }); } catch (_) {}
50
+ sprite._borderRadiusMask = null;
51
+ }
52
+
53
+ if (cropShape === 'circle') {
54
+ // Вписанная окружность в локальных координатах спрайта (local = texture frame px)
55
+ const r = Math.min(frameW, frameH) / 2;
56
+ const g = new PIXI.Graphics();
57
+ g.beginFill(0xffffff);
58
+ g.drawCircle(0, 0, r);
59
+ g.endFill();
60
+ sprite.addChild(g);
61
+ sprite._cropCircleMask = g;
62
+ sprite.mask = g;
63
+ } else if (borderRadius > 0) {
64
+ applyRoundedMask(sprite, borderRadius);
65
+ }
66
+
67
+ } else {
68
+ // Восстановить оригинальную текстуру
69
+ if (src) {
70
+ try {
71
+ const origTex = PIXI.Texture.from(src);
72
+ if (origTex) sprite.texture = origTex;
73
+ } catch (_) {}
74
+ }
75
+
76
+ const texW = sprite.texture.width || 1;
77
+ const texH = sprite.texture.height || 1;
78
+ sprite.scale.set(dW / texW, dH / texH);
79
+ sprite.x = Math.round(position.x + dW / 2);
80
+ sprite.y = Math.round(position.y + dH / 2);
81
+
82
+ // Сбросить borderRadius-маску и переприменить если нужно
83
+ if (sprite._borderRadiusMask) {
84
+ if (sprite.mask === sprite._borderRadiusMask) sprite.mask = null;
85
+ try { sprite._borderRadiusMask.destroy({ children: true }); } catch (_) {}
86
+ sprite._borderRadiusMask = null;
87
+ }
88
+ if (borderRadius > 0) {
89
+ applyRoundedMask(sprite, borderRadius);
90
+ }
91
+ }
92
+ }