@jjlmoya/utils-audiovisual 1.5.0 → 1.7.0

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.
Files changed (43) hide show
  1. package/package.json +58 -58
  2. package/src/category/i18n/fr.ts +1 -1
  3. package/src/tests/schemas_fulfillment.test.ts +23 -0
  4. package/src/tests/title_quality.test.ts +55 -0
  5. package/src/tool/chromaticLens/component.astro +38 -38
  6. package/src/tool/chromaticLens/i18n/en.ts +1 -1
  7. package/src/tool/chromaticLens/i18n/es.ts +1 -1
  8. package/src/tool/chromaticLens/i18n/fr.ts +1 -1
  9. package/src/tool/collageMaker/component.astro +47 -47
  10. package/src/tool/collageMaker/i18n/en.ts +1 -1
  11. package/src/tool/collageMaker/i18n/es.ts +1 -1
  12. package/src/tool/collageMaker/i18n/fr.ts +1 -1
  13. package/src/tool/exifCleaner/component.astro +49 -48
  14. package/src/tool/exifCleaner/i18n/en.ts +2 -2
  15. package/src/tool/exifCleaner/i18n/es.ts +2 -2
  16. package/src/tool/exifCleaner/i18n/fr.ts +3 -3
  17. package/src/tool/imageCompressor/component.astro +144 -106
  18. package/src/tool/imageCompressor/i18n/en.ts +12 -2
  19. package/src/tool/imageCompressor/i18n/es.ts +13 -3
  20. package/src/tool/imageCompressor/i18n/fr.ts +12 -2
  21. package/src/tool/imageCompressor/index.ts +10 -0
  22. package/src/tool/printQualityCalculator/component.astro +129 -104
  23. package/src/tool/printQualityCalculator/i18n/en.ts +17 -3
  24. package/src/tool/printQualityCalculator/i18n/es.ts +19 -5
  25. package/src/tool/printQualityCalculator/i18n/fr.ts +18 -4
  26. package/src/tool/printQualityCalculator/index.ts +14 -0
  27. package/src/tool/privacyBlur/component.astro +35 -35
  28. package/src/tool/privacyBlur/i18n/en.ts +1 -1
  29. package/src/tool/privacyBlur/i18n/es.ts +1 -1
  30. package/src/tool/privacyBlur/i18n/fr.ts +1 -1
  31. package/src/tool/subtitleSync/component.astro +42 -42
  32. package/src/tool/subtitleSync/i18n/en.ts +1 -1
  33. package/src/tool/subtitleSync/i18n/es.ts +1 -1
  34. package/src/tool/subtitleSync/i18n/fr.ts +3 -3
  35. package/src/tool/timelapseCalculator/component.astro +41 -42
  36. package/src/tool/tvDistance/component.astro +55 -55
  37. package/src/tool/tvDistance/i18n/en.ts +1 -1
  38. package/src/tool/tvDistance/i18n/es.ts +1 -1
  39. package/src/tool/tvDistance/i18n/fr.ts +1 -1
  40. package/src/tool/videoFrameExtractor/component.astro +54 -54
  41. package/src/tool/videoFrameExtractor/i18n/en.ts +1 -1
  42. package/src/tool/videoFrameExtractor/i18n/es.ts +1 -1
  43. package/src/tool/videoFrameExtractor/i18n/fr.ts +1 -1
@@ -246,7 +246,7 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
246
246
  ctx.fillRect(0, 0, W, H);
247
247
  for (let i = 0; i < Math.min(images.length, pos.length); i++) {
248
248
  const p = pos[i]!;
249
- await drawImage(ctx, images[i]!.src, p[0], p[1], p[2], p[3]);
249
+ await drawImage(ctx, images[i]!.src, { x: p[0], y: p[1], w: p[2], h: p[3] });
250
250
  if (gap > 0) { ctx.strokeStyle = bcInput.value; ctx.lineWidth = gap; ctx.strokeRect(p[0]-gap/2, p[1]-gap/2, p[2]+gap, p[3]+gap); }
251
251
  }
252
252
  canvas.style.display = '';
@@ -301,7 +301,7 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
301
301
  </script>
302
302
 
303
303
  <style>
304
- .cm-root {
304
+ :global(.cm-root) {
305
305
  --cm-bg: #fff;
306
306
  --cm-bg-muted: #f8fafc;
307
307
  --cm-border: #e2e8f0;
@@ -317,7 +317,7 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
317
317
  padding: 1rem;
318
318
  }
319
319
 
320
- :global(.theme-dark) .cm-root {
320
+ :global(.theme-dark .cm-root) {
321
321
  --cm-bg: #1e293b;
322
322
  --cm-bg-muted: #0f172a;
323
323
  --cm-border: #334155;
@@ -328,7 +328,7 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
328
328
  --cm-shadow: rgba(0,0,0,0.4);
329
329
  }
330
330
 
331
- .cm-card {
331
+ :global(.cm-card) {
332
332
  background: var(--cm-bg);
333
333
  border: 1px solid var(--cm-border);
334
334
  border-radius: var(--cm-radius);
@@ -336,7 +336,7 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
336
336
  overflow: hidden;
337
337
  }
338
338
 
339
- .cm-top-row {
339
+ :global(.cm-top-row) {
340
340
  display: grid;
341
341
  grid-template-columns: 300px 1fr;
342
342
  gap: 0;
@@ -348,7 +348,7 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
348
348
  }
349
349
  }
350
350
 
351
- .cm-left-col {
351
+ :global(.cm-left-col) {
352
352
  padding: 1.25rem;
353
353
  border-right: 1px solid var(--cm-border);
354
354
  display: flex;
@@ -363,7 +363,7 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
363
363
  }
364
364
  }
365
365
 
366
- .cm-drop-zone {
366
+ :global(.cm-drop-zone) {
367
367
  position: relative;
368
368
  background: var(--cm-bg-muted);
369
369
  border: 2px dashed var(--cm-border);
@@ -379,7 +379,7 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
379
379
  color: var(--cm-primary);
380
380
  }
381
381
 
382
- .cm-drop-zone input[type="file"] {
382
+ :global(.cm-drop-zone input[type="file"]) {
383
383
  position: absolute;
384
384
  inset: 0;
385
385
  opacity: 0;
@@ -388,32 +388,32 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
388
388
  height: 100%;
389
389
  }
390
390
 
391
- .cm-drop-zone:hover,
392
- .cm-drop-zone-over {
391
+ :global(.cm-drop-zone:hover),
392
+ :global(.cm-drop-zone-over) {
393
393
  border-color: var(--cm-primary);
394
394
  background: var(--cm-primary-light);
395
395
  }
396
396
 
397
- .cm-drop-title {
397
+ :global(.cm-drop-title) {
398
398
  font-size: 0.9rem;
399
399
  font-weight: 700;
400
400
  color: var(--cm-text);
401
401
  margin: 0;
402
402
  }
403
403
 
404
- .cm-drop-sub {
404
+ :global(.cm-drop-sub) {
405
405
  font-size: 0.75rem;
406
406
  color: var(--cm-text-muted);
407
407
  margin: 0;
408
408
  }
409
409
 
410
- .cm-drop-link {
410
+ :global(.cm-drop-link) {
411
411
  color: var(--cm-primary);
412
412
  font-weight: 600;
413
413
  cursor: pointer;
414
414
  }
415
415
 
416
- .cm-section-label {
416
+ :global(.cm-section-label) {
417
417
  display: block;
418
418
  font-size: 0.65rem;
419
419
  font-weight: 700;
@@ -422,13 +422,13 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
422
422
  color: var(--cm-text-muted);
423
423
  }
424
424
 
425
- .cm-section-header {
425
+ :global(.cm-section-header) {
426
426
  display: flex;
427
427
  justify-content: space-between;
428
428
  align-items: center;
429
429
  }
430
430
 
431
- .cm-badge {
431
+ :global(.cm-badge) {
432
432
  background: var(--cm-primary-light);
433
433
  color: var(--cm-primary);
434
434
  font-size: 0.65rem;
@@ -437,14 +437,14 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
437
437
  border-radius: 9999px;
438
438
  }
439
439
 
440
- .cm-thumbs {
440
+ :global(.cm-thumbs) {
441
441
  display: grid;
442
442
  grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
443
443
  gap: 0.35rem;
444
444
  margin-top: 0.5rem;
445
445
  }
446
446
 
447
- .cm-thumb {
447
+ :global(.cm-thumb) {
448
448
  position: relative;
449
449
  aspect-ratio: 1;
450
450
  border-radius: 0.4rem;
@@ -453,18 +453,18 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
453
453
  transition: border-color 0.15s;
454
454
  }
455
455
 
456
- .cm-thumb:hover {
456
+ :global(.cm-thumb:hover) {
457
457
  border-color: var(--cm-primary);
458
458
  }
459
459
 
460
- .cm-thumb img {
460
+ :global(.cm-thumb img) {
461
461
  width: 100%;
462
462
  height: 100%;
463
463
  object-fit: cover;
464
464
  display: block;
465
465
  }
466
466
 
467
- .cm-thumb-num {
467
+ :global(.cm-thumb-num) {
468
468
  position: absolute;
469
469
  bottom: 2px;
470
470
  left: 3px;
@@ -475,7 +475,7 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
475
475
  line-height: 1;
476
476
  }
477
477
 
478
- .cm-thumb-del {
478
+ :global(.cm-thumb-del) {
479
479
  position: absolute;
480
480
  top: 2px;
481
481
  right: 2px;
@@ -493,15 +493,15 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
493
493
  transition: background 0.15s;
494
494
  }
495
495
 
496
- .cm-thumb:hover .cm-thumb-del {
496
+ :global(.cm-thumb:hover .cm-thumb-del) {
497
497
  display: flex;
498
498
  }
499
499
 
500
- .cm-thumb-del:hover {
500
+ :global(.cm-thumb-del:hover) {
501
501
  background: #dc2626;
502
502
  }
503
503
 
504
- .cm-preview-col {
504
+ :global(.cm-preview-col) {
505
505
  padding: 1.25rem;
506
506
  display: flex;
507
507
  flex-direction: column;
@@ -509,7 +509,7 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
509
509
  min-height: 260px;
510
510
  }
511
511
 
512
- .cm-preview-placeholder {
512
+ :global(.cm-preview-placeholder) {
513
513
  flex: 1;
514
514
  background: var(--cm-bg-muted);
515
515
  border: 2px dashed var(--cm-border);
@@ -522,12 +522,12 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
522
522
  color: var(--cm-text-muted);
523
523
  }
524
524
 
525
- .cm-preview-placeholder p {
525
+ :global(.cm-preview-placeholder p) {
526
526
  font-size: 0.8rem;
527
527
  margin: 0;
528
528
  }
529
529
 
530
- .cm-canvas {
530
+ :global(.cm-canvas) {
531
531
  width: 100%;
532
532
  height: auto;
533
533
  border-radius: 0.75rem;
@@ -535,7 +535,7 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
535
535
  display: block;
536
536
  }
537
537
 
538
- .cm-dims-badge {
538
+ :global(.cm-dims-badge) {
539
539
  font-size: 0.65rem;
540
540
  font-weight: 600;
541
541
  color: var(--cm-text-muted);
@@ -545,7 +545,7 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
545
545
  padding: 0.15rem 0.5rem;
546
546
  }
547
547
 
548
- .cm-section-divider {
548
+ :global(.cm-section-divider) {
549
549
  border-top: 1px solid var(--cm-border);
550
550
  padding: 1rem 1.25rem;
551
551
  display: flex;
@@ -553,13 +553,13 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
553
553
  gap: 0.75rem;
554
554
  }
555
555
 
556
- .cm-layouts {
556
+ :global(.cm-layouts) {
557
557
  display: grid;
558
558
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
559
559
  gap: 0.4rem;
560
560
  }
561
561
 
562
- .cm-layout-btn {
562
+ :global(.cm-layout-btn) {
563
563
  display: flex;
564
564
  flex-direction: column;
565
565
  align-items: center;
@@ -574,31 +574,31 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
574
574
  position: relative;
575
575
  }
576
576
 
577
- .cm-layout-btn span {
577
+ :global(.cm-layout-btn span) {
578
578
  font-size: 0.55rem;
579
579
  font-weight: 600;
580
580
  text-align: center;
581
581
  line-height: 1.2;
582
582
  }
583
583
 
584
- .cm-layout-btn:hover:not(:disabled) {
584
+ :global(.cm-layout-btn:hover:not(:disabled)) {
585
585
  border-color: var(--cm-primary);
586
586
  color: var(--cm-primary);
587
587
  background: var(--cm-primary-light);
588
588
  }
589
589
 
590
- .cm-layout-btn-active {
590
+ :global(.cm-layout-btn-active) {
591
591
  border-color: var(--cm-primary);
592
592
  background: var(--cm-primary-light);
593
593
  color: var(--cm-primary);
594
594
  }
595
595
 
596
- .cm-layout-btn-disabled {
596
+ :global(.cm-layout-btn-disabled) {
597
597
  opacity: 0.45;
598
598
  cursor: not-allowed;
599
599
  }
600
600
 
601
- .cm-layout-need {
601
+ :global(.cm-layout-need) {
602
602
  position: absolute;
603
603
  top: 2px;
604
604
  right: 4px;
@@ -612,37 +612,37 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
612
612
  line-height: 1.4;
613
613
  }
614
614
 
615
- .cm-settings-inline {
615
+ :global(.cm-settings-inline) {
616
616
  display: flex;
617
617
  flex-wrap: wrap;
618
618
  align-items: flex-end;
619
619
  gap: 1rem;
620
620
  }
621
621
 
622
- .cm-setting {
622
+ :global(.cm-setting) {
623
623
  display: flex;
624
624
  flex-direction: column;
625
625
  gap: 0.3rem;
626
626
  }
627
627
 
628
- .cm-setting-label {
628
+ :global(.cm-setting-label) {
629
629
  font-size: 0.7rem;
630
630
  font-weight: 600;
631
631
  color: var(--cm-text-muted);
632
632
  }
633
633
 
634
- .cm-slider {
634
+ :global(.cm-slider) {
635
635
  accent-color: var(--cm-primary);
636
636
  width: 120px;
637
637
  }
638
638
 
639
- .cm-color-row {
639
+ :global(.cm-color-row) {
640
640
  display: flex;
641
641
  align-items: center;
642
642
  gap: 0.5rem;
643
643
  }
644
644
 
645
- .cm-color-swatch {
645
+ :global(.cm-color-swatch) {
646
646
  width: 32px;
647
647
  height: 32px;
648
648
  border: 2px solid var(--cm-border);
@@ -652,14 +652,14 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
652
652
  background: transparent;
653
653
  }
654
654
 
655
- .cm-color-code {
655
+ :global(.cm-color-code) {
656
656
  font-size: 0.7rem;
657
657
  font-weight: 600;
658
658
  color: var(--cm-text-muted);
659
659
  font-variant-numeric: tabular-nums;
660
660
  }
661
661
 
662
- .cm-download-btn {
662
+ :global(.cm-download-btn) {
663
663
  display: flex;
664
664
  align-items: center;
665
665
  gap: 0.4rem;
@@ -677,13 +677,13 @@ const dropSub = (ui.dropSub ?? '').replace('{link}', `<span class="cm-drop-link"
677
677
  margin-left: auto;
678
678
  }
679
679
 
680
- .cm-download-btn:disabled {
680
+ :global(.cm-download-btn:disabled) {
681
681
  opacity: 0.4;
682
682
  cursor: not-allowed;
683
683
  box-shadow: none;
684
684
  }
685
685
 
686
- .cm-download-btn:not(:disabled):hover {
686
+ :global(.cm-download-btn:not(:disabled):hover) {
687
687
  transform: translateY(-1px);
688
688
  box-shadow: 0 6px 18px rgba(99,102,241,0.4);
689
689
  }
@@ -2,7 +2,7 @@ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dt
2
2
  import type { CollageMakerUI, CollageMakerLocaleContent } from '../index';
3
3
 
4
4
  const slug = 'free-online-photo-collage-maker-professional-compositions';
5
- const title = 'Online Collage Maker - Design professional compositions';
5
+ const title = 'Online Collage Maker: Design professional compositions';
6
6
  const description = 'Create photo collages for free in seconds. Choose from multiple layouts, adjust borders, and download in high quality without watermarks.';
7
7
 
8
8
  const ui: CollageMakerUI = {
@@ -2,7 +2,7 @@ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dt
2
2
  import type { CollageMakerUI, CollageMakerLocaleContent } from '../index';
3
3
 
4
4
  const slug = 'creador-collage-fotos';
5
- const title = 'Creador de Collages Online - Diseña composiciones profesionales';
5
+ const title = 'Creador de Collages Online: Diseña composiciones profesionales';
6
6
  const description = 'Crea collages de fotos gratis en segundos. Elige entre múltiples diseños, ajusta bordes y descarga en alta calidad sin marcas de agua.';
7
7
 
8
8
  const ui: CollageMakerUI = {
@@ -2,7 +2,7 @@ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dt
2
2
  import type { CollageMakerUI, CollageMakerLocaleContent } from '../index';
3
3
 
4
4
  const slug = 'createur-collage-photos-gratuit-compositions-professionnelles';
5
- const title = 'Créateur de Collages en Ligne - Concevez des compositions professionnelles';
5
+ const title = 'Créateur de Collages en Ligne: Concevez des compositions professionnelles';
6
6
  const description = 'Créez des collages de photos gratuitement en quelques secondes. Choisissez parmi plusieurs mises en page, ajustez les bordures et téléchargez en haute qualité sans filigrane.';
7
7
 
8
8
  const ui: CollageMakerUI = {
@@ -73,24 +73,24 @@ const { ui } = Astro.props;
73
73
  import { extractExif, createCleanImage } from './logic';
74
74
  import type { ExifCleanerUI } from './index';
75
75
 
76
- function renderMetadata(tags: Record<string, string | number | boolean | undefined>, container: HTMLElement, riskTitle: string) {
77
- const keys = Object.keys(tags);
78
- if (keys.length === 0) {
79
- container.innerHTML = '<div class="ec-no-metadata">No se encontraron metadatos.</div>';
80
- return;
81
- }
82
- let html = `<div class="ec-metadata-title">${riskTitle}</div><ul class="ec-metadata-list">`;
83
- if (tags.GPSLocation) html += `<li><span>GPS:</span> <span>${tags.GPSLocation}</span></li>`;
84
- if (tags.Model) html += `<li><span>CÁMARA:</span> <span>${tags.Model}</span></li>`;
85
- if (tags.DateTimeOriginal) html += `<li><span>FECHA:</span> <span>${tags.DateTimeOriginal}</span></li>`;
86
- container.innerHTML = html + '</ul>';
87
- }
88
-
89
76
  function init() {
90
77
  const root = document.getElementById('ec-root');
91
78
  if (!root) return;
92
79
 
93
80
  const ui = JSON.parse(root.dataset.ui ?? '{}') as ExifCleanerUI;
81
+
82
+ function renderMetadata(tags: Record<string, string | number | boolean | undefined>, container: HTMLElement, riskTitle: string) {
83
+ const keys = Object.keys(tags);
84
+ if (keys.length === 0) {
85
+ container.innerHTML = `<div class="ec-no-metadata">${ui.noMetadataFound}</div>`;
86
+ return;
87
+ }
88
+ let html = `<div class="ec-metadata-title">${riskTitle}</div><ul class="ec-metadata-list">`;
89
+ if (tags.GPSLocation) html += `<li><span>${ui.gpsLabel}</span> <span>${tags.GPSLocation}</span></li>`;
90
+ if (tags.Model) html += `<li><span>${ui.cameraLabel}</span> <span>${tags.Model}</span></li>`;
91
+ if (tags.DateTimeOriginal) html += `<li><span>${ui.dateLabel}</span> <span>${tags.DateTimeOriginal}</span></li>`;
92
+ container.innerHTML = html + '</ul>';
93
+ }
94
94
  const initial = root.querySelector('#ec-initial') as HTMLElement;
95
95
  const processing = root.querySelector('#ec-processing') as HTMLElement;
96
96
  const result = root.querySelector('#ec-result') as HTMLElement;
@@ -114,6 +114,7 @@ const { ui } = Astro.props;
114
114
  const blob = await createCleanImage(img);
115
115
 
116
116
  downloadBtn.onclick = () => {
117
+ if (!blob) return;
117
118
  const url = URL.createObjectURL(blob);
118
119
  const a = document.createElement('a');
119
120
  a.href = url;
@@ -161,7 +162,7 @@ const { ui } = Astro.props;
161
162
  </script>
162
163
 
163
164
  <style>
164
- .ec-root {
165
+ :global(.ec-root) {
165
166
  --ec-bg: #fff;
166
167
  --ec-bg-elevated: #f8fafc;
167
168
  --ec-border: #e2e8f0;
@@ -177,7 +178,7 @@ const { ui } = Astro.props;
177
178
  margin: 0 auto;
178
179
  }
179
180
 
180
- :global(.theme-dark) .ec-root {
181
+ :global(.theme-dark .ec-root) {
181
182
  --ec-bg: #18181b;
182
183
  --ec-bg-elevated: #27272a;
183
184
  --ec-border: #3f3f46;
@@ -189,7 +190,7 @@ const { ui } = Astro.props;
189
190
  --ec-shadow: rgba(0, 0, 0, 0.3);
190
191
  }
191
192
 
192
- .ec-card {
193
+ :global(.ec-card) {
193
194
  background: var(--ec-bg);
194
195
  border: 1px solid var(--ec-border);
195
196
  border-radius: 3rem;
@@ -199,7 +200,7 @@ const { ui } = Astro.props;
199
200
  overflow: hidden;
200
201
  }
201
202
 
202
- .ec-drop {
203
+ :global(.ec-drop) {
203
204
  padding: 4rem 2rem;
204
205
  border: 3px dashed var(--ec-border);
205
206
  border-radius: 2.5rem;
@@ -212,13 +213,13 @@ const { ui } = Astro.props;
212
213
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
213
214
  }
214
215
 
215
- .ec-drop:hover,
216
- .ec-drop-active {
216
+ :global(.ec-drop:hover),
217
+ :global(.ec-drop-active) {
217
218
  border-color: var(--ec-accent);
218
219
  background: var(--ec-accent-alpha-hover);
219
220
  }
220
221
 
221
- .ec-drop-icon {
222
+ :global(.ec-drop-icon) {
222
223
  width: 6rem;
223
224
  height: 6rem;
224
225
  background: var(--ec-accent-alpha);
@@ -229,19 +230,19 @@ const { ui } = Astro.props;
229
230
  color: var(--ec-accent);
230
231
  }
231
232
 
232
- .ec-drop-icon svg {
233
+ :global(.ec-drop-icon svg) {
233
234
  width: 3rem;
234
235
  height: 3rem;
235
236
  }
236
237
 
237
- .ec-title {
238
+ :global(.ec-title) {
238
239
  font-size: 2.5rem;
239
240
  font-weight: 950;
240
241
  color: var(--ec-text);
241
242
  margin: 0;
242
243
  }
243
244
 
244
- .ec-subtitle {
245
+ :global(.ec-subtitle) {
245
246
  font-size: 1.15rem;
246
247
  color: var(--ec-text-muted);
247
248
  max-width: 500px;
@@ -249,14 +250,14 @@ const { ui } = Astro.props;
249
250
  font-weight: 700;
250
251
  }
251
252
 
252
- .ec-badges {
253
+ :global(.ec-badges) {
253
254
  display: flex;
254
255
  gap: 1rem;
255
256
  flex-wrap: wrap;
256
257
  justify-content: center;
257
258
  }
258
259
 
259
- .ec-badge {
260
+ :global(.ec-badge) {
260
261
  padding: 0.6rem 1.25rem;
261
262
  background: var(--ec-bg-elevated);
262
263
  border-radius: 2rem;
@@ -268,13 +269,13 @@ const { ui } = Astro.props;
268
269
  gap: 0.5rem;
269
270
  }
270
271
 
271
- .ec-badge svg {
272
+ :global(.ec-badge svg) {
272
273
  width: 1rem;
273
274
  height: 1rem;
274
275
  flex-shrink: 0;
275
276
  }
276
277
 
277
- .ec-processing {
278
+ :global(.ec-processing) {
278
279
  padding: 5rem;
279
280
  display: flex;
280
281
  flex-direction: column;
@@ -283,7 +284,7 @@ const { ui } = Astro.props;
283
284
  gap: 1.5rem;
284
285
  }
285
286
 
286
- .ec-spinner {
287
+ :global(.ec-spinner) {
287
288
  width: 4rem;
288
289
  height: 4rem;
289
290
  border: 4px solid var(--ec-accent-alpha);
@@ -292,45 +293,45 @@ const { ui } = Astro.props;
292
293
  animation: ec-spin 0.8s linear infinite;
293
294
  }
294
295
 
295
- .ec-processing-text {
296
+ :global(.ec-processing-text) {
296
297
  font-weight: 800;
297
298
  color: var(--ec-text);
298
299
  margin: 0;
299
300
  }
300
301
 
301
- .ec-result {
302
+ :global(.ec-result) {
302
303
  padding: 2.5rem;
303
304
  display: flex;
304
305
  flex-direction: column;
305
306
  animation: ec-slide-up 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
306
307
  }
307
308
 
308
- .ec-result-layout {
309
+ :global(.ec-result-layout) {
309
310
  display: grid;
310
311
  grid-template-columns: 1fr 1fr;
311
312
  gap: 3rem;
312
313
  }
313
314
 
314
315
  @media (max-width: 800px) {
315
- .ec-result-layout {
316
+ :global(.ec-result-layout) {
316
317
  grid-template-columns: 1fr;
317
318
  }
318
319
  }
319
320
 
320
- .ec-preview-col {
321
+ :global(.ec-preview-col) {
321
322
  display: flex;
322
323
  flex-direction: column;
323
324
  gap: 1.5rem;
324
325
  }
325
326
 
326
- .ec-preview-img {
327
+ :global(.ec-preview-img) {
327
328
  width: 100%;
328
329
  border-radius: 1.5rem;
329
330
  box-shadow: 0 20px 50px var(--ec-shadow);
330
331
  display: block;
331
332
  }
332
333
 
333
- .ec-metadata {
334
+ :global(.ec-metadata) {
334
335
  background: var(--ec-bg-elevated);
335
336
  border: 1px solid var(--ec-border);
336
337
  border-radius: 1.25rem;
@@ -341,7 +342,7 @@ const { ui } = Astro.props;
341
342
  justify-content: center;
342
343
  }
343
344
 
344
- .ec-no-metadata {
345
+ :global(.ec-no-metadata) {
345
346
  display: flex;
346
347
  align-items: center;
347
348
  justify-content: center;
@@ -350,14 +351,14 @@ const { ui } = Astro.props;
350
351
  text-align: center;
351
352
  }
352
353
 
353
- .ec-metadata-title {
354
+ :global(.ec-metadata-title) {
354
355
  color: var(--ec-accent);
355
356
  font-weight: 700;
356
357
  margin-bottom: 1rem;
357
358
  font-size: 0.9rem;
358
359
  }
359
360
 
360
- .ec-metadata-list {
361
+ :global(.ec-metadata-list) {
361
362
  list-style: none;
362
363
  padding: 0;
363
364
  margin: 0;
@@ -366,7 +367,7 @@ const { ui } = Astro.props;
366
367
  gap: 0.75rem;
367
368
  }
368
369
 
369
- .ec-metadata-list li {
370
+ :global(.ec-metadata-list li) {
370
371
  display: flex;
371
372
  justify-content: space-between;
372
373
  gap: 1rem;
@@ -375,21 +376,21 @@ const { ui } = Astro.props;
375
376
  color: var(--ec-text);
376
377
  }
377
378
 
378
- .ec-metadata-list li span:first-child {
379
+ :global(.ec-metadata-list li span:first-child) {
379
380
  font-weight: 700;
380
381
  }
381
382
 
382
- .ec-metadata-list li span:last-child {
383
+ :global(.ec-metadata-list li span:last-child) {
383
384
  color: var(--ec-text-muted);
384
385
  }
385
386
 
386
- .ec-actions-col {
387
+ :global(.ec-actions-col) {
387
388
  display: flex;
388
389
  flex-direction: column;
389
390
  gap: 1.5rem;
390
391
  }
391
392
 
392
- .ec-btn {
393
+ :global(.ec-btn) {
393
394
  padding: 1.25rem;
394
395
  border-radius: 1.5rem;
395
396
  font-weight: 950;
@@ -403,29 +404,29 @@ const { ui } = Astro.props;
403
404
  transition: all 0.2s;
404
405
  }
405
406
 
406
- .ec-btn svg {
407
+ :global(.ec-btn svg) {
407
408
  width: 1.25rem;
408
409
  height: 1.25rem;
409
410
  }
410
411
 
411
- .ec-btn-primary {
412
+ :global(.ec-btn-primary) {
412
413
  background: var(--ec-accent);
413
414
  color: #fff;
414
415
  box-shadow: 0 15px 35px -10px var(--ec-accent);
415
416
  }
416
417
 
417
- .ec-btn-primary:hover {
418
+ :global(.ec-btn-primary:hover) {
418
419
  transform: translateY(-2px);
419
420
  box-shadow: 0 20px 45px -10px var(--ec-accent);
420
421
  }
421
422
 
422
- .ec-btn-secondary {
423
+ :global(.ec-btn-secondary) {
423
424
  background: var(--ec-bg-elevated);
424
425
  border: 1px solid var(--ec-border);
425
426
  color: var(--ec-text);
426
427
  }
427
428
 
428
- .ec-btn-secondary:hover {
429
+ :global(.ec-btn-secondary:hover) {
429
430
  border-color: var(--ec-accent);
430
431
  color: var(--ec-accent);
431
432
  }