@jjlmoya/utils-creative 1.2.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 (71) hide show
  1. package/package.json +64 -0
  2. package/src/category/i18n/en.ts +9 -0
  3. package/src/category/i18n/es.ts +9 -0
  4. package/src/category/i18n/fr.ts +9 -0
  5. package/src/category/index.ts +34 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +6 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +27 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/faq_count.test.ts +19 -0
  17. package/src/tests/locale_completeness.test.ts +42 -0
  18. package/src/tests/mocks/astro_mock.js +2 -0
  19. package/src/tests/no_h1_in_components.test.ts +48 -0
  20. package/src/tests/seo_length.test.ts +22 -0
  21. package/src/tests/tool_validation.test.ts +17 -0
  22. package/src/tool/bead-pattern-generator/bibliography.astro +18 -0
  23. package/src/tool/bead-pattern-generator/component.astro +372 -0
  24. package/src/tool/bead-pattern-generator/i18n/en.ts +61 -0
  25. package/src/tool/bead-pattern-generator/i18n/es.ts +68 -0
  26. package/src/tool/bead-pattern-generator/i18n/fr.ts +61 -0
  27. package/src/tool/bead-pattern-generator/index.ts +37 -0
  28. package/src/tool/bead-pattern-generator/seo.astro +14 -0
  29. package/src/tool/bead-pattern-generator/style.css +511 -0
  30. package/src/tool/dice-roller/bibliography.astro +17 -0
  31. package/src/tool/dice-roller/component.astro +230 -0
  32. package/src/tool/dice-roller/i18n/en.ts +87 -0
  33. package/src/tool/dice-roller/i18n/es.ts +89 -0
  34. package/src/tool/dice-roller/i18n/fr.ts +87 -0
  35. package/src/tool/dice-roller/index.ts +37 -0
  36. package/src/tool/dice-roller/seo.astro +14 -0
  37. package/src/tool/dice-roller/style.css +482 -0
  38. package/src/tool/excuse-generator/bibliography.astro +18 -0
  39. package/src/tool/excuse-generator/component.astro +140 -0
  40. package/src/tool/excuse-generator/i18n/en.ts +80 -0
  41. package/src/tool/excuse-generator/i18n/es.ts +84 -0
  42. package/src/tool/excuse-generator/i18n/fr.ts +80 -0
  43. package/src/tool/excuse-generator/index.ts +42 -0
  44. package/src/tool/excuse-generator/seo.astro +14 -0
  45. package/src/tool/excuse-generator/style.css +316 -0
  46. package/src/tool/fortune-cookie/bibliography.astro +18 -0
  47. package/src/tool/fortune-cookie/component.astro +299 -0
  48. package/src/tool/fortune-cookie/i18n/en.ts +85 -0
  49. package/src/tool/fortune-cookie/i18n/es.ts +90 -0
  50. package/src/tool/fortune-cookie/i18n/fr.ts +85 -0
  51. package/src/tool/fortune-cookie/index.ts +40 -0
  52. package/src/tool/fortune-cookie/seo.astro +14 -0
  53. package/src/tool/fortune-cookie/style.css +332 -0
  54. package/src/tool/synesthesia-painter/bibliography.astro +17 -0
  55. package/src/tool/synesthesia-painter/component.astro +110 -0
  56. package/src/tool/synesthesia-painter/i18n/en.ts +80 -0
  57. package/src/tool/synesthesia-painter/i18n/es.ts +82 -0
  58. package/src/tool/synesthesia-painter/i18n/fr.ts +80 -0
  59. package/src/tool/synesthesia-painter/index.ts +39 -0
  60. package/src/tool/synesthesia-painter/seo.astro +14 -0
  61. package/src/tool/synesthesia-painter/style.css +234 -0
  62. package/src/tool/zalgo-generator/bibliography.astro +18 -0
  63. package/src/tool/zalgo-generator/component.astro +195 -0
  64. package/src/tool/zalgo-generator/i18n/en.ts +60 -0
  65. package/src/tool/zalgo-generator/i18n/es.ts +67 -0
  66. package/src/tool/zalgo-generator/i18n/fr.ts +60 -0
  67. package/src/tool/zalgo-generator/index.ts +38 -0
  68. package/src/tool/zalgo-generator/seo.astro +14 -0
  69. package/src/tool/zalgo-generator/style.css +558 -0
  70. package/src/tools.ts +4 -0
  71. package/src/types.ts +72 -0
@@ -0,0 +1,80 @@
1
+ import type { SynesthesiaPainterLocaleContent } from '../index';
2
+
3
+ export const content: SynesthesiaPainterLocaleContent = {
4
+ slug: 'peintre-de-synesthesie',
5
+ title: 'Peintre de Synesthésie',
6
+ description: 'Visualisez la couleur des mots selon la synesthésie graphème-couleur. Chaque lettre possède sa propre couleur, transformant le texte en art chromatique.',
7
+ faqTitle: 'Questions Fréquemment Posées',
8
+ bibliographyTitle: 'Bibliographie de l\'Esprit',
9
+ ui: {
10
+ title: 'Peintre de Synesthésie',
11
+ description: 'Transformez vos mots en art chromatique.',
12
+ modeLetters: 'Lettres',
13
+ modeDots: 'Points',
14
+ modeAura: 'Aura',
15
+ placeholder: 'Écrivez ici...',
16
+ footerText: 'Écrivez pour découvrir votre palette de couleurs personnelle',
17
+ clearBtn: 'Effacer',
18
+ faqTitle: 'FAQ',
19
+ bibliographyTitle: 'Références'
20
+ },
21
+ seo: [
22
+ { type: 'title', text: 'Qu\'est-ce que la Synesthésie Graphème-Couleur ?', level: 2 },
23
+ { type: 'paragraph', html: 'La <strong>synesthésie</strong> est une condition neurologique dans laquelle la stimulation d\'un sens déclenche automatiquement une réponse dans un autre. La variante la plus étudiée et la plus répandue est la <strong>synesthésie graphème-couleur</strong> : ceux qui en sont atteints perçoivent chaque lettre ou chiffre avec une couleur intrinsèque, constante et vive. Ce n\'est ni de l\'imagination ni une métaphore ; pour un synesthète, la lettre "A" est rouge de la même manière que le feu est chaud. Cet outil applique une <em>palette statistique</em> basée sur les couleurs les plus fréquemment rapportées pour chaque graphème dans les études de population.' },
24
+ { type: 'title', text: 'Neurosciences : La Théorie de l\'Activation Croisée', level: 3 },
25
+ { type: 'paragraph', html: 'Le modèle neurologique le plus largement accepté pour la synesthésie graphème-couleur est l\'<strong>activation croisée</strong>. Les zones du cortex temporal impliquées dans la reconnaissance de la forme des lettres (gyrus fusiforme) sont anatomiquement adjacentes aux régions qui traitent la couleur (zone V4). Chez les personnes synesthètes, il existe une connectivité structurelle ou fonctionnelle accrue entre ces régions, de sorte que la reconnaissance d\'une lettre active également les neurones de la couleur. La recherche par neuro-imagerie fonctionnelle (IRMf) a confirmé que les synesthètes montrent une véritable activation de V4 lors de la lecture d\'un texte, même s\'il est monochromatique.' },
26
+ { type: 'tip', title: 'Les Trois Modes de Visualisation', html: '<strong>Lettres :</strong> Le texte original coloré par graphème. Idéal pour voir la "mélodie chromatique" d\'un texte complet. <strong>Points :</strong> Chaque caractère devient un cercle de sa couleur ; le texte disparaît et seule la musique des couleurs subsiste. <strong>Aura :</strong> Les lettres émettent un halo de leur couleur, comme si le texte rayonnait de sa propre énergie.' },
27
+ { type: 'title', text: 'Statistiques et Universaux de Couleur', level: 3 },
28
+ { type: 'paragraph', html: 'Bien que les couleurs synesthésiques soient uniques à chaque individu, des études comme celles de Simner et al. (2006) et Eagleman et al. (2007) ont trouvé des schémas statistiques significatifs. La voyelle <strong>A</strong> a tendance à être rouge pour la majorité ; le <strong>O</strong> est blanc ou noir ; le <strong>S</strong> apparaît dans des tons bleu-vert ou vert ; le <strong>E</strong> apparaît comme vert ou blanc. Fait intéressant, les associations couleur-lettre sont plus cohérentes au sein d\'une culture linguistique qu\'entre différentes cultures, ce qui suggère un rôle de l\'apprentissage précoce de l\'alphabet.' },
29
+ { type: 'list', items: [
30
+ '<strong>Prévalence :</strong> Environ 4 % de la population présente une synesthésie graphème-couleur à un certain degré, bien que des études plus récentes portent ce chiffre à 6-8 % en incluant les formes subcliniques.',
31
+ '<strong>Biais de genre :</strong> La synesthésie est 3 à 6 fois plus fréquente chez les femmes que chez les hommes, bien que les causes de cette différence ne soient pas encore totalement expliquées.',
32
+ '<strong>Hérédité :</strong> Elle a une composante génétique claire : elle a tendance à être présente dans les familles, bien que pas toujours sous le même type de synesthésie.',
33
+ '<strong>Constance :</strong> Contrairement aux associations apprises, les couleurs synesthésiques sont extraordinairement stables dans le temps. Des études de suivi sur 10 ans démontrent une cohérence supérieure à 90 % dans les associations graphème-couleur.',
34
+ '<strong>Synesthètes célèbres :</strong> Vladimir Nabokov, Wassily Kandinsky, Nikola Tesla et Billy Joel ont publiquement décrit des expériences synesthésiques qui ont influencé leur travail.',
35
+ ]},
36
+ { type: 'stats', items: [
37
+ { value: '4–8%', label: 'Population synesthète', icon: 'mdi:brain' },
38
+ { value: '90%+', label: 'Constance des couleurs sur 10 ans', icon: 'mdi:check-circle' },
39
+ { value: '3–6×', label: 'Plus fréquent chez les femmes', icon: 'mdi:gender-female' },
40
+ { value: '26+10', label: 'Lettres et chiffres colorés', icon: 'mdi:alphabetical' },
41
+ ], columns: 4 },
42
+ { type: 'title', text: 'Art et Synesthésie : Quand les Sens Fusionnent', level: 3 },
43
+ { type: 'paragraph', html: '<strong>Wassily Kandinsky</strong>, fondateur de l\'expressionnisme abstrait, expérimentait à la fois la synesthésie graphème-couleur et musique-couleur : il entendait les instruments en couleurs (le jaune était une trompette, le bleu profond un violoncelle) et utilisait ces perceptions pour créer sa théorie de l\'art abstrait. En musique, <strong>Alexandre Scriabine</strong> a composé <em>Prométhée : Le Poème du Feu</em> avec une partie pour "clavier à lumières" (tastiera per luce), conçue pour projeter des couleurs correspondant à chaque note.' },
44
+ { type: 'tip', title: 'Palette de Couleurs de cet Outil', html: 'Les attributions de couleurs s\'inspirent des données statistiques les plus courantes dans la littérature scientifique. <strong>A → rouge</strong>, <strong>E → vert</strong>, <strong>I → blanc/noir selon le fond</strong>, <strong>O → noir/blanc</strong>, <strong>U → ambre</strong>. Les consonnes suivent des schémas moins uniformes, mais le contraste avec l\'arrière-plan est toujours privilégié pour garantir la lisibilité.' },
45
+ ],
46
+ faq: [
47
+ {
48
+ question: 'Tous les synesthètes voient-ils les mêmes couleurs pour chaque lettre ?',
49
+ answer: 'Non. Les couleurs synesthésiques sont uniques à chaque personne. Il existe des tendances statistiques (le A a tendance à être rouge pour beaucoup), mais aucun couple de synesthètes n\'a exactement la même palette. Cet outil utilise les couleurs les plus fréquemment rapportées dans les études de population, et non les "bonnes" couleurs.',
50
+ },
51
+ {
52
+ question: 'Puis-je développer la synesthésie en utilisant cet outil de manière continue ?',
53
+ answer: 'Pas au sens neurologique strict. La véritable synesthésie est une caractéristique du système nerveux, pas une compétence acquise. Cependant, l\'utilisation répétée d\'associations couleur-lettre peut créer de forts souvenirs associatifs. Certaines études suggèrent que pratiquer ces associations peut améliorer la mémoire textuelle.',
54
+ },
55
+ {
56
+ question: 'À quoi sert le mode "Aura" ?',
57
+ answer: 'Le mode Aura simule la façon dont certains synesthètes décrivent voir les couleurs "flotter" ou "rayonner" autour des lettres plutôt qu\'intégrées à celles-ci. Cela crée une expérience visuelle plus atmosphérique et immersive, particulièrement sur un fond sombre.',
58
+ },
59
+ {
60
+ question: 'Le mode "Points" a-t-il une base scientifique ?',
61
+ answer: 'C\'est une abstraction artistique. Il réduit le texte à son "essence chromatique" en éliminant la forme reconnaissable des lettres. Le résultat ressemble à des visualisations de données chromatiques ou à des peintures pointillistes, et permet de voir la "signature colorée" d\'un texte sans que le sens n\'interfère.',
62
+ },
63
+ {
64
+ question: 'Pourquoi certaines lettres comme I et O sont-elles blanches ou noires ?',
65
+ answer: 'Dans les études sur la synesthésie, les voyelles I et O, ainsi que la lettre W, sont fréquemment décrites comme blanches, transparentes ou noires. Cet outil adapte ces couleurs au fond actif : blanc sur fond sombre, noir sur fond clair, pour toujours garantir la visibilité.',
66
+ },
67
+ ],
68
+ bibliography: [
69
+ { name: 'Simner et al. (2006) – Synaesthesia: The prevalence of atypical cross-modal experiences', url: 'https://www.ncbi.nlm.nih.gov/pmc/articles/PMC1626536/' },
70
+ { name: 'Eagleman et al. (2007) – A standardized test battery for the study of synesthesia', url: 'https://www.sciencedirect.com/science/article/pii/S0010945207000087' },
71
+ { name: 'Kandinsky, W. – Du Spirituel dans l\'Art (1911)', url: 'https://fr.wikipedia.org/wiki/Du_spirituel_dans_l\'art' },
72
+ ],
73
+ howTo: [
74
+ { name: 'Écrire du texte', text: 'Cliquez sur la zone d\'écriture et commencez à taper. Chaque lettre apparaîtra colorée selon son association synesthésique statistique.' },
75
+ { name: 'Changer le mode de visualisation', text: 'Utilisez les boutons en haut à droite pour basculer entre Lettres (texte coloré), Points (cercles de couleur) et Aura (lettres lumineuses avec halos chromatiques).' },
76
+ { name: 'Explorer différents textes', text: 'Écrivez des noms, des mots dans différentes langues ou des phrases pour découvrir leur palette chromatique unique. Les mots longs créent des dégradés visuels fascinants.' },
77
+ { name: 'Effacer et recommencer', text: 'Utilisez le bouton "Effacer" dans la barre inférieure pour vider le canevas et explorer un nouveau texte.' },
78
+ ],
79
+ schemas: []
80
+ };
@@ -0,0 +1,39 @@
1
+ import type { CreativeToolEntry, ToolLocaleContent, ToolDefinition } from '../../types';
2
+ import SynesthesiaPainterComponent from './component.astro';
3
+ import SynesthesiaPainterSEO from './seo.astro';
4
+ import SynesthesiaPainterBibliography from './bibliography.astro';
5
+
6
+ export interface SynesthesiaPainterUI {
7
+ [key: string]: string;
8
+ title: string;
9
+ description: string;
10
+ modeLetters: string;
11
+ modeDots: string;
12
+ modeAura: string;
13
+ placeholder: string;
14
+ footerText: string;
15
+ clearBtn: string;
16
+ faqTitle: string;
17
+ bibliographyTitle: string;
18
+ }
19
+
20
+ export type SynesthesiaPainterLocaleContent = ToolLocaleContent<SynesthesiaPainterUI>;
21
+
22
+ export const synesthesiaPainter: CreativeToolEntry<SynesthesiaPainterUI> = {
23
+ id: 'synesthesia-painter',
24
+ icons: { bg: 'mdi:brush', fg: 'mdi:eye' },
25
+ i18n: {
26
+ es: () => import('./i18n/es').then((m) => m.content),
27
+ en: () => import('./i18n/en').then((m) => m.content),
28
+ fr: () => import('./i18n/fr').then((m) => m.content),
29
+ },
30
+ };
31
+
32
+ export { SynesthesiaPainterComponent, SynesthesiaPainterSEO, SynesthesiaPainterBibliography };
33
+
34
+ export const SYNESTHESIA_PAINTER_TOOL: ToolDefinition = {
35
+ entry: synesthesiaPainter,
36
+ Component: SynesthesiaPainterComponent,
37
+ SEOComponent: SynesthesiaPainterSEO,
38
+ BibliographyComponent: SynesthesiaPainterBibliography,
39
+ };
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { synesthesiaPainter } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'es' } = Astro.props;
11
+ const content = await synesthesiaPainter.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -0,0 +1,234 @@
1
+ .synesthesia-painter {
2
+ --sp-bg: #fff;
3
+ --sp-bg-muted: #f8fafc;
4
+ --sp-text: #0f172a;
5
+ --sp-text-muted: #475569;
6
+ --sp-text-dim: #64748b;
7
+ --sp-border: #e2e8f0;
8
+ --sp-shadow: rgba(0, 0, 0, 0.08);
9
+ --sp-primary: #6366f1;
10
+ --sp-primary-on: #fff;
11
+ --sp-header-bg: rgba(255, 255, 255, 0.92);
12
+ }
13
+
14
+ .theme-dark .synesthesia-painter {
15
+ --sp-bg: #0f172a;
16
+ --sp-bg-muted: #1e293b;
17
+ --sp-text: #f8fafc;
18
+ --sp-text-muted: #94a3b8;
19
+ --sp-text-dim: #64748b;
20
+ --sp-border: #334155;
21
+ --sp-shadow: rgba(0, 0, 0, 0.35);
22
+ --sp-primary: #818cf8;
23
+ --sp-header-bg: rgba(30, 41, 59, 0.92);
24
+ }
25
+
26
+ .synesthesia-painter-card {
27
+ position: relative;
28
+ min-height: 420px;
29
+ background-color: var(--sp-bg);
30
+ border-radius: 2rem;
31
+ border: 1px solid var(--sp-border);
32
+ box-shadow: 0 20px 40px -8px var(--sp-shadow);
33
+ display: flex;
34
+ flex-direction: column;
35
+ overflow: hidden;
36
+ }
37
+
38
+ /* ── mode selector ── */
39
+ .synesthesia-painter-header {
40
+ position: absolute;
41
+ top: 1rem;
42
+ right: 1rem;
43
+ z-index: 30;
44
+ background-color: var(--sp-header-bg);
45
+ backdrop-filter: blur(8px);
46
+ padding: 0.3rem;
47
+ border-radius: 9999px;
48
+ border: 1px solid var(--sp-border);
49
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
50
+ display: flex;
51
+ gap: 0.2rem;
52
+ }
53
+
54
+ .synesthesia-painter-mode-btn {
55
+ padding: 0.4rem 0.875rem;
56
+ border-radius: 9999px;
57
+ font-size: 0.7rem;
58
+ font-weight: 700;
59
+ text-transform: uppercase;
60
+ letter-spacing: 0.06em;
61
+ transition: background 0.2s, color 0.2s;
62
+ color: var(--sp-text-muted);
63
+ background: none;
64
+ border: none;
65
+ cursor: pointer;
66
+ }
67
+
68
+ .synesthesia-painter-mode-btn:hover {
69
+ background-color: var(--sp-bg-muted);
70
+ color: var(--sp-text);
71
+ }
72
+
73
+ .synesthesia-painter-mode-btn.active {
74
+ background-color: var(--sp-primary);
75
+ color: var(--sp-primary-on);
76
+ box-shadow: 0 2px 8px rgba(99, 102, 241, 0.35);
77
+ }
78
+
79
+ /* ── canvas area ── */
80
+ .synesthesia-painter-canvas-wrapper {
81
+ position: relative;
82
+ flex-grow: 1;
83
+ padding: 2rem;
84
+ padding-top: 5rem;
85
+ cursor: text;
86
+ }
87
+
88
+ @media (min-width: 768px) {
89
+ .synesthesia-painter-canvas-wrapper {
90
+ padding: 3rem;
91
+ padding-top: 5rem;
92
+ }
93
+ }
94
+
95
+ .synesthesia-painter-viz-layer {
96
+ word-break: break-all;
97
+ white-space: pre-wrap;
98
+ line-height: 1.625;
99
+ outline: none;
100
+ font-size: 2.5rem;
101
+ min-height: 100%;
102
+ }
103
+
104
+ .synesthesia-painter-input {
105
+ position: absolute;
106
+ inset: 0;
107
+ opacity: 0;
108
+ cursor: text;
109
+ z-index: -10;
110
+ width: 100%;
111
+ height: 100%;
112
+ resize: none;
113
+ border: none;
114
+ background: transparent;
115
+ }
116
+
117
+ .synesthesia-painter-placeholder {
118
+ position: absolute;
119
+ top: 5rem;
120
+ left: 2rem;
121
+ pointer-events: none;
122
+ opacity: 0.35;
123
+ user-select: none;
124
+ }
125
+
126
+ @media (min-width: 768px) {
127
+ .synesthesia-painter-placeholder {
128
+ left: 3rem;
129
+ }
130
+ }
131
+
132
+ .synesthesia-painter-placeholder-text {
133
+ color: var(--sp-text-dim);
134
+ font-size: 2.25rem;
135
+ font-weight: 300;
136
+ font-style: italic;
137
+ }
138
+
139
+ /* ── footer ── */
140
+ .synesthesia-painter-footer {
141
+ background-color: var(--sp-bg-muted);
142
+ border-top: 1px solid var(--sp-border);
143
+ padding: 0.875rem 2rem;
144
+ display: flex;
145
+ align-items: center;
146
+ justify-content: space-between;
147
+ gap: 1rem;
148
+ }
149
+
150
+ .synesthesia-painter-tooltip {
151
+ font-size: 0.75rem;
152
+ color: var(--sp-text-muted);
153
+ font-weight: 500;
154
+ margin: 0;
155
+ }
156
+
157
+ .synesthesia-painter-clear-btn {
158
+ font-size: 0.7rem;
159
+ font-weight: 700;
160
+ color: var(--sp-text-dim);
161
+ text-transform: uppercase;
162
+ letter-spacing: 0.06em;
163
+ background: none;
164
+ border: none;
165
+ cursor: pointer;
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 0.375rem;
169
+ transition: color 0.2s;
170
+ white-space: nowrap;
171
+ }
172
+
173
+ .synesthesia-painter-clear-btn:hover {
174
+ color: #ef4444;
175
+ }
176
+
177
+ /* ── characters ── */
178
+ .synesthesia-painter-char {
179
+ display: inline-block;
180
+ color: var(--char-color);
181
+ transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
182
+ animation: sp-fade-in 0.2s ease-out forwards;
183
+ }
184
+
185
+ @keyframes sp-fade-in {
186
+ from {
187
+ opacity: 0;
188
+ transform: translate-y(4px);
189
+ }
190
+
191
+ to {
192
+ opacity: 1;
193
+ transform: translate-y(0);
194
+ }
195
+ }
196
+
197
+ /* mode: dots */
198
+ .synesthesia-painter-viz-layer.dots .synesthesia-painter-char {
199
+ color: transparent;
200
+ font-size: 0;
201
+ width: 1rem;
202
+ height: 1rem;
203
+ border-radius: 50%;
204
+ background-color: var(--char-color);
205
+ margin: 0.25rem;
206
+ box-shadow: 0 0 8px var(--char-color);
207
+ }
208
+
209
+ /* mode: aura */
210
+ .synesthesia-painter-viz-layer.aura .synesthesia-painter-char {
211
+ color: rgba(255, 255, 255, 0.15);
212
+ text-shadow: 0 0 18px var(--char-color), 0 0 36px var(--char-color);
213
+ }
214
+
215
+ .theme-dark .synesthesia-painter-viz-layer.aura .synesthesia-painter-char {
216
+ color: rgba(255, 255, 255, 0.85);
217
+ text-shadow: 0 0 14px var(--char-color), 0 0 28px var(--char-color), 0 0 56px var(--char-color);
218
+ }
219
+
220
+ /* cursor */
221
+ .synesthesia-painter-cursor {
222
+ display: inline-block;
223
+ width: 2px;
224
+ height: 2.5rem;
225
+ background-color: var(--sp-primary);
226
+ animation: sp-blink 1s step-end infinite;
227
+ vertical-align: text-bottom;
228
+ margin-left: 2px;
229
+ }
230
+
231
+ @keyframes sp-blink {
232
+ 0%, 100% { opacity: 1; }
233
+ 50% { opacity: 0; }
234
+ }
@@ -0,0 +1,18 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { zalgoGenerator, type ZalgoGeneratorLocaleContent } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'es' } = Astro.props;
11
+ const localeContentLoader = (zalgoGenerator.i18n as Record<string, () => Promise<ZalgoGeneratorLocaleContent>>)[locale];
12
+ const content = localeContentLoader ? await localeContentLoader() : null;
13
+ if (!content) return null;
14
+
15
+ const { bibliography } = content;
16
+ ---
17
+
18
+ <SharedBibliography links={bibliography} />
@@ -0,0 +1,195 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+ import './style.css';
4
+ ---
5
+
6
+ <section class="zg-section">
7
+ <div class="zg-grid">
8
+ <div class="zg-left">
9
+ <div class="zg-config-card">
10
+ <div class="zg-config-hover-bg"></div>
11
+ <h3 class="zg-config-title">
12
+ <Icon name="mdi:tune-variant" style="width:1.25rem;height:1.25rem;color:#9333ea" />
13
+ Configuración
14
+ </h3>
15
+
16
+ <div class="zg-config-body">
17
+ <div class="zg-field">
18
+ <div class="zg-field-header">
19
+ <label class="zg-field-label">Intensidad</label>
20
+ <span id="intensity-value" class="zg-intensity-val">50%</span>
21
+ </div>
22
+ <input type="range" id="intensity-slider" min="0.1" max="1.5" step="0.1" value="0.5" class="zg-slider" />
23
+ </div>
24
+
25
+ <div class="zg-toggles">
26
+ <label class="zg-toggle-row">
27
+ <span class="zg-toggle-label">Corrupción Superior</span>
28
+ <div class="zg-switch">
29
+ <input type="checkbox" id="opt-up" checked class="zg-switch-input" />
30
+ <div class="zg-switch-track"></div>
31
+ </div>
32
+ </label>
33
+ <label class="zg-toggle-row">
34
+ <span class="zg-toggle-label">Ruido Central</span>
35
+ <div class="zg-switch">
36
+ <input type="checkbox" id="opt-middle" checked class="zg-switch-input" />
37
+ <div class="zg-switch-track"></div>
38
+ </div>
39
+ </label>
40
+ <label class="zg-toggle-row">
41
+ <span class="zg-toggle-label">Corrupción Inferior</span>
42
+ <div class="zg-switch">
43
+ <input type="checkbox" id="opt-down" checked class="zg-switch-input" />
44
+ <div class="zg-switch-track"></div>
45
+ </div>
46
+ </label>
47
+ </div>
48
+
49
+ <button id="reset-options" class="zg-reset-btn">
50
+ <Icon name="mdi:history" style="width:1rem;height:1rem" />
51
+ Restablecer Valores
52
+ </button>
53
+ </div>
54
+ </div>
55
+
56
+ <div class="zg-warning">
57
+ <p>ADVERTENCIA: El texto Zalgo utiliza caracteres combinados de Unicode que pueden desbordar visualmente su contenedor. Úsese con precaución en redes sociales.</p>
58
+ </div>
59
+ </div>
60
+
61
+ <div class="zg-right">
62
+ <div class="zg-editor-card">
63
+ <div class="zg-editor-bar">
64
+ <div class="zg-editor-bar-left">
65
+ <Icon name="mdi:code-tags" style="width:1rem;height:1rem;color:#9333ea;opacity:0.5" />
66
+ <span class="zg-editor-filename">Zalgo_Editor.sh</span>
67
+ </div>
68
+ </div>
69
+ <textarea
70
+ id="input-text"
71
+ class="zg-textarea"
72
+ placeholder="Escribe aquí el mensaje que deseas corromper..."
73
+ ></textarea>
74
+ <div class="zg-editor-footer">
75
+ <span id="char-count" class="zg-char-count">0 CARACTERES</span>
76
+ <div class="zg-footer-sep"></div>
77
+ <button id="copy-btn" class="zg-copy-btn">
78
+ <Icon name="mdi:content-copy" style="width:1.25rem;height:1.25rem" />
79
+ Copiar Resultado
80
+ </button>
81
+ <button id="clear-btn" class="zg-clear-btn">
82
+ <Icon name="mdi:trash-can-outline" style="width:1.25rem;height:1.25rem" />
83
+ </button>
84
+ </div>
85
+ </div>
86
+
87
+ <div class="zg-preview-wrap">
88
+ <div class="zg-preview-badge">Vista Previa en Tiempo Real</div>
89
+ <div id="fake-ui-body" class="zg-preview-body">
90
+ <div class="zg-preview-dots"></div>
91
+ <div id="zalgo-output" class="zg-output">
92
+ <span class="zg-output-empty">Vacío</span>
93
+ </div>
94
+ <div id="glitch-overlay" class="zg-glitch-overlay">
95
+ <div class="zg-glitch-line zg-glitch-line-1"></div>
96
+ <div class="zg-glitch-line zg-glitch-line-2"></div>
97
+ </div>
98
+ </div>
99
+ <p class="zg-preview-note">Nota: Algunos navegadores pueden limitar el renderizado de caracteres combinados</p>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ </section>
104
+
105
+ <script>
106
+ 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"];
107
+ 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"];
108
+ 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"];
109
+
110
+ function addCombiningChars(result: string, count: number, chars: string[], enabled: boolean): string {
111
+ if (!enabled) return result;
112
+ for (let j = 0; j < count; j++) result += chars[Math.floor(Math.random() * chars.length)];
113
+ return result;
114
+ }
115
+
116
+ function processZalgo(text: string, opts: { up: boolean; middle: boolean; down: boolean; intensity: number }): string {
117
+ let result = "";
118
+ const count = Math.floor(opts.intensity * 30);
119
+ for (let i = 0; i < text.length; i++) {
120
+ result += text[i];
121
+ if (/\s/.test(text[i] ?? '')) continue;
122
+ result = addCombiningChars(result, count, UP, opts.up);
123
+ result = addCombiningChars(result, Math.max(1, Math.floor(count/3)), MID, opts.middle);
124
+ result = addCombiningChars(result, count, DOWN, opts.down);
125
+ }
126
+ return result;
127
+ }
128
+
129
+ const input = document.getElementById("input-text") as HTMLTextAreaElement;
130
+ const output = document.getElementById("zalgo-output") as HTMLDivElement;
131
+ const intensitySlider = document.getElementById("intensity-slider") as HTMLInputElement;
132
+ const intensityValue = document.getElementById("intensity-value");
133
+ const optUp = document.getElementById("opt-up") as HTMLInputElement;
134
+ const optMiddle = document.getElementById("opt-middle") as HTMLInputElement;
135
+ const optDown = document.getElementById("opt-down") as HTMLInputElement;
136
+ const copyBtn = document.getElementById("copy-btn");
137
+ const clearBtn = document.getElementById("clear-btn");
138
+ const charCount = document.getElementById("char-count");
139
+ const resetBtn = document.getElementById("reset-options");
140
+ const glitchOverlay = document.getElementById("glitch-overlay") as HTMLElement;
141
+ const fakeUiBody = document.getElementById("fake-ui-body") as HTMLElement;
142
+
143
+ function update() {
144
+ const text = input.value;
145
+ const intensity = parseFloat(intensitySlider.value);
146
+ if (charCount) charCount.textContent = `${text.length} CARACTERES`;
147
+ if (intensityValue) intensityValue.textContent = `${Math.round(intensity * 100)}%`;
148
+ if (!text.trim()) {
149
+ output.innerHTML = '<span class="zg-output-empty">Vacío</span>';
150
+ glitchOverlay.style.opacity = "0";
151
+ fakeUiBody.style.borderColor = "";
152
+ fakeUiBody.style.boxShadow = "";
153
+ return;
154
+ }
155
+ const zalgoText = processZalgo(text, { up: optUp.checked, middle: optMiddle.checked, down: optDown.checked, intensity });
156
+ output.textContent = zalgoText;
157
+ if (intensity > 1.0) {
158
+ glitchOverlay.style.opacity = "1";
159
+ fakeUiBody.style.borderColor = intensity > 1.3 ? "rgba(147,51,234,0.3)" : "";
160
+ fakeUiBody.style.boxShadow = `inset 0 0 ${intensity * 10}px rgba(147,51,234,0.1)`;
161
+ } else {
162
+ glitchOverlay.style.opacity = "0";
163
+ fakeUiBody.style.borderColor = "";
164
+ fakeUiBody.style.boxShadow = "";
165
+ }
166
+ if (intensity > 1.2) output.style.setProperty("--zalgo-color","#9333ea");
167
+ else output.style.removeProperty("--zalgo-color");
168
+ }
169
+
170
+ input?.addEventListener("input", update);
171
+ intensitySlider?.addEventListener("input", update);
172
+ optUp?.addEventListener("change", update);
173
+ optMiddle?.addEventListener("change", update);
174
+ optDown?.addEventListener("change", update);
175
+
176
+ copyBtn?.addEventListener("click", () => {
177
+ const text = output.textContent || "";
178
+ if (text && text !== "Vacío") {
179
+ navigator.clipboard.writeText(text);
180
+ const orig = copyBtn.innerHTML;
181
+ copyBtn.innerHTML = "¡Copiado!";
182
+ setTimeout(() => { copyBtn.innerHTML = orig; }, 2000);
183
+ }
184
+ });
185
+
186
+ clearBtn?.addEventListener("click", () => { input.value = ""; update(); });
187
+
188
+ resetBtn?.addEventListener("click", () => {
189
+ intensitySlider.value = "0.5";
190
+ optUp.checked = true; optMiddle.checked = true; optDown.checked = true;
191
+ update();
192
+ });
193
+
194
+ update();
195
+ </script>
@@ -0,0 +1,60 @@
1
+ import type { ZalgoGeneratorLocaleContent } from '../index';
2
+
3
+ export const content: ZalgoGeneratorLocaleContent = {
4
+ slug: 'zalgo-generator',
5
+ title: 'Zalgo Generator',
6
+ description: 'Corrupt your messages with cascading overflowing Unicode characters. Adjust intensity and direction of the glitch effect.',
7
+ faqTitle: 'Frequently Asked Questions',
8
+ bibliographyTitle: 'Chaos Bibliography',
9
+ ui: {
10
+ title: 'Zalgo Generator',
11
+ description: 'E̴v̵e̸r̸y̵t̸h̵i̸n̴g̴ ̷i̸s̶ ̶c̶o̷r̵r̷u̷p̶t̸',
12
+ inputPlaceholder: 'Type your normal message here...',
13
+ intensityLabel: 'Corruption Level',
14
+ outputLabel: 'Corrupted Result',
15
+ copyBtn: 'Copy Chaos',
16
+ copied: 'Copied!',
17
+ faqTitle: 'FAQ',
18
+ bibliographyTitle: 'References'
19
+ },
20
+ seo: [
21
+ { type: 'title', text: 'What Is Zalgo Text and How Does Visual Corruption Work?', level: 2 },
22
+ { type: 'paragraph', html: 'Zalgo Text is a form of typographic manipulation that exploits a specific feature of the Unicode standard: <strong>combining characters</strong>. Unlike normal characters, these diacritics take up no horizontal space — they stack vertically on top of the base letter, creating that "digital chaos" or "cosmic horror" aesthetic so popular in internet culture.' },
23
+ { type: 'title', text: 'Anatomy of the Process', level: 3 },
24
+ { type: 'paragraph', html: 'Our generator processes each character independently, injecting random bursts of Unicode code points in three distinct vectors: <strong>upper</strong> (diacritics that stack above), <strong>middle</strong> (which pierce through the letter), and <strong>lower</strong> (hanging below).' },
25
+ { type: 'tip', title: 'Corruption Algorithm', html: 'For each base character, a <code>count = intensity × 30</code> is calculated. That many random diacritics are added in each vector. At intensity 1.5 you can get up to 45 combining characters per letter.' },
26
+ { type: 'title', text: 'Etiquette and Applications', level: 3 },
27
+ { type: 'list', items: [
28
+ '<strong>Social Media:</strong> Grab attention on Instagram or TikTok. Perfect for bios seeking to break with convention.',
29
+ '<strong>Horror Storytelling:</strong> Dramatize fiction narratives, creepypastas, or simulations of compromised systems.',
30
+ '<strong>Accessibility:</strong> Warning — Zalgo text is unreadable by screen readers. Use it only as visual decoration, never for critical content.',
31
+ '<strong>SEO:</strong> Never use Zalgo in core keywords (H1, meta titles). Indexing bots may fail to normalize these characters.',
32
+ ]},
33
+ { type: 'title', text: 'The Origin: From Something Awful to Glitch Art', level: 3 },
34
+ { type: 'paragraph', html: 'Zalgo did not begin as a generator, but as an intervention in classic comic strips. The user Shmorky, in the mid-2000s, began deforming characters like Nancy or Archie, adding stains and distortions. The phrase <strong>"He comes"</strong> sealed the fate of these works, announcing the arrival of an entity that devoured reality.' },
35
+ { type: 'glossary', items: [
36
+ { term: 'Combining Character', definition: 'A Unicode code point designed to be placed on top of, below, or through a base character. Used legitimately in languages like Arabic, Vietnamese, and Hindi.' },
37
+ { term: 'Diacritic', definition: 'A mark added to a base letter to modify its pronunciation or meaning. Zalgo abuses these to create visual overflow.' },
38
+ { term: 'Unicode Block', definition: 'A contiguous range of Unicode code points. Zalgo characters mostly come from the "Combining Diacritical Marks" block (U+0300–U+036F).' },
39
+ { term: 'Glitch Art', definition: 'An aesthetic that intentionally incorporates or simulates errors, artifacts, and corruptions in digital media as an expressive technique.' },
40
+ ]},
41
+ ],
42
+ faq: [
43
+ { question: 'What is Zalgo text?', answer: 'It is a type of text that uses Unicode combining characters (diacritics) excessively. When stacked vertically, these characters "overflow" their original line, creating a visual effect of corruption, disorder, or horror popular in internet culture.' },
44
+ { question: 'Why does Zalgo text look so strange?', answer: 'It exploits a feature of the Unicode standard that allows adding marks above, below, or through a base letter. Since there is no strict limit on how many marks can be added, the text can "invade" lines above or below.' },
45
+ { question: 'Can I use this text on social media?', answer: 'Yes, most modern platforms (Instagram, Twitter, Discord) support Unicode. However, some networks or devices may filter or truncate excess characters at very high intensity to maintain interface readability.' },
46
+ { question: 'How can I remove the Zalgo effect from text?', answer: 'To clean corrupted text, you can use JavaScript string normalization or simply paste it into a basic text editor that only accepts plain text. Our tool is purely creative and does not damage the original content.' },
47
+ ],
48
+ bibliography: [
49
+ { name: 'Unicode Standard - Combining Characters', url: 'https://www.unicode.org/standard/principles.html#Combining_Characters' },
50
+ { name: 'The Zalgo Text Phenomenon', url: 'https://knowyourmeme.com/memes/zalgo' },
51
+ { name: 'MDN - String normalization', url: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize' },
52
+ ],
53
+ howTo: [
54
+ { name: 'Write the base message', text: 'Enter the text you want to "corrupt" in the main text box.' },
55
+ { name: 'Adjust the chaos intensity', text: 'Move the slider to define how many combining characters to stack. Higher intensity = harder to read.' },
56
+ { name: 'Select the overflow direction', text: 'Choose whether corruption should grow upward, downward, or in all directions simultaneously.' },
57
+ { name: 'Copy the result', text: 'Click the copy button. The resulting text includes all the invisible bytes needed to maintain the glitch effect.' },
58
+ ],
59
+ schemas: []
60
+ };