@jjlmoya/utils-creative 1.5.0 → 1.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-creative",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -1,4 +1,11 @@
1
1
  ---
2
+ import type { BeadPatternGeneratorUI } from './index';
3
+
4
+ interface Props {
5
+ ui: BeadPatternGeneratorUI;
6
+ }
7
+
8
+ const { ui } = Astro.props;
2
9
  ---
3
10
 
4
11
  <div class="bead-wrapper">
@@ -8,13 +15,13 @@
8
15
  <div class="bead-inner">
9
16
  <div class="bead-header">
10
17
  <div class="bead-header-title">
11
- <h2>Laboratorio de Patrones</h2>
12
- <p>Ingeniería de color para tus manos</p>
18
+ <h2>{ui.title}</h2>
19
+ <p>{ui.subtitle}</p>
13
20
  </div>
14
21
 
15
22
  <div class="bead-controls">
16
23
  <div class="bead-control-group">
17
- <label class="bead-control-label">Tamaño (Ancho)</label>
24
+ <label class="bead-control-label">{ui.gridSizeLabel}</label>
18
25
  <div class="bead-slider-row">
19
26
  <input type="range" id="grid-width" min="10" max="100" value="40" class="bead-slider bead-slider-pink" />
20
27
  <span id="grid-width-value" class="bead-slider-val bead-val-pink">40</span>
@@ -22,7 +29,7 @@
22
29
  </div>
23
30
 
24
31
  <div class="bead-control-group">
25
- <label class="bead-control-label">Colores</label>
32
+ <label class="bead-control-label">{ui.colorCountLabel}</label>
26
33
  <div class="bead-slider-row">
27
34
  <input type="range" id="color-count" min="2" max="32" value="12" class="bead-slider bead-slider-purple" />
28
35
  <span id="color-count-value" class="bead-slider-val bead-val-purple">12</span>
@@ -30,16 +37,16 @@
30
37
  </div>
31
38
 
32
39
  <div class="bead-control-group">
33
- <label class="bead-control-label">Opciones</label>
40
+ <label class="bead-control-label">{ui.optionsLabel}</label>
34
41
  <div class="bead-options-row">
35
42
  <label class="bead-checkbox-label">
36
43
  <input type="checkbox" id="show-rulers" class="bead-checkbox" />
37
- <span>Reglas</span>
44
+ <span>{ui.rulersLabel}</span>
38
45
  </label>
39
46
  <div class="bead-divider"></div>
40
- <label class="bead-checkbox-label" title="Patrón Sorpresa">
47
+ <label class="bead-checkbox-label" title={ui.symbolsTooltip}>
41
48
  <input type="checkbox" id="show-symbols" class="bead-checkbox" />
42
- <span>Símbolos</span>
49
+ <span>{ui.symbolsLabel}</span>
43
50
  </label>
44
51
  </div>
45
52
  </div>
@@ -48,7 +55,7 @@
48
55
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
49
56
  <path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd"></path>
50
57
  </svg>
51
- <span>Descargar</span>
58
+ <span>{ui.downloadBtn}</span>
52
59
  </button>
53
60
  </div>
54
61
  </div>
@@ -62,21 +69,21 @@
62
69
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
63
70
  </svg>
64
71
  </div>
65
- <h3>Carga tu Visión</h3>
66
- <p>y deja que la magia digital ocurra</p>
72
+ <h3>{ui.uploadTitle}</h3>
73
+ <p>{ui.uploadSubtitle}</p>
67
74
  </div>
68
75
  </div>
69
76
 
70
77
  <div id="result-area" class="bead-result bead-hidden">
71
78
  <div class="bead-result-inner">
72
79
  <div class="bead-palette-col">
73
- <h3 class="bead-palette-title">ADN Cromático</h3>
80
+ <h3 class="bead-palette-title">{ui.paletteTitle}</h3>
74
81
  <div id="palette-grid" class="bead-palette-grid"></div>
75
82
  <button id="reupload-btn" class="bead-reupload-btn">
76
83
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor">
77
84
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
78
85
  </svg>
79
- Cambiar Imagen
86
+ {ui.reuploadBtn}
80
87
  </button>
81
88
  </div>
82
89
  <div class="bead-canvas-col">
@@ -86,8 +93,8 @@
86
93
 
87
94
  <div class="bead-tunnel-bar">
88
95
  <div>
89
- <h3 class="bead-tunnel-title">Visión de Túnel</h3>
90
- <p class="bead-tunnel-sub">Tu asistente de precisión fila por fila.</p>
96
+ <h3 class="bead-tunnel-title">{ui.tunnelVisionTitle}</h3>
97
+ <p class="bead-tunnel-sub">{ui.tunnelVisionSubtitle}</p>
91
98
  </div>
92
99
  <div class="bead-tunnel-controls">
93
100
  <button id="focus-prev" class="bead-tunnel-btn">▲</button>
@@ -590,6 +597,7 @@
590
597
  min-height: 25rem;
591
598
  display: flex;
592
599
  flex-direction: column;
600
+ gap: 3rem;
593
601
  }
594
602
 
595
603
  .bead-drop-zone {
@@ -716,13 +724,13 @@
716
724
  gap: 0.5rem;
717
725
  }
718
726
 
719
- .bead-palette-item {
727
+ :global(.bead-palette-item) {
720
728
  display: flex;
721
729
  flex-direction: column;
722
730
  align-items: center;
723
731
  }
724
732
 
725
- .bead-color-swatch {
733
+ :global(.bead-color-swatch) {
726
734
  width: 100%;
727
735
  aspect-ratio: 1;
728
736
  border-radius: 50%;
@@ -734,21 +742,21 @@
734
742
  justify-content: center;
735
743
  }
736
744
 
737
- :global(.theme-dark) .bead-color-swatch {
745
+ :global(.theme-dark .bead-color-swatch) {
738
746
  border-color: #1f2937;
739
747
  outline-color: #374151;
740
748
  }
741
749
 
742
- .bead-symbol {
750
+ :global(.bead-symbol) {
743
751
  font-size: 0.75rem;
744
752
  font-weight: 700;
745
753
  user-select: none;
746
754
  }
747
- .bead-symbol-dark {
755
+ :global(.bead-symbol-dark) {
748
756
  color: #000;
749
757
  }
750
758
 
751
- .bead-symbol-light {
759
+ :global(.bead-symbol-light) {
752
760
  color: #fff;
753
761
  }
754
762
 
@@ -7,12 +7,24 @@ export const content: BeadPatternGeneratorLocaleContent = {
7
7
  faqTitle: 'Frequently Asked Questions',
8
8
  bibliographyTitle: 'Artisan Bibliography',
9
9
  ui: {
10
- title: 'Pattern Generator',
10
+ title: 'Pattern Laboratory',
11
+ subtitle: 'Color engineering for your hands',
11
12
  description: 'From photo to bead scheme.',
13
+ gridSizeLabel: 'Size (Width)',
14
+ colorCountLabel: 'Colors',
15
+ optionsLabel: 'Options',
16
+ rulersLabel: 'Rulers',
17
+ symbolsTooltip: 'Surprise Pattern',
18
+ symbolsLabel: 'Symbols',
19
+ downloadBtn: 'Download',
20
+ uploadTitle: 'Upload Your Vision',
21
+ uploadSubtitle: 'and let the digital magic happen',
22
+ paletteTitle: 'Chromatic DNA',
23
+ reuploadBtn: 'Change Image',
24
+ tunnelVisionTitle: 'Tunnel Vision',
25
+ tunnelVisionSubtitle: 'Your precision assistant row by row.',
12
26
  uploadLabel: 'Upload your photo',
13
- gridSizeLabel: 'Grid size (beads)',
14
27
  pixelateBtn: 'Generate Pattern',
15
- downloadBtn: 'Download Scheme',
16
28
  faqTitle: 'FAQ',
17
29
  bibliographyTitle: 'References'
18
30
  },
@@ -7,12 +7,24 @@ export const content: BeadPatternGeneratorLocaleContent = {
7
7
  faqTitle: 'Preguntas Frecuentes',
8
8
  bibliographyTitle: 'Bibliografía del Artesano',
9
9
  ui: {
10
- title: 'Generador de Patrones',
10
+ title: 'Laboratorio de Patrones',
11
+ subtitle: 'Ingeniería de color para tus manos',
11
12
  description: 'De fotografía a esquema de cuentas.',
13
+ gridSizeLabel: 'Tamaño (Ancho)',
14
+ colorCountLabel: 'Colores',
15
+ optionsLabel: 'Opciones',
16
+ rulersLabel: 'Reglas',
17
+ symbolsTooltip: 'Patrón Sorpresa',
18
+ symbolsLabel: 'Símbolos',
19
+ downloadBtn: 'Descargar',
20
+ uploadTitle: 'Carga tu Visión',
21
+ uploadSubtitle: 'y deja que la magia digital ocurra',
22
+ paletteTitle: 'ADN Cromático',
23
+ reuploadBtn: 'Cambiar Imagen',
24
+ tunnelVisionTitle: 'Visión de Túnel',
25
+ tunnelVisionSubtitle: 'Tu asistente de precisión fila por fila.',
12
26
  uploadLabel: 'Sube tu foto',
13
- gridSizeLabel: 'Tamaño de la cuadrícula (beads)',
14
27
  pixelateBtn: 'Generar Patrón',
15
- downloadBtn: 'Descargar Esquema',
16
28
  faqTitle: 'FAQ',
17
29
  bibliographyTitle: 'Referencias'
18
30
  },
@@ -7,12 +7,24 @@ export const content: BeadPatternGeneratorLocaleContent = {
7
7
  faqTitle: 'Questions Fréquemment Posées',
8
8
  bibliographyTitle: 'Bibliographie de l\'Artisan',
9
9
  ui: {
10
- title: 'Générateur de Modèles',
10
+ title: 'Laboratoire de Modèles',
11
+ subtitle: 'Ingénierie chromatique pour vos mains',
11
12
  description: 'De la photo au schéma de perles.',
13
+ gridSizeLabel: 'Taille (Largeur)',
14
+ colorCountLabel: 'Couleurs',
15
+ optionsLabel: 'Options',
16
+ rulersLabel: 'Règles',
17
+ symbolsTooltip: 'Motif Surprise',
18
+ symbolsLabel: 'Symboles',
19
+ downloadBtn: 'Télécharger',
20
+ uploadTitle: 'Chargez Votre Vision',
21
+ uploadSubtitle: 'et laissez la magie numérique opérer',
22
+ paletteTitle: 'ADN Chromatique',
23
+ reuploadBtn: 'Changer l\'Image',
24
+ tunnelVisionTitle: 'Vision de Tunnel',
25
+ tunnelVisionSubtitle: 'Votre assistant de précision rangée par rangée.',
12
26
  uploadLabel: 'Chargez votre photo',
13
- gridSizeLabel: 'Taille de grille (perles)',
14
27
  pixelateBtn: 'Générer le Modèle',
15
- downloadBtn: 'Télécharger le Schéma',
16
28
  faqTitle: 'FAQ',
17
29
  bibliographyTitle: 'Références'
18
30
  },
@@ -7,10 +7,22 @@ export interface BeadPatternGeneratorUI {
7
7
  [key: string]: string;
8
8
  title: string;
9
9
  description: string;
10
- uploadLabel: string;
10
+ subtitle: string;
11
11
  gridSizeLabel: string;
12
- pixelateBtn: string;
12
+ colorCountLabel: string;
13
+ optionsLabel: string;
14
+ rulersLabel: string;
15
+ symbolsTooltip: string;
16
+ symbolsLabel: string;
13
17
  downloadBtn: string;
18
+ uploadTitle: string;
19
+ uploadSubtitle: string;
20
+ paletteTitle: string;
21
+ reuploadBtn: string;
22
+ tunnelVisionTitle: string;
23
+ tunnelVisionSubtitle: string;
24
+ uploadLabel: string;
25
+ pixelateBtn: string;
14
26
  faqTitle: string;
15
27
  bibliographyTitle: string;
16
28
  }
@@ -1,11 +1,18 @@
1
1
  ---
2
+ import type { DiceRollerUI } from './index';
3
+
4
+ interface Props {
5
+ ui: DiceRollerUI;
6
+ }
7
+
8
+ const { ui } = Astro.props;
2
9
  ---
3
10
 
4
- <div id="dice-roller-root" class="dr-root">
11
+ <div id="dice-roller-root" class="dr-root" data-ui={JSON.stringify(ui)}>
5
12
  <div class="dr-panel">
6
13
 
7
14
  <div class="dr-selector">
8
- <p class="dr-label">Añadir dados a la bolsa</p>
15
+ <p class="dr-label">{ui.addDiceLabel}</p>
9
16
  <div class="dr-dice-grid">
10
17
  <button class="dr-die-btn" data-sides="4">d4</button>
11
18
  <button class="dr-die-btn" data-sides="6">d6</button>
@@ -19,16 +26,16 @@
19
26
 
20
27
  <div class="dr-pool-section">
21
28
  <div class="dr-pool-header">
22
- <p class="dr-label">Bolsa de dados</p>
23
- <button id="dr-clear-pool" class="dr-clear-btn">Vaciar</button>
29
+ <p class="dr-label">{ui.bagLabel}</p>
30
+ <button id="dr-clear-pool" class="dr-clear-btn">{ui.emptyBagBtn}</button>
24
31
  </div>
25
32
  <div id="dr-pool" class="dr-pool-display">
26
- <span class="dr-pool-empty">Haz clic en los dados para añadirlos</span>
33
+ <span class="dr-pool-empty">{ui.emptyBagText}</span>
27
34
  </div>
28
35
  </div>
29
36
 
30
37
  <div class="dr-modifier-section">
31
- <label class="dr-label" for="dr-modifier">Modificador</label>
38
+ <label class="dr-label" for="dr-modifier">{ui.modifierLabel}</label>
32
39
  <div class="dr-modifier-controls">
33
40
  <button id="dr-mod-dec" class="dr-mod-btn">−</button>
34
41
  <span id="dr-modifier-val" class="dr-modifier-display">0</span>
@@ -37,7 +44,7 @@
37
44
  </div>
38
45
 
39
46
  <button id="dr-roll-btn" class="dr-roll-btn" disabled>
40
- <span id="dr-roll-text">Lanzar dados</span>
47
+ <span id="dr-roll-text">{ui.rollBtn}</span>
41
48
  </button>
42
49
 
43
50
  </div>
@@ -46,11 +53,11 @@
46
53
 
47
54
  <div class="dr-result-card" id="dr-result-card">
48
55
  <div class="dr-result-empty" id="dr-result-empty">
49
- <p>Añade dados y lanza</p>
56
+ <p>{ui.preRollText}</p>
50
57
  </div>
51
58
  <div class="dr-result-content" id="dr-result-content" style="display:none">
52
59
  <div class="dr-total-area">
53
- <span class="dr-total-label">Total</span>
60
+ <span class="dr-total-label">{ui.totalLabel}</span>
54
61
  <span id="dr-total" class="dr-total-val">0</span>
55
62
  <span id="dr-roll-expr" class="dr-roll-expr"></span>
56
63
  </div>
@@ -60,11 +67,11 @@
60
67
 
61
68
  <div class="dr-history-section">
62
69
  <div class="dr-history-header">
63
- <span class="dr-label">Historial</span>
64
- <button id="dr-clear-history" class="dr-clear-btn">Limpiar</button>
70
+ <span class="dr-label">{ui.historyLabel}</span>
71
+ <button id="dr-clear-history" class="dr-clear-btn">{ui.clearHistoryBtn}</button>
65
72
  </div>
66
73
  <div id="dr-history-list" class="dr-history-list">
67
- <p class="dr-history-empty">El historial de tiradas aparecerá aquí</p>
74
+ <p class="dr-history-empty">{ui.emptyHistoryText}</p>
68
75
  </div>
69
76
  </div>
70
77
 
@@ -72,6 +79,10 @@
72
79
  </div>
73
80
 
74
81
  <script>
82
+ import type { DiceRollerUI } from './index';
83
+ const root = document.getElementById('dice-roller-root') as HTMLElement;
84
+ const ui = JSON.parse(root.dataset.ui || '{}') as DiceRollerUI;
85
+
75
86
  type Pool = { sides: number; count: number }[];
76
87
 
77
88
  let pool: Pool = [];
@@ -94,9 +105,9 @@
94
105
 
95
106
  function renderPool(): void {
96
107
  if (pool.length === 0) {
97
- poolDisplay.innerHTML = '<span class="dr-pool-empty">Haz clic en los dados para añadirlos</span>';
108
+ poolDisplay.innerHTML = `<span class="dr-pool-empty">${ui.emptyBagText}</span>`;
98
109
  rollBtn.disabled = true;
99
- rollText.textContent = 'Lanzar dados';
110
+ rollText.textContent = ui.rollBtn;
100
111
  return;
101
112
  }
102
113
 
@@ -106,14 +117,15 @@
106
117
  chip.className = 'dr-pool-chip';
107
118
  chip.innerHTML = `
108
119
  <span class="dr-chip-label">${count}d${sides}</span>
109
- <button class="dr-chip-remove" data-sides="${sides}" title="Quitar un d${sides}">−</button>
120
+ <button class="dr-chip-remove" data-sides="${sides}">−</button>
110
121
  `;
111
122
  poolDisplay.appendChild(chip);
112
123
  });
113
124
 
114
125
  const total = poolTotal();
115
126
  rollBtn.disabled = false;
116
- rollText.textContent = `Lanzar ${total} dado${total !== 1 ? 's' : ''}`;
127
+ const label = total === 1 ? ui.rollOneLabel : ui.rollManyLabel;
128
+ rollText.textContent = label.replace('$COUNT', total.toString());
117
129
  }
118
130
 
119
131
  function addDie(sides: number): void {
@@ -224,7 +236,7 @@
224
236
  rollBtn.addEventListener('click', rollAll);
225
237
 
226
238
  document.getElementById('dr-clear-history')?.addEventListener('click', () => {
227
- historyList.innerHTML = '<p class="dr-history-empty">El historial de tiradas aparecerá aquí</p>';
239
+ historyList.innerHTML = `<p class="dr-history-empty">${ui.emptyHistoryText}</p>`;
228
240
  });
229
241
  </script>
230
242
 
@@ -378,7 +390,7 @@
378
390
  font-style: italic;
379
391
  }
380
392
 
381
- .dr-pool-chip {
393
+ :global(.dr-pool-chip) {
382
394
  display: flex;
383
395
  align-items: center;
384
396
  gap: 0.25rem;
@@ -388,13 +400,13 @@
388
400
  padding: 0.2rem 0.5rem 0.2rem 0.75rem;
389
401
  }
390
402
 
391
- .dr-chip-label {
403
+ :global(.dr-chip-label) {
392
404
  font-size: 0.8rem;
393
405
  font-weight: 700;
394
406
  color: var(--dr-primary);
395
407
  }
396
408
 
397
- .dr-chip-remove {
409
+ :global(.dr-chip-remove) {
398
410
  width: 1.125rem;
399
411
  height: 1.125rem;
400
412
  border-radius: 50%;
@@ -411,7 +423,7 @@
411
423
  padding: 0;
412
424
  }
413
425
 
414
- .dr-chip-remove:hover {
426
+ :global(.dr-chip-remove:hover) {
415
427
  background: var(--dr-primary-dark);
416
428
  }
417
429
 
@@ -581,7 +593,7 @@
581
593
  justify-content: center;
582
594
  }
583
595
 
584
- .dr-result-pip {
596
+ :global(.dr-result-pip) {
585
597
  display: flex;
586
598
  flex-direction: column;
587
599
  align-items: center;
@@ -594,24 +606,24 @@
594
606
  transition: border-color 0.2s;
595
607
  }
596
608
 
597
- .dr-pip-max {
609
+ :global(.dr-pip-max) {
598
610
  border-color: var(--dr-success);
599
611
  background: rgba(16, 185, 129, 0.1);
600
612
  }
601
613
 
602
- .dr-pip-min {
614
+ :global(.dr-pip-min) {
603
615
  border-color: var(--dr-danger);
604
616
  background: rgba(239, 68, 68, 0.1);
605
617
  }
606
618
 
607
- .dr-pip-val {
619
+ :global(.dr-pip-val) {
608
620
  font-size: 1.25rem;
609
621
  font-weight: 800;
610
622
  color: var(--dr-text);
611
623
  line-height: 1;
612
624
  }
613
625
 
614
- .dr-pip-die {
626
+ :global(.dr-pip-die) {
615
627
  font-size: 0.65rem;
616
628
  color: var(--dr-text-muted);
617
629
  font-weight: 600;
@@ -662,7 +674,7 @@
662
674
  margin: 1rem 0;
663
675
  }
664
676
 
665
- .dr-history-item {
677
+ :global(.dr-history-item) {
666
678
  display: grid;
667
679
  grid-template-columns: 1fr auto auto;
668
680
  align-items: center;
@@ -686,7 +698,7 @@
686
698
  }
687
699
  }
688
700
 
689
- .dr-hist-expr {
701
+ :global(.dr-hist-expr) {
690
702
  color: var(--dr-text-muted);
691
703
  font-size: 0.75rem;
692
704
  overflow: hidden;
@@ -694,13 +706,13 @@
694
706
  white-space: nowrap;
695
707
  }
696
708
 
697
- .dr-hist-result {
709
+ :global(.dr-hist-result) {
698
710
  font-weight: 800;
699
711
  font-size: 1rem;
700
712
  color: var(--dr-primary);
701
713
  }
702
714
 
703
- .dr-hist-time {
715
+ :global(.dr-hist-time) {
704
716
  font-size: 0.7rem;
705
717
  color: var(--dr-text-muted);
706
718
  }
@@ -14,7 +14,16 @@ export const content: DiceRollerLocaleContent = {
14
14
  historyLabel: 'History',
15
15
  clearHistoryBtn: 'Clear History',
16
16
  faqTitle: 'FAQ',
17
- bibliographyTitle: 'References'
17
+ bibliographyTitle: 'References',
18
+ addDiceLabel: 'Add dice to the bag',
19
+ bagLabel: 'Dice bag',
20
+ emptyBagBtn: 'Empty',
21
+ emptyBagText: 'Click the dice to add them',
22
+ modifierLabel: 'Modifier',
23
+ rollManyLabel: 'Roll $COUNT dice',
24
+ rollOneLabel: 'Roll $COUNT die',
25
+ preRollText: 'Add dice and roll',
26
+ emptyHistoryText: 'Roll history will appear here'
18
27
  },
19
28
  seo: [
20
29
  { type: 'title', text: 'The Art of Randomness: History and Mathematics of Dice', level: 2 },
@@ -14,7 +14,16 @@ export const content: DiceRollerLocaleContent = {
14
14
  historyLabel: 'Historial',
15
15
  clearHistoryBtn: 'Limpiar Historial',
16
16
  faqTitle: 'FAQ',
17
- bibliographyTitle: 'Referencias'
17
+ bibliographyTitle: 'Referencias',
18
+ addDiceLabel: 'Añadir dados a la bolsa',
19
+ bagLabel: 'Bolsa de dados',
20
+ emptyBagBtn: 'Vaciar',
21
+ emptyBagText: 'Haz clic en los dados para añadirlos',
22
+ modifierLabel: 'Modificador',
23
+ rollManyLabel: 'Lanzar $COUNT dados',
24
+ rollOneLabel: 'Lanzar $COUNT dado',
25
+ preRollText: 'Añade dados y lanza',
26
+ emptyHistoryText: 'El historial de tiradas aparecerá aquí'
18
27
  },
19
28
  seo: [
20
29
  { type: 'title', text: 'El arte de la aleatoriedad: historia y matemáticas de los dados', level: 2 },
@@ -14,7 +14,16 @@ export const content: DiceRollerLocaleContent = {
14
14
  historyLabel: 'Historique',
15
15
  clearHistoryBtn: 'Effacer l\'historique',
16
16
  faqTitle: 'FAQ',
17
- bibliographyTitle: 'Références'
17
+ bibliographyTitle: 'Références',
18
+ addDiceLabel: 'Ajouter des dés au sac',
19
+ bagLabel: 'Sac de dés',
20
+ emptyBagBtn: 'Vider',
21
+ emptyBagText: 'Cliquez sur les dés pour les ajouter',
22
+ modifierLabel: 'Modificateur',
23
+ rollManyLabel: 'Lancer $COUNT dés',
24
+ rollOneLabel: 'Lancer $COUNT dé',
25
+ preRollText: 'Ajoutez des dés et lancez',
26
+ emptyHistoryText: 'L\'historique des lancers apparaîtra ici'
18
27
  },
19
28
  seo: [
20
29
  { type: 'title', text: 'L\'Art du Hasard : Histoire et Mathématiques des Dés', level: 2 },
@@ -13,6 +13,15 @@ export interface DiceRollerUI {
13
13
  clearHistoryBtn: string;
14
14
  faqTitle: string;
15
15
  bibliographyTitle: string;
16
+ addDiceLabel: string;
17
+ bagLabel: string;
18
+ emptyBagBtn: string;
19
+ emptyBagText: string;
20
+ modifierLabel: string;
21
+ rollManyLabel: string;
22
+ rollOneLabel: string;
23
+ preRollText: string;
24
+ emptyHistoryText: string;
16
25
  }
17
26
 
18
27
  export type DiceRollerLocaleContent = ToolLocaleContent<DiceRollerUI>;
@@ -470,25 +470,35 @@ const ASSETS = {
470
470
  top: 50%;
471
471
  left: 50%;
472
472
  transform: translate(-50%, -50%) scale(0.5);
473
- width: 75%;
474
- max-width: 280px;
473
+ width: 85%;
474
+ max-width: 320px;
475
+ background-color: #fff;
476
+ border-radius: 2px;
477
+ box-shadow:
478
+ 0 10px 15px -3px rgba(0, 0, 0, 0.2),
479
+ 0 4px 6px -2px rgba(0, 0, 0, 0.1),
480
+ inset 0 0 20px rgba(0, 0, 0, 0.05);
475
481
  opacity: 0;
476
482
  transition-property: all;
477
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
478
- transition-duration: 1000ms;
483
+ transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
484
+ transition-duration: 800ms;
479
485
  z-index: 0;
486
+ overflow: hidden;
480
487
  }
481
488
 
482
489
  .fortune-cookie-paper.visible {
483
490
  opacity: 1;
484
- transform: translate(-50%, -50%) scale(1);
491
+ transform: translate(-50%, -50%) scale(1) rotate(-1deg);
485
492
  z-index: 20;
486
493
  }
487
494
 
488
495
  .fortune-cookie-paper-img {
489
496
  width: 100%;
490
- filter: drop-shadow(0 20px 13px rgba(0, 0, 0, 0.03)) drop-shadow(0 8px 5px rgba(0, 0, 0, 0.08));
491
- transform: rotate(1deg);
497
+ height: auto;
498
+ display: block;
499
+ opacity: 0.15;
500
+ pointer-events: none;
501
+ mix-blend-mode: multiply;
492
502
  }
493
503
 
494
504
  .fortune-cookie-paper-content {
@@ -498,23 +508,24 @@ const ASSETS = {
498
508
  flex-direction: column;
499
509
  align-items: center;
500
510
  justify-content: center;
501
- padding: 2rem;
511
+ padding: 1.5rem;
502
512
  text-align: center;
513
+ z-index: 10;
503
514
  }
504
515
 
505
516
  .fortune-cookie-text {
506
- color: #0f172a;
507
- font-weight: 900;
517
+ color: #1e293b;
518
+ font-weight: 800;
508
519
  font-size: 1.125rem;
509
- line-height: 1.25;
520
+ line-height: 1.4;
510
521
  transform: rotate(-1deg);
511
522
  user-select: text;
523
+ margin: 0;
512
524
  }
513
525
 
514
526
  @media (min-width: 768px) {
515
527
  .fortune-cookie-text {
516
528
  font-size: 1.25rem;
517
- line-height: 1.75rem;
518
529
  }
519
530
  }
520
531
 
@@ -527,12 +538,12 @@ const ASSETS = {
527
538
  transform: rotate(-1deg);
528
539
  }
529
540
 
530
- .fortune-cookie-number {
541
+ :global(.fortune-cookie-number) {
531
542
  font-size: 0.75rem;
532
543
  line-height: 1rem;
533
544
  font-weight: 700;
534
- background-color: rgba(255, 255, 255, 0.9);
535
- color: #450a0a;
545
+ background-color: #f1f5f9;
546
+ color: #dc2626;
536
547
  padding-left: 0.5rem;
537
548
  padding-right: 0.5rem;
538
549
  padding-top: 0.125rem;
@@ -621,7 +632,7 @@ const ASSETS = {
621
632
  40%, 60% { transform: translate3d(4px, 0, 0) rotate(4deg); }
622
633
  }
623
634
 
624
- .fortune-cookie-crumb {
635
+ :global(.fortune-cookie-crumb) {
625
636
  position: absolute;
626
637
  width: 20px;
627
638
  height: 20px;
@@ -282,7 +282,7 @@ const { ui } = Astro.props;
282
282
  color: #ef4444;
283
283
  }
284
284
 
285
- .synesthesia-painter-char {
285
+ :global(.synesthesia-painter-char) {
286
286
  display: inline-block;
287
287
  color: var(--char-color);
288
288
  transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
@@ -301,7 +301,7 @@ const { ui } = Astro.props;
301
301
  }
302
302
  }
303
303
 
304
- .synesthesia-painter-viz-layer.dots .synesthesia-painter-char {
304
+ :global(.synesthesia-painter-viz-layer.dots .synesthesia-painter-char) {
305
305
  color: transparent;
306
306
  font-size: 0;
307
307
  width: 1rem;
@@ -312,17 +312,17 @@ const { ui } = Astro.props;
312
312
  box-shadow: 0 0 8px var(--char-color);
313
313
  }
314
314
 
315
- .synesthesia-painter-viz-layer.aura .synesthesia-painter-char {
315
+ :global(.synesthesia-painter-viz-layer.aura .synesthesia-painter-char) {
316
316
  color: rgba(255, 255, 255, 0.15);
317
317
  text-shadow: 0 0 18px var(--char-color), 0 0 36px var(--char-color);
318
318
  }
319
319
 
320
- :global(.theme-dark) .synesthesia-painter-viz-layer.aura .synesthesia-painter-char {
320
+ :global(.theme-dark .synesthesia-painter-viz-layer.aura .synesthesia-painter-char) {
321
321
  color: rgba(255, 255, 255, 0.85);
322
322
  text-shadow: 0 0 14px var(--char-color), 0 0 28px var(--char-color), 0 0 56px var(--char-color);
323
323
  }
324
324
 
325
- .synesthesia-painter-cursor {
325
+ :global(.synesthesia-painter-cursor) {
326
326
  display: inline-block;
327
327
  width: 2px;
328
328
  height: 2.5rem;
@@ -1,21 +1,28 @@
1
1
  ---
2
2
  import { Icon } from 'astro-icon/components';
3
+ import type { ZalgoGeneratorUI } from './index';
4
+
5
+ interface Props {
6
+ ui: ZalgoGeneratorUI;
7
+ }
8
+
9
+ const { ui } = Astro.props;
3
10
  ---
4
11
 
5
- <section class="zg-section">
12
+ <section class="zg-section" id="zalgo-generator-root" data-ui={JSON.stringify(ui)}>
6
13
  <div class="zg-grid">
7
14
  <div class="zg-left">
8
15
  <div class="zg-config-card">
9
16
  <div class="zg-config-hover-bg"></div>
10
17
  <h3 class="zg-config-title">
11
18
  <Icon name="mdi:tune-variant" style="width:1.25rem;height:1.25rem;color:#9333ea" />
12
- Configuración
19
+ {ui.configTitle}
13
20
  </h3>
14
21
 
15
22
  <div class="zg-config-body">
16
23
  <div class="zg-field">
17
24
  <div class="zg-field-header">
18
- <label class="zg-field-label">Intensidad</label>
25
+ <label class="zg-field-label">{ui.intensity}</label>
19
26
  <span id="intensity-value" class="zg-intensity-val">50%</span>
20
27
  </div>
21
28
  <input type="range" id="intensity-slider" min="0.1" max="1.5" step="0.1" value="0.5" class="zg-slider" />
@@ -23,21 +30,21 @@ import { Icon } from 'astro-icon/components';
23
30
 
24
31
  <div class="zg-toggles">
25
32
  <label class="zg-toggle-row">
26
- <span class="zg-toggle-label">Corrupción Superior</span>
33
+ <span class="zg-toggle-label">{ui.upLabel}</span>
27
34
  <div class="zg-switch">
28
35
  <input type="checkbox" id="opt-up" checked class="zg-switch-input" />
29
36
  <div class="zg-switch-track"></div>
30
37
  </div>
31
38
  </label>
32
39
  <label class="zg-toggle-row">
33
- <span class="zg-toggle-label">Ruido Central</span>
40
+ <span class="zg-toggle-label">{ui.midLabel}</span>
34
41
  <div class="zg-switch">
35
42
  <input type="checkbox" id="opt-middle" checked class="zg-switch-input" />
36
43
  <div class="zg-switch-track"></div>
37
44
  </div>
38
45
  </label>
39
46
  <label class="zg-toggle-row">
40
- <span class="zg-toggle-label">Corrupción Inferior</span>
47
+ <span class="zg-toggle-label">{ui.downLabel}</span>
41
48
  <div class="zg-switch">
42
49
  <input type="checkbox" id="opt-down" checked class="zg-switch-input" />
43
50
  <div class="zg-switch-track"></div>
@@ -47,13 +54,13 @@ import { Icon } from 'astro-icon/components';
47
54
 
48
55
  <button id="reset-options" class="zg-reset-btn">
49
56
  <Icon name="mdi:history" style="width:1rem;height:1rem" />
50
- Restablecer Valores
57
+ {ui.resetBtn}
51
58
  </button>
52
59
  </div>
53
60
  </div>
54
61
 
55
62
  <div class="zg-warning">
56
- <p>ADVERTENCIA: El texto Zalgo utiliza caracteres combinados de Unicode que pueden desbordar visualmente su contenedor. Úsese con precaución en redes sociales.</p>
63
+ <p>{ui.warningText}</p>
57
64
  </div>
58
65
  </div>
59
66
 
@@ -68,14 +75,14 @@ import { Icon } from 'astro-icon/components';
68
75
  <textarea
69
76
  id="input-text"
70
77
  class="zg-textarea"
71
- placeholder="Escribe aquí el mensaje que deseas corromper..."
78
+ placeholder={ui.inputPlaceholder}
72
79
  ></textarea>
73
80
  <div class="zg-editor-footer">
74
- <span id="char-count" class="zg-char-count">0 CARACTERES</span>
81
+ <span id="char-count" class="zg-char-count">0 {ui.charLabel}</span>
75
82
  <div class="zg-footer-sep"></div>
76
83
  <button id="copy-btn" class="zg-copy-btn">
77
84
  <Icon name="mdi:content-copy" style="width:1.25rem;height:1.25rem" />
78
- Copiar Resultado
85
+ {ui.copyBtn}
79
86
  </button>
80
87
  <button id="clear-btn" class="zg-clear-btn">
81
88
  <Icon name="mdi:trash-can-outline" style="width:1.25rem;height:1.25rem" />
@@ -84,24 +91,28 @@ import { Icon } from 'astro-icon/components';
84
91
  </div>
85
92
 
86
93
  <div class="zg-preview-wrap">
87
- <div class="zg-preview-badge">Vista Previa en Tiempo Real</div>
94
+ <div class="zg-preview-badge">{ui.previewLabel}</div>
88
95
  <div id="fake-ui-body" class="zg-preview-body">
89
96
  <div class="zg-preview-dots"></div>
90
97
  <div id="zalgo-output" class="zg-output">
91
- <span class="zg-output-empty">Vacío</span>
98
+ <span class="zg-output-empty">{ui.emptyLabel}</span>
92
99
  </div>
93
100
  <div id="glitch-overlay" class="zg-glitch-overlay">
94
101
  <div class="zg-glitch-line zg-glitch-line-1"></div>
95
102
  <div class="zg-glitch-line zg-glitch-line-2"></div>
96
103
  </div>
97
104
  </div>
98
- <p class="zg-preview-note">Nota: Algunos navegadores pueden limitar el renderizado de caracteres combinados</p>
105
+ <p class="zg-preview-note">{ui.noteText}</p>
99
106
  </div>
100
107
  </div>
101
108
  </div>
102
109
  </section>
103
110
 
104
111
  <script>
112
+ import type { ZalgoGeneratorUI } from './index';
113
+ const root = document.getElementById('zalgo-generator-root') as HTMLElement;
114
+ const ui = JSON.parse(root.dataset.ui || '{}') as ZalgoGeneratorUI;
115
+
105
116
  const UP = ["\u030d","\u030e","\u0304","\u0305","\u033f","\u0311","\u0306","\u0310","\u0352","\u0357","\u0351","\u0307","\u0308","\u030a","\u0342","\u0343","\u0344","\u034a","\u034b","\u034c","\u0303","\u0302","\u030c","\u0350","\u0300","\u0301","\u030b","\u030f","\u0312","\u0313","\u0314","\u033d","\u0309","\u0363","\u0364","\u0365","\u0366","\u0367","\u0368","\u0369","\u036a","\u036b","\u036c","\u036d","\u036e","\u036f","\u033e","\u035b","\u0346","\u031a"];
106
117
  const MID = ["\u0315","\u031b","\u0340","\u0341","\u0358","\u0321","\u0322","\u0327","\u0328","\u0334","\u0335","\u0336","\u034f","\u035c","\u035d","\u035e","\u035f","\u0360","\u0362","\u0338","\u0337","\u0361","\u0489"];
107
118
  const DOWN = ["\u0316","\u0317","\u0318","\u0319","\u031c","\u031d","\u031e","\u031f","\u0320","\u0324","\u0325","\u0326","\u0329","\u032a","\u032b","\u032c","\u032d","\u032e","\u032f","\u0330","\u0331","\u0332","\u0333","\u0339","\u033a","\u033b","\u033c","\u0345","\u0347","\u0348","\u0349","\u034d","\u034e","\u0353","\u0354","\u0355","\u0356","\u0359","\u035a","\u0323"];
@@ -142,10 +153,10 @@ import { Icon } from 'astro-icon/components';
142
153
  function update() {
143
154
  const text = input.value;
144
155
  const intensity = parseFloat(intensitySlider.value);
145
- if (charCount) charCount.textContent = `${text.length} CARACTERES`;
156
+ if (charCount) charCount.textContent = `${text.length} ${ui.charLabel}`;
146
157
  if (intensityValue) intensityValue.textContent = `${Math.round(intensity * 100)}%`;
147
158
  if (!text.trim()) {
148
- output.innerHTML = '<span class="zg-output-empty">Vacío</span>';
159
+ output.innerHTML = `<span class="zg-output-empty">${ui.emptyLabel}</span>`;
149
160
  glitchOverlay.style.opacity = "0";
150
161
  fakeUiBody.style.borderColor = "";
151
162
  fakeUiBody.style.boxShadow = "";
@@ -174,10 +185,10 @@ import { Icon } from 'astro-icon/components';
174
185
 
175
186
  copyBtn?.addEventListener("click", () => {
176
187
  const text = output.textContent || "";
177
- if (text && text !== "Vacío") {
188
+ if (text && text !== ui.emptyLabel) {
178
189
  navigator.clipboard.writeText(text);
179
190
  const orig = copyBtn.innerHTML;
180
- copyBtn.innerHTML = "¡Copiado!";
191
+ copyBtn.innerHTML = ui.copied || '¡Copiado!';
181
192
  setTimeout(() => { copyBtn.innerHTML = orig; }, 2000);
182
193
  }
183
194
  });
@@ -15,7 +15,18 @@ export const content: ZalgoGeneratorLocaleContent = {
15
15
  copyBtn: 'Copy Chaos',
16
16
  copied: 'Copied!',
17
17
  faqTitle: 'FAQ',
18
- bibliographyTitle: 'References'
18
+ bibliographyTitle: 'References',
19
+ configTitle: 'Configuration',
20
+ intensity: 'Intensity',
21
+ upLabel: 'Upper Corruption',
22
+ midLabel: 'Middle Noise',
23
+ downLabel: 'Lower Corruption',
24
+ resetBtn: 'Reset Values',
25
+ warningText: 'WARNING: Zalgo text uses combined Unicode characters that may visually overflow their container. Use with caution on social media.',
26
+ previewLabel: 'Real-time Preview',
27
+ emptyLabel: 'Empty',
28
+ charLabel: 'CHARACTERS',
29
+ noteText: 'Note: Some browsers may limit the rendering of combined characters'
19
30
  },
20
31
  seo: [
21
32
  { type: 'title', text: 'What Is Zalgo Text and How Does Visual Corruption Work?', level: 2 },
@@ -15,7 +15,18 @@ export const content: ZalgoGeneratorLocaleContent = {
15
15
  copyBtn: 'Copiar Caos',
16
16
  copied: '¡Copiado!',
17
17
  faqTitle: 'FAQ',
18
- bibliographyTitle: 'Referencias'
18
+ bibliographyTitle: 'Referencias',
19
+ configTitle: 'Configuración',
20
+ intensity: 'Intensidad',
21
+ upLabel: 'Corrupción Superior',
22
+ midLabel: 'Ruido Central',
23
+ downLabel: 'Corrupción Inferior',
24
+ resetBtn: 'Restablecer Valores',
25
+ warningText: 'ADVERTENCIA: El texto Zalgo utiliza caracteres combinados de Unicode que pueden desbordar visualmente su contenedor. Úsese con precaución en redes sociales.',
26
+ previewLabel: 'Vista Previa en Tiempo Real',
27
+ emptyLabel: 'Vacío',
28
+ charLabel: 'CARACTERES',
29
+ noteText: 'Nota: Algunos navegadores pueden limitar el renderizado de caracteres combinados'
19
30
  },
20
31
  seo: [
21
32
  { type: 'title', text: '¿Qué es el Texto Zalgo y cómo funciona la corrupción visual?', level: 2 },
@@ -15,7 +15,18 @@ export const content: ZalgoGeneratorLocaleContent = {
15
15
  copyBtn: 'Copier le Chaos',
16
16
  copied: 'Copié !',
17
17
  faqTitle: 'FAQ',
18
- bibliographyTitle: 'Références'
18
+ bibliographyTitle: 'Références',
19
+ configTitle: 'Configuration',
20
+ intensity: 'Intensité',
21
+ upLabel: 'Corruption Supérieure',
22
+ midLabel: 'Bruit Central',
23
+ downLabel: 'Corruption Inférieure',
24
+ resetBtn: 'Réinitialiser les Valeurs',
25
+ warningText: 'AVERTISSEMENT : Le texte Zalgo utilise des caractères combinés Unicode qui peuvent déborder visuellement de leur conteneur. À utiliser avec précaution sur les réseaux sociaux.',
26
+ previewLabel: 'Aperçu en Temps Réel',
27
+ emptyLabel: 'Vide',
28
+ charLabel: 'CARACTÈRES',
29
+ noteText: 'Note : Certains navigateurs peuvent limiter le rendu des caractères combinés'
19
30
  },
20
31
  seo: [
21
32
  { type: 'title', text: 'Qu\'est-ce que le Texte Zalgo et comment fonctionne la corruption visuelle ?', level: 2 },
@@ -14,6 +14,17 @@ export interface ZalgoGeneratorUI {
14
14
  copied: string;
15
15
  faqTitle: string;
16
16
  bibliographyTitle: string;
17
+ configTitle: string;
18
+ intensity: string;
19
+ upLabel: string;
20
+ midLabel: string;
21
+ downLabel: string;
22
+ resetBtn: string;
23
+ warningText: string;
24
+ previewLabel: string;
25
+ emptyLabel: string;
26
+ charLabel: string;
27
+ noteText: string;
17
28
  }
18
29
 
19
30
  export type ZalgoGeneratorLocaleContent = ToolLocaleContent<ZalgoGeneratorUI>;