@ -2,15 +2,16 @@
/**
* Un capítulo del taller: un principio del curso o la ficha de un género.
*
* La ficha ya no lleva barra lateral. Los datos del género —compás, metro,
* tempo, rima— van arriba y a todo lo ancho, en una franja comparable de un
* vistazo, y la prosa debajo con su medida de lectura. Antes eran una lista
* de definiciones apretada en una columna estrecha a la derecha: los datos
* quedaban en letra pequeña y el texto, escorado.
* Es la primera página del sitio que lee de PostgreSQL y no de
* `src/content/**`, y por eso enseña lo que en un archivo no cabía: los
* países con su relación —cuna o adopción—, la instrumentación con el peso
* de cada instrumento, los letristas, las obras de referencia y el linaje.
*
* Cuando el género tenga sus propias tablas —instrumentos, letristas, obras
* de referencia, linaje—, cada uno entra como bloque a lo ancho debajo de la
* prosa, en el mismo sitio donde hoy están la temática y los rasgos.
* La ficha no lleva barra lateral de datos: los cuatro comparables van
* arriba en una franja a todo lo ancho, la prosa debajo con su medida de
* lectura y su índice al lado, y lo demás en bloques anchos. Un texto no
* puede llenar novecientos píxeles; lo que cubre ese ancho es lo que va a su
* lado y lo que va debajo.
*/
import { resolve } from '$app/paths';
import Meta from '$lib/components/Meta.svelte';
@ -20,25 +21,90 @@
const capitulo = $derived(data.capitulo);
const ficha = $derived(capitulo.ficha);
const Cuerpo = $derived(data.Cuerpo);
const esFicha = $derived(capitulo.tipo === 'ficha');
/*
* Cada lista sale del script y no del template. Con `{ #if ficha } ` alrededor
* de veinte bloques, cualquier cambio dentro obliga a razonar otra vez si el
* compilador sigue sabiendo que no es nula; así el template solo pregunta si
* hay algo que pintar.
*/
const paises = $derived(ficha?.paises ?? []);
const alias = $derived(ficha?.alias ?? []);
const secciones = $derived(ficha?.secciones ?? []);
const instrumentos = $derived(ficha?.instrumentos ?? []);
const personas = $derived(ficha?.personas ?? []);
const obras = $derived(ficha?.obras ?? []);
const rasgos = $derived(ficha?.rasgos ?? []);
const temas = $derived(ficha?.temas ?? []);
const vieneDe = $derived(ficha?.linaje.filter((l) => l.direccion === 'de') ?? []);
const vieneDeEl = $derived(ficha?.linaje.filter((l) => l.direccion === 'hacia') ?? []);
/**
* La franja de datos: solo lo corto y comparable.
* La franja: solo lo corto y comparable.
*
* `estructura` se queda fuera a propósito —es una frase entera, «A · B ·
* interludio · A», y en una celda de dato queda apretada—; va debajo, con su
* sitio.
* La estructura de las canciones se queda fuera —es una frase entera y en
* una celda queda apretada—; entra como sección, con su sitio.
*/
const franja = $derived(
[
['Compás', ficha.compas],
['Metro', ficha.metrica],
['Tempo', ficha.tempo],
['Rima', ficha.rima]
].filter((fila): fila is [string, string] => Boolean(fila[1]))
(
[
['Compás', ficha?.compasNota],
['Metro', ficha?.metricaNota],
['Tempo', ficha?.tempo],
['Rima', ficha?.rima]
] as [string, string | null | undefined][]
).filter((fila): fila is [string, string] => Boolean(fila[1]))
);
/**
* El año de origen, solo si no está ya en el lugar.
*
* Muchos lugares vienen fechados de los archivos —«Santiago de Cuba, 1883»—
* y la semilla puso además el año en su columna, que es donde sirve para
* ordenar. Sin esta comprobación salía «Santiago de Cuba, 1883, 1883».
*/
const anoAparte = $derived(
ficha?.origenAno & & !ficha.origenLugar?.includes(String(ficha.origenAno))
? ficha.origenAno
: null
);
/**
* Los papeles se guardan como código y se leen como palabra. En la base son
* un vocabulario cerrado —por eso «interprete» va sin tilde—; el acento es
* cosa de la pantalla.
*/
const PAPEL: Record< string , string > = {
fundador: 'fundador',
letrista: 'letrista',
compositor: 'compositor',
interprete: 'intérprete'
};
/**
* «Argentina · 1901– 1951», «Cuba», «n. 1957», o nada si no se sabe nada.
*
* Se arma aquí y no en la plantilla porque Svelte recorta los espacios en los
* bordes de un bloque: el separador escrito como ` · ` dentro de un `{ #if } `
* salía pegado a las dos palabras.
*/
function procedencia(p: {
pais: string | null;
nacimiento: number | null;
defuncion: number | null;
}): string {
const vida =
p.nacimiento & & p.defuncion
? `${ p . nacimiento } – ${ p . defuncion } `
: p.nacimiento
? `n. ${ p . nacimiento } `
: p.defuncion
? `m. ${ p . defuncion } `
: '';
return [p.pais, vida].filter(Boolean).join(' · ');
}
const migas = $derived([
{ texto : 'Taller de letras' , href : resolve ( '/taller' ) } ,
{ texto : esFicha ? 'Fichas de creación' : 'Los principios' }
@ -56,16 +122,37 @@
< h1 > { capitulo . titulo } </ h1 >
{ #if alias . length > 0 }
< p class = "alias" > o { alias . map (( a ) => `«$ { a . nombre } » `).join(', ') } </ p >
{ /if }
{ #if capitulo . resumen }
< p class = "entradilla" > { capitulo . resumen } </ p >
{ /if }
{ #if esFicha && ficha . origen }
< p class = "origen muted" > { ficha . origen } </ p >
{ #if ficha ? . origenLugar }
< p class = "origen muted" >
{ ficha . origenLugar }{ #if anoAparte } , { anoAparte }{ /if }
< / p >
{ /if }
<!--
La cuna se distingue del país que lo adoptó. Es lo que pedía «los estilos
deberían estar clasificados por países»: no una etiqueta, una relación con
su matiz.
-->
{ #if paises . length > 0 }
< ul class = "paises" >
{ #each paises as pais ( pais . codigo )}
< li class:cuna = { pais . relacion === 'cuna' } >
{ pais . nombre }{ #if pais . relacion === 'cuna' } < span class = "relacion" > cuna</ span > { /if }
< / li >
{ /each }
< / ul >
{ /if }
< / header >
{ #if esFicha && franja . length > 0 }
{ #if franja. length > 0 }
< div class = "datos" >
{ #each franja as [ etiqueta , valor ] ( etiqueta )}
< div class = "dato" >
@ -77,26 +164,28 @@
{ /if }
< div class = "lectura" >
<!--
El cuerpo llega compuesto del servidor. Es contenido propio y de un solo
autor; cuando exista la administración habrá que sanearlo, y el sitio para
hacerlo es `$lib/server/markdown`, en un punto y no en cada plantilla.
-->
< article class = "texto" >
{ #if Cuerpo }
< Cuerpo / >
{ /if }
<!-- eslint - disable - next - line svelte/no - at - html - tags -->
{ @html capitulo . html }
< / article >
<!--
Los apartados del capítulo, al lado del texto.
No es la lista de datos de antes —esa subió a la franja—: es el índice de lo
que se está leyendo, el mismo recurso que llevan los textos legales. Y
cubre el ancho que la prosa no puede cubrir sola: para llenar novecientos
píxeles con texto haría falta letra de veinticuatro puntos, y a menos de
eso salen ciento veinte caracteres por línea, que no hay quien siga.
Los apartados del capítulo, al lado del texto. Cubren el ancho que la prosa
no puede cubrir sola: para llenar novecientos píxeles con texto haría falta
letra de veinticuatro puntos, y a menos de eso salen ciento veinte
caracteres por línea, que no hay quien siga.
-->
{ #if data . apartados . length > 1 }
{ #if capitulo . apartados . length > 1 }
< aside class = "apartados" >
< nav aria-label = "Apartados de este capítulo" >
< p class = "rotulo" id = "lista-apartados" > En este capítulo< / p >
< ul aria-labelledby = "lista-apartados" >
{ #each data . apartados as apartado ( apartado . ancla )}
{ #each capitulo . apartados as apartado ( apartado . ancla )}
< li >< a href = "# { apartado . ancla } " > { apartado . texto } </ a ></ li >
{ /each }
< / ul >
@ -105,52 +194,181 @@
{ /if }
< / div >
{ #if esFicha }
{ #if ficha . estructura }
< section class = "bloque" >
< h2 > Estructura de las canciones< / h2 >
< p class = "estructura" > { ficha . estructura } </ p >
< / section >
{ /if }
{ #each secciones as seccion ( seccion . clave )}
< section class = "bloque" >
< h2 > { seccion . titulo } </ h2 >
<!-- eslint - disable - next - line svelte/no - at - html - tags -->
< div class = "texto" > { @html seccion . html } </ div >
< / section >
{ /each }
{ #if instrumentos . length > 0 }
< section class = "bloque" >
< h2 > Instrumentación< / h2 >
< ul class = "instrumentos" >
{ #each instrumentos as i ( i . slug )}
< li >
< span class = "nombre" > { i . nombre } </ span >
< span class = "peso { i . importancia } " > { i . importancia } </ span >
< span class = "papel muted" >
{ i . papel ?? i . familia }
{ #if i . afinacion } < span class = "afinacion" > Afinación: { i . afinacion } </ span > { /if }
< / span >
< / li >
{ /each }
< / ul >
< / section >
{ /if }
{ #if ficha . temas . length > 0 }
< section class = "bloque" >
< h2 > De qué se habla< / h2 >
< ul class = "temas" >
{ #each ficha . temas as tema ( tema )}
< li > { tema } </ li >
{ /each }
< / ul >
< / section >
{ /if }
{ #if personas . length > 0 }
< section class = "bloque" >
< h2 > Quién lo escribió< / h2 >
< ul class = "personas" >
{ #each personas as p ( p . slug + p . papel )}
< li >
< span class = "nombre" > { p . nombre } </ span >
< span class = "vida muted" > { procedencia ( p )} </ span >
< span class = "papel muted" > { PAPEL [ p . papel ] ?? p . papel } </ span >
< / li >
{ /each }
< / ul >
< / section >
{ /if }
{ #if ficha . rasgos . length > 0 }
< section class = "bloque" >
< h2 > Rasgos de escritura< / h2 >
< ul class = "rasgos" >
{ #each ficha . rasgos as rasgo ( rasgo )}
< li > { rasgo } </ li >
{ /each }
< / ul >
< / section >
{ /if }
{ #if obras . length > 0 }
< section class = "bloque" >
< h2 > Obras de referencia< / h2 >
< ul class = "obras" >
{ #each obras as obra ( obra . titulo + obra . ano )}
< li >
< span class = "ano" > { obra . ano ?? '—' } </ span >
< div >
< p class = "titulo" > { obra . titulo } </ p >
{ #if obra . autores . length > 0 }
< p class = "autor muted" > { obra . autores . join ( ' · ' )} </ p >
{ /if }
{ #if obra . cita }
< p class = "cita" > { obra . cita } </ p >
{ /if }
{ #if obra . nota }
< p class = "nota muted" > { obra . nota } </ p >
{ /if }
< / div >
< / li >
{ /each }
< / ul >
<!--
Dos versos de una letra ajena son cita; la letra entera es otra cosa.
Decirlo aquí, donde están, es lo que hace que se cumpla.
-->
< p class = "aviso muted" >
Los fragmentos se reproducen al amparo del derecho de cita, para ilustrar el estilo. Las
letras completas son de sus autores y no se alojan aquí.
< / p >
< / section >
{ /if }
{ #if vieneDe . length > 0 || vieneDeEl . length > 0 }
< section class = "bloque" >
< h2 > Linaje< / h2 >
< div class = "linaje" >
{ #if vieneDe . length > 0 }
< div class = "rama" >
< p class = "rotulo" > De dónde viene< / p >
< ul >
{ #each vieneDe as rama ( rama . slug + rama . tipo )}
< li class:fusiona = { rama . tipo === 'fusiona' } >
{ #if rama . capitulo }
< a href = { resolve ( '/taller/[slug]' , { slug : rama.capitulo })} > { rama . nombre } </a >
{ : else }
< span class = "nombre" > { rama . nombre } </ span >
{ /if }
< span class = "como" > { rama . tipo } </ span >
{ #if rama . nota } < span class = "porque muted" > { rama . nota } </ span > { /if }
< / li >
{ /each }
< / ul >
< / div >
{ /if }
{ #if vieneDeEl . length > 0 }
< div class = "rama" >
< p class = "rotulo" > Qué viene de él< / p >
< ul >
{ #each vieneDeEl as rama ( rama . slug + rama . tipo )}
< li class:fusiona = { rama . tipo === 'fusiona' } >
{ #if rama . capitulo }
< a href = { resolve ( '/taller/[slug]' , { slug : rama.capitulo })} > { rama . nombre } </a >
{ : else }
< span class = "nombre" > { rama . nombre } </ span >
{ /if }
< span class = "como" > { rama . tipo } </ span >
{ #if rama . nota } < span class = "porque muted" > { rama . nota } </ span > { /if }
< / li >
{ /each }
< / ul >
< / div >
{ /if }
< / div >
< / section >
{ /if }
<!--
La temática y los rasgos no comparten forma. La temática son etiquetas
disfrazadas de frase —«El amor no correspondido»— y se leen en rejilla; los
rasgos son frases de verdad y se leen en lista.
-->
{ #if temas . length > 0 }
< section class = "bloque" >
< h2 > De qué se habla< / h2 >
< ul class = "temas" >
{ #each temas as tema ( tema . slug )}
< li >
< a href = { resolve ( '/etiquetas/[etiqueta]' , { etiqueta : tema.slug })} > { tema . nombre } </a >
< / li >
{ /each }
< / ul >
< / section >
{ /if }
{ #if rasgos . length > 0 }
< section class = "bloque" >
< h2 > Rasgos de escritura< / h2 >
< ul class = "rasgos" >
{ #each rasgos as rasgo ( rasgo )}
< li > { rasgo } </ li >
{ /each }
< / ul >
< / section >
{ /if }
<!--
Que se sepa lo que está sin verificar. La ficha la escribió un modelo y se
publica con el nombre del titular; callarlo sería lo cómodo.
-->
{ #if ficha ? . sinRevisar }
< p class = "sin-revisar" >
Los datos de esta ficha —instrumentación, letristas, obras de referencia y linaje— los reunió un
modelo de lenguaje y < strong > están pendientes de revisión< / strong > : las fechas y las
atribuciones no se han comprobado una a una.
< / p >
{ /if }
{ #if data . anterior || data . siguiente }
{ #if capitulo. anterior || capitulo . siguiente }
< nav class = "vecinos" aria-label = "Otros capítulos" >
{ #if data . anterior }
< a class = "vecino" href = { resolve ( '/taller/[slug]' , { slug : data.anterior.slug })} >
{ #if capitulo . anterior }
< a class = "vecino" href = { resolve ( '/taller/[slug]' , { slug : capitulo .anterior.slug })} >
< span class = "muted" > Anterior< / span >
< span > { data . anterior . titulo } </ span >
< span > { capitulo . anterior . titulo } </ span >
< / a >
{ : else }
< span > < / span >
{ /if }
{ #if data . siguiente }
< a class = "vecino derecha" href = { resolve ( '/taller/[slug]' , { slug : data.siguiente.slug })} >
{ #if capitulo . siguiente }
< a class = "vecino derecha" href = { resolve ( '/taller/[slug]' , { slug : capitulo .siguiente.slug })} >
< span class = "muted" > Siguiente< / span >
< span > { data . siguiente . titulo } </ span >
< span > { capitulo . siguiente . titulo } </ span >
< / a >
{ /if }
< / nav >
@ -173,6 +391,14 @@
font-size: var(--fs-3xl);
}
.alias {
margin: var(--sp-1) 0 0;
font-family: var(--font-display);
font-style: italic;
font-size: var(--fs-lg);
color: var(--c-text-muted);
}
.entradilla {
font-size: var(--fs-lg);
color: var(--c-text-muted);
@ -194,6 +420,38 @@
letter-spacing: 0.1em;
}
.paises {
display: flex;
flex-wrap: wrap;
gap: var(--sp-2);
list-style: none;
margin: var(--sp-4) 0 0;
padding: 0;
}
.paises li {
display: flex;
align-items: center;
gap: var(--sp-2);
font-size: var(--fs-xs);
padding: 0.2rem 0.6rem;
border: 1px solid var(--c-border);
border-radius: var(--radius-boton);
color: var(--c-text-muted);
}
.paises li.cuna {
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);
}
.relacion {
text-transform: uppercase;
letter-spacing: 0.1em;
opacity: 0.75;
}
/* --- La franja de datos ---------------------------------------------- */
/*
@ -234,8 +492,7 @@
* Las dos columnas miden lo suyo —el texto su medida de lectura, el índice lo
* que necesita— y lo que sobra se va al corredor. Con anchos fijos quedaban
* ciento veinte píxeles muertos al final de la fila, que es exactamente el
* hueco que se ve como un fallo. Así el bloque llega hasta donde llega la
* franja de datos de arriba.
* hueco que se ve como un fallo.
*/
.lectura {
display: grid;
@ -247,6 +504,7 @@
.texto {
min-width: 0;
max-width: 62ch;
}
.apartados {
@ -324,17 +582,194 @@
border-bottom: 1px solid var(--c-border);
}
.estructura {
font-family: var(--font-display);
font-size: var(--fs-lg);
max-width: 62ch;
/* Las tres listas comparten caja: filas separadas por una línea de un píxel. */
.instrumentos,
.personas,
.obras {
display: grid;
gap: 1px;
background: var(--c-border);
border: 1px solid var(--c-border);
border-radius: var(--radius-tarjeta);
overflow: hidden;
list-style: none;
margin: 0;
padding: 0;
}
.instrumentos li,
.personas li,
.obras li {
background: var(--c-bg-raised);
}
.instrumentos li {
display: grid;
grid-template-columns: 12rem 7rem minmax(0, 1fr);
gap: var(--sp-4);
align-items: center;
padding: var(--sp-3) var(--sp-4);
}
.instrumentos .nombre,
.personas .nombre {
font-weight: 500;
}
.papel {
font-size: var(--fs-sm);
}
.afinacion {
display: block;
font-size: var(--fs-xs);
font-family: var(--font-mono);
}
/*
* La temática son etiquetas disfrazadas de frase —«El amor no
* correspondido»— y se leen en rejilla; los rasgos son frases de verdad y se
* leen en lista. Por eso no comparten forma.
* El peso no es adorno: sin él, el tres cubano y una trompeta ocasional
* pesarían igual en la ficha, y no es verdad.
*/
.peso {
justify-self: start;
font-size: var(--fs-xs);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
padding: 0.15rem 0.5rem;
border-radius: var(--radius-boton);
white-space: nowrap;
color: var(--c-text-muted);
border: 1px solid var(--c-border);
}
.peso.esencial {
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);
}
.peso.ocasional {
border-style: dashed;
}
.personas li {
display: grid;
grid-template-columns: minmax(0, 1fr) 14rem 9rem;
gap: var(--sp-4);
align-items: baseline;
padding: var(--sp-3) var(--sp-4);
}
.personas .vida,
.personas .papel {
font-size: var(--fs-sm);
font-variant-numeric: tabular-nums;
}
.obras li {
display: grid;
grid-template-columns: 3.5rem minmax(0, 1fr);
gap: var(--sp-4);
align-items: baseline;
padding: var(--sp-4);
}
.obras .ano {
font-family: var(--font-mono);
font-size: var(--fs-sm);
color: var(--c-accent);
font-variant-numeric: tabular-nums;
}
.obras .titulo {
margin: 0;
font-family: var(--font-display);
font-size: var(--fs-lg);
}
.obras .autor,
.obras .nota {
margin: var(--sp-1) 0 0;
font-size: var(--fs-sm);
}
.obras .cita {
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;
white-space: pre-line;
}
.aviso {
margin-top: var(--sp-3);
font-size: var(--fs-xs);
max-width: 68ch;
}
/* --- Linaje -------------------------------------------------------------- */
.linaje {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
gap: var(--sp-4);
align-items: start;
}
.rama {
padding: var(--sp-4);
background: var(--c-bg-raised);
border: 1px solid var(--c-border);
border-radius: var(--radius-tarjeta);
}
.rama ul {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: var(--sp-3);
}
.rama li {
display: grid;
gap: 0.1rem;
padding-left: var(--sp-3);
border-left: 2px solid var(--c-border);
}
/* Una fusión no es una derivación, y se ve. */
.rama li.fusiona {
border-left-color: var(--c-accent);
}
.rama a,
.rama .nombre {
font-family: var(--font-display);
font-size: var(--fs-lg);
text-decoration: none;
}
/* Sin ficha propia todavía: se nombra, pero no promete una página. */
.rama .nombre {
color: var(--c-text-muted);
}
.rama .como {
font-size: var(--fs-xs);
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--c-text-muted);
}
.rama .porque {
font-size: var(--fs-sm);
}
/* --- Temática y rasgos ---------------------------------------------------- */
.temas {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
@ -344,12 +779,19 @@
padding: 0;
}
.temas li {
.temas a {
display: block;
padding: var(--sp-3) var(--sp-4);
background: var(--c-bg-raised);
border: 1px solid var(--c-border);
border-radius: var(--radius-tarjeta);
font-family: var(--font-display);
text-decoration: none;
}
.temas a:hover {
border-color: color-mix(in oklab, var(--c-accent) 45%, transparent);
color: var(--c-accent);
}
.rasgos {
@ -367,6 +809,15 @@
color: var(--c-text-muted);
}
.sin-revisar {
margin-top: var(--sp-7);
padding: var(--sp-4);
border: 1px dashed var(--c-border);
border-radius: var(--radius-tarjeta);
font-size: var(--fs-sm);
color: var(--c-text-muted);
}
/* --- Vecinos ----------------------------------------------------------- */
.vecinos {
@ -392,4 +843,12 @@
.vecino.derecha {
text-align: right;
}
@media (max-width: 52rem) {
.instrumentos li,
.personas li {
grid-template-columns: minmax(0, 1fr);
gap: var(--sp-1);
}
}
< / style >