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 FichaDeCancion from '$lib/components/FichaDeCancion.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
|
|
|
import Meta from '$lib/components/Meta.svelte';
|
|
|
|
|
import Pestanas, { type Pestana } from '$lib/components/Pestanas.svelte';
|
«Cupido sin flechas»: diez canciones de verdad, con sus prompts
El primer disco real del catálogo. Hasta ahora todo era inventado salvo
«Dorina».
`npm run album:importar -- "<carpeta>"` lee la carpeta y dice lo que haría;
solo escribe con `--escribir`, y hay que pedirlo. Es idempotente por slug, así
que corregir un `.txt` y volver a lanzarlo actualiza en vez de duplicar.
El desmontaje de los `.txt` está en `scripts/album/carpeta.mjs`, compartido con
el lector: si Suno vuelve a cambiar lo que pone al copiar, se arregla en un
sitio. De cada archivo salen tres cosas —el prompt de estilo, la letra con
etiquetas y la letra limpia de detrás de la raya— y la que se publica es la
limpia; la de las etiquetas es lo que se le pega a Suno, y va en el prompt.
Los títulos salen del nombre del máster porque las carpetas están sin acentuar
—«Quèdate», «Pague»—, y `--titulo` corrige lo que ni siquiera eso acierta: el
archivo francés pone «libére» donde va «libère». El idioma se deduce contando
palabras frecuentes: nueve en español y una en francés. La fecha sale del
máster más reciente, que es lo más cerca que hay de «cuándo quedó terminado».
Y el prompt de cada canción, que es lo que obligó a la migración 0012: `prompt`
ataba a un género y estos son de canciones concretas. No sustituye a
`genero_id`, se suma, y es coherente con lo que el modelo ya preveía —
`ejemplo_id` apunta al audio que salió del prompt—.
## El hueco de la portada
Diez fichas publicadas sin imagen destaparon algo que llevaba ahí desde
siempre: un `<img>` sin `src` es el icono de imagen rota del navegador, y eso
salía en siete sitios —la ficha del disco, la del tema, las dos tarjetas, el
carrito, la cuenta y la barra del reproductor—. Nunca se había visto porque
todo el catálogo tenía imagen.
`SinPortada` ocupa el hueco con la inicial del título. No dice «falta la
portada»: eso es un problema nuestro, no de quien mira. Y va a hacer falta más,
porque el texto de un disco entra por un lado y sus archivos por otro.
El botón de reproducir ya se apagaba solo sin audio; eso estaba bien hecho.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4 weeks ago
|
|
|
import SinPortada from '$lib/components/SinPortada.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
|
|
|
import VideoYoutube from '$lib/components/VideoYoutube.svelte';
|
|
|
|
|
import { aPista, aPistaDeVersion, aPistas } from '$lib/player/pista';
|
|
|
|
|
import { CANCIONES_POR_PACK, formatearPrecio, PRECIO_PACK_CENTIMOS } from '$lib/tienda';
|
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';
|
|
|
|
|
import { listaDeNombres, segundosADuracion } from '$lib/utils/format';
|
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);
|
El catálogo se sirve de la base: música, canciones, discos y etiquetas
Lo que bloqueaba la fase 4 era el cliente, no las rutas: el catálogo
viajaba en el paquete de JavaScript y las tarjetas, el carrito, la cuenta
y el reproductor lo consultaban por su cuenta. Baja por `page.data` desde
el layout —veintiún kilobytes, medidos, menos de lo que ocupaba en el
paquete— y `$lib/catalogo/cliente.svelte` lo ata a las mismas consultas
que usa el servidor. La otra opción era pasar por props lo que cada
componente busca solo, y eso obligaba a tocar la compra, el carrito y el
reproductor a la vez.
Siete rutas dejan de ser cargadores universales: música, estilo, álbum,
canción, portada y las dos de etiquetas. La ficha del tema se resuelve
entera en el servidor, incluida la letra, que se pide sola y no viaja con
el catálogo.
Faltaba dónde guardar dos textos: la introducción de cada estilo y las
notas de cada disco eran el cuerpo de su Markdown y el volcado se quedó
solo con el frontmatter. Migración 0010 y `npm run db:completar` los
rellena.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4 weeks ago
|
|
|
const letra = $derived(data.letra);
|
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
|
|
|
|
|
|
|
|
/** 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 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">
|
«Cupido sin flechas»: diez canciones de verdad, con sus prompts
El primer disco real del catálogo. Hasta ahora todo era inventado salvo
«Dorina».
`npm run album:importar -- "<carpeta>"` lee la carpeta y dice lo que haría;
solo escribe con `--escribir`, y hay que pedirlo. Es idempotente por slug, así
que corregir un `.txt` y volver a lanzarlo actualiza en vez de duplicar.
El desmontaje de los `.txt` está en `scripts/album/carpeta.mjs`, compartido con
el lector: si Suno vuelve a cambiar lo que pone al copiar, se arregla en un
sitio. De cada archivo salen tres cosas —el prompt de estilo, la letra con
etiquetas y la letra limpia de detrás de la raya— y la que se publica es la
limpia; la de las etiquetas es lo que se le pega a Suno, y va en el prompt.
Los títulos salen del nombre del máster porque las carpetas están sin acentuar
—«Quèdate», «Pague»—, y `--titulo` corrige lo que ni siquiera eso acierta: el
archivo francés pone «libére» donde va «libère». El idioma se deduce contando
palabras frecuentes: nueve en español y una en francés. La fecha sale del
máster más reciente, que es lo más cerca que hay de «cuándo quedó terminado».
Y el prompt de cada canción, que es lo que obligó a la migración 0012: `prompt`
ataba a un género y estos son de canciones concretas. No sustituye a
`genero_id`, se suma, y es coherente con lo que el modelo ya preveía —
`ejemplo_id` apunta al audio que salió del prompt—.
## El hueco de la portada
Diez fichas publicadas sin imagen destaparon algo que llevaba ahí desde
siempre: un `<img>` sin `src` es el icono de imagen rota del navegador, y eso
salía en siete sitios —la ficha del disco, la del tema, las dos tarjetas, el
carrito, la cuenta y la barra del reproductor—. Nunca se había visto porque
todo el catálogo tenía imagen.
`SinPortada` ocupa el hueco con la inicial del título. No dice «falta la
portada»: eso es un problema nuestro, no de quien mira. Y va a hacer falta más,
porque el texto de un disco entra por un lado y sus archivos por otro.
El botón de reproducir ya se apagaba solo sin audio; eso estaba bien hecho.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4 weeks ago
|
|
|
{#if data.portada}
|
|
|
|
|
<img
|
|
|
|
|
class="portada"
|
|
|
|
|
src={data.portada}
|
|
|
|
|
alt="Portada de {cancion.titulo}"
|
|
|
|
|
width="280"
|
|
|
|
|
height="280"
|
|
|
|
|
/>
|
|
|
|
|
{:else}
|
|
|
|
|
<SinPortada titulo={cancion.titulo} lado={280} />
|
|
|
|
|
{/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 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'}
|
El catálogo se sirve de la base: música, canciones, discos y etiquetas
Lo que bloqueaba la fase 4 era el cliente, no las rutas: el catálogo
viajaba en el paquete de JavaScript y las tarjetas, el carrito, la cuenta
y el reproductor lo consultaban por su cuenta. Baja por `page.data` desde
el layout —veintiún kilobytes, medidos, menos de lo que ocupaba en el
paquete— y `$lib/catalogo/cliente.svelte` lo ata a las mismas consultas
que usa el servidor. La otra opción era pasar por props lo que cada
componente busca solo, y eso obligaba a tocar la compra, el carrito y el
reproductor a la vez.
Siete rutas dejan de ser cargadores universales: música, estilo, álbum,
canción, portada y las dos de etiquetas. La ficha del tema se resuelve
entera en el servidor, incluida la letra, que se pide sola y no viaja con
el catálogo.
Faltaba dónde guardar dos textos: la introducción de cada estilo y las
notas de cada disco eran el cuerpo de su Markdown y el volcado se quedó
solo con el frontmatter. Migración 0010 y `npm run db:completar` los
rellena.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4 weeks ago
|
|
|
{#if letra}
|
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}
|
El catálogo se sirve de la base: música, canciones, discos y etiquetas
Lo que bloqueaba la fase 4 era el cliente, no las rutas: el catálogo
viajaba en el paquete de JavaScript y las tarjetas, el carrito, la cuenta
y el reproductor lo consultaban por su cuenta. Baja por `page.data` desde
el layout —veintiún kilobytes, medidos, menos de lo que ocupaba en el
paquete— y `$lib/catalogo/cliente.svelte` lo ata a las mismas consultas
que usa el servidor. La otra opción era pasar por props lo que cada
componente busca solo, y eso obligaba a tocar la compra, el carrito y el
reproductor a la vez.
Siete rutas dejan de ser cargadores universales: música, estilo, álbum,
canción, portada y las dos de etiquetas. La ficha del tema se resuelve
entera en el servidor, incluida la letra, que se pide sola y no viaja con
el catálogo.
Faltaba dónde guardar dos textos: la introducción de cada estilo y las
notas de cada disco eran el cuerpo de su Markdown y el volcado se quedó
solo con el frontmatter. Migración 0010 y `npm run db:completar` los
rellena.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4 weeks ago
|
|
|
<!-- Compuesta en el servidor; ver `$lib/server/markdown`. -->
|
|
|
|
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
|
|
|
{@html letra}
|
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>
|
|
|
|
|
|
|
|
|
|
{#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>
|
|
|
|
|
|
|
|
|
|
<FichaDeCancion
|
|
|
|
|
{cancion}
|
|
|
|
|
album={data.album}
|
|
|
|
|
estilo={data.estilo}
|
|
|
|
|
valoracion={data.valoracion}
|
|
|
|
|
miValoracion={data.miValoracion}
|
|
|
|
|
/>
|
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>
|
|
|
|
|
|
|
|
|
|
{#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
|
|
|
.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);
|
Cantos menos redondos, tarjetas coherentes y portada a ras
Los botones dejan de ser cápsulas y las tarjetas pierden la mitad del canto. De
paso se ordena el sistema: había tres radios sueltos y una píldora de 999px
repetida en quince archivos, y quedan dos —uno para lo que se pulsa, otro para
las superficies—. Los botones de icono suelto siguen redondos, que ahí sí tiene
sentido.
Las fichas del taller no usaban la tarjeta compartida: tenían su propio borde,
su propio canto y su propio hover, así que se veían de otra familia que las de
música. Ahora usan `.tarjeta`, con el mismo reflejo bajo el puntero y el mismo
subrayado.
Y la portada arranca en el borde de la ventana, con la cabecera flotando encima.
Para eso la cabecera pasa a medir su propio alto y publicarlo: estaba escrito a
mano en 4,75rem cuando mide 68px, y todo lo que dependía de ese número —el salto
a un ancla, las barras laterales pegajosas— quedaba desplazado.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
|
|
|
border-radius: var(--radius-tarjeta);
|
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
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
.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>
|