@jjlmoya/utils-creative 1.22.0 → 1.23.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 +2 -2
- package/src/tests/seo_parity.test.ts +60 -0
- package/src/tests/translation_copy.test.ts +124 -0
- package/src/tool/bead-pattern-generator/i18n/es.ts +1 -8
- package/src/tool/dice-roller/i18n/es.ts +6 -8
- package/src/tool/excuse-generator/i18n/es.ts +0 -6
- package/src/tool/fortune-cookie/i18n/es.ts +0 -5
- package/src/tool/synesthesia-painter/i18n/es.ts +3 -5
- package/src/tool/zalgo-generator/i18n/es.ts +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jjlmoya/utils-creative",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"check": "astro check",
|
|
29
29
|
"type-check": "astro check",
|
|
30
30
|
"test": "vitest run",
|
|
31
|
-
"preversion": "npm run lint && npm run test",
|
|
31
|
+
"preversion": "npm run lint && npm run test && npm run build",
|
|
32
32
|
"postversion": "git push && git push --tags",
|
|
33
33
|
"patch": "npm version patch",
|
|
34
34
|
"minor": "npm version minor",
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ALL_ENTRIES } from '../entries';
|
|
3
|
+
import type { KnownLocale } from '../types';
|
|
4
|
+
|
|
5
|
+
interface ExpectedCounts {
|
|
6
|
+
seo: number;
|
|
7
|
+
faq: number;
|
|
8
|
+
howTo: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function countItems(arr: unknown[] | undefined): number {
|
|
12
|
+
return arr?.length ?? 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function verifyLocaleParity(
|
|
16
|
+
entry: typeof ALL_ENTRIES[number],
|
|
17
|
+
loc: KnownLocale,
|
|
18
|
+
expected: ExpectedCounts,
|
|
19
|
+
): Promise<void> {
|
|
20
|
+
const locContent = await entry.i18n[loc]?.();
|
|
21
|
+
expect(locContent, `Locale ${loc} missing content`).toBeDefined();
|
|
22
|
+
|
|
23
|
+
const locSeoCount = countItems(locContent?.seo);
|
|
24
|
+
const locFaqCount = countItems(locContent?.faq);
|
|
25
|
+
const locHowToCount = countItems(locContent?.howTo);
|
|
26
|
+
|
|
27
|
+
expect(
|
|
28
|
+
locSeoCount,
|
|
29
|
+
`Locale ${loc} SEO sections count (${locSeoCount}) must match EN (${expected.seo})`,
|
|
30
|
+
).toBe(expected.seo);
|
|
31
|
+
expect(
|
|
32
|
+
locFaqCount,
|
|
33
|
+
`Locale ${loc} FAQ items count (${locFaqCount}) must match EN (${expected.faq})`,
|
|
34
|
+
).toBe(expected.faq);
|
|
35
|
+
expect(
|
|
36
|
+
locHowToCount,
|
|
37
|
+
`Locale ${loc} HowTo steps count (${locHowToCount}) must match EN (${expected.howTo})`,
|
|
38
|
+
).toBe(expected.howTo);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('SEO & i18n Structural Parity Suite', () => {
|
|
42
|
+
ALL_ENTRIES.forEach((entry) => {
|
|
43
|
+
describe(`Tool: ${entry.id}`, () => {
|
|
44
|
+
it('all 15 locales should have identical SEO section counts and types as English', async () => {
|
|
45
|
+
const enContent = await entry.i18n.en?.();
|
|
46
|
+
expect(enContent).toBeDefined();
|
|
47
|
+
const expected: ExpectedCounts = {
|
|
48
|
+
seo: countItems(enContent?.seo),
|
|
49
|
+
faq: countItems(enContent?.faq),
|
|
50
|
+
howTo: countItems(enContent?.howTo),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const locales = Object.keys(entry.i18n) as KnownLocale[];
|
|
54
|
+
for (const loc of locales) {
|
|
55
|
+
await verifyLocaleParity(entry, loc, expected);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ALL_ENTRIES } from '../entries';
|
|
3
|
+
|
|
4
|
+
const COPY_THRESHOLD = 0.9;
|
|
5
|
+
|
|
6
|
+
const STRUCTURAL_KEYS = new Set([
|
|
7
|
+
'@context',
|
|
8
|
+
'@type',
|
|
9
|
+
'applicationCategory',
|
|
10
|
+
'columns',
|
|
11
|
+
'highlight',
|
|
12
|
+
'icon',
|
|
13
|
+
'level',
|
|
14
|
+
'operatingSystem',
|
|
15
|
+
'position',
|
|
16
|
+
'positive',
|
|
17
|
+
'price',
|
|
18
|
+
'priceCurrency',
|
|
19
|
+
'slug',
|
|
20
|
+
'trend',
|
|
21
|
+
'type',
|
|
22
|
+
'url',
|
|
23
|
+
'value',
|
|
24
|
+
'variant',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
function normalizeText(value: string): string {
|
|
28
|
+
return value
|
|
29
|
+
.replace(/<[^>]*>/g, ' ')
|
|
30
|
+
.replace(/&(?:amp|lt|gt|quot|apos|nbsp);/gi, ' ')
|
|
31
|
+
.replace(/[\u2018\u2019]/g, "'")
|
|
32
|
+
.replace(/[\u201c\u201d]/g, '"')
|
|
33
|
+
.replace(/\s+/g, ' ')
|
|
34
|
+
.trim()
|
|
35
|
+
.toLocaleLowerCase();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function collectText(value: unknown, path: string, parts: string[]): void {
|
|
39
|
+
if (typeof value === 'string') {
|
|
40
|
+
const normalized = normalizeText(value);
|
|
41
|
+
if (normalized.length >= 2) parts.push(normalized);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
value.forEach((item, index) => collectText(item, `${path}[${index}]`, parts));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!value || typeof value !== 'object') return;
|
|
51
|
+
|
|
52
|
+
Object.entries(value).forEach(([key, child]) => {
|
|
53
|
+
if (STRUCTURAL_KEYS.has(key)) return;
|
|
54
|
+
collectText(child, `${path}.${key}`, parts);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function localeCorpus(content: unknown): string {
|
|
59
|
+
if (!content || typeof content !== 'object') return '';
|
|
60
|
+
|
|
61
|
+
const record = content as Record<string, unknown>;
|
|
62
|
+
const parts: string[] = [];
|
|
63
|
+
collectText(record.title, 'title', parts);
|
|
64
|
+
collectText(record.description, 'description', parts);
|
|
65
|
+
collectText(record.faqTitle, 'faqTitle', parts);
|
|
66
|
+
collectText(record.faq, 'faq', parts);
|
|
67
|
+
collectText(record.seo, 'seo', parts);
|
|
68
|
+
collectText(record.schemas, 'schemas', parts);
|
|
69
|
+
return parts.join(' ');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function tokenCounts(text: string): Map<string, number> {
|
|
73
|
+
const counts = new Map<string, number>();
|
|
74
|
+
for (const token of text.match(/[\p{L}\p{N}]+/gu) ?? []) {
|
|
75
|
+
counts.set(token, (counts.get(token) ?? 0) + 1);
|
|
76
|
+
}
|
|
77
|
+
return counts;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function copySimilarity(left: string, right: string): number {
|
|
81
|
+
const leftCounts = tokenCounts(left);
|
|
82
|
+
const rightCounts = tokenCounts(right);
|
|
83
|
+
const leftTotal = [...leftCounts.values()].reduce((sum, count) => sum + count, 0);
|
|
84
|
+
const rightTotal = [...rightCounts.values()].reduce((sum, count) => sum + count, 0);
|
|
85
|
+
if (leftTotal === 0 || rightTotal === 0) return 0;
|
|
86
|
+
|
|
87
|
+
let shared = 0;
|
|
88
|
+
for (const [token, count] of leftCounts) {
|
|
89
|
+
shared += Math.min(count, rightCounts.get(token) ?? 0);
|
|
90
|
+
}
|
|
91
|
+
return (2 * shared) / (leftTotal + rightTotal);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
describe('Locales must not copy another locale wholesale', () => {
|
|
95
|
+
ALL_ENTRIES.forEach((entry) => {
|
|
96
|
+
it(`${entry.id} is not at least ${COPY_THRESHOLD * 100}% identical to another locale`, async () => {
|
|
97
|
+
const corpora = new Map<string, string>();
|
|
98
|
+
|
|
99
|
+
for (const [locale, loader] of Object.entries(entry.i18n)) {
|
|
100
|
+
if (!loader) continue;
|
|
101
|
+
corpora.set(locale, localeCorpus(await loader()));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const locales = [...corpora.keys()];
|
|
105
|
+
const violations: string[] = [];
|
|
106
|
+
|
|
107
|
+
for (let leftIndex = 0; leftIndex < locales.length; leftIndex += 1) {
|
|
108
|
+
for (let rightIndex = leftIndex + 1; rightIndex < locales.length; rightIndex += 1) {
|
|
109
|
+
const left = locales[leftIndex];
|
|
110
|
+
const right = locales[rightIndex];
|
|
111
|
+
const similarity = copySimilarity(corpora.get(left) ?? '', corpora.get(right) ?? '');
|
|
112
|
+
|
|
113
|
+
if (similarity >= COPY_THRESHOLD) {
|
|
114
|
+
violations.push(`${left} ↔ ${right}: ${(similarity * 100).toFixed(1)}%`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
expect(violations, `Locale copy threshold exceeded in ${entry.id}`).toEqual([]);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
|
|
@@ -96,20 +96,13 @@ export const content: BeadPatternGeneratorLocaleContent = {
|
|
|
96
96
|
'<strong>Mosaicos:</strong> Murales de gran escala usando teselas cerámicas simplificadas.',
|
|
97
97
|
'<strong>Retro Gaming:</strong> Assets y sprites con estética 8-bit auténtica en segundos.',
|
|
98
98
|
]},
|
|
99
|
-
{ type: 'paragraph', html: 'En una era de pantallas efímeras, crear algo físico es un acto revolucionario. Esta herramienta no busca automatizar el arte, sino <strong>empoderar al artesano</strong>. Te damos la precisión computacional para que tus manos puedan construir legados duraderos.' },
|
|
100
99
|
{ type: 'stats', items: [
|
|
101
100
|
{ value: '10-100', label: 'Ancho del patrón (cuentas)', icon: 'mdi:arrow-expand-horizontal' },
|
|
102
101
|
{ value: '2-32', label: 'Colores en la paleta', icon: 'mdi:palette-swatch' },
|
|
103
102
|
{ value: 'K-Means', label: 'Algoritmo de cuantización', icon: 'mdi:function-variant' },
|
|
104
103
|
{ value: 'ZIP', label: 'Exportación (patrón + guía)', icon: 'mdi:zip-box' },
|
|
105
104
|
], columns: 4 },
|
|
106
|
-
{ type: '
|
|
107
|
-
{ term: 'Miyuki Delica', definition: 'Cuentas de cristal japonesas de alta precisión, cilíndricas y uniformes. Ideales para joyería y patrones geométricos complejos.' },
|
|
108
|
-
{ term: 'Hama Beads', definition: 'Cuentas de plástico que se fusionan con calor (plancha). Perfectas para proyectos de pixel art de gran formato.' },
|
|
109
|
-
{ term: 'Cuantización de color', definition: 'Proceso matemático para reducir los miles de colores de una imagen a una paleta limitada, manteniendo la fidelidad visual máxima posible.' },
|
|
110
|
-
{ term: 'Visión de túnel', definition: 'Modo de trabajo fila a fila que oscurece el resto del patrón para ayudar a concentrarse en la fila activa sin errores.' },
|
|
111
|
-
{ term: 'K-Means Clustering', definition: 'Algoritmo de aprendizaje no supervisado que agrupa píxeles por similitud cromática para encontrar los colores "centroide" de la imagen.' },
|
|
112
|
-
]},
|
|
105
|
+
{ type: 'paragraph', html: 'En una era de pantallas efímeras, crear algo físico es un acto revolucionario. Esta herramienta no busca automatizar el arte, sino <strong>empoderar al artesano</strong>. Te damos la precisión computacional para que tus manos puedan construir legados duraderos.' },
|
|
113
106
|
],
|
|
114
107
|
faq,
|
|
115
108
|
bibliography,
|
|
@@ -107,20 +107,18 @@ export const content: DiceRollerLocaleContent = {
|
|
|
107
107
|
'<strong>d20 - Icosaedro:</strong> El rey del rol. Veinte caras triangulares. Protagonista del sistema d20 de D&D: determina éxito o fracaso de casi todas las acciones.',
|
|
108
108
|
'<strong>d100 - Percentil:</strong> Dos d10 combinados (decenas + unidades). Define probabilidades finas en sistemas como Call of Cthulhu o Warhammer Fantasy.',
|
|
109
109
|
]},
|
|
110
|
-
{ type: 'title', text: 'Probabilidades y curvas de distribución', level: 3 },
|
|
111
|
-
{ type: 'paragraph', html: 'Cuando lanzas un único dado, obtienes una <strong>distribución uniforme discreta</strong>: cada resultado tiene exactamente la misma probabilidad (1/n). Pero en cuanto combinas múltiples dados, la magia matemática transforma esa curva plana en una <strong>distribución normal aproximada</strong>. Por eso 2d6 no es lo mismo que 1d12: con dos dados de seis la probabilidad de obtener 7 es 6/36 ≈ 16,7%, mientras que los extremos (2 y 12) solo ocurren el 2,8% del tiempo.' },
|
|
112
|
-
{ type: 'tip', title: 'Ventaja y Desventaja (sistema D&D 5e)', html: 'El mecanismo de <strong>Ventaja</strong> consiste en lanzar 2d20 y quedarse con el mayor resultado. Matemáticamente, esto sube la media de 10,5 a aproximadamente 13,8. La <strong>Desventaja</strong> hace lo contrario: toma el menor, bajando la media a unos 7,2. Nuestro tirador te permite simular esto añadiendo dos d20 a la bolsa y comparando los resultados individualmente.' },
|
|
113
|
-
{ type: 'title', text: 'Modificadores: el puente entre dados y personaje', level: 3 },
|
|
114
|
-
{ type: 'paragraph', html: 'En la mayoría de sistemas de rol, el dado no actúa solo: se suma o resta un <strong>modificador</strong> que representa las habilidades del personaje. En D&D 5e, un modificador de Fuerza +5 significa que lanzas 1d20, sumas 5, y compruebas el resultado contra la Clase de Armadura del enemigo. Nuestro tirador incorpora un control de modificador para reflejar fielmente estas mecánicas.' },
|
|
115
|
-
{ type: 'title', text: 'Integridad aleatoria: ¿son justos los dados digitales?', level: 3 },
|
|
116
|
-
{ type: 'paragraph', html: 'El método <code>Math.random()</code> de JavaScript utiliza un algoritmo <strong>xorshift128+</strong> (en los motores V8 modernos), que produce valores pseudoaleatorios con un período de 2¹²⁸. Para los propósitos del juego de mesa y del rol, esta calidad estadística es completamente suficiente. De hecho, supera en uniformidad a un dado físico mal equilibrado. La crítica principal es que no es criptográficamente seguro (no deberías usar <code>Math.random()</code> para claves de seguridad), pero para determinar si tu Ladrón roba con éxito es más que adecuado.' },
|
|
117
|
-
{ type: 'tip', title: 'Bolsa de dados y notación estándar', html: 'La notación <strong>XdY+Z</strong> es el estándar de facto en juegos de rol: X dados de Y caras con modificador Z. "3d6+2" significa lanzar tres dados de seis y sumar 2 al total. Nuestra herramienta utiliza esta notación en el historial para que puedas compartir o anotar tus tiradas fácilmente.' },
|
|
118
110
|
{ type: 'stats', items: [
|
|
119
111
|
{ value: '5000 a.C.', label: 'Primeros dados (astrágalos)', icon: 'mdi:history' },
|
|
120
112
|
{ value: '1974', label: 'D&D popularizó el set', icon: 'mdi:sword' },
|
|
121
113
|
{ value: '7 tipos', label: 'Dados estándar de rol', icon: 'mdi:dice-multiple' },
|
|
122
114
|
{ value: '2¹²⁸', label: 'Período del aleatorio JS', icon: 'mdi:function-variant' },
|
|
123
115
|
], columns: 4 },
|
|
116
|
+
{ type: 'title', text: 'Probabilidades y curvas de distribución', level: 3 },
|
|
117
|
+
{ type: 'paragraph', html: 'Cuando lanzas un único dado, obtienes una <strong>distribución uniforme discreta</strong>: cada resultado tiene exactamente la misma probabilidad (1/n). Pero en cuanto combinas múltiples dados, la magia matemática transforma esa curva plana en una <strong>distribución normal aproximada</strong>. Por eso 2d6 no es lo mismo que 1d12: con dos dados de seis la probabilidad de obtener 7 es 6/36 ≈ 16,7%, mientras que los extremos (2 y 12) solo ocurren el 2,8% del tiempo.' },
|
|
118
|
+
{ type: 'tip', title: 'Ventaja y Desventaja (sistema D&D 5e)', html: 'El mecanismo de <strong>Ventaja</strong> consiste en lanzar 2d20 y quedarse con el mayor resultado. Matemáticamente, esto sube la media de 10,5 a aproximadamente 13,8. La <strong>Desventaja</strong> hace lo contrario: toma el menor, bajando la media a unos 7,2. Nuestro tirador te permite simular esto añadiendo dos d20 a la bolsa y comparando los resultados individualmente.' },
|
|
119
|
+
{ type: 'title', text: 'Modificadores: el puente entre dados y personaje', level: 3 },
|
|
120
|
+
{ type: 'paragraph', html: 'En la mayoría de sistemas de rol, el dado no actúa solo: se suma o resta un <strong>modificador</strong> que representa las habilidades del personaje. En D&D 5e, un modificador de Fuerza +5 significa que lanzas 1d20, sumas 5, y compruebas el resultado contra la Clase de Armadura del enemigo. Nuestro tirador incorpora un control de modificador para reflejar fielmente estas mecánicas.' },
|
|
121
|
+
{ type: 'tip', title: 'Bolsa de dados y notación estándar', html: 'La notación <strong>XdY+Z</strong> es el estándar de facto en juegos de rol: X dados de Y caras con modificador Z. "3d6+2" significa lanzar tres dados de seis y sumar 2 al total. Nuestra herramienta utiliza esta notación en el historial para que puedas compartir o anotar tus tiradas fácilmente.' },
|
|
124
122
|
{ type: 'glossary', items: [
|
|
125
123
|
{ term: 'Crítico', definition: 'Obtener el valor máximo en un dado (ej. sacar 20 en un d20). Normalmente activa efectos especiales de bonificación en la mayoría de sistemas.' },
|
|
126
124
|
{ term: 'Pifia / Nat 1', definition: 'Sacar el valor mínimo (1) en un dado. A menudo resulta en un fallo dramático o consecuencia negativa.' },
|
|
@@ -105,12 +105,6 @@ export const content: ExcuseGeneratorLocaleContent = {
|
|
|
105
105
|
'<strong>Revolución Industrial:</strong> "La máquina de vapor se ha sobrecalentado".',
|
|
106
106
|
'<strong>Era Digital:</strong> "Se me ha caído internet justo en medio de una actualización".'
|
|
107
107
|
]},
|
|
108
|
-
{ type: 'stats', items: [
|
|
109
|
-
{ value: '20', label: 'Sujetos posibles', icon: 'mdi:account-multiple' },
|
|
110
|
-
{ value: '20', label: 'Acciones disponibles', icon: 'mdi:lightning-bolt' },
|
|
111
|
-
{ value: '20', label: 'Desenlaces distintos', icon: 'mdi:door-open' },
|
|
112
|
-
{ value: '8.000', label: 'Combinaciones posibles', icon: 'mdi:shuffle-variant' },
|
|
113
|
-
], columns: 4 },
|
|
114
108
|
{ type: 'proscons', items: [
|
|
115
109
|
{ pro: 'Alivia la presión social al instante', con: 'El uso excesivo erosiona la confianza' },
|
|
116
110
|
{ pro: 'Protege tu energía y tus límites personales', con: 'Puede generar culpa si se usa sin cuidado' },
|
|
@@ -117,11 +117,6 @@ export const content: FortuneCookieLocaleContent = {
|
|
|
117
117
|
{ value: '25', label: 'Fortunas en este oráculo', icon: 'mdi:star-shooting' },
|
|
118
118
|
{ value: '1/día', label: 'Un destino diario', icon: 'mdi:calendar-today' },
|
|
119
119
|
], columns: 4 },
|
|
120
|
-
{ type: 'proscons', items: [
|
|
121
|
-
{ pro: 'Momento de pausa y reflexión diaria', con: 'Sin base científica predictiva real' },
|
|
122
|
-
{ pro: 'Inspiración positiva con frases atemporales', con: 'El mensaje es el mismo sin importar el contexto' },
|
|
123
|
-
{ pro: 'Completamente privado, sin datos enviados al servidor', con: 'Solo una fortuna al día (¡diseño intencionado!)' },
|
|
124
|
-
]},
|
|
125
120
|
],
|
|
126
121
|
faq,
|
|
127
122
|
bibliography,
|
|
@@ -101,17 +101,15 @@ export const content: SynesthesiaPainterLocaleContent = {
|
|
|
101
101
|
'<strong>Constancia:</strong> A diferencia de las asociaciones aprendidas, los colores sinestésicos son extraordinariamente estables a lo largo del tiempo. Estudios de seguimiento a 10 años demuestran más del 90% de consistencia en las asociaciones grafema-color.',
|
|
102
102
|
'<strong>Sinestésicos famosos:</strong> Vladimir Nabokov, Wassily Kandinsky, Nikola Tesla y Billy Joel han descrito públicamente experiencias sinestésicas que influyeron en sus obras.',
|
|
103
103
|
]},
|
|
104
|
-
{ type: 'title', text: 'Arte y sinestesia: cuando los sentidos se fusionan', level: 3 },
|
|
105
|
-
{ type: 'paragraph', html: 'La sinestesia ha tenido una influencia profunda en el arte del siglo XX. <strong>Wassily Kandinsky</strong>, fundador del expresionismo abstracto, experimentaba tanto sinestesia grafema-color como música-color: escuchaba los instrumentos en colores (el amarillo era un trompetín, el azul profundo un chelo) y usó estas percepciones para crear su teoría del arte abstracto. Su obra <em>Composición IV</em> es inseparable de esta experiencia sensorial. En música, <strong>Alexander Scriabin</strong> compuso el <em>Prometheus: El Poema del Fuego</em> con una parte para "tastiera per luce" (teclado de luz), diseñada para proyectar los colores correspondientes a cada nota.' },
|
|
106
|
-
{ type: 'tip', title: 'Paleta cromática de esta herramienta', html: 'La asignación de colores está inspirada en los datos estadísticos más comunes de la literatura científica y en la tradición artística de la sinestesia. <strong>A → rojo</strong>, <strong>E → verde</strong>, <strong>I → blanco/negro según el fondo</strong>, <strong>O → negro/blanco</strong>, <strong>U → ámbar</strong>. Las consonantes siguen patrones menos uniformes, pero siempre se prioriza el contraste con el fondo para garantizar legibilidad.' },
|
|
107
|
-
{ type: 'title', text: 'Aplicaciones prácticas: diseño, música y terapia', level: 3 },
|
|
108
|
-
{ type: 'paragraph', html: 'Más allá de la curiosidad científica, la sinestesia grafema-color tiene aplicaciones concretas. En <strong>diseño tipográfico</strong>, entender estas asociaciones ayuda a elegir paletas de color para logotipos que "resuenen" con el nombre de la marca. En <strong>educación</strong>, se han desarrollado sistemas de aprendizaje del abecedario que usan los colores estadísticamente reportados para facilitar la memorización en niños pequeños. En <strong>musicoterapia</strong>, las visualizaciones sinestésicas se usan para ayudar a pacientes con afasia a reconectar con el lenguaje a través del canal visual-cromático.' },
|
|
109
104
|
{ type: 'stats', items: [
|
|
110
105
|
{ value: '4-8%', label: 'Población con sinestesia', icon: 'mdi:brain' },
|
|
111
106
|
{ value: '+90%', label: 'Consistencia de color a 10 años', icon: 'mdi:check-circle' },
|
|
112
107
|
{ value: '3-6×', label: 'Más frecuente en mujeres', icon: 'mdi:gender-female' },
|
|
113
108
|
{ value: '26+10', label: 'Letras y dígitos coloreados', icon: 'mdi:alphabetical' },
|
|
114
109
|
], columns: 4 },
|
|
110
|
+
{ type: 'title', text: 'Arte y sinestesia: cuando los sentidos se fusionan', level: 3 },
|
|
111
|
+
{ type: 'paragraph', html: 'La sinestesia ha tenido una influencia profunda en el arte del siglo XX. <strong>Wassily Kandinsky</strong>, fundador del expresionismo abstracto, experimentaba tanto sinestesia grafema-color como música-color: escuchaba los instrumentos en colores (el amarillo era un trompetín, el azul profundo un chelo) y usó estas percepciones para crear su teoría del arte abstracto. Su obra <em>Composición IV</em> es inseparable de esta experiencia sensorial. En música, <strong>Alexander Scriabin</strong> compuso el <em>Prometheus: El Poema del Fuego</em> con una parte para "tastiera per luce" (teclado de luz), diseñada para proyectar los colores correspondientes a cada nota.' },
|
|
112
|
+
{ type: 'tip', title: 'Paleta cromática de esta herramienta', html: 'La asignación de colores está inspirada en los datos estadísticos más comunes de la literatura científica y en la tradición artística de la sinestesia. <strong>A → rojo</strong>, <strong>E → verde</strong>, <strong>I → blanco/negro según el fondo</strong>, <strong>O → negro/blanco</strong>, <strong>U → ámbar</strong>. Las consonantes siguen patrones menos uniformes, pero siempre se prioriza el contraste con el fondo para garantizar legibilidad.' },
|
|
115
113
|
],
|
|
116
114
|
faq,
|
|
117
115
|
bibliography,
|
|
@@ -95,13 +95,6 @@ export const content: ZalgoGeneratorLocaleContent = {
|
|
|
95
95
|
]},
|
|
96
96
|
{ type: 'title', text: 'El Origen: De Something Awful al Glitch Art', level: 3 },
|
|
97
97
|
{ type: 'paragraph', html: 'Zalgo no nació como un generador, sino como una intervención en tiras cómicas clásicas. El usuario Shmorky, a mediados de los 2000, empezó a deformar personajes como Nancy o Archie, añadiendo manchas y distorsiones. La frase <strong>"He comes"</strong> sellaba el destino de estas obras, anunciando la llegada de una entidad que devoraba la realidad.' },
|
|
98
|
-
{ type: 'paragraph', html: 'La transición al texto puro fue un descubrimiento técnico fascinante. Al identificar los caracteres combinados destinados a lenguas con diacríticos complejos, los usuarios de foros aprendieron que podían "apuñalar" las palabras originales, creando esa sensación de sangrado tipográfico tan icónica.' },
|
|
99
|
-
{ type: 'stats', items: [
|
|
100
|
-
{ value: '50', label: 'Diacríticos superiores disponibles', icon: 'mdi:arrow-up-bold' },
|
|
101
|
-
{ value: '23', label: 'Modificadores centrales', icon: 'mdi:minus' },
|
|
102
|
-
{ value: '40', label: 'Diacríticos inferiores disponibles', icon: 'mdi:arrow-down-bold' },
|
|
103
|
-
{ value: '×30', label: 'Multiplicador de intensidad máxima', icon: 'mdi:lightning-bolt' },
|
|
104
|
-
], columns: 4 },
|
|
105
98
|
{ type: 'glossary', items: [
|
|
106
99
|
{ term: 'Carácter combinado', definition: 'Punto de código Unicode diseñado para colocarse encima, debajo o a través de un carácter base. Se usa legítimamente en árabe, vietnamita e hindi.' },
|
|
107
100
|
{ term: 'Diacrítico', definition: 'Marca añadida a una letra base para modificar su pronunciación o significado. Zalgo abusa de ellos para crear desbordamiento visual.' },
|