Con lo que pediste: portada con mapa de Iberoamérica, buscador y un aside que empieza por Inicio, Los principios, Glosario y Letristas antes de la lista de géneros. El mapa es interactivo de verdad: cada país se pinta según lo que tenga —marcado si es cuna de algún género, tenue si solo lo adoptó, apagado si no hay nada—, y al pulsarlo la lista de abajo se parte en «nació aquí» y «también se canta ahí». Esa diferencia es lo que un mapa cuenta mejor que una lista. Los contornos son de `BlankMap-Americas.svg` de Wikimedia, en dominio público, recortados a los veinte países de Iberoamérica en América. De 640 KB a 36 KB comprimidos, y solo en esta ruta: coordenadas a un decimal y fuera los 134 islotes que a este tamaño miden menos de un píxel. Va por script y no pegado a mano, para poder rehacerlo. Cada país lleva además un punto de agarre calculado —el promedio de los vértices de su costa, no el centro de su rectángulo, que en Cuba cae en el mar—: sin él, Puerto Rico mide cuatro píxeles y una isla alargada no hay quien la acierte. «Letristas» es nueva: las mismas personas que salen repartidas en nueve fichas, aquí juntas, con su papel en cada género y sus obras. La página del capítulo pasa a `$lib/components/Capitulo.svelte` y la usan las dos secciones: son setecientas líneas, y copiarlas habría sido dos sitios donde arreglar cada cosa. Las direcciones viejas redirigen con 301 —`/taller/bolero` a `/generos/bolero`— resolviendo contra el catálogo de capítulos, no contra una lista escrita a mano que alguien tenga que ampliar. Un enlace compartido no puede empezar a dar 404 porque hayamos reordenado el menú. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>master
parent
acf8908c5d
commit
d74700f9b0
|
After Width: | Height: | Size: 623 KiB |
@ -0,0 +1,190 @@
|
||||
/**
|
||||
* Genera `src/lib/mapa/iberoamerica.ts` a partir del mapa de Wikimedia.
|
||||
*
|
||||
* node scripts/mapa/generar.mjs [ruta al svg]
|
||||
*
|
||||
* El original es `BlankMap-Americas.svg`, de Lokal_Profil, en dominio público:
|
||||
* https://commons.wikimedia.org/wiki/File:BlankMap-Americas.svg
|
||||
*
|
||||
* De sus cincuenta y cinco países se conservan los veinte de Iberoamérica en
|
||||
* América, y de cada uno todos sus trazados unidos en uno. Dos recortes bajan
|
||||
* el archivo de 640 KB a algo que puede viajar en una página:
|
||||
*
|
||||
* - **Las coordenadas, a un decimal.** El mapa mide dos mil setecientas
|
||||
* unidades de ancho y se dibuja a setecientos píxeles: la décima de unidad no
|
||||
* llega ni a la décima de píxel.
|
||||
* - **Fuera los islotes.** Chile trae ciento cincuenta y cinco trazados, casi
|
||||
* todos rocas del sur que a este tamaño ocupan menos de un píxel. Se
|
||||
* descartan los que no llegan al umbral; las islas que sí se ven —Galápagos,
|
||||
* Chiloé, las Malvinas— lo pasan de sobra.
|
||||
*
|
||||
* Se deja como script y no como un archivo pegado a mano para que se pueda
|
||||
* rehacer: el día que haga falta añadir un país o cambiar el umbral, esto es
|
||||
* una línea, y a mano sería volver a empezar.
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
|
||||
const ORIGEN = process.argv[2] ?? 'scripts/mapa/BlankMap-Americas.svg';
|
||||
const DESTINO = 'src/lib/mapa/iberoamerica.ts';
|
||||
|
||||
/** Los veinte, en el orden en que se leen de norte a sur. */
|
||||
const PAISES = [
|
||||
'mx',
|
||||
'gt',
|
||||
'sv',
|
||||
'hn',
|
||||
'ni',
|
||||
'cr',
|
||||
'pa',
|
||||
'cu',
|
||||
'do',
|
||||
'pr',
|
||||
've',
|
||||
'co',
|
||||
'ec',
|
||||
'pe',
|
||||
'bo',
|
||||
'py',
|
||||
'cl',
|
||||
'ar',
|
||||
'uy',
|
||||
'br'
|
||||
];
|
||||
|
||||
/** Un trazado más pequeño que esto no se ve, y sí se nota en el peso. */
|
||||
const MINIMO = 2;
|
||||
|
||||
const svg = fs.readFileSync(ORIGEN, 'utf8');
|
||||
|
||||
/*
|
||||
* Se recorren las etiquetas con expresión regular y no con un analizador de
|
||||
* XML: hace falta saber en qué grupo va cada trazado, y para eso basta con
|
||||
* llevar la cuenta de los `<g>` abiertos.
|
||||
*/
|
||||
const trozos = Object.fromEntries(PAISES.map((c) => [c, []]));
|
||||
const pila = [];
|
||||
|
||||
for (const [, cierre, etiqueta, atributos, suelta] of svg.matchAll(
|
||||
/<(\/?)(g|path)\b([^>]*?)(\/?)>/g
|
||||
)) {
|
||||
if (etiqueta === 'g') {
|
||||
if (cierre) pila.pop();
|
||||
else pila.push(atributos.match(/id="([^"]+)"/)?.[1] ?? null);
|
||||
continue;
|
||||
}
|
||||
if (cierre) continue;
|
||||
|
||||
const propio = atributos.match(/id="([^"]+)"/)?.[1] ?? null;
|
||||
const codigo = [propio, ...[...pila].reverse()].find((c) => c && PAISES.includes(c));
|
||||
const d = atributos.match(/d="([^"]+)"/)?.[1];
|
||||
if (codigo && d) trozos[codigo].push(d);
|
||||
void suelta;
|
||||
}
|
||||
|
||||
/** El rectángulo que ocupa un trazado, para saber si merece la pena. */
|
||||
function tamano(d) {
|
||||
const numeros = [...d.matchAll(/-?\d+(?:\.\d+)?/g)].map((m) => Number(m[0]));
|
||||
const xs = numeros.filter((_, i) => i % 2 === 0);
|
||||
const ys = numeros.filter((_, i) => i % 2 === 1);
|
||||
if (xs.length === 0) return { ancho: 0, alto: 0, xs, ys };
|
||||
return {
|
||||
ancho: Math.max(...xs) - Math.min(...xs),
|
||||
alto: Math.max(...ys) - Math.min(...ys),
|
||||
xs,
|
||||
ys
|
||||
};
|
||||
}
|
||||
|
||||
const unaDecimal = (d) => d.replace(/-?\d+\.\d+/g, (n) => String(Math.round(Number(n) * 10) / 10));
|
||||
|
||||
const contorno = {};
|
||||
const centro = {};
|
||||
let descartados = 0;
|
||||
|
||||
for (const codigo of PAISES) {
|
||||
const buenos = trozos[codigo].filter((d) => {
|
||||
const { ancho, alto } = tamano(d);
|
||||
const vale = ancho >= MINIMO || alto >= MINIMO;
|
||||
if (!vale) descartados++;
|
||||
return vale;
|
||||
});
|
||||
contorno[codigo] = unaDecimal(buenos.join(' '));
|
||||
|
||||
/*
|
||||
* Un punto donde agarrar el país.
|
||||
*
|
||||
* Es el promedio de los vértices del trazado más grande, no el centro de su
|
||||
* rectángulo: el de Cuba cae en el mar, porque la isla es alargada y curva.
|
||||
* Con el promedio de la costa el punto queda dentro, y además da algo que
|
||||
* pulsar en Puerto Rico o El Salvador, que a esta escala miden cuatro
|
||||
* píxeles.
|
||||
*/
|
||||
const mayor = buenos.reduce(
|
||||
(a, b) => (tamano(b).ancho * tamano(b).alto > tamano(a).ancho * tamano(a).alto ? b : a),
|
||||
buenos[0] ?? ''
|
||||
);
|
||||
const { xs, ys } = tamano(mayor);
|
||||
const medio = (ns) => ns.reduce((t, n) => t + n, 0) / ns.length;
|
||||
centro[codigo] = [medio(xs), medio(ys)].map((n) => Math.round(n * 10) / 10);
|
||||
}
|
||||
|
||||
/* El encuadre sale de lo que queda, no del mapa entero. */
|
||||
const xs = [];
|
||||
const ys = [];
|
||||
for (const d of Object.values(contorno)) {
|
||||
const t = tamano(d);
|
||||
xs.push(...t.xs);
|
||||
ys.push(...t.ys);
|
||||
}
|
||||
|
||||
const MARGEN = 10;
|
||||
const x0 = Math.min(...xs) - MARGEN;
|
||||
const y0 = Math.min(...ys) - MARGEN;
|
||||
const encuadre = [x0, y0, Math.max(...xs) - x0 + MARGEN, Math.max(...ys) - y0 + MARGEN]
|
||||
.map((n) => n.toFixed(1))
|
||||
.join(' ');
|
||||
|
||||
const cuerpo = PAISES.map(
|
||||
(c) =>
|
||||
`\t{ codigo: '${c.toUpperCase()}', centro: [${centro[c].join(', ')}], d: '${contorno[c]}' }`
|
||||
).join(',\n');
|
||||
|
||||
fs.writeFileSync(
|
||||
DESTINO,
|
||||
`/**
|
||||
* El mapa de Iberoamérica, país a país.
|
||||
*
|
||||
* **Generado. No se edita a mano:** \`node scripts/mapa/generar.mjs\`, que
|
||||
* explica de dónde sale y qué se recorta.
|
||||
*
|
||||
* Los contornos son de \`BlankMap-Americas.svg\` de Wikimedia Commons, obra de
|
||||
* Lokal_Profil, en dominio público.
|
||||
*
|
||||
* España y Portugal no están: el original es de las Américas. El día que hagan
|
||||
* falta habrá que traerlos de otro mapa o enseñarlos aparte.
|
||||
*/
|
||||
|
||||
export interface PaisDelMapa {
|
||||
/** ISO 3166-1 alfa-2, como en la tabla \`pais\`. */
|
||||
codigo: string;
|
||||
/** Un punto dentro del país, para colgar de él lo que haya que pulsar. */
|
||||
centro: [number, number];
|
||||
/** El contorno, con todas sus islas en un solo trazado. */
|
||||
d: string;
|
||||
}
|
||||
|
||||
/** El encuadre justo de los veinte países, no el del mapa original. */
|
||||
export const ENCUADRE = '${encuadre}';
|
||||
|
||||
export const PAISES: PaisDelMapa[] = [
|
||||
${cuerpo}
|
||||
];
|
||||
`
|
||||
);
|
||||
|
||||
const kb = (fs.statSync(DESTINO).size / 1024).toFixed(0);
|
||||
console.log(` países ${PAISES.length}`);
|
||||
console.log(` descartados ${descartados} trazados por debajo de ${MINIMO} unidades`);
|
||||
console.log(` encuadre ${encuadre}`);
|
||||
console.log(` tamaño ${kb} KB`);
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,110 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* El índice de la sección de géneros.
|
||||
*
|
||||
* Arriba las cuatro páginas que no son un género —el inicio, los principios
|
||||
* del taller, el glosario y los letristas— y debajo la lista. Van separadas
|
||||
* porque son cosas distintas: las de arriba se visitan una vez y la lista se
|
||||
* recorre.
|
||||
*
|
||||
* «Los principios» apunta a `/taller`, que es otra sección: son el curso, y
|
||||
* las fichas se leen con él al lado.
|
||||
*/
|
||||
import { resolve } from '$app/paths';
|
||||
|
||||
interface Entrada {
|
||||
slug: string;
|
||||
titulo: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
generos: Entrada[];
|
||||
/** Slug del género abierto, si estamos en uno. */
|
||||
actual?: string;
|
||||
}
|
||||
|
||||
let { generos, actual }: Props = $props();
|
||||
|
||||
const paginas = [
|
||||
{ href: resolve('/generos'), texto: 'Inicio', clave: '' },
|
||||
{ href: resolve('/taller'), texto: 'Los principios', clave: 'principios' },
|
||||
{ href: resolve('/generos/glosario'), texto: 'Glosario', clave: 'glosario' },
|
||||
{ href: resolve('/generos/letristas'), texto: 'Letristas', clave: 'letristas' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<nav class="indice" aria-label="Contenido de géneros">
|
||||
<section>
|
||||
<ul>
|
||||
{#each paginas as pagina (pagina.clave)}
|
||||
<li>
|
||||
<a href={pagina.href} aria-current={pagina.clave === actual ? 'page' : undefined}>
|
||||
{pagina.texto}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p class="rotulo" id="lista-generos">Géneros</p>
|
||||
<ul aria-labelledby="lista-generos">
|
||||
{#each generos as genero (genero.slug)}
|
||||
<li>
|
||||
<a
|
||||
href={resolve('/generos/[slug]', { slug: genero.slug })}
|
||||
aria-current={genero.slug === actual ? 'page' : undefined}
|
||||
>
|
||||
{genero.titulo}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.indice {
|
||||
display: grid;
|
||||
gap: var(--sp-5);
|
||||
font-size: var(--fs-sm);
|
||||
}
|
||||
|
||||
.rotulo {
|
||||
margin: 0 0 var(--sp-2);
|
||||
font-size: var(--fs-xs);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: var(--sp-1) 0;
|
||||
line-height: 1.35;
|
||||
color: var(--c-text-muted);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--c-accent);
|
||||
}
|
||||
|
||||
a[aria-current='page'] {
|
||||
color: var(--c-text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* El bloque de arriba se separa de la lista con una línea, no con aire. */
|
||||
section + section {
|
||||
padding-top: var(--sp-4);
|
||||
border-top: 1px solid var(--c-border);
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,181 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* El mapa de Iberoamérica con los géneros de cada país.
|
||||
*
|
||||
* Un país se pinta según lo que tenga: apagado si no hay nada, marcado si es
|
||||
* cuna de algún género, y tenue si solo lo adoptó. Es la información que el
|
||||
* mapa aporta y que una lista no da: dónde nacieron las cosas y por dónde se
|
||||
* extendieron.
|
||||
*
|
||||
* Se puede recorrer con el teclado. Cada país es un `<button>` dentro del
|
||||
* SVG, no un `<path>` con un `onclick`: así entra en el orden de tabulación
|
||||
* y se anuncia con su nombre.
|
||||
*/
|
||||
import { ENCUADRE, PAISES } from '$lib/mapa/iberoamerica';
|
||||
import type { GeneroEnElMapa } from '$lib/server/taller';
|
||||
|
||||
interface Props {
|
||||
generos: GeneroEnElMapa[];
|
||||
/** El país elegido, en ISO alfa-2. Vacío si no hay ninguno. */
|
||||
pais: string;
|
||||
/** Se llama al pulsar un país; el mismo otra vez lo deselecciona. */
|
||||
alElegir: (codigo: string) => void;
|
||||
}
|
||||
|
||||
let { generos, pais, alElegir }: Props = $props();
|
||||
|
||||
/** Lo que tiene cada país, para saber cómo pintarlo y qué anunciar. */
|
||||
const porPais = $derived.by(() => {
|
||||
const mapa: Record<string, { nombre: string; cuna: string[]; adoptados: string[] }> = {};
|
||||
|
||||
for (const genero of generos) {
|
||||
for (const p of genero.paises) {
|
||||
const suyo = (mapa[p.codigo] ??= { nombre: p.nombre, cuna: [], adoptados: [] });
|
||||
if (p.relacion === 'cuna') suyo.cuna.push(genero.nombre);
|
||||
else suyo.adoptados.push(genero.nombre);
|
||||
}
|
||||
}
|
||||
return mapa;
|
||||
});
|
||||
|
||||
/** Lo que lee un lector de pantalla y lo que sale al pasar el ratón. */
|
||||
function resumen(codigo: string): string {
|
||||
const suyo = porPais[codigo];
|
||||
if (!suyo) return '';
|
||||
|
||||
const partes = [];
|
||||
if (suyo.cuna.length > 0) partes.push(`cuna de ${suyo.cuna.join(', ')}`);
|
||||
if (suyo.adoptados.length > 0) partes.push(`también se canta ${suyo.adoptados.join(', ')}`);
|
||||
return `${suyo.nombre}: ${partes.join('; ')}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mapa">
|
||||
<svg viewBox={ENCUADRE} role="group" aria-label="Géneros por país">
|
||||
{#each PAISES as p (p.codigo)}
|
||||
{@const suyo = porPais[p.codigo]}
|
||||
{#if suyo}
|
||||
<!--
|
||||
El contorno pinta; el punto de encima es lo que se pulsa y lo que
|
||||
entra en el orden de tabulación. Hacen falta los dos: Puerto Rico
|
||||
mide cuatro píxeles y Cuba es tan alargada que apuntar a su centro
|
||||
es apuntar al mar.
|
||||
-->
|
||||
<g
|
||||
class="pais"
|
||||
class:cuna={suyo.cuna.length > 0}
|
||||
class:elegido={pais === p.codigo}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-pressed={pais === p.codigo}
|
||||
aria-label={resumen(p.codigo)}
|
||||
onclick={() => alElegir(p.codigo)}
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
alElegir(p.codigo);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<title>{resumen(p.codigo)}</title>
|
||||
<path class="tierra" d={p.d} />
|
||||
<circle class="punto" cx={p.centro[0]} cy={p.centro[1]} r="9" />
|
||||
</g>
|
||||
{:else}
|
||||
<!-- Sin géneros: se dibuja para que el mapa sea un mapa, y no se pulsa. -->
|
||||
<path class="pais vacio" d={p.d} aria-hidden="true" />
|
||||
{/if}
|
||||
{/each}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/*
|
||||
* Iberoamérica es casi tan alta como ancha, así que el límite lo pone el
|
||||
* alto: a todo lo ancho ocupaba la pantalla entera y había que bajar para
|
||||
* ver que debajo hay un buscador.
|
||||
*/
|
||||
.mapa {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: min(100%, 26rem);
|
||||
max-height: 58vh;
|
||||
}
|
||||
|
||||
path {
|
||||
stroke: var(--c-bg);
|
||||
stroke-width: 1.2;
|
||||
transition:
|
||||
fill 0.15s var(--suave),
|
||||
stroke 0.15s var(--suave);
|
||||
}
|
||||
|
||||
/* Un país sin géneros no se pulsa, así que tampoco se ilumina. */
|
||||
.vacio {
|
||||
fill: color-mix(in oklab, var(--c-bg-raised) 55%, transparent);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.pais {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tres estados y no dos: donde nació algo pesa más que donde se adoptó, y
|
||||
* esa diferencia es lo que el mapa cuenta mejor que una lista.
|
||||
*/
|
||||
.pais .tierra {
|
||||
fill: color-mix(in oklab, var(--c-accent) 22%, var(--c-bg-raised));
|
||||
}
|
||||
|
||||
.pais.cuna .tierra {
|
||||
fill: color-mix(in oklab, var(--c-accent) 55%, var(--c-bg-raised));
|
||||
}
|
||||
|
||||
.pais:hover .tierra,
|
||||
.pais:focus-visible .tierra {
|
||||
fill: var(--c-accent);
|
||||
}
|
||||
|
||||
.pais.elegido .tierra {
|
||||
fill: var(--c-accent);
|
||||
stroke: var(--c-text);
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.pais:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* El punto es el área de golpeo y solo se ve cuando hace falta: transparente
|
||||
* en reposo, marcado al pasar por encima, al enfocar con el teclado y en el
|
||||
* país elegido.
|
||||
*/
|
||||
.punto {
|
||||
fill: transparent;
|
||||
stroke: none;
|
||||
transition: fill 0.15s var(--suave);
|
||||
}
|
||||
|
||||
.pais:focus-visible .punto {
|
||||
fill: color-mix(in oklab, var(--c-text) 70%, transparent);
|
||||
}
|
||||
|
||||
.pais.elegido .punto {
|
||||
fill: var(--c-text);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
path,
|
||||
.punto {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,10 @@
|
||||
import { indiceDelTaller } from '$lib/server/taller';
|
||||
|
||||
/**
|
||||
* El índice de géneros vive en el layout, así que se carga una vez y lo tienen
|
||||
* la portada de la sección y cada ficha.
|
||||
*/
|
||||
export async function load() {
|
||||
const { fichas } = await indiceDelTaller();
|
||||
return { fichas };
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* Estructura de la sección de géneros.
|
||||
*
|
||||
* El mismo reparto que el taller —índice a la izquierda y contenido al
|
||||
* lado—, porque son dos mitades de lo mismo: el curso explica cómo se
|
||||
* escribe y las fichas, cómo suena cada estilo. Se separaron en dos
|
||||
* secciones cuando el catálogo de géneros pasó a tener portada propia con
|
||||
* mapa y buscador: como capítulo dentro del taller ya no cabía.
|
||||
*/
|
||||
import { page } from '$app/state';
|
||||
import IndiceDeGeneros from '$lib/components/IndiceDeGeneros.svelte';
|
||||
|
||||
let { data, children } = $props();
|
||||
|
||||
/** La página abierta: el slug de un género, «glosario», «letristas» o nada. */
|
||||
const actual = $derived(page.url.pathname.split('/')[2] ?? '');
|
||||
</script>
|
||||
|
||||
<div class="container generos">
|
||||
<aside class="indice">
|
||||
<IndiceDeGeneros generos={data.fichas} {actual} />
|
||||
</aside>
|
||||
|
||||
<div class="principal">
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.generos {
|
||||
display: grid;
|
||||
grid-template-columns: 13rem minmax(0, 1fr);
|
||||
gap: var(--sp-6);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
/* Se separa de la cabecera pegajosa en vez de deslizarse bajo ella. */
|
||||
.indice {
|
||||
position: sticky;
|
||||
top: calc(var(--altura-cabecera) + var(--sp-5));
|
||||
max-height: calc(100vh - var(--altura-cabecera) - var(--sp-6));
|
||||
overflow-y: auto;
|
||||
padding-top: var(--sp-7);
|
||||
}
|
||||
|
||||
.principal {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
.generos {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sin sitio para la columna, el índice pasa detrás del contenido: saber
|
||||
* qué géneros hay importa igual en un móvil, así que se mueve, no se
|
||||
* esconde.
|
||||
*/
|
||||
.indice {
|
||||
position: static;
|
||||
order: 1;
|
||||
max-height: none;
|
||||
padding-top: var(--sp-5);
|
||||
border-top: 1px solid var(--c-border);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,5 @@
|
||||
import { generosParaLaPortada } from '$lib/server/taller';
|
||||
|
||||
export async function load() {
|
||||
return { generos: await generosParaLaPortada() };
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { obtenerCapitulo } from '$lib/server/taller';
|
||||
|
||||
export async function load({ params }) {
|
||||
const capitulo = await obtenerCapitulo(params.slug);
|
||||
if (!capitulo) error(404, 'Ese género no existe.');
|
||||
|
||||
// Los principios viven en `/taller`; aquí solo las fichas.
|
||||
if (capitulo.tipo !== 'ficha') error(404, 'Ese género no existe.');
|
||||
|
||||
return { capitulo };
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import Capitulo from '$lib/components/Capitulo.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<Capitulo
|
||||
capitulo={data.capitulo}
|
||||
seccion={{ texto: 'Géneros', href: resolve('/generos') }}
|
||||
enlaceA={(slug) => resolve('/generos/[slug]', { slug })}
|
||||
/>
|
||||
@ -0,0 +1,414 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* El glosario.
|
||||
*
|
||||
* Pensado para doscientas entradas, no para las veintinueve de hoy: con una
|
||||
* lista suelta, el día que crezca hay que bajar cinco pantallas para llegar
|
||||
* a la eme. Por eso lleva las tres cosas con las que se consulta un
|
||||
* diccionario —buscar, saltar a una letra, acotar por materia— y va agrupado
|
||||
* por inicial.
|
||||
*
|
||||
* La definición sale en el mismo globo que dentro de los capítulos: se lee
|
||||
* lo mismo en los dos sitios y no hay dos maneras de enseñarlo.
|
||||
*/
|
||||
import { resolve } from '$app/paths';
|
||||
import Meta from '$lib/components/Meta.svelte';
|
||||
import Migas from '$lib/components/Migas.svelte';
|
||||
import GlobosDeGlosario, { MATERIAS } from '$lib/components/GlobosDeGlosario.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
let consulta = $state('');
|
||||
let materia = $state('todas');
|
||||
let lista = $state<HTMLElement | null>(null);
|
||||
|
||||
/** Sin tildes y en minúsculas: quien busca «compas» quiere «compás». */
|
||||
const plano = (t: string) => t.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '');
|
||||
|
||||
/** Las materias que existen de verdad, con cuántos términos tiene cada una. */
|
||||
const materias = $derived.by(() => {
|
||||
// Objeto y no `Map`: es una cuenta local que se tira al salir de aquí, y
|
||||
// un `Map` dentro de estado reactivo obliga a usar el de Svelte.
|
||||
const cuenta: Record<string, number> = {};
|
||||
for (const t of data.glosario) {
|
||||
for (const c of new Set(t.acepciones.map((a) => a.categoria))) {
|
||||
cuenta[c] = (cuenta[c] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
return Object.entries(cuenta)
|
||||
.map(([clave, cuantos]) => ({ clave, nombre: MATERIAS[clave] ?? clave, cuantos }))
|
||||
.sort((a, b) => a.nombre.localeCompare(b.nombre, 'es'));
|
||||
});
|
||||
|
||||
/*
|
||||
* Busca en la palabra y en la definición. Lo segundo importa: media consulta
|
||||
* a un glosario no es «qué significa esto» sino «cómo se llamaba lo de las
|
||||
* dos vocales que se juntan».
|
||||
*/
|
||||
const encontrados = $derived.by(() => {
|
||||
const q = plano(consulta.trim());
|
||||
|
||||
return data.glosario.filter((t) => {
|
||||
if (materia !== 'todas' && !t.acepciones.some((a) => a.categoria === materia)) return false;
|
||||
if (!q) return true;
|
||||
return (
|
||||
plano(t.termino).includes(q) || t.acepciones.some((a) => plano(a.definicion).includes(q))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
/** La inicial con la que se agrupa, sin tilde: «ó» va en la O. */
|
||||
const inicialDe = (termino: string) => plano(termino).charAt(0).toUpperCase();
|
||||
|
||||
const grupos = $derived.by(() => {
|
||||
const porLetra: Record<string, typeof data.glosario> = {};
|
||||
for (const t of encontrados) {
|
||||
const letra = inicialDe(t.termino);
|
||||
porLetra[letra] = [...(porLetra[letra] ?? []), t];
|
||||
}
|
||||
return Object.entries(porLetra).sort(([a], [b]) => a.localeCompare(b, 'es'));
|
||||
});
|
||||
|
||||
/** El abecedario entero: las letras sin términos se ven apagadas, no desaparecen. */
|
||||
const ABECEDARIO = 'ABCDEFGHIJKLMNÑOPQRSTUVWXYZ'.split('');
|
||||
const conTerminos = $derived(new Set(grupos.map(([letra]) => letra)));
|
||||
|
||||
const migas = [{ texto: 'Géneros', href: resolve('/generos') }, { texto: 'Glosario' }];
|
||||
</script>
|
||||
|
||||
<Meta
|
||||
titulo="Glosario del taller"
|
||||
descripcion="Los términos que aparecen al escribir una letra: sinalefa, compás, rima asonante, montuno."
|
||||
/>
|
||||
|
||||
<Migas {migas} />
|
||||
|
||||
<!-- La definición sale en el mismo globo que en los capítulos. -->
|
||||
<GlobosDeGlosario glosario={data.glosario} dentro={lista} ampliada />
|
||||
|
||||
<header class="portada">
|
||||
<p class="rotulo">Taller de letras</p>
|
||||
<h1>Glosario</h1>
|
||||
<p class="entradilla">
|
||||
Las palabras que hacen falta para hablar de una letra. Pulsa una para leer su definición; las
|
||||
mismas salen marcadas dentro de los capítulos.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<!--
|
||||
La barra de consulta, pegada arriba: con la lista larga, tener que subir a
|
||||
buscar es justo lo que hace que se abandone un glosario.
|
||||
-->
|
||||
<div class="consulta">
|
||||
<div class="buscador">
|
||||
<label class="visually-hidden" for="buscar-termino">Buscar un término</label>
|
||||
<input
|
||||
id="buscar-termino"
|
||||
type="search"
|
||||
bind:value={consulta}
|
||||
placeholder="Buscar por palabra o por lo que significa"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<p class="cuenta muted" role="status">
|
||||
{encontrados.length} de {data.glosario.length}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="materias" role="group" aria-label="Filtrar por materia">
|
||||
<button
|
||||
type="button"
|
||||
class="materia"
|
||||
aria-pressed={materia === 'todas'}
|
||||
onclick={() => (materia = 'todas')}
|
||||
>
|
||||
Todas
|
||||
</button>
|
||||
{#each materias as m (m.clave)}
|
||||
<button
|
||||
type="button"
|
||||
class="materia"
|
||||
aria-pressed={materia === m.clave}
|
||||
onclick={() => (materia = m.clave)}
|
||||
>
|
||||
{m.nombre}
|
||||
<span class="cuantos">{m.cuantos}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<nav class="abecedario" aria-label="Saltar a una letra">
|
||||
{#each ABECEDARIO as letra (letra)}
|
||||
{#if conTerminos.has(letra)}
|
||||
<a href="#letra-{letra}">{letra}</a>
|
||||
{:else}
|
||||
<span aria-hidden="true">{letra}</span>
|
||||
{/if}
|
||||
{/each}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{#if grupos.length === 0}
|
||||
<p class="vacio muted">
|
||||
Nada con «{consulta.trim()}». Prueba con una palabra suelta: «rima», «verso», «acento».
|
||||
</p>
|
||||
{:else}
|
||||
<div class="grupos" bind:this={lista}>
|
||||
{#each grupos as [letra, terminos] (letra)}
|
||||
<section class="grupo" id="letra-{letra}">
|
||||
<h2>{letra}</h2>
|
||||
|
||||
<ul class="terminos">
|
||||
{#each terminos as termino (termino.slug)}
|
||||
<li>
|
||||
<button type="button" class="palabra" data-termino={termino.slug}>
|
||||
{termino.termino}
|
||||
<span class="materia-de">
|
||||
{MATERIAS[termino.acepciones[0].categoria] ??
|
||||
termino.acepciones[0].categoria}{#if termino.acepciones.length > 1}<span
|
||||
class="acepciones">· {termino.acepciones.length} acepciones</span
|
||||
>{/if}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
/*
|
||||
* El título y la entradilla se reparten la fila en vez de apilarse.
|
||||
* Apilados y limitados a la medida de lectura dejaban media pantalla en
|
||||
* blanco encima de la barra de búsqueda, que es lo primero que se ve al
|
||||
* entrar.
|
||||
*/
|
||||
.portada {
|
||||
max-width: 62ch;
|
||||
padding-block: var(--sp-6) var(--sp-4);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: var(--fs-3xl);
|
||||
}
|
||||
|
||||
.rotulo {
|
||||
margin: 0 0 var(--sp-2);
|
||||
font-size: var(--fs-xs);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.14em;
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
.entradilla {
|
||||
font-size: var(--fs-lg);
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
/* --- La barra de consulta ----------------------------------------------- */
|
||||
|
||||
.consulta {
|
||||
position: sticky;
|
||||
top: var(--altura-cabecera);
|
||||
z-index: 5;
|
||||
display: grid;
|
||||
gap: var(--sp-3);
|
||||
padding-block: var(--sp-4);
|
||||
margin-bottom: var(--sp-5);
|
||||
background: var(--c-bg);
|
||||
}
|
||||
|
||||
.buscador {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-4);
|
||||
}
|
||||
|
||||
input[type='search'] {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
padding: var(--boton-relleno);
|
||||
border: 1px solid var(--c-border);
|
||||
border-radius: var(--radius-boton);
|
||||
background: var(--c-bg-sunken);
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
font-size: var(--boton-texto);
|
||||
}
|
||||
|
||||
input[type='search']:focus-visible {
|
||||
outline: 2px solid var(--c-accent);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.cuenta {
|
||||
flex: 0 0 auto;
|
||||
margin: 0;
|
||||
font-size: var(--fs-sm);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.materias {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--sp-2);
|
||||
}
|
||||
|
||||
.materia {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: var(--sp-2);
|
||||
padding: 0.25rem 0.7rem;
|
||||
border: 1px solid var(--c-border);
|
||||
border-radius: var(--radius-boton);
|
||||
background: transparent;
|
||||
color: var(--c-text-muted);
|
||||
font: inherit;
|
||||
font-size: var(--fs-xs);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.materia:hover {
|
||||
color: var(--c-text);
|
||||
border-color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
.materia[aria-pressed='true'] {
|
||||
color: var(--c-accent);
|
||||
border-color: color-mix(in oklab, var(--c-accent) 45%, transparent);
|
||||
background: color-mix(in oklab, var(--c-accent) 10%, transparent);
|
||||
}
|
||||
|
||||
.cuantos {
|
||||
font-family: var(--font-mono);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/*
|
||||
* El abecedario entero, con las letras vacías apagadas. Si desaparecieran,
|
||||
* la fila cambiaría de longitud en cada búsqueda y no habría dónde apuntar.
|
||||
*/
|
||||
.abecedario {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.15rem;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--fs-sm);
|
||||
}
|
||||
|
||||
.abecedario a,
|
||||
.abecedario span {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-width: 1.6rem;
|
||||
padding: 0.1rem;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.abecedario a {
|
||||
color: var(--c-accent);
|
||||
}
|
||||
|
||||
.abecedario a:hover {
|
||||
background: color-mix(in oklab, var(--c-accent) 16%, transparent);
|
||||
}
|
||||
|
||||
.abecedario span {
|
||||
color: color-mix(in oklab, var(--c-text-muted) 45%, transparent);
|
||||
}
|
||||
|
||||
/* --- Los grupos ---------------------------------------------------------- */
|
||||
|
||||
.grupos {
|
||||
display: grid;
|
||||
gap: var(--sp-6);
|
||||
}
|
||||
|
||||
.grupo {
|
||||
/* Para que al saltar por el ancla no quede bajo la barra pegajosa. */
|
||||
scroll-margin-top: calc(var(--altura-cabecera) + 9rem);
|
||||
}
|
||||
|
||||
.grupo h2 {
|
||||
margin: 0 0 var(--sp-3);
|
||||
padding-bottom: var(--sp-2);
|
||||
border-bottom: 1px solid var(--c-border);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--fs-lg);
|
||||
color: var(--c-accent);
|
||||
}
|
||||
|
||||
/*
|
||||
* Columnas de texto, no una rejilla de celdas.
|
||||
*
|
||||
* Con rejilla, una letra de un solo término —la be tiene «bandoneón» y nada
|
||||
* más— dejaba tres celdas vacías pintadas del color del borde: media fila de
|
||||
* gris a la derecha de una palabra. Un índice de diccionario fluye: donde no
|
||||
* hay palabras no hay nada que pintar.
|
||||
*/
|
||||
.terminos {
|
||||
columns: 4 14rem;
|
||||
column-gap: var(--sp-6);
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.terminos li {
|
||||
/* Que una entrada no se parta entre el pie de una columna y la siguiente. */
|
||||
break-inside: avoid;
|
||||
margin-bottom: var(--sp-1);
|
||||
}
|
||||
|
||||
/*
|
||||
* La mayúscula es de la presentación, no del dato: el término se guarda en
|
||||
* minúscula porque así se busca y así se compara con el texto al marcarlo.
|
||||
* `::first-letter` y no `capitalize`, que pondría «Rima Asonante».
|
||||
*/
|
||||
.palabra::first-letter {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.palabra {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--sp-2) var(--sp-2) var(--sp-2) 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--fs-lg);
|
||||
line-height: 1.25;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.palabra:hover,
|
||||
.palabra:focus-visible {
|
||||
color: var(--c-accent);
|
||||
}
|
||||
|
||||
/*
|
||||
* La materia, debajo y en minúscula. En versalitas pesaba tanto como la
|
||||
* palabra que acompaña, y aquí lo que se busca con la vista es la palabra.
|
||||
*/
|
||||
.materia-de {
|
||||
display: block;
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--fs-sm);
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
.acepciones {
|
||||
margin-left: 0.35rem;
|
||||
}
|
||||
|
||||
.vacio {
|
||||
padding: var(--sp-6) 0;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,5 @@
|
||||
import { letristas } from '$lib/server/taller';
|
||||
|
||||
export async function load() {
|
||||
return { letristas: await letristas() };
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,189 +0,0 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* El glosario entero.
|
||||
*
|
||||
* Los globos del texto sirven para no perder el hilo mientras se lee; esta
|
||||
* página es para lo otro: mirar un término a propósito, o recorrerlos para
|
||||
* ver qué vocabulario hace falta. Por eso aquí sí va la ampliación, que en
|
||||
* un globo no cabría sin tapar el texto.
|
||||
*/
|
||||
import { resolve } from '$app/paths';
|
||||
import Meta from '$lib/components/Meta.svelte';
|
||||
import Migas from '$lib/components/Migas.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
const CATEGORIAS: Record<string, string> = {
|
||||
metrica: 'Métrica',
|
||||
rima: 'Rima',
|
||||
musica: 'Música',
|
||||
estructura: 'Estructura',
|
||||
interpretacion: 'Interpretación',
|
||||
instrumento: 'Instrumento'
|
||||
};
|
||||
|
||||
const migas = [{ texto: 'Taller de letras', href: resolve('/taller') }, { texto: 'Glosario' }];
|
||||
</script>
|
||||
|
||||
<Meta
|
||||
titulo="Glosario del taller"
|
||||
descripcion="Los términos que aparecen al escribir una letra: sinalefa, compás, rima asonante, montuno."
|
||||
/>
|
||||
|
||||
<Migas {migas} />
|
||||
|
||||
<header class="portada">
|
||||
<p class="rotulo">Taller de letras</p>
|
||||
<h1>Glosario</h1>
|
||||
<p class="entradilla">
|
||||
Las palabras que hacen falta para hablar de una letra. Salen marcadas en el texto de los
|
||||
capítulos: al pulsarlas se abre su definición sin salir de donde estabas.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<ul class="terminos">
|
||||
{#each data.glosario as termino (termino.slug)}
|
||||
<li id={termino.slug}>
|
||||
<h2>{termino.termino}</h2>
|
||||
|
||||
<ol class="acepciones" class:una={termino.acepciones.length === 1}>
|
||||
{#each termino.acepciones as acepcion (acepcion.numero)}
|
||||
<li>
|
||||
<p class="categoria">{CATEGORIAS[acepcion.categoria] ?? acepcion.categoria}</p>
|
||||
<p class="definicion">{acepcion.definicion}</p>
|
||||
{#if acepcion.ampliacion}
|
||||
<p class="ampliacion muted">{acepcion.ampliacion}</p>
|
||||
{/if}
|
||||
{#if acepcion.ejemplo}
|
||||
<p class="ejemplo">{acepcion.ejemplo}</p>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<style>
|
||||
.portada {
|
||||
max-width: 62ch;
|
||||
margin-bottom: var(--sp-6);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: var(--fs-3xl);
|
||||
}
|
||||
|
||||
.rotulo {
|
||||
margin: 0 0 var(--sp-2);
|
||||
font-size: var(--fs-xs);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.14em;
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
.entradilla {
|
||||
font-size: var(--fs-lg);
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dos columnas cuando caben. Un glosario es una lista de piezas cortas e
|
||||
* independientes: en una sola columna a lo alto obliga a bajar el doble para
|
||||
* ver lo mismo.
|
||||
*/
|
||||
.terminos {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(24rem, 100%), 1fr));
|
||||
gap: var(--sp-4);
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.terminos > li {
|
||||
padding: var(--sp-4);
|
||||
background: var(--c-bg-raised);
|
||||
border: 1px solid var(--c-border);
|
||||
border-radius: var(--radius-tarjeta);
|
||||
/* Para que al llegar por el ancla no quede bajo la cabecera pegajosa. */
|
||||
scroll-margin-top: calc(var(--altura-cabecera) + var(--sp-4));
|
||||
}
|
||||
|
||||
.terminos h2 {
|
||||
margin: 0 0 var(--sp-3);
|
||||
padding-bottom: var(--sp-2);
|
||||
border-bottom: 1px solid var(--c-border);
|
||||
font-size: var(--fs-lg);
|
||||
}
|
||||
|
||||
.acepciones {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: var(--sp-4);
|
||||
counter-reset: acepcion;
|
||||
}
|
||||
|
||||
/* El número solo si hay más de una: con una sola, un «1» sugiere que falta algo. */
|
||||
.acepciones:not(.una) > li::before {
|
||||
counter-increment: acepcion;
|
||||
content: counter(acepcion) '.';
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--c-accent);
|
||||
}
|
||||
|
||||
.acepciones > li {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
gap: 0 var(--sp-2);
|
||||
}
|
||||
|
||||
.acepciones.una > li {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.acepciones > li::before {
|
||||
grid-row: 1 / -1;
|
||||
}
|
||||
|
||||
.acepciones p {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.acepciones.una p {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.categoria {
|
||||
margin: 0 0 var(--sp-1);
|
||||
font-size: var(--fs-xs);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
|
||||
.definicion {
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.ampliacion {
|
||||
margin: var(--sp-2) 0 0;
|
||||
font-size: var(--fs-sm);
|
||||
}
|
||||
|
||||
.ejemplo {
|
||||
margin: var(--sp-2) 0 0;
|
||||
padding-left: var(--sp-3);
|
||||
border-left: 2px solid var(--c-border);
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-size: var(--fs-sm);
|
||||
color: var(--c-text-muted);
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in new issue