tokimeki-image-editor 0.1.8 → 0.1.10

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.
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">import '../i18n';
2
2
  import { _ } from 'svelte-i18n';
3
3
  import { Redo2, RotateCcw, Undo2 } from 'lucide-svelte';
4
- import { loadImage, calculateFitScale, exportCanvas, downloadImage, applyTransform } from '../utils/canvas';
4
+ import { loadImage, calculateFitScale, exportCanvas, downloadImage, applyTransform, applyTransformWithWebGPU } from '../utils/canvas';
5
5
  import { createEmptyHistory, createSnapshot, addToHistory, undo, redo, canUndo, canRedo } from '../utils/history';
6
6
  import { createDefaultAdjustments } from '../utils/adjustments';
7
7
  import Toolbar from './Toolbar.svelte';
@@ -200,10 +200,11 @@ function handleStampAreasChange(stampAreas) {
200
200
  state.stampAreas = stampAreas;
201
201
  saveToHistory();
202
202
  }
203
- function handleExport() {
203
+ async function handleExport() {
204
204
  if (!state.imageData.original)
205
205
  return;
206
- const exportCanvas = applyTransform(state.imageData.original, state.transform, state.adjustments, state.cropArea, state.blurAreas, state.stampAreas);
206
+ // Use WebGPU for export when available
207
+ const exportCanvas = await applyTransformWithWebGPU(state.imageData.original, state.transform, state.adjustments, state.cropArea, state.blurAreas, state.stampAreas);
207
208
  const dataUrl = exportCanvas.toDataURL(state.exportOptions.format === 'jpeg' ? 'image/jpeg' : 'image/png', state.exportOptions.quality);
208
209
  const filename = `edited-image-${Date.now()}.${state.exportOptions.format}`;
209
210
  downloadImage(dataUrl, filename);
@@ -211,10 +212,11 @@ function handleExport() {
211
212
  onExport(dataUrl);
212
213
  }
213
214
  }
214
- function handleComplete() {
215
+ async function handleComplete() {
215
216
  if (!state.imageData.original || !onComplete)
216
217
  return;
217
- const exportCanvas = applyTransform(state.imageData.original, state.transform, state.adjustments, state.cropArea, state.blurAreas, state.stampAreas);
218
+ // Use WebGPU for export when available
219
+ const exportCanvas = await applyTransformWithWebGPU(state.imageData.original, state.transform, state.adjustments, state.cropArea, state.blurAreas, state.stampAreas);
218
220
  const format = state.exportOptions.format === 'jpeg' ? 'image/jpeg' : 'image/png';
219
221
  const dataUrl = exportCanvas.toDataURL(state.exportOptions.format === 'jpeg' ? 'image/jpeg' : 'image/png', state.exportOptions.quality);
220
222
  // Convert to blob
@@ -324,421 +326,422 @@ function handleKeyDown(event) {
324
326
  handleRedo();
325
327
  }
326
328
  }
327
- </script>
328
-
329
- <svelte:window onkeydown={handleKeyDown} />
330
-
331
- <div class="image-editor" style="width: {width}px;">
332
- {#if !isStandalone && state.imageData.original}
333
- <div class="embedded-controls">
334
- <button class="embedded-btn embedded-btn-cancel" onclick={handleCancel}>
335
- {$_('editor.cancel')}
336
- </button>
337
- <button class="embedded-btn embedded-btn-apply" onclick={handleComplete}>
338
- {$_('editor.apply')}
339
- </button>
340
-
341
- <div class="editor-history-controls">
342
- <button
343
- class="editor-history-btn"
344
- disabled={!canUndo(state.history)}
345
- onclick={handleUndo}
346
- title={$_('toolbar.undo')}
347
- >
348
- <Undo2 size={20} />
349
- </button>
350
-
351
- <button
352
- class="editor-history-btn"
353
- disabled={!canRedo(state.history)}
354
- onclick={handleRedo}
355
- title={$_('toolbar.redo')}
356
- >
357
- <Redo2 size={20} />
358
- </button>
359
-
360
- <button
361
- class="editor-history-btn"
362
- disabled={!!state.imageData.original}
363
- onclick={handleReset}
364
- title={$_('editor.reset')}
365
- >
366
- <RotateCcw size={20} />
367
- </button>
368
- </div>
369
- </div>
370
- {/if}
371
-
372
- <div class="editor-body">
373
- {#if !state.imageData.original && isStandalone}
374
- <div
375
- class="upload-area"
376
- role="button"
377
- tabindex="0"
378
- ondrop={handleDrop}
379
- ondragover={handleDragOver}
380
- onclick={openFileDialog}
381
- onkeydown={(e) => e.key === 'Enter' && openFileDialog()}
382
- >
383
- <p>{$_('editor.dropImageHere')}</p>
384
- <input
385
- bind:this={fileInput}
386
- type="file"
387
- accept="image/png,image/jpeg,image/jpg"
388
- onchange={handleFileInputChange}
389
- style="display: none;"
390
- />
391
- </div>
392
- {:else if !state.imageData.original && !isStandalone}
393
- <div class="no-image-message">
394
- <p>{$_('editor.noImage')}</p>
395
- </div>
396
- {:else}
397
- <div
398
- class="canvas-container"
399
- onwheel={(e) => {
400
- e.preventDefault();
401
- const delta = -e.deltaY * 0.001;
402
- handleZoom(delta, e.clientX, e.clientY);
403
- }}
404
- bind:clientWidth
405
- bind:clientHeight
406
- >
407
- <Canvas
408
- bind:canvas={canvasElement}
409
- width={clientWidth || width}
410
- height={clientHeight || height}
411
- image={state.imageData.original}
412
- viewport={state.viewport}
413
- transform={state.transform}
414
- adjustments={state.adjustments}
415
- cropArea={state.cropArea}
416
- blurAreas={state.blurAreas}
417
- stampAreas={state.stampAreas}
418
- onZoom={handleZoom}
419
- />
420
-
421
- {#if state.mode === 'crop'}
422
- <CropTool
423
- canvas={canvasElement}
424
- image={state.imageData.original}
425
- viewport={state.viewport}
426
- transform={state.transform}
427
- onApply={handleCropApply}
428
- onCancel={() => state.mode = null}
429
- onViewportChange={handleViewportChange}
430
- onTransformChange={handleTransformChange}
431
- />
432
- {:else if state.mode === 'blur'}
433
- <BlurTool
434
- canvas={canvasElement}
435
- image={state.imageData.original}
436
- viewport={state.viewport}
437
- transform={state.transform}
438
- blurAreas={state.blurAreas}
439
- cropArea={state.cropArea}
440
- onUpdate={handleBlurAreasChange}
441
- onClose={() => state.mode = null}
442
- onViewportChange={handleViewportChange}
443
- />
444
- {:else if state.mode === 'stamp'}
445
- <StampTool
446
- canvas={canvasElement}
447
- image={state.imageData.original}
448
- viewport={state.viewport}
449
- transform={state.transform}
450
- stampAreas={state.stampAreas}
451
- cropArea={state.cropArea}
452
- onUpdate={handleStampAreasChange}
453
- onClose={() => state.mode = null}
454
- onViewportChange={handleViewportChange}
455
- />
456
- {/if}
457
-
458
- {#if state.mode === 'adjust'}
459
- <div class="tools-panel">
460
- <AdjustTool
461
- adjustments={state.adjustments}
462
- onChange={handleAdjustmentsChange}
463
- onClose={() => state.mode = null}
464
- />
465
- </div>
466
- {:else if state.mode === 'filter'}
467
- <div class="tools-panel">
468
- <FilterTool
469
- image={state.imageData.original}
470
- adjustments={state.adjustments}
471
- transform={state.transform}
472
- cropArea={state.cropArea}
473
- onChange={handleFilterApply}
474
- onClose={() => state.mode = null}
475
- />
476
- </div>
477
- {:else if state.mode === 'export' && isStandalone}
478
- <div class="tools-panel">
479
- <ExportTool
480
- options={state.exportOptions}
481
- onChange={(options) => state.exportOptions = { ...state.exportOptions, ...options }}
482
- onExport={handleExport}
483
- onClose={() => state.mode = null}
484
- />
485
- </div>
486
- {/if}
487
- </div>
488
- {/if}
489
- </div>
490
-
491
- <div class="editor-header">
492
- <Toolbar
493
- mode={state.mode}
494
- hasImage={!!state.imageData.original}
495
- canUndo={canUndo(state.history)}
496
- canRedo={canRedo(state.history)}
497
- isStandalone={isStandalone}
498
- onModeChange={handleModeChange}
499
- onReset={handleReset}
500
- onUndo={handleUndo}
501
- onRedo={handleRedo}
502
- />
503
- </div>
504
- </div>
505
-
506
- <style>
507
- :global(input[type='range']) {
508
- appearance: none;
509
- }
510
-
511
- .image-editor {
512
- display: flex;
513
- flex-direction: column;
514
- gap: 1rem;
515
- padding: 1rem;
516
- background: #1a1a1a;
517
- border-radius: 8px;
518
- color: #fff;
519
- }
520
-
521
- @media (max-width: 767px) {
522
-
523
- .image-editor {
524
- width: 100% !important;
525
- height: 90vh
526
- }
527
- }
528
-
529
- .editor-header {
530
- display: flex;
531
- align-items: center;
532
- width: 100%;
533
- }
534
-
535
- .editor-body {
536
- display: flex;
537
- flex-direction: column;
538
- gap: 1rem;
539
- }
540
-
541
- @media (max-width: 767px) {
542
-
543
- .editor-body {
544
- flex: 1;
545
- justify-content: center
546
- }
547
- }
548
-
549
- .upload-area {
550
- display: flex;
551
- align-items: center;
552
- justify-content: center;
553
- min-height: 400px;
554
- border: 2px dashed #666;
555
- border-radius: 8px;
556
- background: #2a2a2a;
557
- cursor: pointer;
558
- transition: all 0.2s;
559
- }
560
-
561
- .upload-area:hover {
562
- border-color: #888;
563
- background: #333;
564
- }
565
-
566
- .upload-area p {
567
- margin: 0;
568
- font-size: 1.1rem;
569
- color: #999;
570
- }
571
-
572
- @media (max-width: 767px) {
573
-
574
- .upload-area p {
575
- font-size: .75rem
576
- }
577
- }
578
-
579
- .canvas-container {
580
- position: relative;
581
- display: flex;
582
- justify-content: center;
583
- align-items: center;
584
- background: #2a2a2a;
585
- border-radius: 8px;
586
- overflow: hidden;
587
- }
588
-
589
- @media (max-width: 767px) {
590
-
591
- .canvas-container {
592
- flex: 1;
593
- min-height: 0
594
- }
595
- }
596
-
597
- .tools-panel {
598
- padding: 1rem;
599
- background: #2a2a2a;
600
- border-radius: 8px;
601
- position: absolute;
602
- width: min-content;
603
- right: 1rem;
604
- top: 1rem;
605
- bottom: 1rem;
606
- overflow-y: auto;
607
- scrollbar-width: thin;
608
- }
609
-
610
- @media (max-width: 767px) {
611
-
612
- .tools-panel {
613
- position: absolute;
614
- left: 0;
615
- right: 0;
616
- top: auto;
617
- bottom: 0;
618
- width: auto;
619
- max-height: 50vh;
620
- border-radius: 16px 16px 0 0;
621
- z-index: 1001
622
- }
623
- }
624
-
625
- .no-image-message {
626
- display: flex;
627
- align-items: center;
628
- justify-content: center;
629
- min-height: 400px;
630
- background: #2a2a2a;
631
- border-radius: 8px;
632
- }
633
-
634
- .no-image-message p {
635
- margin: 0;
636
- font-size: 1.1rem;
637
- color: #999;
638
- }
639
-
640
- .embedded-controls {
641
- position: relative;
642
- display: flex;
643
- justify-content: space-between;
644
- gap: 1rem;
645
- z-index: 1000;
646
- }
647
-
648
- @media (max-width: 767px) {
649
-
650
- }
651
-
652
- .embedded-btn {
653
- padding: 0 1rem;
654
- border: none;
655
- font-size: .9rem;
656
- font-weight: 600;
657
- cursor: pointer;
658
- transition: all 0.2s;
659
- border-radius: 4px;
660
- height: 36px;
661
- }
662
-
663
- .embedded-btn:hover {
664
- opacity: .8;
665
- }
666
-
667
- @media (max-width: 767px) {
668
-
669
- .embedded-btn {
670
- padding: 0;
671
- font-size: .75rem;
672
- min-width: 80px
673
- }
674
- }
675
-
676
- .embedded-btn-cancel {
677
- background: #666;
678
- color: #fff;
679
- }
680
-
681
- .embedded-btn-apply {
682
- background: var(--primary-color, #63b97b);
683
- color: #fff;
684
- }
685
-
686
- .experimental {
687
- padding: 0 1rem;
688
- background: var(--primary-color, #63b97b);
689
- opacity: .7;
690
- color: #fff;
691
- height: 40px;
692
- border-radius: 20px;
693
- letter-spacing: .05em;
694
- font-size: 16px;
695
- font-weight: bold;
696
- display: flex;
697
- align-items: center;
698
- margin: 0 auto 0 0;
699
- }
700
-
701
- .editor-history-controls {
702
- position: absolute;
703
- right: 0;
704
- left: 0;
705
- top: 0;
706
- margin: auto;
707
- width: fit-content;
708
- display: flex;
709
- align-items: center;
710
- justify-content: center;
711
- overflow: hidden;
712
- border-radius: 8px;
713
- border: 1px solid #444;
714
- box-sizing: border-box;
715
- }
716
-
717
- .editor-history-btn {
718
- appearance: none;
719
- box-shadow: none;
720
- border: none;
721
- background: #333;
722
- width: 36px;
723
- height: 36px;
724
- display: grid;
725
- place-content: center;
726
- color: #fff;
727
- cursor: pointer;
728
- transition: all .3s ease-in-out;
729
- border-right: 1px solid #444;
730
- }
731
-
732
- .editor-history-btn:last-child {
733
- border-right: none;
734
- }
735
-
736
- .editor-history-btn:hover {
737
- opacity: .7;
738
- }
739
-
740
- .editor-history-btn:disabled {
741
- background: #222;
742
- color: #333;
743
- cursor: not-allowed;
744
- }</style>
329
+ </script>
330
+
331
+ <svelte:window onkeydown={handleKeyDown} />
332
+
333
+ <div class="image-editor" style="width: {width}px;">
334
+ {#if !isStandalone && state.imageData.original}
335
+ <div class="embedded-controls">
336
+ <button class="embedded-btn embedded-btn-cancel" onclick={handleCancel}>
337
+ {$_('editor.cancel')}
338
+ </button>
339
+ <button class="embedded-btn embedded-btn-apply" onclick={handleComplete}>
340
+ {$_('editor.apply')}
341
+ </button>
342
+
343
+ <div class="editor-history-controls">
344
+ <button
345
+ class="editor-history-btn"
346
+ disabled={!canUndo(state.history)}
347
+ onclick={handleUndo}
348
+ title={$_('toolbar.undo')}
349
+ >
350
+ <Undo2 size={20} />
351
+ </button>
352
+
353
+ <button
354
+ class="editor-history-btn"
355
+ disabled={!canRedo(state.history)}
356
+ onclick={handleRedo}
357
+ title={$_('toolbar.redo')}
358
+ >
359
+ <Redo2 size={20} />
360
+ </button>
361
+
362
+ <button
363
+ class="editor-history-btn"
364
+ disabled={!!state.imageData.original}
365
+ onclick={handleReset}
366
+ title={$_('editor.reset')}
367
+ >
368
+ <RotateCcw size={20} />
369
+ </button>
370
+ </div>
371
+ </div>
372
+ {/if}
373
+
374
+ <div class="editor-body">
375
+ {#if !state.imageData.original && isStandalone}
376
+ <div
377
+ class="upload-area"
378
+ role="button"
379
+ tabindex="0"
380
+ ondrop={handleDrop}
381
+ ondragover={handleDragOver}
382
+ onclick={openFileDialog}
383
+ onkeydown={(e) => e.key === 'Enter' && openFileDialog()}
384
+ >
385
+ <p>{$_('editor.dropImageHere')}</p>
386
+ <input
387
+ bind:this={fileInput}
388
+ type="file"
389
+ accept="image/png,image/jpeg,image/jpg"
390
+ onchange={handleFileInputChange}
391
+ style="display: none;"
392
+ />
393
+ </div>
394
+ {:else if !state.imageData.original && !isStandalone}
395
+ <div class="no-image-message">
396
+ <p>{$_('editor.noImage')}</p>
397
+ </div>
398
+ {:else}
399
+ <div
400
+ class="canvas-container"
401
+ onwheel={(e) => {
402
+ e.preventDefault();
403
+ const delta = -e.deltaY * 0.001;
404
+ handleZoom(delta, e.clientX, e.clientY);
405
+ }}
406
+ bind:clientWidth
407
+ bind:clientHeight
408
+ >
409
+ <Canvas
410
+ bind:canvas={canvasElement}
411
+ width={clientWidth || width}
412
+ height={clientHeight || height}
413
+ image={state.imageData.original}
414
+ viewport={state.viewport}
415
+ transform={state.transform}
416
+ adjustments={state.adjustments}
417
+ cropArea={state.cropArea}
418
+ blurAreas={state.blurAreas}
419
+ stampAreas={state.stampAreas}
420
+ onZoom={handleZoom}
421
+ onViewportChange={handleViewportChange}
422
+ />
423
+
424
+ {#if state.mode === 'crop'}
425
+ <CropTool
426
+ canvas={canvasElement}
427
+ image={state.imageData.original}
428
+ viewport={state.viewport}
429
+ transform={state.transform}
430
+ onApply={handleCropApply}
431
+ onCancel={() => state.mode = null}
432
+ onViewportChange={handleViewportChange}
433
+ onTransformChange={handleTransformChange}
434
+ />
435
+ {:else if state.mode === 'blur'}
436
+ <BlurTool
437
+ canvas={canvasElement}
438
+ image={state.imageData.original}
439
+ viewport={state.viewport}
440
+ transform={state.transform}
441
+ blurAreas={state.blurAreas}
442
+ cropArea={state.cropArea}
443
+ onUpdate={handleBlurAreasChange}
444
+ onClose={() => state.mode = null}
445
+ onViewportChange={handleViewportChange}
446
+ />
447
+ {:else if state.mode === 'stamp'}
448
+ <StampTool
449
+ canvas={canvasElement}
450
+ image={state.imageData.original}
451
+ viewport={state.viewport}
452
+ transform={state.transform}
453
+ stampAreas={state.stampAreas}
454
+ cropArea={state.cropArea}
455
+ onUpdate={handleStampAreasChange}
456
+ onClose={() => state.mode = null}
457
+ onViewportChange={handleViewportChange}
458
+ />
459
+ {/if}
460
+
461
+ {#if state.mode === 'adjust'}
462
+ <div class="tools-panel">
463
+ <AdjustTool
464
+ adjustments={state.adjustments}
465
+ onChange={handleAdjustmentsChange}
466
+ onClose={() => state.mode = null}
467
+ />
468
+ </div>
469
+ {:else if state.mode === 'filter'}
470
+ <div class="tools-panel">
471
+ <FilterTool
472
+ image={state.imageData.original}
473
+ adjustments={state.adjustments}
474
+ transform={state.transform}
475
+ cropArea={state.cropArea}
476
+ onChange={handleFilterApply}
477
+ onClose={() => state.mode = null}
478
+ />
479
+ </div>
480
+ {:else if state.mode === 'export' && isStandalone}
481
+ <div class="tools-panel">
482
+ <ExportTool
483
+ options={state.exportOptions}
484
+ onChange={(options) => state.exportOptions = { ...state.exportOptions, ...options }}
485
+ onExport={handleExport}
486
+ onClose={() => state.mode = null}
487
+ />
488
+ </div>
489
+ {/if}
490
+ </div>
491
+ {/if}
492
+ </div>
493
+
494
+ <div class="editor-header">
495
+ <Toolbar
496
+ mode={state.mode}
497
+ hasImage={!!state.imageData.original}
498
+ canUndo={canUndo(state.history)}
499
+ canRedo={canRedo(state.history)}
500
+ isStandalone={isStandalone}
501
+ onModeChange={handleModeChange}
502
+ onReset={handleReset}
503
+ onUndo={handleUndo}
504
+ onRedo={handleRedo}
505
+ />
506
+ </div>
507
+ </div>
508
+
509
+ <style>
510
+ :global(input[type='range']) {
511
+ appearance: none;
512
+ }
513
+
514
+ .image-editor {
515
+ display: flex;
516
+ flex-direction: column;
517
+ gap: 1rem;
518
+ padding: 1rem;
519
+ background: #1a1a1a;
520
+ border-radius: 8px;
521
+ color: #fff;
522
+ }
523
+
524
+ @media (max-width: 767px) {
525
+
526
+ .image-editor {
527
+ width: 100% !important;
528
+ height: 90vh
529
+ }
530
+ }
531
+
532
+ .editor-header {
533
+ display: flex;
534
+ align-items: center;
535
+ width: 100%;
536
+ }
537
+
538
+ .editor-body {
539
+ display: flex;
540
+ flex-direction: column;
541
+ gap: 1rem;
542
+ }
543
+
544
+ @media (max-width: 767px) {
545
+
546
+ .editor-body {
547
+ flex: 1;
548
+ justify-content: center
549
+ }
550
+ }
551
+
552
+ .upload-area {
553
+ display: flex;
554
+ align-items: center;
555
+ justify-content: center;
556
+ min-height: 400px;
557
+ border: 2px dashed #666;
558
+ border-radius: 8px;
559
+ background: #2a2a2a;
560
+ cursor: pointer;
561
+ transition: all 0.2s;
562
+ }
563
+
564
+ .upload-area:hover {
565
+ border-color: #888;
566
+ background: #333;
567
+ }
568
+
569
+ .upload-area p {
570
+ margin: 0;
571
+ font-size: 1.1rem;
572
+ color: #999;
573
+ }
574
+
575
+ @media (max-width: 767px) {
576
+
577
+ .upload-area p {
578
+ font-size: .75rem
579
+ }
580
+ }
581
+
582
+ .canvas-container {
583
+ position: relative;
584
+ display: flex;
585
+ justify-content: center;
586
+ align-items: center;
587
+ background: #2a2a2a;
588
+ border-radius: 8px;
589
+ overflow: hidden;
590
+ }
591
+
592
+ @media (max-width: 767px) {
593
+
594
+ .canvas-container {
595
+ flex: 1;
596
+ min-height: 0
597
+ }
598
+ }
599
+
600
+ .tools-panel {
601
+ padding: 1rem;
602
+ background: #2a2a2a;
603
+ border-radius: 8px;
604
+ position: absolute;
605
+ width: min-content;
606
+ right: 1rem;
607
+ top: 1rem;
608
+ bottom: 1rem;
609
+ overflow-y: auto;
610
+ scrollbar-width: thin;
611
+ }
612
+
613
+ @media (max-width: 767px) {
614
+
615
+ .tools-panel {
616
+ position: absolute;
617
+ left: 0;
618
+ right: 0;
619
+ top: auto;
620
+ bottom: 0;
621
+ width: auto;
622
+ max-height: 50vh;
623
+ border-radius: 16px 16px 0 0;
624
+ z-index: 1001
625
+ }
626
+ }
627
+
628
+ .no-image-message {
629
+ display: flex;
630
+ align-items: center;
631
+ justify-content: center;
632
+ min-height: 400px;
633
+ background: #2a2a2a;
634
+ border-radius: 8px;
635
+ }
636
+
637
+ .no-image-message p {
638
+ margin: 0;
639
+ font-size: 1.1rem;
640
+ color: #999;
641
+ }
642
+
643
+ .embedded-controls {
644
+ position: relative;
645
+ display: flex;
646
+ justify-content: space-between;
647
+ gap: 1rem;
648
+ z-index: 1000;
649
+ }
650
+
651
+ @media (max-width: 767px) {
652
+
653
+ }
654
+
655
+ .embedded-btn {
656
+ padding: 0 1rem;
657
+ border: none;
658
+ font-size: .9rem;
659
+ font-weight: 600;
660
+ cursor: pointer;
661
+ transition: all 0.2s;
662
+ border-radius: 4px;
663
+ height: 36px;
664
+ }
665
+
666
+ .embedded-btn:hover {
667
+ opacity: .8;
668
+ }
669
+
670
+ @media (max-width: 767px) {
671
+
672
+ .embedded-btn {
673
+ padding: 0;
674
+ font-size: .75rem;
675
+ min-width: 80px
676
+ }
677
+ }
678
+
679
+ .embedded-btn-cancel {
680
+ background: #666;
681
+ color: #fff;
682
+ }
683
+
684
+ .embedded-btn-apply {
685
+ background: var(--primary-color, #63b97b);
686
+ color: #fff;
687
+ }
688
+
689
+ .experimental {
690
+ padding: 0 1rem;
691
+ background: var(--primary-color, #63b97b);
692
+ opacity: .7;
693
+ color: #fff;
694
+ height: 40px;
695
+ border-radius: 20px;
696
+ letter-spacing: .05em;
697
+ font-size: 16px;
698
+ font-weight: bold;
699
+ display: flex;
700
+ align-items: center;
701
+ margin: 0 auto 0 0;
702
+ }
703
+
704
+ .editor-history-controls {
705
+ position: absolute;
706
+ right: 0;
707
+ left: 0;
708
+ top: 0;
709
+ margin: auto;
710
+ width: fit-content;
711
+ display: flex;
712
+ align-items: center;
713
+ justify-content: center;
714
+ overflow: hidden;
715
+ border-radius: 8px;
716
+ border: 1px solid #444;
717
+ box-sizing: border-box;
718
+ }
719
+
720
+ .editor-history-btn {
721
+ appearance: none;
722
+ box-shadow: none;
723
+ border: none;
724
+ background: #333;
725
+ width: 36px;
726
+ height: 36px;
727
+ display: grid;
728
+ place-content: center;
729
+ color: #fff;
730
+ cursor: pointer;
731
+ transition: all .3s ease-in-out;
732
+ border-right: 1px solid #444;
733
+ }
734
+
735
+ .editor-history-btn:last-child {
736
+ border-right: none;
737
+ }
738
+
739
+ .editor-history-btn:hover {
740
+ opacity: .7;
741
+ }
742
+
743
+ .editor-history-btn:disabled {
744
+ background: #222;
745
+ color: #333;
746
+ cursor: not-allowed;
747
+ }</style>