Web de Senza Paura: catálogo, taller, blog y tienda
Sitio del letrista y compositor, con SvelteKit 2 y Svelte 5.
El catálogo es contenido, no base de datos: estilos, álbumes, canciones y
capítulos son Markdown en `src/content/`, validados al compilar y compilados
dentro del bundle, así que recorrer la música no hace ni una consulta. La base
de datos guarda solo lo que generan las personas: cuentas, carritos, pedidos,
compras, valoraciones y favoritos.
Lo que hay:
- Música agrupada por estilos, con página propia para cada estilo, álbum y
tema, buscador y tabla ordenable.
- Reproductor persistente que sobrevive a la navegación. Suena un fragmento;
el archivo completo solo se obtiene comprando.
- Taller de letras: los principios del oficio y una ficha de creación por
estilo hispanoamericano.
- Blog con feed RSS, y etiquetas que cruzan blog y catálogo.
- Tienda con cuentas por código de correo, carrito, Stripe y descargas
protegidas.
- Panel de contenido en `/admin` que escribe los mismos Markdown.
- Tema oscuro de marca y su inversión clara, aplicado antes de pintar.
Lo que falta y no es código: las claves de Stripe y de correo, el tratamiento
del IVA, las páginas de condiciones y privacidad, y el material real. Las
canciones de ejemplo son inventadas y están para sustituirse; el audio son
marcadores salvo un tema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
<script lang="ts">
|
|
|
|
|
import { resolve } from '$app/paths';
|
|
|
|
|
import BotonCarrito from '$lib/components/BotonCarrito.svelte';
|
|
|
|
|
import BotonFavorito from '$lib/components/BotonFavorito.svelte';
|
|
|
|
|
import BotonReproducir from '$lib/components/BotonReproducir.svelte';
|
|
|
|
|
import EnlacesPlataformas from '$lib/components/EnlacesPlataformas.svelte';
|
|
|
|
|
import Meta from '$lib/components/Meta.svelte';
|
|
|
|
|
import Pestanas, { type Pestana } from '$lib/components/Pestanas.svelte';
|
|
|
|
|
import Valoracion from '$lib/components/Valoracion.svelte';
|
|
|
|
|
import VideoYoutube from '$lib/components/VideoYoutube.svelte';
|
|
|
|
|
import { slugDeEtiqueta } from '$lib/content';
|
|
|
|
|
import { aPista, aPistaDeVersion, aPistas } from '$lib/player/pista';
|
|
|
|
|
import { CANCIONES_POR_PACK, formatearPrecio, PRECIO_PACK_CENTIMOS } from '$lib/tienda';
|
|
|
|
|
import { listaDeNombres, segundosADuracion } from '$lib/utils/format';
|
Deja de repetir el fondo de la portada, las migas y las etiquetas
Tres piezas estaban copiadas en varios sitios y ahora se escriben una vez: el
fondo a sangre de la portada, las migas de pan y la pastilla de una etiqueta.
El fondo ya había divergido entre la cabecera y las láminas de llamada: el
degradado era distinto por descuido y no por decisión, que es lo que pasa con el
marcado duplicado.
Las migas pasan a ser una lista ordenada, que es su marcado correcto: un lector
de pantalla anuncia cuántos escalones hay y en cuál se va, y el último deja de
ser un enlace a donde ya estás.
Tres cosas salieron de hacerlo, ninguna cosmética:
- Con el texto como clave del bucle, un disco y un tema que se llaman igual
—«Como si nada» es las dos cosas— daban claves duplicadas y tumbaban la ficha
al hidratar. El SSR salía bien, así que solo se veía en el navegador.
- Al compartir el fondo, las tres láminas montaban el clip de vídeo. En tema
claro eso eran tres copias del mismo vídeo en la portada. Ahora lo pide solo
la cabecera.
- La lista de temas de un disco no tenía nombre, así que no se distinguía de
ninguna otra lista de la página. Ahora se llama «Temas».
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
import Migas from '$lib/components/Migas.svelte';
|
Web de Senza Paura: catálogo, taller, blog y tienda
Sitio del letrista y compositor, con SvelteKit 2 y Svelte 5.
El catálogo es contenido, no base de datos: estilos, álbumes, canciones y
capítulos son Markdown en `src/content/`, validados al compilar y compilados
dentro del bundle, así que recorrer la música no hace ni una consulta. La base
de datos guarda solo lo que generan las personas: cuentas, carritos, pedidos,
compras, valoraciones y favoritos.
Lo que hay:
- Música agrupada por estilos, con página propia para cada estilo, álbum y
tema, buscador y tabla ordenable.
- Reproductor persistente que sobrevive a la navegación. Suena un fragmento;
el archivo completo solo se obtiene comprando.
- Taller de letras: los principios del oficio y una ficha de creación por
estilo hispanoamericano.
- Blog con feed RSS, y etiquetas que cruzan blog y catálogo.
- Tienda con cuentas por código de correo, carrito, Stripe y descargas
protegidas.
- Panel de contenido en `/admin` que escribe los mismos Markdown.
- Tema oscuro de marca y su inversión clara, aplicado antes de pintar.
Lo que falta y no es código: las claves de Stripe y de correo, el tratamiento
del IVA, las páginas de condiciones y privacidad, y el material real. Las
canciones de ejemplo son inventadas y están para sustituirse; el audio son
marcadores salvo un tema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
|
|
|
|
|
let { data } = $props();
|
|
|
|
|
|
|
|
|
|
const cancion = $derived(data.cancion);
|
|
|
|
|
const Cuerpo = $derived(data.Cuerpo);
|
|
|
|
|
|
|
|
|
|
/** El contexto es el álbum entero; el índice, la posición del tema en él. */
|
|
|
|
|
const pistas = $derived(aPistas(data.hermanas));
|
|
|
|
|
const indice = $derived(data.hermanas.findIndex((c) => c.slug === cancion.slug));
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Las alternativas musicales forman su propio contexto, con la versión
|
|
|
|
|
* principal a la cabeza: así se pueden comparar arreglos seguidos sin que
|
|
|
|
|
* el reproductor salte al tema siguiente del disco.
|
|
|
|
|
*/
|
|
|
|
|
const pistasDeVersiones = $derived([
|
|
|
|
|
aPista(cancion),
|
|
|
|
|
...cancion.versiones.map((v) => aPistaDeVersion(cancion, v))
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Las pestañas del cuerpo. La letra va primero y abierta: es el contenido
|
|
|
|
|
* principal de la ficha. Las otras dos solo aparecen si el tema las tiene.
|
|
|
|
|
*/
|
|
|
|
|
const pestanas: Pestana[] = $derived([
|
|
|
|
|
{ id: 'letra', etiqueta: cancion.instrumental ? 'Sobre la pieza' : 'Letra' },
|
|
|
|
|
...(cancion.inspiracion.length > 0 ? [{ id: 'inspiracion', etiqueta: 'De dónde sale' }] : []),
|
|
|
|
|
...(cancion.versiones.length > 0
|
|
|
|
|
? [{ id: 'versiones', etiqueta: 'Alternativas musicales' }]
|
|
|
|
|
: []),
|
|
|
|
|
...(cancion.videos.length > 0
|
|
|
|
|
? [{ id: 'videos', etiqueta: cancion.videos.length === 1 ? 'Vídeo' : 'Vídeos' }]
|
|
|
|
|
: [])
|
|
|
|
|
]);
|
|
|
|
|
|
Carrusel en la portada y letras a dos columnas
La portada abre con tres láminas: el disco en el tocadiscos, una invitación al
taller y otra a la tienda. Un carrusel mal hecho es de las piezas más hostiles
que hay —cambia mientras lees, no se para, y para un lector de pantalla es un
cajón que aparece y desaparece—, así que este se para al pasar el puntero o al
entrar el foco, tiene botón de pausa, no gira solo con `prefers-reduced-motion`
y deja las láminas ocultas con `inert`, fuera del orden de tabulación.
Las letras que no están en español se enfrentan con su traducción en dos
columnas, verso a verso. Cada columna declara su `lang`, que es lo que impide
que un lector de pantalla lea el inglés con voz española, y la traducción se
pinta con `pre-line`: es verso, y perder los cortes la deja inservible para
seguir la original. El nombre del idioma sale de `Intl.DisplayNames`.
Sobre el reproductor: ya sobrevivía a la navegación, porque vive en el layout
raíz fuera de `main`. Lo que faltaba era comprobarlo de verdad; el test decía
que la barra seguía visible, no que siguiera sonando. Ahora mira que la posición
avance.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
/**
|
|
|
|
|
* La letra va a dos columnas cuando no está en español y hay traducción:
|
|
|
|
|
* original a la izquierda, castellano a la derecha. Con una sola de las dos
|
|
|
|
|
* cosas no hay nada que enfrentar, y una columna vacía sería peor que
|
|
|
|
|
* ninguna.
|
|
|
|
|
*/
|
|
|
|
|
const enEspanol = $derived(cancion.idioma.split('-')[0] === 'es');
|
|
|
|
|
const aDosColumnas = $derived(!enEspanol && Boolean(cancion.traduccion));
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* El nombre del idioma en español, sin tabla que mantener. Si el navegador o
|
|
|
|
|
* el servidor no saben traducirlo, se queda el código, que sigue diciendo
|
|
|
|
|
* algo.
|
|
|
|
|
*/
|
|
|
|
|
const nombreIdioma = $derived.by(() => {
|
|
|
|
|
try {
|
|
|
|
|
return (
|
|
|
|
|
new Intl.DisplayNames(['es'], { type: 'language' }).of(cancion.idioma) ?? cancion.idioma
|
|
|
|
|
);
|
|
|
|
|
} catch {
|
|
|
|
|
return cancion.idioma;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Web de Senza Paura: catálogo, taller, blog y tienda
Sitio del letrista y compositor, con SvelteKit 2 y Svelte 5.
El catálogo es contenido, no base de datos: estilos, álbumes, canciones y
capítulos son Markdown en `src/content/`, validados al compilar y compilados
dentro del bundle, así que recorrer la música no hace ni una consulta. La base
de datos guarda solo lo que generan las personas: cuentas, carritos, pedidos,
compras, valoraciones y favoritos.
Lo que hay:
- Música agrupada por estilos, con página propia para cada estilo, álbum y
tema, buscador y tabla ordenable.
- Reproductor persistente que sobrevive a la navegación. Suena un fragmento;
el archivo completo solo se obtiene comprando.
- Taller de letras: los principios del oficio y una ficha de creación por
estilo hispanoamericano.
- Blog con feed RSS, y etiquetas que cruzan blog y catálogo.
- Tienda con cuentas por código de correo, carrito, Stripe y descargas
protegidas.
- Panel de contenido en `/admin` que escribe los mismos Markdown.
- Tema oscuro de marca y su inversión clara, aplicado antes de pintar.
Lo que falta y no es código: las claves de Stripe y de correo, el tratamiento
del IVA, las páginas de condiciones y privacidad, y el material real. Las
canciones de ejemplo son inventadas y están para sustituirse; el audio son
marcadores salvo un tema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
const registro = $derived(cancion.registro);
|
|
|
|
|
|
|
|
|
|
/** El rol se guarda sin tildes por ser clave de datos; se pinta con ellas. */
|
|
|
|
|
const etiquetasRol: Record<string, string> = {
|
|
|
|
|
letra: 'Letra',
|
|
|
|
|
musica: 'Música',
|
|
|
|
|
'letra y musica': 'Letra y música'
|
|
|
|
|
};
|
|
|
|
|
const hayRegistro = $derived(
|
|
|
|
|
Boolean(
|
|
|
|
|
registro.iswc ||
|
|
|
|
|
registro.isrc ||
|
|
|
|
|
registro.entidad ||
|
|
|
|
|
registro.editorial ||
|
|
|
|
|
registro.reparto.length > 0
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const descripcion = $derived(
|
|
|
|
|
`${cancion.titulo}. Letra de ${listaDeNombres(cancion.letra)}, música de ${listaDeNombres(cancion.musica)}.`
|
|
|
|
|
);
|
Deja de repetir el fondo de la portada, las migas y las etiquetas
Tres piezas estaban copiadas en varios sitios y ahora se escriben una vez: el
fondo a sangre de la portada, las migas de pan y la pastilla de una etiqueta.
El fondo ya había divergido entre la cabecera y las láminas de llamada: el
degradado era distinto por descuido y no por decisión, que es lo que pasa con el
marcado duplicado.
Las migas pasan a ser una lista ordenada, que es su marcado correcto: un lector
de pantalla anuncia cuántos escalones hay y en cuál se va, y el último deja de
ser un enlace a donde ya estás.
Tres cosas salieron de hacerlo, ninguna cosmética:
- Con el texto como clave del bucle, un disco y un tema que se llaman igual
—«Como si nada» es las dos cosas— daban claves duplicadas y tumbaban la ficha
al hidratar. El SSR salía bien, así que solo se veía en el navegador.
- Al compartir el fondo, las tres láminas montaban el clip de vídeo. En tema
claro eso eran tres copias del mismo vídeo en la portada. Ahora lo pide solo
la cabecera.
- La lista de temas de un disco no tenía nombre, así que no se distinguía de
ninguna otra lista de la página. Ahora se llama «Temas».
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
|
|
|
|
|
const migas = $derived([
|
|
|
|
|
{ texto: 'Música', href: resolve('/musica') },
|
|
|
|
|
...(data.estilo
|
|
|
|
|
? [
|
|
|
|
|
{
|
|
|
|
|
texto: data.estilo.nombre,
|
|
|
|
|
href: resolve('/musica/[estilo]', { estilo: data.estilo.slug })
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
: []),
|
|
|
|
|
...(data.album
|
|
|
|
|
? [{ texto: data.album.titulo, href: resolve('/albumes/[slug]', { slug: data.album.slug }) }]
|
|
|
|
|
: []),
|
|
|
|
|
{ texto: cancion.titulo }
|
|
|
|
|
]);
|
Web de Senza Paura: catálogo, taller, blog y tienda
Sitio del letrista y compositor, con SvelteKit 2 y Svelte 5.
El catálogo es contenido, no base de datos: estilos, álbumes, canciones y
capítulos son Markdown en `src/content/`, validados al compilar y compilados
dentro del bundle, así que recorrer la música no hace ni una consulta. La base
de datos guarda solo lo que generan las personas: cuentas, carritos, pedidos,
compras, valoraciones y favoritos.
Lo que hay:
- Música agrupada por estilos, con página propia para cada estilo, álbum y
tema, buscador y tabla ordenable.
- Reproductor persistente que sobrevive a la navegación. Suena un fragmento;
el archivo completo solo se obtiene comprando.
- Taller de letras: los principios del oficio y una ficha de creación por
estilo hispanoamericano.
- Blog con feed RSS, y etiquetas que cruzan blog y catálogo.
- Tienda con cuentas por código de correo, carrito, Stripe y descargas
protegidas.
- Panel de contenido en `/admin` que escribe los mismos Markdown.
- Tema oscuro de marca y su inversión clara, aplicado antes de pintar.
Lo que falta y no es código: las claves de Stripe y de correo, el tratamiento
del IVA, las páginas de condiciones y privacidad, y el material real. Las
canciones de ejemplo son inventadas y están para sustituirse; el audio son
marcadores salvo un tema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<Meta titulo={cancion.titulo} {descripcion} imagen={data.portada} />
|
|
|
|
|
|
|
|
|
|
<div class="container">
|
Deja de repetir el fondo de la portada, las migas y las etiquetas
Tres piezas estaban copiadas en varios sitios y ahora se escriben una vez: el
fondo a sangre de la portada, las migas de pan y la pastilla de una etiqueta.
El fondo ya había divergido entre la cabecera y las láminas de llamada: el
degradado era distinto por descuido y no por decisión, que es lo que pasa con el
marcado duplicado.
Las migas pasan a ser una lista ordenada, que es su marcado correcto: un lector
de pantalla anuncia cuántos escalones hay y en cuál se va, y el último deja de
ser un enlace a donde ya estás.
Tres cosas salieron de hacerlo, ninguna cosmética:
- Con el texto como clave del bucle, un disco y un tema que se llaman igual
—«Como si nada» es las dos cosas— daban claves duplicadas y tumbaban la ficha
al hidratar. El SSR salía bien, así que solo se veía en el navegador.
- Al compartir el fondo, las tres láminas montaban el clip de vídeo. En tema
claro eso eran tres copias del mismo vídeo en la portada. Ahora lo pide solo
la cabecera.
- La lista de temas de un disco no tenía nombre, así que no se distinguía de
ninguna otra lista de la página. Ahora se llama «Temas».
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
<Migas {migas} />
|
Web de Senza Paura: catálogo, taller, blog y tienda
Sitio del letrista y compositor, con SvelteKit 2 y Svelte 5.
El catálogo es contenido, no base de datos: estilos, álbumes, canciones y
capítulos son Markdown en `src/content/`, validados al compilar y compilados
dentro del bundle, así que recorrer la música no hace ni una consulta. La base
de datos guarda solo lo que generan las personas: cuentas, carritos, pedidos,
compras, valoraciones y favoritos.
Lo que hay:
- Música agrupada por estilos, con página propia para cada estilo, álbum y
tema, buscador y tabla ordenable.
- Reproductor persistente que sobrevive a la navegación. Suena un fragmento;
el archivo completo solo se obtiene comprando.
- Taller de letras: los principios del oficio y una ficha de creación por
estilo hispanoamericano.
- Blog con feed RSS, y etiquetas que cruzan blog y catálogo.
- Tienda con cuentas por código de correo, carrito, Stripe y descargas
protegidas.
- Panel de contenido en `/admin` que escribe los mismos Markdown.
- Tema oscuro de marca y su inversión clara, aplicado antes de pintar.
Lo que falta y no es código: las claves de Stripe y de correo, el tratamiento
del IVA, las páginas de condiciones y privacidad, y el material real. Las
canciones de ejemplo son inventadas y están para sustituirse; el audio son
marcadores salvo un tema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
|
|
|
|
|
<header class="ficha">
|
|
|
|
|
<img
|
|
|
|
|
class="portada"
|
|
|
|
|
src={data.portada}
|
|
|
|
|
alt="Portada de {cancion.titulo}"
|
|
|
|
|
width="280"
|
|
|
|
|
height="280"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<div class="titulo">
|
|
|
|
|
<h1>{cancion.titulo}</h1>
|
|
|
|
|
<p class="meta muted">
|
|
|
|
|
<!--
|
|
|
|
|
El album de singles es sintetico y se titula «Singles», asi que
|
|
|
|
|
nombrarlo aqui diria «Single del estilo Singles». Lo que situa un
|
|
|
|
|
tema suelto es su estilo; a la coleccion se llega por las migas.
|
|
|
|
|
-->
|
|
|
|
|
{#if data.album?.sintetico && data.estilo}
|
|
|
|
|
Single del estilo
|
|
|
|
|
<a href={resolve('/musica/[estilo]', { estilo: data.estilo.slug })}>
|
|
|
|
|
{data.estilo.nombre}
|
|
|
|
|
</a>
|
|
|
|
|
{:else if data.album}
|
|
|
|
|
Del disco
|
|
|
|
|
<a href={resolve('/albumes/[slug]', { slug: data.album.slug })}>{data.album.titulo}</a>
|
|
|
|
|
{/if}
|
|
|
|
|
{#if cancion.ano}
|
|
|
|
|
<span aria-hidden="true">·</span> {cancion.ano}
|
|
|
|
|
{/if}
|
|
|
|
|
{#if cancion.duracion}
|
|
|
|
|
<span aria-hidden="true">·</span>
|
|
|
|
|
{segundosADuracion(cancion.duracion)}
|
|
|
|
|
{/if}
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div class="acciones">
|
|
|
|
|
<BotonReproducir
|
|
|
|
|
{pistas}
|
|
|
|
|
indice={Math.max(0, indice)}
|
|
|
|
|
texto="Escuchar la muestra"
|
|
|
|
|
variante="solido"
|
|
|
|
|
tamano={18}
|
|
|
|
|
/>
|
|
|
|
|
<BotonCarrito {cancion} />
|
|
|
|
|
<BotonFavorito cancionSlug={cancion.slug} />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{#if cancion.alaVenta}
|
|
|
|
|
<p class="venta muted">
|
|
|
|
|
En la web suena un fragmento. La descarga completa en MP3, con todas sus versiones, va en
|
|
|
|
|
packs de {CANCIONES_POR_PACK} canciones por {formatearPrecio(PRECIO_PACK_CENTIMOS)}.
|
|
|
|
|
</p>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<EnlacesPlataformas enlaces={cancion.enlaces} etiqueta="Escuchar {cancion.titulo} en" />
|
|
|
|
|
</div>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<div class="cuerpo">
|
|
|
|
|
<div class="principal">
|
|
|
|
|
<Pestanas {pestanas} etiqueta="Contenido del tema">
|
|
|
|
|
{#snippet panel(id)}
|
|
|
|
|
{#if id === 'letra'}
|
|
|
|
|
{#if Cuerpo}
|
Carrusel en la portada y letras a dos columnas
La portada abre con tres láminas: el disco en el tocadiscos, una invitación al
taller y otra a la tienda. Un carrusel mal hecho es de las piezas más hostiles
que hay —cambia mientras lees, no se para, y para un lector de pantalla es un
cajón que aparece y desaparece—, así que este se para al pasar el puntero o al
entrar el foco, tiene botón de pausa, no gira solo con `prefers-reduced-motion`
y deja las láminas ocultas con `inert`, fuera del orden de tabulación.
Las letras que no están en español se enfrentan con su traducción en dos
columnas, verso a verso. Cada columna declara su `lang`, que es lo que impide
que un lector de pantalla lea el inglés con voz española, y la traducción se
pinta con `pre-line`: es verso, y perder los cortes la deja inservible para
seguir la original. El nombre del idioma sale de `Intl.DisplayNames`.
Sobre el reproductor: ya sobrevivía a la navegación, porque vive en el layout
raíz fuera de `main`. Lo que faltaba era comprobarlo de verdad; el test decía
que la barra seguía visible, no que siguiera sonando. Ahora mira que la posición
avance.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
<div class="letra" class:pareada={aDosColumnas}>
|
|
|
|
|
<div class="texto" class:versos={!cancion.instrumental} lang={cancion.idioma}>
|
|
|
|
|
{#if aDosColumnas}
|
|
|
|
|
<p class="rotulo-columna muted">Letra original · {nombreIdioma}</p>
|
|
|
|
|
{/if}
|
|
|
|
|
<Cuerpo />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{#if aDosColumnas}
|
|
|
|
|
<!--
|
|
|
|
|
`pre-line` conserva el corte de los versos sin necesidad de
|
|
|
|
|
compilar Markdown: una traducción que pierde los cortes deja
|
|
|
|
|
de servir para seguir la original línea a línea.
|
|
|
|
|
-->
|
|
|
|
|
<div class="texto versos traduccion" lang="es">
|
|
|
|
|
<p class="rotulo-columna muted">Traducción al español</p>
|
|
|
|
|
<p class="verso-a-verso">{cancion.traduccion}</p>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
Web de Senza Paura: catálogo, taller, blog y tienda
Sitio del letrista y compositor, con SvelteKit 2 y Svelte 5.
El catálogo es contenido, no base de datos: estilos, álbumes, canciones y
capítulos son Markdown en `src/content/`, validados al compilar y compilados
dentro del bundle, así que recorrer la música no hace ni una consulta. La base
de datos guarda solo lo que generan las personas: cuentas, carritos, pedidos,
compras, valoraciones y favoritos.
Lo que hay:
- Música agrupada por estilos, con página propia para cada estilo, álbum y
tema, buscador y tabla ordenable.
- Reproductor persistente que sobrevive a la navegación. Suena un fragmento;
el archivo completo solo se obtiene comprando.
- Taller de letras: los principios del oficio y una ficha de creación por
estilo hispanoamericano.
- Blog con feed RSS, y etiquetas que cruzan blog y catálogo.
- Tienda con cuentas por código de correo, carrito, Stripe y descargas
protegidas.
- Panel de contenido en `/admin` que escribe los mismos Markdown.
- Tema oscuro de marca y su inversión clara, aplicado antes de pintar.
Lo que falta y no es código: las claves de Stripe y de correo, el tratamiento
del IVA, las páginas de condiciones y privacidad, y el material real. Las
canciones de ejemplo son inventadas y están para sustituirse; el audio son
marcadores salvo un tema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
</div>
|
|
|
|
|
{:else}
|
|
|
|
|
<p class="muted">
|
|
|
|
|
{cancion.instrumental
|
|
|
|
|
? 'Todavía no hay notas publicadas de esta pieza.'
|
|
|
|
|
: 'La letra de este tema aún no está publicada.'}
|
|
|
|
|
</p>
|
|
|
|
|
{/if}
|
|
|
|
|
{:else if id === 'inspiracion'}
|
|
|
|
|
<div class="texto">
|
|
|
|
|
{#each cancion.inspiracion as parrafo, i (i)}
|
|
|
|
|
<p>{parrafo}</p>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
{:else if id === 'videos'}
|
|
|
|
|
<div class="videos">
|
|
|
|
|
{#each cancion.videos as video (video.youtube)}
|
|
|
|
|
<VideoYoutube youtube={video.youtube} titulo={video.titulo} poster={data.portada} />
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
<p class="muted aviso">
|
|
|
|
|
El vídeo se carga solo al pulsarlo: hasta entonces esta página no pide nada a YouTube.
|
|
|
|
|
</p>
|
|
|
|
|
{:else if id === 'versiones'}
|
|
|
|
|
<p class="muted">
|
|
|
|
|
El mismo tema en otros arreglos. Quien compra la canción se lleva todas sus versiones.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<ul class="versiones">
|
|
|
|
|
{#each cancion.versiones as version, i (version.id)}
|
|
|
|
|
<li class="version">
|
|
|
|
|
<BotonReproducir pistas={pistasDeVersiones} indice={i + 1} />
|
|
|
|
|
<div class="datos">
|
|
|
|
|
<p class="nombre">{version.nombre}</p>
|
|
|
|
|
{#if version.nota}
|
|
|
|
|
<p class="nota muted">{version.nota}</p>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
<span class="duracion muted">
|
|
|
|
|
{version.duracion ? segundosADuracion(version.duracion) : '--:--'}
|
|
|
|
|
</span>
|
|
|
|
|
</li>
|
|
|
|
|
{/each}
|
|
|
|
|
</ul>
|
|
|
|
|
{/if}
|
|
|
|
|
{/snippet}
|
|
|
|
|
</Pestanas>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<aside class="lateral">
|
|
|
|
|
<!--
|
|
|
|
|
Los datos del tema y la autoría van separados a propósito: la ficha
|
|
|
|
|
son hechos del registro (cuándo, dónde, cuánto dura) y los créditos
|
|
|
|
|
son personas. Mezclarlos en una sola lista los vuelve ilegibles.
|
|
|
|
|
-->
|
|
|
|
|
<section class="bloque">
|
|
|
|
|
<Valoracion
|
|
|
|
|
cancionSlug={cancion.slug}
|
|
|
|
|
media={data.valoracion.media}
|
|
|
|
|
cuantas={data.valoracion.cuantas}
|
|
|
|
|
mia={data.miValoracion}
|
|
|
|
|
/>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section class="bloque">
|
|
|
|
|
<h2>Ficha</h2>
|
|
|
|
|
<dl>
|
|
|
|
|
{#if data.album}
|
|
|
|
|
<dt>{data.album.sintetico ? 'Publicación' : 'Álbum'}</dt>
|
|
|
|
|
<dd>
|
|
|
|
|
<a href={resolve('/albumes/[slug]', { slug: data.album.slug })}>
|
|
|
|
|
{data.album.sintetico ? 'Single suelto' : data.album.titulo}
|
|
|
|
|
</a>
|
|
|
|
|
</dd>
|
|
|
|
|
|
|
|
|
|
{#if !data.album.sintetico && cancion.numero !== null}
|
|
|
|
|
<dt>Pista</dt>
|
|
|
|
|
<dd>{cancion.numero} de {data.album.canciones.length}</dd>
|
|
|
|
|
{/if}
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if cancion.ano}
|
|
|
|
|
<dt>Año</dt>
|
|
|
|
|
<dd>{cancion.ano}</dd>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if cancion.duracion}
|
|
|
|
|
<dt>Duración</dt>
|
|
|
|
|
<dd>{segundosADuracion(cancion.duracion)}</dd>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if cancion.versiones.length > 0}
|
|
|
|
|
<dt>Versiones</dt>
|
|
|
|
|
<dd>{cancion.versiones.length + 1}</dd>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if data.estilo}
|
|
|
|
|
<dt>Estilo</dt>
|
|
|
|
|
<dd>
|
|
|
|
|
<a href={resolve('/musica/[estilo]', { estilo: data.estilo.slug })}>
|
|
|
|
|
{data.estilo.nombre}
|
|
|
|
|
</a>
|
|
|
|
|
</dd>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if cancion.etiquetas.length > 0}
|
|
|
|
|
<dt>Etiquetas</dt>
|
|
|
|
|
<dd>
|
Deja de repetir el fondo de la portada, las migas y las etiquetas
Tres piezas estaban copiadas en varios sitios y ahora se escriben una vez: el
fondo a sangre de la portada, las migas de pan y la pastilla de una etiqueta.
El fondo ya había divergido entre la cabecera y las láminas de llamada: el
degradado era distinto por descuido y no por decisión, que es lo que pasa con el
marcado duplicado.
Las migas pasan a ser una lista ordenada, que es su marcado correcto: un lector
de pantalla anuncia cuántos escalones hay y en cuál se va, y el último deja de
ser un enlace a donde ya estás.
Tres cosas salieron de hacerlo, ninguna cosmética:
- Con el texto como clave del bucle, un disco y un tema que se llaman igual
—«Como si nada» es las dos cosas— daban claves duplicadas y tumbaban la ficha
al hidratar. El SSR salía bien, así que solo se veía en el navegador.
- Al compartir el fondo, las tres láminas montaban el clip de vídeo. En tema
claro eso eran tres copias del mismo vídeo en la portada. Ahora lo pide solo
la cabecera.
- La lista de temas de un disco no tenía nombre, así que no se distinguía de
ninguna otra lista de la página. Ahora se llama «Temas».
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
<ul class="chips">
|
Web de Senza Paura: catálogo, taller, blog y tienda
Sitio del letrista y compositor, con SvelteKit 2 y Svelte 5.
El catálogo es contenido, no base de datos: estilos, álbumes, canciones y
capítulos son Markdown en `src/content/`, validados al compilar y compilados
dentro del bundle, así que recorrer la música no hace ni una consulta. La base
de datos guarda solo lo que generan las personas: cuentas, carritos, pedidos,
compras, valoraciones y favoritos.
Lo que hay:
- Música agrupada por estilos, con página propia para cada estilo, álbum y
tema, buscador y tabla ordenable.
- Reproductor persistente que sobrevive a la navegación. Suena un fragmento;
el archivo completo solo se obtiene comprando.
- Taller de letras: los principios del oficio y una ficha de creación por
estilo hispanoamericano.
- Blog con feed RSS, y etiquetas que cruzan blog y catálogo.
- Tienda con cuentas por código de correo, carrito, Stripe y descargas
protegidas.
- Panel de contenido en `/admin` que escribe los mismos Markdown.
- Tema oscuro de marca y su inversión clara, aplicado antes de pintar.
Lo que falta y no es código: las claves de Stripe y de correo, el tratamiento
del IVA, las páginas de condiciones y privacidad, y el material real. Las
canciones de ejemplo son inventadas y están para sustituirse; el audio son
marcadores salvo un tema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
{#each cancion.etiquetas as etiqueta (etiqueta)}
|
|
|
|
|
<li>
|
|
|
|
|
<a
|
Deja de repetir el fondo de la portada, las migas y las etiquetas
Tres piezas estaban copiadas en varios sitios y ahora se escriben una vez: el
fondo a sangre de la portada, las migas de pan y la pastilla de una etiqueta.
El fondo ya había divergido entre la cabecera y las láminas de llamada: el
degradado era distinto por descuido y no por decisión, que es lo que pasa con el
marcado duplicado.
Las migas pasan a ser una lista ordenada, que es su marcado correcto: un lector
de pantalla anuncia cuántos escalones hay y en cuál se va, y el último deja de
ser un enlace a donde ya estás.
Tres cosas salieron de hacerlo, ninguna cosmética:
- Con el texto como clave del bucle, un disco y un tema que se llaman igual
—«Como si nada» es las dos cosas— daban claves duplicadas y tumbaban la ficha
al hidratar. El SSR salía bien, así que solo se veía en el navegador.
- Al compartir el fondo, las tres láminas montaban el clip de vídeo. En tema
claro eso eran tres copias del mismo vídeo en la portada. Ahora lo pide solo
la cabecera.
- La lista de temas de un disco no tenía nombre, así que no se distinguía de
ninguna otra lista de la página. Ahora se llama «Temas».
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
class="chip"
|
Web de Senza Paura: catálogo, taller, blog y tienda
Sitio del letrista y compositor, con SvelteKit 2 y Svelte 5.
El catálogo es contenido, no base de datos: estilos, álbumes, canciones y
capítulos son Markdown en `src/content/`, validados al compilar y compilados
dentro del bundle, así que recorrer la música no hace ni una consulta. La base
de datos guarda solo lo que generan las personas: cuentas, carritos, pedidos,
compras, valoraciones y favoritos.
Lo que hay:
- Música agrupada por estilos, con página propia para cada estilo, álbum y
tema, buscador y tabla ordenable.
- Reproductor persistente que sobrevive a la navegación. Suena un fragmento;
el archivo completo solo se obtiene comprando.
- Taller de letras: los principios del oficio y una ficha de creación por
estilo hispanoamericano.
- Blog con feed RSS, y etiquetas que cruzan blog y catálogo.
- Tienda con cuentas por código de correo, carrito, Stripe y descargas
protegidas.
- Panel de contenido en `/admin` que escribe los mismos Markdown.
- Tema oscuro de marca y su inversión clara, aplicado antes de pintar.
Lo que falta y no es código: las claves de Stripe y de correo, el tratamiento
del IVA, las páginas de condiciones y privacidad, y el material real. Las
canciones de ejemplo son inventadas y están para sustituirse; el audio son
marcadores salvo un tema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
href={resolve('/etiquetas/[etiqueta]', {
|
|
|
|
|
etiqueta: slugDeEtiqueta(etiqueta)
|
|
|
|
|
})}
|
|
|
|
|
>
|
|
|
|
|
{etiqueta}
|
|
|
|
|
</a>
|
|
|
|
|
</li>
|
|
|
|
|
{/each}
|
|
|
|
|
</ul>
|
|
|
|
|
</dd>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if data.album?.sello}
|
|
|
|
|
<dt>Sello</dt>
|
|
|
|
|
<dd>{data.album.sello}</dd>
|
|
|
|
|
{/if}
|
|
|
|
|
</dl>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section class="bloque">
|
|
|
|
|
<h2>Créditos</h2>
|
|
|
|
|
<dl>
|
|
|
|
|
{#if !cancion.instrumental}
|
|
|
|
|
<dt>Letra</dt>
|
|
|
|
|
<dd>{listaDeNombres(cancion.letra)}</dd>
|
|
|
|
|
{/if}
|
|
|
|
|
<dt>Música</dt>
|
|
|
|
|
<dd>{listaDeNombres(cancion.musica)}</dd>
|
|
|
|
|
{#if cancion.interpretes.length > 0}
|
|
|
|
|
<dt>Interpretación</dt>
|
|
|
|
|
<dd>{listaDeNombres(cancion.interpretes)}</dd>
|
|
|
|
|
{/if}
|
|
|
|
|
</dl>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
{#if hayRegistro}
|
|
|
|
|
<!--
|
|
|
|
|
Los identificadores son públicos por su naturaleza —el ISRC va en
|
|
|
|
|
cada plataforma, el ISWC en los repertorios— y quien quiera
|
|
|
|
|
licenciar la obra los necesita para localizarla y saber con quién
|
|
|
|
|
negociar. Lo que no se rellene, no se pinta.
|
|
|
|
|
-->
|
|
|
|
|
<section class="bloque">
|
|
|
|
|
<h2>Registro</h2>
|
|
|
|
|
<dl>
|
|
|
|
|
{#if registro.iswc}
|
|
|
|
|
<dt title="Identifica la obra, la misma en todas sus versiones">ISWC</dt>
|
|
|
|
|
<dd class="codigo">{registro.iswc}</dd>
|
|
|
|
|
{/if}
|
|
|
|
|
{#if registro.isrc}
|
|
|
|
|
<dt title="Identifica esta grabación en concreto">ISRC</dt>
|
|
|
|
|
<dd class="codigo">{registro.isrc}</dd>
|
|
|
|
|
{/if}
|
|
|
|
|
{#if registro.entidad}
|
|
|
|
|
<dt>Entidad</dt>
|
|
|
|
|
<dd>
|
|
|
|
|
{registro.entidad}{#if registro.numero}<span class="muted"
|
|
|
|
|
> · {registro.numero}</span
|
|
|
|
|
>{/if}
|
|
|
|
|
</dd>
|
|
|
|
|
{/if}
|
|
|
|
|
{#if registro.editorial}
|
|
|
|
|
<dt>Editorial</dt>
|
|
|
|
|
<dd>{registro.editorial}</dd>
|
|
|
|
|
{/if}
|
|
|
|
|
</dl>
|
|
|
|
|
|
|
|
|
|
{#if registro.reparto.length > 0}
|
|
|
|
|
<p class="subtitulo">Reparto de autoría</p>
|
|
|
|
|
<ul class="reparto">
|
|
|
|
|
{#each registro.reparto as parte (parte.nombre + parte.rol)}
|
|
|
|
|
<li>
|
|
|
|
|
<span class="quien">{parte.nombre}</span>
|
|
|
|
|
<span class="rol muted">{etiquetasRol[parte.rol] ?? parte.rol}</span>
|
|
|
|
|
<span class="parte">{parte.porcentaje} %</span>
|
|
|
|
|
</li>
|
|
|
|
|
{/each}
|
|
|
|
|
</ul>
|
|
|
|
|
{/if}
|
|
|
|
|
</section>
|
|
|
|
|
{/if}
|
|
|
|
|
</aside>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{#if data.anterior || data.siguiente}
|
|
|
|
|
<nav class="vecinos" aria-label="Otros temas del disco">
|
|
|
|
|
{#if data.anterior}
|
|
|
|
|
<a class="vecino" href={resolve('/canciones/[slug]', { slug: data.anterior.slug })}>
|
|
|
|
|
<span class="muted">Anterior</span>
|
|
|
|
|
<span>{data.anterior.titulo}</span>
|
|
|
|
|
</a>
|
|
|
|
|
{:else}
|
|
|
|
|
<span></span>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if data.siguiente}
|
|
|
|
|
<a
|
|
|
|
|
class="vecino derecha"
|
|
|
|
|
href={resolve('/canciones/[slug]', { slug: data.siguiente.slug })}
|
|
|
|
|
>
|
|
|
|
|
<span class="muted">Siguiente</span>
|
|
|
|
|
<span>{data.siguiente.titulo}</span>
|
|
|
|
|
</a>
|
|
|
|
|
{/if}
|
|
|
|
|
</nav>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<style>
|
Carrusel en la portada y letras a dos columnas
La portada abre con tres láminas: el disco en el tocadiscos, una invitación al
taller y otra a la tienda. Un carrusel mal hecho es de las piezas más hostiles
que hay —cambia mientras lees, no se para, y para un lector de pantalla es un
cajón que aparece y desaparece—, así que este se para al pasar el puntero o al
entrar el foco, tiene botón de pausa, no gira solo con `prefers-reduced-motion`
y deja las láminas ocultas con `inert`, fuera del orden de tabulación.
Las letras que no están en español se enfrentan con su traducción en dos
columnas, verso a verso. Cada columna declara su `lang`, que es lo que impide
que un lector de pantalla lea el inglés con voz española, y la traducción se
pinta con `pre-line`: es verso, y perder los cortes la deja inservible para
seguir la original. El nombre del idioma sale de `Intl.DisplayNames`.
Sobre el reproductor: ya sobrevivía a la navegación, porque vive en el layout
raíz fuera de `main`. Lo que faltaba era comprobarlo de verdad; el test decía
que la barra seguía visible, no que siguiera sonando. Ahora mira que la posición
avance.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
/*
|
|
|
|
|
* A dos columnas se leen en paralelo, que es para lo que sirve tener la
|
|
|
|
|
* traducción al lado. Al estrecharse caen una debajo de otra: partir un
|
|
|
|
|
* verso para que quepan dos columnas sería peor que apilarlas.
|
|
|
|
|
*/
|
|
|
|
|
.letra.pareada {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
gap: var(--sp-5) var(--sp-6);
|
|
|
|
|
align-items: start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rotulo-columna {
|
|
|
|
|
margin: 0 0 var(--sp-3);
|
|
|
|
|
padding-bottom: var(--sp-2);
|
|
|
|
|
border-bottom: 1px solid var(--c-border);
|
|
|
|
|
font-family: var(--font-body);
|
|
|
|
|
font-size: var(--fs-xs);
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 0.08em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.verso-a-verso {
|
|
|
|
|
margin: 0;
|
|
|
|
|
white-space: pre-line;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 52rem) {
|
|
|
|
|
.letra.pareada {
|
|
|
|
|
grid-template-columns: minmax(0, 1fr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Web de Senza Paura: catálogo, taller, blog y tienda
Sitio del letrista y compositor, con SvelteKit 2 y Svelte 5.
El catálogo es contenido, no base de datos: estilos, álbumes, canciones y
capítulos son Markdown en `src/content/`, validados al compilar y compilados
dentro del bundle, así que recorrer la música no hace ni una consulta. La base
de datos guarda solo lo que generan las personas: cuentas, carritos, pedidos,
compras, valoraciones y favoritos.
Lo que hay:
- Música agrupada por estilos, con página propia para cada estilo, álbum y
tema, buscador y tabla ordenable.
- Reproductor persistente que sobrevive a la navegación. Suena un fragmento;
el archivo completo solo se obtiene comprando.
- Taller de letras: los principios del oficio y una ficha de creación por
estilo hispanoamericano.
- Blog con feed RSS, y etiquetas que cruzan blog y catálogo.
- Tienda con cuentas por código de correo, carrito, Stripe y descargas
protegidas.
- Panel de contenido en `/admin` que escribe los mismos Markdown.
- Tema oscuro de marca y su inversión clara, aplicado antes de pintar.
Lo que falta y no es código: las claves de Stripe y de correo, el tratamiento
del IVA, las páginas de condiciones y privacidad, y el material real. Las
canciones de ejemplo son inventadas y están para sustituirse; el audio son
marcadores salvo un tema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
/* Las etiquetas son enlaces, no datos sueltos: llevan a lo que agrupan. */
|
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.ficha {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: minmax(0, 17rem) minmax(0, 1fr);
|
|
|
|
|
gap: var(--sp-6);
|
|
|
|
|
align-items: start;
|
|
|
|
|
padding-bottom: var(--sp-5);
|
|
|
|
|
border-bottom: 1px solid var(--c-border);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.portada {
|
|
|
|
|
width: 100%;
|
|
|
|
|
aspect-ratio: 1;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
border: 1px solid var(--c-border);
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
background: var(--c-bg-sunken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.meta {
|
|
|
|
|
font-size: var(--fs-sm);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.acciones {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: var(--sp-3);
|
|
|
|
|
margin-bottom: var(--sp-3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.venta {
|
|
|
|
|
max-width: 46ch;
|
|
|
|
|
font-size: var(--fs-xs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.cuerpo {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: minmax(0, 1fr) minmax(0, 16rem);
|
|
|
|
|
gap: var(--sp-6);
|
|
|
|
|
padding-block: var(--sp-6);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* La letra se compone en bloque estrecho y con los saltos de línea del
|
|
|
|
|
* original: en una canción, dónde termina cada verso es parte del texto.
|
|
|
|
|
*/
|
|
|
|
|
.texto {
|
|
|
|
|
max-width: 46ch;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.versos {
|
|
|
|
|
font-family: var(--font-versos);
|
|
|
|
|
font-size: var(--fs-lg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.versos :global(p) {
|
|
|
|
|
margin-bottom: var(--sp-5);
|
|
|
|
|
line-height: 1.75;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.videos {
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: var(--sp-4);
|
|
|
|
|
max-width: 44rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.aviso {
|
|
|
|
|
margin-top: var(--sp-3);
|
|
|
|
|
font-size: var(--fs-xs);
|
|
|
|
|
max-width: 44rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.versiones {
|
|
|
|
|
list-style: none;
|
|
|
|
|
margin: var(--sp-4) 0 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
border-top: 1px solid var(--c-border);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.version {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: var(--sp-3);
|
|
|
|
|
padding: var(--sp-2) var(--sp-1);
|
|
|
|
|
border-bottom: 1px solid var(--c-border);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.version .datos p {
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nombre {
|
|
|
|
|
font-size: var(--fs-sm);
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nota {
|
|
|
|
|
font-size: var(--fs-xs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.duracion {
|
|
|
|
|
font-size: var(--fs-sm);
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.lateral {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: var(--sp-5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.lateral h2 {
|
|
|
|
|
font-size: var(--fs-lg);
|
|
|
|
|
margin-bottom: var(--sp-2);
|
|
|
|
|
padding-bottom: var(--sp-2);
|
|
|
|
|
border-bottom: 1px solid var(--c-border);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.lateral dl {
|
|
|
|
|
margin: 0;
|
|
|
|
|
font-size: var(--fs-sm);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.lateral dt {
|
|
|
|
|
font-size: var(--fs-xs);
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 0.08em;
|
|
|
|
|
color: var(--c-text-muted);
|
|
|
|
|
margin-top: var(--sp-3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* El primero pega con el encabezado de su bloque; los demás se separan. */
|
|
|
|
|
.lateral dt:first-of-type {
|
|
|
|
|
margin-top: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.lateral dd {
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Los códigos se leen dígito a dígito: monoespaciada y sin ligaduras. */
|
|
|
|
|
.codigo {
|
|
|
|
|
font-family: var(--font-mono);
|
|
|
|
|
font-size: var(--fs-xs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.subtitulo {
|
|
|
|
|
margin: var(--sp-4) 0 var(--sp-2);
|
|
|
|
|
font-size: var(--fs-xs);
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 0.08em;
|
|
|
|
|
color: var(--c-text-muted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reparto {
|
|
|
|
|
list-style: none;
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
font-size: var(--fs-sm);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reparto li {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
|
|
|
gap: 0 var(--sp-2);
|
|
|
|
|
padding-block: var(--sp-1);
|
|
|
|
|
border-bottom: 1px solid var(--c-border);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reparto .rol {
|
|
|
|
|
grid-column: 1;
|
|
|
|
|
font-size: var(--fs-xs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reparto .parte {
|
|
|
|
|
grid-row: 1 / span 2;
|
|
|
|
|
grid-column: 2;
|
|
|
|
|
align-self: center;
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.vecinos {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
|
|
gap: var(--sp-4);
|
|
|
|
|
padding-block: var(--sp-5);
|
|
|
|
|
border-top: 1px solid var(--c-border);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.vecino {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
font-size: var(--fs-sm);
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.vecino:hover span:last-child {
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.vecino.derecha {
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 48rem) {
|
|
|
|
|
.ficha,
|
|
|
|
|
.cuerpo {
|
|
|
|
|
grid-template-columns: minmax(0, 1fr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.portada {
|
|
|
|
|
max-width: 14rem;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|