diff --git a/e2e/taller.e2e.ts b/e2e/taller.e2e.ts
index 9442c8e..c2b7faa 100644
--- a/e2e/taller.e2e.ts
+++ b/e2e/taller.e2e.ts
@@ -271,6 +271,87 @@ test.describe('Taller de letras', () => {
await expect(aviso).toContainText('versos');
});
+ /* --- El glosario ------------------------------------------------------- */
+
+ test('los términos del glosario salen marcados en el texto', async ({ page }) => {
+ // Lo que se pidió: «cada vez que aparezca un término del glosario en el
+ // texto, estará marcado para visualizarse con un popup».
+ await page.goto('/taller/son-cubano');
+
+ const marcados = page.locator('.texto .termino');
+ await expect(marcados.first()).toBeVisible();
+
+ // Cada término una sola vez por apartado, no en cada aparición: un texto
+ // con nueve «compás» subrayados no hay quien lo lea.
+ const slugs = await marcados.evaluateAll((bs) =>
+ bs.map((b) => (b as HTMLElement).dataset.termino)
+ );
+ expect(new Set(slugs).size).toBeGreaterThan(1);
+ });
+
+ test('al pulsar un término se abre su definición', async ({ page }) => {
+ await page.goto('/taller/son-cubano');
+
+ const montuno = page.locator('.texto .termino[data-termino="montuno"]').first();
+ await montuno.click();
+
+ const globo = page.getByRole('dialog', { name: /Definición de montuno/ });
+ await expect(globo).toBeVisible();
+ await expect(globo).toContainText('llamada y respuesta');
+
+ // Y se cierra con Escape, devolviendo el foco a donde estaba.
+ await page.keyboard.press('Escape');
+ await expect(globo).toBeHidden();
+ await expect(montuno).toBeFocused();
+ });
+
+ test('el globo no tapa el término que se acaba de pulsar', async ({ page }) => {
+ await page.setViewportSize({ width: 1280, height: 900 });
+ await page.goto('/taller/son-cubano');
+
+ const termino = page.locator('.texto .termino').first();
+ await termino.click();
+
+ const suyo = (await termino.boundingBox())!;
+ const globo = (await page.locator('.globo').boundingBox())!;
+
+ const seSolapan = globo.y < suyo.y + suyo.height && suyo.y < globo.y + globo.height;
+ expect(seSolapan).toBe(false);
+ });
+
+ test('no se marca dentro de un encabezado', async ({ page }) => {
+ // Ensuciaría el índice del capítulo, que sale de los encabezados.
+ await page.goto('/taller/son-cubano');
+ await expect(page.locator('.texto h2 .termino')).toHaveCount(0);
+ });
+
+ test('el glosario tiene su propia página, con las acepciones numeradas', async ({ page }) => {
+ await page.goto('/taller/glosario');
+
+ await expect(page.getByRole('heading', { level: 1 })).toHaveText('Glosario');
+ await expect(page.getByRole('heading', { level: 2, name: 'sinalefa' })).toBeVisible();
+
+ /*
+ * «Clave» tiene dos: patrón rítmico e instrumento. Con una sola
+ * definición por palabra, una de las dos sería falsa, y por eso las
+ * acepciones van numeradas.
+ */
+ const clave = page.locator('#clave');
+ await expect(clave.locator('.acepciones > li')).toHaveCount(2);
+ await expect(clave).toContainText('patrón rítmico');
+ await expect(clave).toContainText('percusión');
+ });
+
+ test('se llega al glosario desde el índice del taller', async ({ page }) => {
+ await page.goto('/taller/bolero');
+ await page
+ .getByRole('navigation', { name: 'Contenido del taller' })
+ .getByRole('link', { name: 'Glosario' })
+ .click();
+
+ await expect(page).toHaveURL('/taller/glosario');
+ });
+
test('el taller está en la navegación principal', async ({ page }) => {
await page.goto('/');
await page
diff --git a/package.json b/package.json
index e1f6cf2..c564d60 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"db:medios": "node --env-file=.env scripts/db/volcar-medios.mjs",
"db:generos": "node --env-file=.env scripts/db/sembrar-generos.mjs",
"db:versos": "node --env-file=.env scripts/db/sembrar-versos.mjs",
+ "db:glosario": "node --env-file=.env scripts/db/sembrar-glosario.mjs",
"db:completar": "node --env-file=.env scripts/db/completar-catalogo.mjs",
"db:comparar": "node --experimental-strip-types --import ./scripts/db/resolver-lib.mjs --env-file=.env scripts/db/comparar-catalogo.mjs"
},
diff --git a/scripts/db/sembrar-glosario.mjs b/scripts/db/sembrar-glosario.mjs
new file mode 100644
index 0000000..79dc49d
--- /dev/null
+++ b/scripts/db/sembrar-glosario.mjs
@@ -0,0 +1,102 @@
+/**
+ * Carga el glosario del taller.
+ *
+ * npm run db:glosario
+ *
+ * Veintiocho términos con sus acepciones numeradas y las formas con las que
+ * aparecen de verdad en el texto. Es lo que hace posible el marcado: sin las
+ * variantes, «sinalefas» en plural no se reconocería y nadie echaría en falta
+ * el globo que no salió.
+ *
+ * Idempotente por slug: reimportar una definición corregida actualiza. Las
+ * acepciones se rehacen enteras —se borran y se vuelven a poner— porque su
+ * número es su identidad y renumerar a mano dejaría huecos.
+ */
+
+import fs from 'node:fs';
+import pg from 'pg';
+
+const SEMILLA = JSON.parse(fs.readFileSync('scripts/db/semilla-glosario.json', 'utf8'));
+
+const slugificar = (t) =>
+ t
+ .toLowerCase()
+ .normalize('NFD')
+ .replace(/[̀-ͯ]/g, '')
+ .replace(/[^a-z0-9]+/g, '-')
+ .replace(/^-+|-+$/g, '');
+
+const c = new pg.Client({ connectionString: process.env.DATABASE_URL, query_timeout: 60_000 });
+await c.connect();
+await c.query('BEGIN');
+
+const contado = { termino: 0, acepcion: 0, variante: 0 };
+const avisos = [];
+
+try {
+ const vistos = new Set();
+
+ for (const t of SEMILLA.terminos) {
+ const slug = slugificar(t.termino);
+ if (vistos.has(slug)) {
+ avisos.push(`«${t.termino}» sale dos veces en la semilla`);
+ continue;
+ }
+ vistos.add(slug);
+
+ const { rows } = await c.query(
+ `INSERT INTO termino (id, slug, termino) VALUES ($1,$2,$3)
+ ON CONFLICT (slug) DO UPDATE SET termino = EXCLUDED.termino
+ RETURNING id`,
+ [crypto.randomUUID(), slug, t.termino]
+ );
+ const terminoId = rows[0].id;
+ contado.termino++;
+
+ await c.query('DELETE FROM acepcion WHERE termino_id = $1', [terminoId]);
+ for (const [i, a] of t.acepciones.entries()) {
+ await c.query(
+ `INSERT INTO acepcion (id, termino_id, numero, categoria, definicion, ampliacion, ejemplo)
+ VALUES ($1,$2,$3,$4,$5,$6,$7)`,
+ [
+ crypto.randomUUID(),
+ terminoId,
+ i + 1,
+ a.categoria,
+ a.definicion,
+ a.ampliacion ?? null,
+ a.ejemplo ?? null
+ ]
+ );
+ contado.acepcion++;
+ }
+
+ /*
+ * El propio término también es una forma buscable, así no hay que
+ * repetirlo en la lista de variantes de los veintiocho.
+ */
+ await c.query('DELETE FROM termino_variante WHERE termino_id = $1', [terminoId]);
+ for (const forma of new Set([t.termino, ...(t.variantes ?? [])])) {
+ await c.query(
+ 'INSERT INTO termino_variante (termino_id, forma) VALUES ($1,$2) ON CONFLICT DO NOTHING',
+ [terminoId, forma.toLowerCase()]
+ );
+ contado.variante++;
+ }
+ }
+
+ await c.query('COMMIT');
+} catch (fallo) {
+ await c.query('ROLLBACK').catch(() => {});
+ console.error('Falló, no se ha escrito nada:');
+ console.error(' ' + fallo.message);
+ await c.end();
+ process.exit(1);
+}
+
+await c.end();
+
+for (const [que, n] of Object.entries(contado)) console.log(` ${que.padEnd(10)} ${n}`);
+for (const aviso of avisos) console.log(` aviso: ${aviso}`);
+console.log('');
+console.log('Glosario cargado.');
diff --git a/scripts/db/semilla-glosario.json b/scripts/db/semilla-glosario.json
new file mode 100644
index 0000000..d889e86
--- /dev/null
+++ b/scripts/db/semilla-glosario.json
@@ -0,0 +1,376 @@
+{
+ "_": [
+ "El glosario del taller.",
+ "",
+ "Las 'variantes' son las formas con las que la palabra aparece de verdad en",
+ "el texto: plurales, femeninos, la forma con tilde y sin ella. El marcado",
+ "busca por ellas, así que una variante que falte es un término que no se",
+ "marca y nadie echa de menos.",
+ "",
+ "Las acepciones van numeradas porque la misma palabra significa cosas",
+ "distintas: 'clave' es el instrumento de percusión, el patrón rítmico y la",
+ "armadura de tonalidad. Con una sola definición, dos de las tres serían",
+ "falsas.",
+ "",
+ "'definicion' es lo que cabe en un globo sin tapar el texto. 'ampliacion' es",
+ "lo que se lee en la página del glosario, y 'ejemplo' un verso donde se vea:",
+ "un término de métrica sin ejemplo no se entiende."
+ ],
+
+ "terminos": [
+ {
+ "termino": "sinalefa",
+ "variantes": ["sinalefas"],
+ "acepciones": [
+ {
+ "categoria": "metrica",
+ "definicion": "Dos vocales que se juntan al cantar y cuentan como una sola sílaba: la última de una palabra y la primera de la siguiente.",
+ "ampliacion": "Es la regla que más cuesta interiorizar y la que más versos arregla. No es opcional: al cantar se produce sola, y contar sin ella da un verso más largo de lo que suena. Cuando molesta —porque junta dos vocales que quieren oírse aparte— se rompe metiendo una consonante o cambiando la palabra.",
+ "ejemplo": "«Dejaste la taza a medias» son ocho sílabas, no nueve: «za·a» va en una."
+ }
+ ]
+ },
+ {
+ "termino": "hiato",
+ "variantes": ["hiatos"],
+ "acepciones": [
+ {
+ "categoria": "metrica",
+ "definicion": "Lo contrario de la sinalefa: dos vocales seguidas que se pronuncian en sílabas separadas.",
+ "ampliacion": "Se usa a propósito cuando hace falta una sílaba más o cuando la sinalefa atropella una palabra que importa. Forzarlo demasiado suena a verso mal medido, no a licencia.",
+ "ejemplo": "«que reías con el otro»: «re·í·as», tres sílabas, porque la tilde rompe el diptongo."
+ }
+ ]
+ },
+ {
+ "termino": "octosílabo",
+ "variantes": ["octosílabos", "octosilabo", "octosilabos"],
+ "acepciones": [
+ {
+ "categoria": "metrica",
+ "definicion": "Verso de ocho sílabas. Es la medida de la copla popular en español y la más usada en la canción.",
+ "ampliacion": "Suena a lengua hablada porque el castellano cae solo en grupos de ocho. Por eso lo usan la ranchera, el son, la cumbia y el bolero clásico: no hay que forzar nada para que quepa una frase entera.",
+ "ejemplo": "«Si me llaman por el nombre»"
+ }
+ ]
+ },
+ {
+ "termino": "endecasílabo",
+ "variantes": ["endecasílabos", "endecasilabo", "endecasilabos"],
+ "acepciones": [
+ {
+ "categoria": "metrica",
+ "definicion": "Verso de once sílabas, con acento obligatorio en la décima. Es el verso culto del español.",
+ "ampliacion": "Da sitio a subordinadas, que es lo que permite tener sintaxis de prosa sin dejar de cantarse. El tango vive de él; el bolero lo alterna con el octosílabo.",
+ "ejemplo": "«Volví a la esquina donde me esperabas»"
+ }
+ ]
+ },
+ {
+ "termino": "heptasílabo",
+ "variantes": ["heptasílabos", "heptasilabo", "heptasilabos"],
+ "acepciones": [
+ {
+ "categoria": "metrica",
+ "definicion": "Verso de siete sílabas. Suele alternar con el endecasílabo para dar respiro.",
+ "ampliacion": "Es medida corta y por eso corta la frase: obliga a decir una cosa por verso. Alternado con versos largos, marca el pulso de una estrofa sin que se note.",
+ "ejemplo": "«y el humo por la casa»"
+ }
+ ]
+ },
+ {
+ "termino": "métrica",
+ "variantes": ["metrica"],
+ "acepciones": [
+ {
+ "categoria": "metrica",
+ "definicion": "La medida de los versos: cuántas sílabas tiene cada uno y cómo se cuentan.",
+ "ampliacion": "No es un adorno académico: la métrica es lo que hace que una letra se pueda cantar sobre una melodía sin atropellar palabras. Un verso de más sobra, y se nota antes en el oído que en el papel.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "escansión",
+ "variantes": ["escansion", "escandir", "escandida", "escandido"],
+ "acepciones": [
+ {
+ "categoria": "metrica",
+ "definicion": "Contar las sílabas de un verso aplicando las reglas: sinalefas, hiatos y la ley del acento final.",
+ "ampliacion": "Escandir es lo que separa medir de contar letras. Se hace con la voz, no con los ojos: si al decirlo en alto la cuenta no sale, la escansión está mal.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "verso agudo",
+ "variantes": ["versos agudos", "aguda", "agudas"],
+ "acepciones": [
+ {
+ "categoria": "metrica",
+ "definicion": "Verso que acaba en palabra aguda. Suma una sílaba a la cuenta.",
+ "ampliacion": "Es la mitad de la ley del acento final, y la que más despista: «yo respondo con el son» tiene siete casillas y es un octosílabo.",
+ "ejemplo": "«yo respondo con el son» — siete sílabas, mide ocho."
+ }
+ ]
+ },
+ {
+ "termino": "rima consonante",
+ "variantes": ["consonante", "rima perfecta"],
+ "acepciones": [
+ {
+ "categoria": "rima",
+ "definicion": "Riman todos los sonidos desde la última sílaba tónica: vocales y consonantes.",
+ "ampliacion": "Es la rima cerrada, la que suena a rima. Pide mucho —hay pocas palabras que rimen así— y por eso empuja a decir lo que rima en vez de lo que se quería decir. Úsala cuando el estilo la exija.",
+ "ejemplo": "techo / lecho, encendido / ruido"
+ }
+ ]
+ },
+ {
+ "termino": "rima asonante",
+ "variantes": ["asonante", "asonantes"],
+ "acepciones": [
+ {
+ "categoria": "rima",
+ "definicion": "Riman solo las vocales desde la última tónica; las consonantes dan igual.",
+ "ampliacion": "Es la rima de la copla popular. Pide mucho menos que la consonante y por eso deja decir lo que hay que decir: en una letra cantada casi nadie nota la diferencia, y el margen que da se agradece en cada verso.",
+ "ejemplo": "Jalisco / dicho — las dos en i-o."
+ }
+ ]
+ },
+ {
+ "termino": "estrofa",
+ "variantes": ["estrofas"],
+ "acepciones": [
+ {
+ "categoria": "estructura",
+ "definicion": "Grupo de versos que funciona como unidad: la parte que cuenta algo antes de volver al estribillo.",
+ "ampliacion": "En una canción la estrofa hace avanzar; el estribillo se queda. Una letra en la que las estrofas no avanzan es una letra que repite el estribillo tres veces con otras palabras.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "estribillo",
+ "variantes": ["estribillos"],
+ "acepciones": [
+ {
+ "categoria": "estructura",
+ "definicion": "La parte que vuelve, igual, cada vez. Es lo que se recuerda y lo que da nombre a la canción.",
+ "ampliacion": "Tiene que poder cantarse sin haber entendido la estrofa. Si necesita contexto para significar algo, no es estribillo: es otra estrofa con la misma melodía.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "puente",
+ "variantes": ["puentes"],
+ "acepciones": [
+ {
+ "categoria": "estructura",
+ "definicion": "La parte que rompe: distinta de la estrofa y del estribillo, y suele aparecer una sola vez.",
+ "ampliacion": "Sirve para que el último estribillo signifique algo distinto del primero. Sin puente, una canción de tres minutos dice lo mismo tres veces.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "colchón",
+ "variantes": ["colchon", "colchones"],
+ "acepciones": [
+ {
+ "categoria": "musica",
+ "definicion": "El fondo sostenido —cuerdas, teclado, coros— sobre el que se apoya la voz sin competir con ella.",
+ "ampliacion": "Al escribir importa porque un colchón admite versos largos y frases alargadas: no hay que dejar hueco. Sobre un acompañamiento marcado, en cambio, cada sílaba cae en un sitio.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "compás",
+ "variantes": ["compas", "compases"],
+ "acepciones": [
+ {
+ "categoria": "musica",
+ "definicion": "Cómo se agrupan los pulsos: de dos en dos, de tres en tres, de cuatro en cuatro.",
+ "ampliacion": "Es lo primero que hay que saber para escribir en un estilo. Un 3/4 pide frases que respiren de tres en tres; un 4/4 marcado admite versos más apretados. Escribir contra el compás se oye enseguida.",
+ "ejemplo": "El vals criollo va en 3/4; el son cubano, en 4/4."
+ }
+ ]
+ },
+ {
+ "termino": "clave",
+ "variantes": ["claves"],
+ "acepciones": [
+ {
+ "categoria": "musica",
+ "definicion": "El patrón rítmico de cinco golpes sobre el que se organiza la música cubana. Va en 3-2 o en 2-3.",
+ "ampliacion": "No es un adorno de la percusión: es la rejilla a la que se ajusta todo lo demás, la letra incluida. Una frase que cae «al revés de la clave» suena mal aunque las sílabas cuadren.",
+ "ejemplo": null
+ },
+ {
+ "categoria": "instrumento",
+ "definicion": "El instrumento de percusión: dos palos de madera que se golpean uno contra otro.",
+ "ampliacion": "Es lo que marca el patrón que lleva su nombre. Suena por encima de todo sin necesitar volumen.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "cinquillo",
+ "variantes": ["cinquillos"],
+ "acepciones": [
+ {
+ "categoria": "musica",
+ "definicion": "Célula rítmica cubana de cinco golpes en un compás. Es el pulso del bolero y del danzón.",
+ "ampliacion": "Condiciona dónde caen los acentos de la letra: los versos se apoyan en él, y por eso un bolero escrito sin tenerlo en cuenta se canta a trompicones.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "montuno",
+ "variantes": ["montunos"],
+ "acepciones": [
+ {
+ "categoria": "estructura",
+ "definicion": "La segunda parte del son, con llamada y respuesta entre el solista y el coro.",
+ "ampliacion": "Es donde la letra deja de contar y empieza a insistir. Las frases se acortan, se repiten y admiten improvisación encima: escribir un montuno es escribir algo que aguante veinte vueltas.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "lunfardo",
+ "variantes": [],
+ "acepciones": [
+ {
+ "categoria": "interpretacion",
+ "definicion": "El habla popular de Buenos Aires y Montevideo. En el tango no es color local: identifica de dónde viene quien habla.",
+ "ampliacion": "Usado de fuera suena a disfraz. Si no es tu lengua, escribe el tango en la tuya: la estructura y la mirada son lo que hacen tango a un tango, no el diccionario.",
+ "ejemplo": "«Dejá el escabio, piba, y escuchá»"
+ }
+ ]
+ },
+ {
+ "termino": "melisma",
+ "variantes": ["melismas", "melismático", "melismatico"],
+ "acepciones": [
+ {
+ "categoria": "interpretacion",
+ "definicion": "Cantar varias notas sobre una sola sílaba, alargándola.",
+ "ampliacion": "Por eso en un bolero cabe menos texto del que parece: cada sílaba se estira. Un verso apretado obliga a atropellar, y el melisma deja de poder hacerse.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "requinto",
+ "variantes": ["requintos"],
+ "acepciones": [
+ {
+ "categoria": "instrumento",
+ "definicion": "Guitarra pequeña afinada más aguda, que hace la introducción y los adornos entre versos del bolero.",
+ "ampliacion": "Es lo que deja huecos entre frase y frase, y por eso una letra de bolero no debe llenarlos: los versos van con aire porque el requinto va a hablar en medio.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "bandoneón",
+ "variantes": ["bandoneon", "bandoneones"],
+ "acepciones": [
+ {
+ "categoria": "instrumento",
+ "definicion": "El fuelle del tango. Su respiración marca el rubato, esa forma de adelantar y retrasar el tiempo.",
+ "ampliacion": "La letra del tango se canta con esa elasticidad, así que admite versos largos sin sonar apretados: el bandoneón espera.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "rubato",
+ "variantes": [],
+ "acepciones": [
+ {
+ "categoria": "interpretacion",
+ "definicion": "Estirar y encoger el tiempo al cantar, sin ceñirse al pulso exacto.",
+ "ampliacion": "Es lo que permite decir un verso como quien habla. También es lo que hace que la métrica importe menos de lo que parece en algunos estilos, y muchísimo en los que no lo admiten.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "copla",
+ "variantes": ["coplas"],
+ "acepciones": [
+ {
+ "categoria": "estructura",
+ "definicion": "Estrofa de cuatro versos octosílabos con rima en los pares. Es la forma popular por excelencia en español.",
+ "ampliacion": "Los impares van libres, y ese es su truco: dejan sitio para decir algo concreto sin pelearse con la rima. Media canción hispanoamericana está escrita en coplas.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "tónica",
+ "variantes": ["tonica", "tónicas", "tonicas", "sílaba tónica", "silaba tonica"],
+ "acepciones": [
+ {
+ "categoria": "metrica",
+ "definicion": "La sílaba en la que cae el acento de una palabra o de un verso.",
+ "ampliacion": "El octosílabo lleva siempre acento en la séptima; el endecasílabo, en la décima. Eso es lo que hace que un verso suene a lo que es, aunque cambien todas las palabras.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "ley del acento final",
+ "variantes": ["acento final"],
+ "acepciones": [
+ {
+ "categoria": "metrica",
+ "definicion": "Un verso se mide hasta su última sílaba tónica más una: el agudo suma una sílaba, el esdrújulo resta una.",
+ "ampliacion": "Es la primera regla que hay que tener automatizada. Sin ella, media escansión sale mal y no se entiende por qué.",
+ "ejemplo": "«yo respondo con el son»: siete casillas, ocho sílabas."
+ }
+ ]
+ },
+ {
+ "termino": "encabalgamiento",
+ "variantes": ["encabalgamientos", "encabalgado", "encabalgada"],
+ "acepciones": [
+ {
+ "categoria": "metrica",
+ "definicion": "Cuando la frase no acaba con el verso y continúa en el siguiente.",
+ "ampliacion": "En una letra cantada es arriesgado: la melodía suele cerrar donde cierra el verso, y una frase que sigue queda partida. Usado a propósito, es lo que da sensación de habla.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "hemistiquio",
+ "variantes": ["hemistiquios"],
+ "acepciones": [
+ {
+ "categoria": "metrica",
+ "definicion": "Cada una de las dos mitades en que una pausa parte un verso largo.",
+ "ampliacion": "En el alejandrino la pausa cae en el centro y las dos mitades se miden aparte, como si fueran dos versos.",
+ "ejemplo": null
+ }
+ ]
+ },
+ {
+ "termino": "voseo",
+ "variantes": [],
+ "acepciones": [
+ {
+ "categoria": "interpretacion",
+ "definicion": "Tratar de «vos» en vez de «tú», con su acento al final del verbo: dejá, escuchá, mirá.",
+ "ampliacion": "Cambia la cuenta del verso, porque convierte en agudas palabras que en el tuteo son llanas. En el tango no es un detalle regional: es la voz.",
+ "ejemplo": "«y escuchá» hace agudo el verso y le suma una sílaba."
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/app.css b/src/app.css
index a102913..85f750c 100644
--- a/src/app.css
+++ b/src/app.css
@@ -777,3 +777,31 @@ a.chip:focus-visible {
transform: none;
}
}
+
+/* --- términos del glosario ------------------------------------------------
+ *
+ * El subrayado punteado es la convención de «esto tiene definición» desde
+ * mucho antes que la web, y se distingue de un enlace de verdad: un enlace
+ * lleva a otra página, esto abre un globo y te deja donde estabas.
+ *
+ * Va aquí y no en un componente porque el botón lo escribe el servidor dentro
+ * del HTML del capítulo, donde el ámbito de Svelte no llega.
+ */
+.termino {
+ border: 0;
+ padding: 0;
+ background: none;
+ font: inherit;
+ color: inherit;
+ cursor: help;
+ text-decoration: underline dotted;
+ text-decoration-color: color-mix(in oklab, var(--c-accent) 60%, transparent);
+ text-underline-offset: 0.2em;
+ text-decoration-thickness: 1px;
+}
+
+.termino:hover,
+.termino:focus-visible {
+ color: var(--c-accent);
+ text-decoration-color: var(--c-accent);
+}
diff --git a/src/lib/components/GlobosDeGlosario.svelte b/src/lib/components/GlobosDeGlosario.svelte
new file mode 100644
index 0000000..7ac368f
--- /dev/null
+++ b/src/lib/components/GlobosDeGlosario.svelte
@@ -0,0 +1,234 @@
+
+
+ e.key === 'Escape' && abierto && cerrar()}
+ onresize={() => abierto && colocar()}
+ onscroll={() => abierto && colocar()}
+/>
+
+{#if abierto}
+
+
+
+
+
{abierto.termino}
+
+
+ {#each abierto.acepciones as acepcion (acepcion.numero)}
+